/* ============================================================
   College of ICT · Jaffna, UI v6 "Royal & Saffron"
   MOBILE-FIRST. Everything above the responsive block at the
   bottom of this file is the MOBILE layout. Two upward
   breakpoints then widen it:
       768px  → tablet
      1100px  → desktop / big screens
      (1400px → large desktop, wider container only)
   Multi-page · solid two-row header · hero slider
   EN/தமிழ் toggle · static Apply flow (email / WhatsApp / SMS)
   Type: Newsreader (editorial serif, headings) + Source Sans 3 (text)
         for BOTH languages, with Noto Serif/Sans Tamil as fallbacks
         for Tamil codepoints only, loaded via <link> in each page
   ============================================================ */
/* Fonts are loaded via <link> in each page's <head>, not @import, a stylesheet @import blocks rendering until it resolves. */

:root{
  --bg:#f7f8fc;
  --surface:#ffffff;
  --surface-2:#eef1f9;
  --ink:#0a1633;
  --ink-2:#2c3a5c;
  --muted:#5d6a8a;
  --line:#e2e7f2;
  --blue:#1d3fae;
  --blue-deep:#122a7d;
  --blue-ink:#0a1330;
  --blue-soft:#e8edfb;
  /* "deep" means more emphasis, so it darkens in light mode and lightens in
     dark. That reads well as text but breaks under white text, where a fill
     must stay dark in both themes. These two carry the fills. In light they
     are the same values as --blue / --blue-deep, so nothing here shifts. */
  --blue-fill:#1d3fae;
  --blue-fill-2:#122a7d;
  --saffron:#ef9d2a;
  --saffron-deep:#d9820f;
  --saffron-soft:#fdf1de;
  --saffron-text:#955600;
  --glass:rgba(255,255,255,.12);
  --glass-line:rgba(255,255,255,.22);

  /* Academic pairing: an editorial serif for headings, a highly legible
     text sans for everything else. Neither Latin face carries Tamil glyphs,
     so a matching Noto Tamil sits at the END of each stack, serif fallback
     for headings, sans for body, which keeps the serif/sans contrast in
     Tamil too. Latin always renders in the primary face, so it is identical
     in both languages; only Tamil codepoints fall through. */
  --f-display:"Newsreader","Noto Serif Tamil",Georgia,"Times New Roman",serif;
  --f-body:"Source Sans 3","Noto Sans Tamil","Segoe UI",system-ui,-apple-system,sans-serif;

  --wrap:1220px;
  --r-sm:12px; --r:18px; --r-lg:26px; --pill:999px;
  --sh-sm:0 1px 2px rgba(10,22,51,.05),0 6px 16px -8px rgba(10,22,51,.10);
  --sh:0 2px 6px rgba(10,22,51,.05),0 28px 60px -28px rgba(10,22,51,.28);
  --sh-blue:0 18px 44px -18px rgba(29,63,174,.55);
  --hi:rgba(255,255,255,.8);          /* top-edge light catch on raised cards */
  --cast:10,22,51;                    /* shadow colour, as r,g,b */
}
:root[data-theme="dark"]{
  --bg:#070d1f; --surface:#0d1530; --surface-2:#111a3a;
  --ink:#eef1fb; --ink-2:#c6cee6; --muted:#8d98b8; --line:#1d2850;
  --blue:#6b88f2; --blue-deep:#8aa2ff; --blue-ink:#060b1a; --blue-soft:#15204a;
  --blue-fill:#3a5bd9; --blue-fill-2:#25409e;
  --saffron:#f5b04c; --saffron-deep:#f7c374; --saffron-soft:#2b2010; --saffron-text:#f7c374;
  --sh-sm:0 1px 2px rgba(0,0,0,.5),0 6px 16px -8px rgba(0,0,0,.6);
  --sh:0 2px 6px rgba(0,0,0,.5),0 30px 60px -28px rgba(0,0,0,.8);
  --sh-blue:0 18px 44px -18px rgba(91,123,239,.5);
  --hi:rgba(255,255,255,.07);
  --cast:0,0,0;
}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --bg:#070d1f; --surface:#0d1530; --surface-2:#111a3a;
    --ink:#eef1fb; --ink-2:#c6cee6; --muted:#8d98b8; --line:#1d2850;
    --blue:#6b88f2; --blue-deep:#8aa2ff; --blue-ink:#060b1a; --blue-soft:#15204a;
    --blue-fill:#3a5bd9; --blue-fill-2:#25409e;
    --saffron:#f5b04c; --saffron-deep:#f7c374; --saffron-soft:#2b2010; --saffron-text:#f7c374;
    --sh-sm:0 1px 2px rgba(0,0,0,.5),0 6px 16px -8px rgba(0,0,0,.6);
    --sh:0 2px 6px rgba(0,0,0,.5),0 30px 60px -28px rgba(0,0,0,.8);
    --sh-blue:0 18px 44px -18px rgba(91,123,239,.5);
  --hi:rgba(255,255,255,.07);
  --cast:0,0,0;
  }
}

/* ---------------- base ---------------- */
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--f-body);
  font-size:16px;line-height:1.62;font-weight:450;-webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;overflow-x:hidden;
  /* Tamil compounds run long and have few break opportunities, so a single
     word could widen a grid track or a heading and scroll the page sideways.
     This inherits everywhere and only breaks a word that would overflow. */
  overflow-wrap:break-word}
img{max-width:100%;display:block}
a{color:inherit;text-decoration:none}
.wrap{max-width:var(--wrap);margin-inline:auto;padding-inline:18px}
/* serif headings need far less negative tracking than a geometric sans */
h1,h2,h3,h4{font-family:var(--f-display);font-weight:600;line-height:1.14;
  letter-spacing:-.008em;text-wrap:balance;margin:0;color:var(--ink)}
p{margin:0}
.section{padding-block:clamp(64px,8vw,110px)}
/* Section headings.
   The block itself stays full width, because it is also a .wrap and would
   otherwise take the auto side margins and drift to the middle of the page
   while the grid beneath it ran the full column. Only the measure of the
   text is held back. */
.head{margin-bottom:52px}
.head h2,.head p{max-width:720px}
.head h2{font-size:clamp(2.15rem,4.6vw,3.5rem);margin:19px 0 0;letter-spacing:-.018em;
  line-height:1.1}
.head p{color:var(--muted);font-size:1.12rem;line-height:1.65;margin-top:20px}
.center{margin-inline:auto;text-align:center}
.center .eyebrow{justify-content:center}
.center h2,.center p{margin-inline:auto}
/* Eyebrow: a tracked label standing behind a slim upright accent. No chip,
   no border, and nothing ruled under the heading; the h2 carries the weight
   on its own. currentColor so the saffron variants on the dark panels get
   the accent for free. */
.eyebrow{display:inline-flex;align-items:center;gap:13px;font-weight:700;font-size:.74rem;
  letter-spacing:.17em;text-transform:uppercase;color:var(--blue)}
.eyebrow::before{content:"";width:3px;height:18px;border-radius:99px;flex:0 0 auto;
  background:linear-gradient(180deg,currentColor,color-mix(in srgb,currentColor 25%,transparent))}

/* ---- home page section heads (.head.feature) ----
   No marker on these at all. The accent on the inner pages exists to mark a
   left edge; centred there is no edge to mark, and putting one on each side
   only brackets the label. The h2 below runs to 64px and anchors the stack on
   its own, so the eyebrow goes back to being what it actually is: a quiet
   caption. What carries the design here is the interval between the three
   sizes and the air around them, not an ornament. */
.head.feature{text-align:center;margin-bottom:clamp(46px,5vw,68px)}
.head.feature .eyebrow{justify-content:center;font-size:.71rem;letter-spacing:.2em}
.head.feature .eyebrow::before{display:none}
.head.feature h2{max-width:900px;margin:19px auto 0;
  font-size:clamp(2.35rem,5.3vw,4.05rem);letter-spacing:-.025em;line-height:1.05}
.head.feature p{max-width:56ch;margin:22px auto 0;font-size:1.13rem;line-height:1.7}
.band{background:var(--surface-2);border-block:1px solid var(--line)}

/* ---------------- buttons ---------------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:.55em;
  font-family:var(--f-body);font-weight:600;font-size:.98rem;padding:.9em 1.55em;
  border-radius:var(--pill);border:1.5px solid transparent;cursor:pointer;
  /* Tamil labels run about a third longer than the English, so nowrap pushed
     the widest of them past a 375px screen and scrolled the whole page
     sideways. Wrap on phones, hold the single line once there is room. */
  white-space:normal;text-wrap:balance;max-width:100%;
  transition:transform .18s ease,box-shadow .2s ease,background .18s,color .18s,border-color .18s}
@media (min-width:768px){.btn{white-space:nowrap}}
.btn svg{transition:transform .2s}
.btn:hover svg{transform:translateX(3px)}
.btn-blue{background:var(--blue-fill);color:#fff;box-shadow:var(--sh-blue)}
.btn-blue:hover{background:var(--blue-fill-2);transform:translateY(-2px)}
.btn-gold{background:var(--saffron);color:#2b1a02}
.btn-gold:hover{background:var(--saffron-deep);transform:translateY(-2px)}
.btn-ghost{border-color:var(--line);color:var(--ink);background:var(--surface)}
.btn-ghost:hover{border-color:var(--blue);color:var(--blue);transform:translateY(-2px)}
.btn-glass{background:var(--glass);border-color:var(--glass-line);color:#fff;backdrop-filter:blur(8px)}
.btn-glass:hover{background:rgba(255,255,255,.2);transform:translateY(-2px)}
.btn-sm{padding:.62em 1.1em;font-size:.87rem}

/* ============================================================
   HEADER, separate solid area (never overlaps content)
   Row 1: utility topbar · Row 2: sticky main nav with dropdowns
   ============================================================ */
.topbar{background:var(--blue-ink);color:#cdd6ee;font-size:.82rem}
.topbar .wrap{display:flex;align-items:center;justify-content:center;gap:14px;
  min-height:38px;flex-wrap:wrap;padding-block:6px}
.topbar a{display:inline-flex;align-items:center;gap:7px;font-weight:600;transition:color .15s}
.topbar a:hover{color:var(--saffron)}
.topbar svg{color:var(--saffron);flex:0 0 auto}
.topbar .addr{display:none;align-items:center;gap:7px;margin-left:auto;color:#9fabce}
.hdr{position:sticky;top:0;z-index:60;background:var(--surface);
  border-bottom:1px solid var(--line);box-shadow:var(--sh-sm)}
.nav{display:flex;align-items:center;gap:14px;height:68px}
/* brand must never shrink or let its text spill into the nav */
.brand{display:flex;align-items:center;gap:12px;flex:0 0 auto;min-width:0}
.brand img{width:40px;height:40px;object-fit:contain;flex:0 0 auto}
.brand-txt{font-family:var(--f-display);font-weight:600;font-size:1.2rem;letter-spacing:0;line-height:1.15;color:var(--ink)}
/* three-line lockup: name, then city and tagline stacked beneath.
   Hidden on phones (name only), restored from the tablet breakpoint up. */
.brand-txt small{display:none;flex-direction:column;gap:1px;margin-top:5px;
  font-family:var(--f-body);font-weight:700;font-size:.57rem;
  letter-spacing:.2em;text-transform:uppercase}
.brand-txt small span{white-space:nowrap;line-height:1.35}
/* The header and the drawer both sit on a light ground, where --muted and
   --saffron measured 3.39:1 and 2.20:1 on 10px text. Both tokens below track
   the theme, so the lockup stays right when the header turns dark. */
.brand-txt small .city{color:var(--ink-2)}
.brand-txt small .tag{color:var(--saffron-text)}
/* the footer stays dark in both themes, so it keeps the light pairing */
.ftr-brand .brand-txt small .city{color:#aeb8d4}
.ftr-brand .brand-txt small .tag{color:var(--saffron)}
/* nav takes the space left between brand and actions, right-aligned */
.nav-links{display:none;gap:2px;align-items:center;
  flex:1 1 auto;justify-content:flex-end;min-width:0;flex-wrap:nowrap}
.nav-links>li{list-style:none;position:relative}
.nav-links{padding:0;margin-block:0}
.nav-links a.top,.nav-links button.top{display:inline-flex;align-items:center;gap:6px;
  padding:10px 13px;border-radius:var(--pill);font-weight:600;font-size:.92rem;color:var(--ink-2);
  background:none;border:0;cursor:pointer;font-family:var(--f-body);transition:.16s;
  white-space:nowrap}
.nav-links a.top:hover,.nav-links button.top:hover,
.nav-links li.open>button.top{background:var(--blue-soft);color:var(--blue)}
.nav-links a.top.active{background:var(--blue-fill);color:#fff}
.nav-links .car{transition:transform .2s}
.nav-links li.open .car{transform:rotate(180deg)}
/* Set by the fit guard in js/main.js when the row would spill over the brand.
   The burger is always safe, an overlapping menu never is. Marked with
   !important because it has to beat the 1280 block whatever the width, and
   the drawer has to come back with it. */
body.nav-cramped .nav-links{display:none!important}
body.nav-cramped .burger{display:inline-flex!important}
body.nav-cramped .mobile-menu{display:block!important}


/* dropdown */
.dd{position:absolute;top:calc(100% + 10px);left:50%;transform:translateX(-50%) translateY(8px);
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r);
  box-shadow:var(--sh);padding:10px;min-width:270px;opacity:0;visibility:hidden;
  transition:opacity .2s ease,transform .2s ease,visibility .2s;z-index:70}
.nav-links li.open>.dd{opacity:1;visibility:visible;transform:translateX(-50%) translateY(0)}
.dd .grp{padding:10px 12px 4px;font-size:.64rem;font-weight:800;letter-spacing:.16em;
  text-transform:uppercase;color:var(--muted)}
.dd a{display:flex;align-items:center;gap:11px;padding:11px 12px;border-radius:var(--r-sm);
  font-weight:600;font-size:.93rem;color:var(--ink-2);transition:.15s}
.dd a:hover{background:var(--blue-soft);color:var(--blue)}
.dd a .mini{width:34px;height:34px;border-radius:10px;flex:0 0 auto;display:grid;place-items:center;
  background:var(--blue-soft);color:var(--blue)}
.dd a:nth-child(odd) .mini{background:var(--saffron-soft);color:var(--saffron-text)}
.dd a span small{display:block;font-weight:500;font-size:.74rem;color:var(--muted)}
.dd .dd-foot{margin-top:6px;padding:10px 12px;border-top:1px solid var(--line)}
.dd .dd-foot a{padding:0;color:var(--blue);font-size:.86rem}
.dd .dd-foot a:hover{background:none;text-decoration:underline}
.nav-actions{display:flex;align-items:center;gap:8px;flex:0 0 auto;margin-left:auto}
/* PHONE AND TABLET: brand, language, theme, burger. On a phone Apply does not
   fit beside them at 390px once the label is in தமிழ். On a tablet it fits,
   but the bottom bar already carries Apply in the thumb zone, and having both
   on screen at once was showing the same button twice. Either way it stays in
   the drawer too. It returns to the header at 1024, where the bar gives way to
   the contact rail and the rail does not carry it. */
.nav-actions .btn-gold{display:none}
/* fixed width sized to the longer Tamil label, so the language and theme
   toggles land in exactly the same place in both languages */
.nav-actions .btn-sm{min-width:132px}
.icon-btn{width:42px;height:42px;border-radius:var(--pill);border:1.5px solid var(--line);
  background:var(--surface);color:var(--ink);cursor:pointer;display:grid;place-items:center;transition:.18s}
.icon-btn:hover{border-color:var(--blue);color:var(--blue)}
.burger{display:grid}

/* mobile menu */
/* Parked off to the right when closed. visibility:hidden matters as well as
   the transform: a fixed panel sitting a screen-width to the right still
   counts toward document width in some engines, which put a horizontal
   scrollbar on every page. */
.mobile-menu{position:fixed;inset:0;z-index:90;background:var(--bg);padding:24px;
  transform:translateX(100%);visibility:hidden;overflow-y:auto;
  transition:transform .32s ease,visibility 0s linear .32s}
.mobile-menu.open{transform:none;visibility:visible;transition-delay:0s,0s}
/* a column: head, the menu itself, then a foot pinned to the bottom so the
   panel never ends halfway down the screen with the CTA stranded mid-air */
.mobile-menu{display:flex;flex-direction:column}
.mobile-menu .mm-top{display:flex;justify-content:space-between;align-items:center;
  gap:14px;padding-bottom:18px;margin-bottom:6px;border-bottom:1px solid var(--line)}
.mobile-menu .mm-top .brand img{width:38px;height:38px}
.mobile-menu .mm-top .brand-txt{font-size:1.08rem}
.mobile-menu .mm-top .brand-txt small{display:flex}
.mm-list{list-style:none;margin:0;padding:0;flex:1 1 auto}
.mm-list>li{border-bottom:1px solid var(--line);position:relative;
  opacity:0;transform:translateY(14px)}
/* items arrive in sequence once the panel is open, not all at once */
.mobile-menu.open .mm-list>li{opacity:1;transform:none;
  transition:opacity .38s ease,transform .42s cubic-bezier(.2,.7,.2,1);
  transition-delay:calc(60ms + var(--i,0) * 45ms)}
/* the page you are on carries a mark, so the menu says where you are */
.mm-list>li.on::before{content:"";position:absolute;left:-24px;top:50%;
  width:4px;height:22px;margin-top:-11px;border-radius:0 3px 3px 0;background:var(--saffron)}
.mm-list>li.on>a{color:var(--ink)}
.mm-list a,.mm-list button.mm-t{display:flex;width:100%;align-items:center;justify-content:space-between;
  padding:16px 6px;font-family:var(--f-display);font-weight:600;font-size:1.32rem;color:var(--ink);
  background:none;border:0;cursor:pointer;text-align:left}
/* Submenus slide rather than snap. 0fr to 1fr on a grid row animates an
   auto height, which plain height cannot do without a fixed pixel value. */
.mm-wrap{display:grid;grid-template-rows:0fr;
  transition:grid-template-rows .34s cubic-bezier(.2,.7,.2,1)}
.mm-list li.open .mm-wrap{grid-template-rows:1fr}
.mm-sub{list-style:none;margin:0;padding:0;overflow:hidden;
  border-left:2px solid var(--line);margin-left:4px}
.mm-list li.open .mm-sub{border-left-color:color-mix(in srgb,var(--saffron) 55%,var(--line))}
.mm-sub a{font-family:var(--f-body);font-size:1rem;font-weight:600;
  color:var(--ink-2);padding:11px 14px}
.mm-sub li:last-child a{padding-bottom:16px}
.mm-list .car{transition:transform .28s cubic-bezier(.2,.7,.2,1)}
.mm-list li.open .car{transform:rotate(180deg)}

/* foot: everything the header offers but the drawer is covering */
.mm-foot{flex:0 0 auto;padding-top:20px;margin-top:20px;border-top:1px solid var(--line)}
.mm-toggles{display:flex;gap:10px;margin-bottom:18px}
.mm-chip{display:inline-flex;align-items:center;gap:8px;cursor:pointer;
  padding:9px 15px;border-radius:var(--pill);font-family:var(--f-body);
  font-weight:700;font-size:.85rem;color:var(--ink-2);
  background:var(--surface-2);border:1px solid var(--line);
  transition:background .2s,color .2s,border-color .2s}
.mm-chip svg{color:var(--blue);flex:0 0 auto}
.mm-chip:active{background:var(--blue-soft);color:var(--blue);
  border-color:color-mix(in srgb,var(--blue) 34%,var(--line))}
.mm-reach{display:grid;gap:11px;margin-bottom:18px}
.mm-reach a{display:flex;align-items:center;gap:10px;
  font-weight:600;font-size:.95rem;color:var(--ink-2)}
.mm-reach svg{color:var(--saffron);flex:0 0 auto}
.mm-apply{width:100%}

@media (prefers-reduced-motion:reduce){
  .mobile-menu.open .mm-list>li{transition:none;transition-delay:0s}
  .mm-list>li{opacity:1;transform:none}
  .mm-wrap{transition:none}
}

/* ============================================================
   HERO SLIDER (home)
   ============================================================ */
.slider{position:relative;color:#fff;overflow:hidden;background:var(--blue-ink)}
.slides{display:flex;transition:transform .65s cubic-bezier(.65,.05,.25,1);will-change:transform}
.slide{position:relative;flex:0 0 100%;min-height:min(78vh,720px);display:flex;align-items:center;overflow:hidden}
.slide-media{position:absolute;inset:0}
.slide-media img{width:100%;height:100%;object-fit:cover;object-position:center 38%;
  transform:scale(1.06);transition:transform 7s linear}
.slide.on .slide-media img{transform:scale(1)}
/* Even, bright scrim, the photo reads equally on the left and the right.
   Headline legibility comes from text-shadow, not from darkening one side. */
.slide-media::before{content:"";position:absolute;inset:0;z-index:1;
  background:linear-gradient(80deg,rgba(7,15,38,.46) 0%,rgba(8,18,48,.42) 55%,rgba(8,18,48,.44) 100%)}
.slide.tint .slide-media::before{background:linear-gradient(80deg,rgba(6,13,34,.94) 15%,rgba(10,23,64,.86) 55%,rgba(18,42,125,.55) 100%)}
.slide-inner{position:relative;z-index:2;width:100%;padding-block:90px 120px}
.slide-inner .grid2{display:grid;grid-template-columns:1fr;gap:44px;align-items:center}
.pill-tag{display:inline-flex;align-items:center;gap:10px;background:var(--glass);
  border:1px solid var(--glass-line);backdrop-filter:blur(8px);
  padding:8px 16px 8px 9px;border-radius:var(--pill);font-size:.84rem;font-weight:600;color:#fff}
.pill-tag b{background:var(--saffron);color:#2b1a02;font-size:.68rem;font-weight:800;
  letter-spacing:.05em;padding:4px 10px;border-radius:var(--pill);text-transform:uppercase}
.slide h1{font-size:clamp(2.5rem,5.6vw,4.6rem);font-weight:600;color:#fff;max-width:780px;margin:24px 0 0;
  text-shadow:0 2px 24px rgba(4,10,26,.6),0 1px 4px rgba(4,10,26,.5)}
.slide h1 em{font-style:normal;color:var(--saffron)}
.slide .lead{font-size:clamp(1.02rem,1.9vw,1.22rem);color:rgba(255,255,255,.92);
  max-width:580px;margin:20px 0 32px;
  text-shadow:0 2px 16px rgba(4,10,26,.65),0 1px 3px rgba(4,10,26,.55)}
.slide .cta{display:flex;gap:14px;flex-wrap:wrap}
/* Posters are matched on height, not width. The slider is a fixed band and the
   eye tracks the top and bottom edges as the slides change, so two posters of
   the same height sit still while two of the same width jump by whatever their
   aspect ratios differ by. Width follows the ratio, which also means neither
   poster is ever cropped: they are made to be read, not filled into a box. */
.slide .poster{display:none;border-radius:var(--r-lg);overflow:hidden;border:1px solid rgba(255,255,255,.2);
  box-shadow:0 30px 70px -25px rgba(0,0,0,.7);transform:rotate(1.4deg);
  height:clamp(340px,48vh,520px);justify-self:end}
.slide .poster img{height:100%;width:auto;display:block}

/* slider controls */
.slider-ui{position:absolute;inset-inline:0;bottom:0;z-index:5;
  background:linear-gradient(180deg,transparent,rgba(6,11,28,.55))}
.slider-ui .wrap{display:flex;align-items:center;gap:20px;padding-block:18px}
.s-arrows{display:flex;gap:10px}
.s-btn{width:46px;height:46px;border-radius:var(--pill);border:1px solid var(--glass-line);
  background:var(--glass);backdrop-filter:blur(10px);color:#fff;cursor:pointer;
  display:grid;place-items:center;transition:.18s}
.s-btn:hover{background:rgba(255,255,255,.24);transform:translateY(-2px)}
.s-dots{display:flex;gap:8px;align-items:center}
.s-dot{position:relative;width:34px;height:5px;border-radius:3px;background:rgba(255,255,255,.28);
  border:0;cursor:pointer;overflow:hidden;padding:0;transition:width .25s}
.s-dot .fill{position:absolute;inset:0;width:0%;background:var(--saffron);border-radius:3px}
.s-dot.on{width:58px}
.s-dot.on .fill{animation:sfill var(--s-dur,6.5s) linear forwards}
@keyframes sfill{from{width:0}to{width:100%}}
.slider.paused .s-dot.on .fill{animation-play-state:paused}
.s-count{margin-left:auto;font-family:var(--f-display);font-weight:600;font-size:.95rem;
  color:rgba(255,255,255,.85);letter-spacing:.06em}
.s-count b{color:var(--saffron);font-size:1.25rem}

/* ============================================================
   PAGE HERO (subpages)
   ============================================================ */
.page-hero{background:var(--blue-ink);color:#fff;position:relative;overflow:hidden}
.page-hero::before{content:"";position:absolute;inset:0;opacity:.55;
  background:radial-gradient(560px 320px at 88% 0%,rgba(239,157,42,.18),transparent 60%),
             radial-gradient(700px 420px at 0% 100%,rgba(29,63,174,.4),transparent 60%)}
.page-hero .wrap{position:relative;z-index:1;padding-block:clamp(52px,7vw,88px)}
.crumbs{display:flex;align-items:center;gap:9px;font-size:.82rem;font-weight:600;
  color:rgba(255,255,255,.6);flex-wrap:wrap}
.crumbs a:hover{color:var(--saffron)}
.crumbs .sep{opacity:.5}
.crumbs .here{color:var(--saffron)}
.page-hero h1{color:#fff;font-size:clamp(2.2rem,4.8vw,3.6rem);margin:18px 0 12px;max-width:820px}
.page-hero .sub{color:rgba(255,255,255,.75);font-size:1.12rem;max-width:640px}
.page-hero .tags{display:flex;gap:10px;flex-wrap:wrap;margin-top:22px}
.tagchip{display:inline-flex;align-items:center;gap:8px;background:var(--glass);
  border:1px solid var(--glass-line);padding:7px 14px;border-radius:var(--pill);
  font-size:.8rem;font-weight:600;color:#fff}
.tagchip .dot{width:7px;height:7px;border-radius:50%;background:var(--saffron)}

/* ============================================================
   CONTENT COMPONENTS
   ============================================================ */
/* program cards */
.prog-grid{display:grid;grid-template-columns:1fr;gap:22px}
.pcard{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:30px;display:flex;flex-direction:column;position:relative;overflow:hidden;
  box-shadow:var(--sh-sm);transition:transform .24s ease,box-shadow .24s,border-color .24s}
.pcard:hover{transform:translateY(-6px);box-shadow:var(--sh);
  border-color:color-mix(in srgb,var(--blue) 38%,var(--line))}
.pcard::before{content:"";position:absolute;inset:0 0 auto 0;height:4px;
  background:linear-gradient(90deg,var(--blue),var(--saffron))}
.pcard .tier{font-weight:800;font-size:.68rem;letter-spacing:.18em;text-transform:uppercase;color:var(--muted)}
.pcard h3{font-size:1.5rem;margin:14px 0 8px}
.pcard .sub{color:var(--muted);font-size:.96rem;min-height:48px}
.pcard ul{list-style:none;padding:0;margin:20px 0;display:grid;gap:12px;flex:1}
.pcard li{display:flex;gap:11px;align-items:flex-start;font-size:.95rem;color:var(--ink-2);font-weight:500}
.pcard li svg{flex:0 0 auto;margin-top:4px;color:var(--blue)}
.pcard .foot{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding-top:18px;border-top:1px solid var(--line)}
.pcard .dur{font-size:.8rem;font-weight:700;color:var(--muted)}
.pcard.featured{background:linear-gradient(168deg,var(--blue-deep),var(--blue) 130%);border-color:transparent;color:#fff}
.pcard.featured::before{background:var(--saffron)}
.pcard.featured h3{color:#fff}
.pcard.featured .sub{color:rgba(255,255,255,.75)}
.pcard.featured li{color:rgba(255,255,255,.9)}
.pcard.featured li svg{color:var(--saffron)}
.pcard.featured .tier{color:var(--saffron)}
.pcard.featured .foot{border-color:rgba(255,255,255,.2)}
.pcard.featured .dur{color:rgba(255,255,255,.72)}
.flag{position:absolute;top:18px;right:18px;background:var(--saffron);color:#2b1a02;font-weight:800;
  font-size:.62rem;letter-spacing:.1em;text-transform:uppercase;padding:5px 11px;border-radius:var(--pill)}

/* spotlight dark band */
.spotlight{background:var(--blue-ink);color:#fff;position:relative;overflow:hidden}
.spotlight::before{content:"";position:absolute;inset:0;opacity:.5;
  background:radial-gradient(700px 420px at 85% 20%,rgba(239,157,42,.16),transparent 65%),
             radial-gradient(600px 400px at 8% 90%,rgba(29,63,174,.35),transparent 60%)}
.spot-grid{position:relative;z-index:1;display:grid;grid-template-columns:1fr;gap:56px;align-items:center}
/* The alumni head is the fifth home heading and uses the same centred
   .head.feature as the other four; only the colours change, because it sits
   on the dark band. It needs lifting above .spotlight::before, which paints
   the glow across the whole section. */
.spotlight .head{position:relative;z-index:1}
.spotlight .head .eyebrow{color:var(--saffron)}
.spotlight .head h2{color:#fff}
.spotlight .head p{color:rgba(255,255,255,.78)}
.medal-card{position:relative;border-radius:var(--r-lg);overflow:hidden;
  border:1px solid rgba(255,255,255,.16);box-shadow:0 30px 70px -25px rgba(0,0,0,.7);
  transform:rotate(-1.2deg);transition:transform .3s ease}
.medal-card:hover{transform:rotate(0deg) scale(1.01)}
/* the two medals, listed rather than run into a sentence: each name is the
   thing being read, so it gets its own line and a marker in the metal */
.medals{list-style:none;padding:0;margin:16px 0 0;display:grid;gap:13px}
.medals li{display:flex;gap:12px;align-items:flex-start;
  color:var(--ink-2);font-size:.98rem;line-height:1.55}
/* --saffron alone measures 1.95:1 on the light ground and washes out at 9px.
   The core takes the readable token and the halo keeps the gold. */
.medals li::before{content:"";flex:0 0 auto;width:9px;height:9px;margin-top:7px;
  border-radius:50%;background:var(--saffron-text);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--saffron) 30%,transparent)}
.medals b{color:var(--ink);font-weight:700}
.medals small{display:block;margin-top:2px;color:var(--muted);font-size:.86rem}
.medal-card img{width:100%;height:auto}
.spot-points{display:grid;gap:16px;margin-top:28px}
.spoint{display:flex;gap:14px;align-items:flex-start}
.spoint .n{width:40px;height:40px;border-radius:12px;flex:0 0 auto;display:grid;place-items:center;
  background:rgba(239,157,42,.14);border:1px solid rgba(239,157,42,.35);color:var(--saffron);
  font-family:var(--f-display);font-weight:600}
.spoint b{display:block;font-weight:600;font-size:1rem;color:#fff}
.spoint span{color:rgba(255,255,255,.66);font-size:.9rem}

/* pathway cards */
.path-grid{display:grid;grid-template-columns:1fr;gap:22px}
.pathc{border:1px solid var(--line);border-radius:var(--r-lg);padding:34px;background:var(--surface);
  position:relative;overflow:hidden;transition:transform .24s,box-shadow .24s;box-shadow:var(--sh-sm);
  display:flex;flex-direction:column}
.pathc:hover{transform:translateY(-5px);box-shadow:var(--sh)}
.pathc .code{display:inline-block;align-self:flex-start;font-weight:800;font-size:.7rem;letter-spacing:.14em;
  color:var(--blue);background:var(--blue-soft);padding:6px 13px;border-radius:var(--pill)}
.pathc h3{font-size:1.6rem;margin:16px 0 10px}
.pathc p{color:var(--muted);flex:1}
.pathc .meta{margin-top:22px;padding-top:20px;border-top:1px solid var(--line);
  display:flex;align-items:center;justify-content:space-between;gap:11px;font-weight:600;
  font-size:.92rem;color:var(--ink-2);flex-wrap:wrap}
.pathc .meta .l{display:flex;align-items:center;gap:10px}
.pathc .meta .dot{width:9px;height:9px;border-radius:50%;background:var(--saffron)}

/* level timeline (course pages) */
.levels{display:grid;gap:0;position:relative;margin-top:8px}
.level{display:grid;grid-template-columns:64px 1fr;gap:20px;position:relative;padding-bottom:34px}
.level:last-child{padding-bottom:0}
.level::before{content:"";position:absolute;left:31px;top:56px;bottom:6px;width:2px;
  background:linear-gradient(180deg,var(--blue),color-mix(in srgb,var(--blue) 20%,transparent))}
.level:last-child::before{display:none}
.level .badge{width:64px;height:64px;border-radius:18px;display:grid;place-items:center;
  background:var(--blue-soft);border:1px solid color-mix(in srgb,var(--blue) 25%,var(--line));
  color:var(--blue);font-family:var(--f-display);font-weight:700;font-size:1.05rem}
.level.gold .badge{background:var(--saffron-soft);border-color:color-mix(in srgb,var(--saffron) 40%,var(--line));color:var(--saffron-text)}
.level h3{font-size:1.3rem;margin:6px 0 6px}
.level p{color:var(--muted);font-size:.98rem;max-width:640px}
.level .award{display:inline-flex;align-items:center;gap:8px;margin-top:12px;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--pill);
  padding:7px 14px;font-size:.8rem;font-weight:700;color:var(--ink-2)}
.level .award svg{color:var(--saffron)}

/* requirement list */
.req{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:30px;box-shadow:var(--sh-sm)}
.req h3{font-size:1.25rem;margin-bottom:16px;display:flex;align-items:center;gap:10px}
.req h3 .n{width:34px;height:34px;border-radius:10px;display:grid;place-items:center;flex:0 0 auto;
  background:var(--blue-soft);color:var(--blue);font-family:var(--f-display);font-size:.9rem}
.req ul{list-style:none;padding:0;margin:0;display:grid;gap:12px}
.req li{display:flex;gap:11px;align-items:flex-start;color:var(--ink-2);font-size:.96rem}
.req li svg{flex:0 0 auto;margin-top:4px;color:var(--blue)}
.req .or{justify-content:center;text-align:center;font-weight:800;font-size:.7rem;letter-spacing:.2em;
  color:var(--muted);text-transform:uppercase;padding:2px 0}
.grid2c{display:grid;grid-template-columns:1fr;gap:22px;align-items:start}
/* A 1fr track floors at its item's min-content width, and overflow-wrap does
   not lower that floor, so a single long Tamil word could push a column past
   the page and scroll it sideways. min-width:0 lets the track win instead. */
.grid2c>*,.grid2>*,.grid3c>*,.notice-grid>*,.facts>*{min-width:0}
/* some grids are declared inline and so have no class to hang the rule on;
   flooring the card components themselves covers those too */
.req,.callout,.fact,.level,.notice,.ann{min-width:0}
.grid3c{display:grid;grid-template-columns:1fr;gap:22px;align-items:stretch}

/* info callout */
/* A notice, not an alert.

   Amber fill, amber outline all the way round, and a circled exclamation is
   the universal grammar of "something is wrong", and none of these say that:
   they explain how the electives add up, or who to ask for the class group.
   Blue carries the same emphasis without the alarm, the fill drops to a tint
   so the panel sits in the page rather than interrupting it, and the outline
   becomes a single rule down the left, which is how an aside is marked in
   print. The heading takes its own line so the panel reads as a short note
   with a title rather than a sentence that ran into another one. */
.callout{display:flex;gap:15px;align-items:flex-start;
  background:color-mix(in srgb,var(--blue) 7%,transparent);
  border:1px solid color-mix(in srgb,var(--blue) 16%,var(--line));
  border-left:3px solid var(--blue);
  border-radius:0 var(--r) var(--r) 0;padding:18px 22px}
.callout svg{flex:0 0 auto;color:var(--blue);margin-top:1px}
.callout b{display:block;color:var(--ink);margin-bottom:5px}
.callout p{color:var(--ink-2);font-size:.95rem;margin:0}

/* why grid */
/* ============================================================
   REASONS, the "why CICT" list on the home page.
   Deliberately not cards: the page already carries the rail, the
   services grid and the stats tiles, and a fourth boxed grid
   would read as filler. Hairline rows instead, with the rule
   doing the separating that a border used to do.
   ============================================================ */
.reasons{list-style:none;margin:0;padding:0;border-top:1px solid var(--line)}
.reason{position:relative;display:grid;grid-template-columns:auto 1fr;
  gap:6px 16px;align-items:start;
  padding:clamp(20px,2.6vw,28px) clamp(6px,1.4vw,18px);
  border-bottom:1px solid var(--line);
  transition:background .32s ease}
.reason:hover{background:color-mix(in srgb,var(--blue) 5%,transparent)}
/* a rule grows down the left edge on hover, instead of a card lifting */
.reason::before{content:"";position:absolute;left:0;top:0;bottom:0;width:2px;
  background:var(--saffron);transform:scaleY(0);
  transition:transform .34s cubic-bezier(.2,.7,.2,1)}
.reason:hover::before{transform:scaleY(1)}

.r-ic{grid-row:span 2;display:grid;place-items:center;flex:0 0 auto;
  width:44px;height:44px;border-radius:13px;
  background:var(--blue-soft);color:var(--blue);
  transition:background .3s,color .3s}
.reason:nth-child(even) .r-ic{background:var(--saffron-soft);color:var(--saffron-text)}
.reason:hover .r-ic{background:var(--blue-fill);color:#fff}
.reason:nth-child(even):hover .r-ic{background:var(--saffron);color:#2b1a02}

.reason h3{font-size:clamp(1.08rem,1.6vw,1.25rem);line-height:1.3;margin:0;align-self:center}
.reason p{color:var(--muted);font-size:.96rem;line-height:1.6;margin:0}

@media (min-width:1024px){
  /* icon, claim and explanation each hold their own column */
  .reason{grid-template-columns:44px minmax(0,1fr) minmax(0,1.45fr);
    align-items:center;gap:0 clamp(20px,2.6vw,38px)}
  .r-ic{grid-row:auto}
  .reason h3{align-self:center}
}
@media (prefers-reduced-motion:reduce){
  .reason::before{transition:none}
}

.why-grid{display:grid;grid-template-columns:1fr;gap:20px}
.feat{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);padding:28px;
  box-shadow:var(--sh-sm);transition:transform .22s,border-color .22s}
.feat:hover{transform:translateY(-4px);border-color:color-mix(in srgb,var(--blue) 40%,var(--line))}
.feat .ic{width:52px;height:52px;border-radius:14px;display:grid;place-items:center;margin-bottom:18px;
  background:var(--blue-soft);color:var(--blue)}
.feat:nth-child(even) .ic{background:var(--saffron-soft);color:var(--saffron-text)}
.feat h3{font-size:1.14rem;margin-bottom:9px}
.feat p{color:var(--muted);font-size:.93rem}

/* ---- the four pillars (about page) ----
   Four short claims did not need four boxes and four icons; that is the stock
   feature grid, and the icons were standing in for ideas they could not carry
   (a mortarboard for "university pathways"). Set as a numbered list the way a
   prospectus sets its sections: hairlines and numerals do the structural work,
   so there is no card chrome at all. At the widest size it opens into three
   columns, which lines the titles up in a column of their own.
   .feat / .why-grid stay put: four other pages still use them. */
.pillars{border-top:1px solid var(--line)}
.pillar{display:grid;grid-template-columns:auto 1fr;column-gap:18px;row-gap:8px;
  align-items:baseline;padding:24px 0;border-bottom:1px solid var(--line)}
.pillar .no{font-family:var(--f-display);font-weight:600;font-size:1.45rem;line-height:1;
  color:var(--blue);font-variant-numeric:tabular-nums}
.pillar:nth-child(even) .no{color:var(--saffron-text)}
.pillar h3{font-family:var(--f-display);font-weight:600;letter-spacing:-.01em;margin:0;
  font-size:clamp(1.18rem,2vw,1.42rem);line-height:1.25;color:var(--ink)}
.pillar p{grid-column:2;margin:0;color:var(--ink-2);font-size:1rem;line-height:1.72;max-width:62ch}
@media (min-width:768px){
  .pillar{grid-template-columns:56px 1fr;column-gap:28px;padding:30px 0}
  .pillar .no{font-size:1.9rem}
}
@media (min-width:1280px){
  .pillar{grid-template-columns:76px minmax(190px,280px) 1fr;column-gap:40px;padding:36px 0}
  .pillar h3{grid-column:2}
  .pillar p{grid-column:3;max-width:58ch}
  .pillar .no{font-size:2.3rem}
}

/* services grid (5 categories) */
.sv-grid{display:grid;grid-template-columns:1fr;gap:18px}
/* A 1fr track floors at its item's min-content width, and a .feat carries 28px
   of padding each side plus a 52px icon tile before any text. Five of those
   across a 1024 viewport could not fit, so the row pushed the page sideways.
   min-width:0 lets the track win; the column count now steps up with room. */
.sv-grid .feat{display:flex;flex-direction:column;min-width:0}
.sv-grid > .feat:nth-child(5){grid-column:auto}
.sv-grid .feat p{flex:1}
.feat .more{margin-top:14px;color:var(--blue);font-weight:700;font-size:.85rem}
.feat:hover .more{text-decoration:underline}

/* framed banner/poster images */
.framed{border-radius:var(--r-lg);overflow:hidden;border:1px solid var(--line);
  box-shadow:var(--sh);background:var(--surface)}
.framed img{width:100%;height:auto;display:block}
.framed.pad{padding:14px}
figure.framed{margin:0}
figure.framed figcaption{padding:12px 4px 2px;font-size:.84rem;font-weight:600;color:var(--muted);text-align:center}

/* ============================================================
   ANNOUNCEMENTS, a framed poster on one side, a dated list on
   the other. Entries are rows, not cards: the date sits in its
   own column so the eye can run straight down it. Entries whose
   date has passed grey off rather than shouting.
   ============================================================ */
.ann-layout{display:grid;grid-template-columns:1fr;gap:clamp(28px,4vw,44px);align-items:start}
.ann-poster{margin:0;position:relative;border-radius:var(--r-lg);overflow:hidden;
  border:1px solid var(--line);background:var(--surface);box-shadow:var(--sh)}
.ann-poster img{width:100%;height:auto;display:block}
.ann-poster figcaption{display:flex;align-items:center;gap:9px;
  padding:13px 16px;border-top:1px solid var(--line);background:var(--surface);
  font-size:.8rem;font-weight:600;color:var(--muted)}
.ann-poster figcaption b{color:var(--ink)}
.ann-poster figcaption .dot{width:7px;height:7px;border-radius:50%;
  background:var(--saffron);flex:0 0 auto}

.ann-list{display:grid;gap:12px}
.ann{display:grid;grid-template-columns:60px 1fr;gap:18px;align-items:start;
  padding:18px 20px;background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r);box-shadow:var(--sh-sm);position:relative;
  transition:transform .24s cubic-bezier(.2,.7,.2,1),box-shadow .24s,border-color .24s}
.ann:hover{transform:translateX(4px);box-shadow:var(--sh);
  border-color:color-mix(in srgb,var(--blue) 32%,var(--line))}

.ann .adate{text-align:center;padding-right:18px;border-right:1px solid var(--line);
  align-self:stretch;display:flex;flex-direction:column;justify-content:center}
.ann .adate .d{font-family:var(--f-display);font-weight:600;font-size:1.75rem;line-height:1;
  color:var(--blue);font-variant-numeric:tabular-nums}
.ann .adate .m{font-size:.6rem;font-weight:800;letter-spacing:.11em;text-transform:uppercase;
  color:var(--muted);margin-top:5px}
html[lang="ta"] .ann .adate .d{font-family:"Noto Serif Tamil",var(--f-display)}
html[lang="ta"] .ann .adate .m,html[lang="ta"] .ann .st{letter-spacing:.02em;text-transform:none}

.ann .st{display:inline-block;font-size:.58rem;font-weight:800;letter-spacing:.11em;
  text-transform:uppercase;padding:3px 9px;border-radius:var(--pill);margin-bottom:8px;
  background:var(--blue-soft);color:var(--blue)}
.ann h3{font-size:1.05rem;line-height:1.3;margin-bottom:6px}
.ann p{color:var(--muted);font-size:.88rem;line-height:1.55}
.ann p a{color:var(--blue);font-weight:700;text-decoration:underline;text-underline-offset:2px}
.ann .go{display:inline-flex;align-items:center;gap:6px;margin-top:11px;
  font-weight:700;font-size:.82rem;color:var(--blue)}
.ann .go svg{transition:transform .24s ease}
.ann:hover .go svg{transform:translateX(4px)}
a.ann .go{pointer-events:none}

/* passed */
.ann.past .adate .d{color:var(--muted)}
.ann.past .st{background:var(--surface-2);color:var(--muted)}
/* open for enrolment */
.ann.open .adate .d{color:var(--saffron-text)}
.ann.open .st{background:var(--saffron-soft);color:var(--saffron-text)}
.ann.open .go{color:var(--saffron-text)}
.ann.open:hover{border-color:color-mix(in srgb,var(--saffron) 42%,var(--line))}

@media (min-width:1024px){
  /* list leads on the left, photo on the right; the photo stretches to the
     height of the list and crops rather than leaving a short column */
  .ann-layout{grid-template-columns:1.04fr .96fr;gap:clamp(36px,4vw,56px);align-items:stretch}
  .ann-poster{display:flex;flex-direction:column}
  /* the photo is landscape and the column is portrait, so it crops on the
     horizontal; anchor left of centre to keep the whole poster wall in frame */
  .ann-poster img{flex:1 1 auto;min-height:0;object-fit:cover;object-position:5% center}
}
@media (prefers-reduced-motion:reduce){
  .ann:hover{transform:none}
  .ann:hover .go svg{transform:none}
}

.notice-grid{display:grid;grid-template-columns:1fr;gap:20px}
.notice{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:24px 22px 20px;box-shadow:var(--sh-sm);position:relative;
  display:flex;flex-direction:column;
  transition:transform .24s cubic-bezier(.2,.7,.2,1),box-shadow .24s,border-color .24s}
.notice:hover{transform:translateY(-4px);box-shadow:var(--sh);
  border-color:color-mix(in srgb,var(--blue) 32%,var(--line))}

.ndate{display:flex;align-items:baseline;gap:9px;
  padding-bottom:14px;margin-bottom:15px;border-bottom:1px solid var(--line)}
.ndate .d{font-family:var(--f-display);font-weight:600;font-size:2rem;line-height:1;
  color:var(--blue);font-variant-numeric:tabular-nums}
.ndate .m{font-size:.66rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted)}
.ndate .st{margin-left:auto;align-self:center;font-size:.6rem;font-weight:800;
  letter-spacing:.11em;text-transform:uppercase;padding:4px 10px;border-radius:var(--pill);
  background:var(--blue-soft);color:var(--blue);white-space:nowrap}
html[lang="ta"] .ndate .d{font-family:"Noto Serif Tamil",var(--f-display)}
html[lang="ta"] .ndate .m,html[lang="ta"] .ndate .st{letter-spacing:.03em;text-transform:none}

/* an entry whose date has gone by */
.notice.past .ndate .d{color:var(--muted)}
.notice.past .ndate .st{background:var(--surface-2);color:var(--muted)}
/* an entry that is open for enrolment */
.notice.open .ndate .d{color:var(--saffron-text)}
.notice.open .ndate .st{background:var(--saffron-soft);color:var(--saffron-text)}
.notice.open:hover{border-color:color-mix(in srgb,var(--saffron) 42%,var(--line))}

.notice h3{font-size:1.18rem;margin-bottom:9px;line-height:1.28}
.notice p{color:var(--muted);font-size:.93rem;flex:1;line-height:1.6}
.notice p a{color:var(--blue);font-weight:700;text-decoration:underline;
  text-underline-offset:2px}
.notice p a:hover{color:var(--blue-deep)}
.notice .go{display:inline-flex;align-items:center;gap:7px;margin-top:18px;
  align-self:flex-start;font-weight:700;font-size:.85rem;color:var(--blue)}
a.notice .go{pointer-events:none}   /* whole card is the link in that case */
.notice.open .go{color:var(--saffron-text)}
.notice .go svg{transition:transform .24s ease}
.notice:hover .go svg{transform:translateX(4px)}
@media (prefers-reduced-motion:reduce){
  .notice:hover{transform:none}
  .notice:hover .go svg{transform:none}
}

/* graduates-by-year bar chart */
.years{display:grid;gap:10px;margin-top:10px}
.yrow{display:grid;grid-template-columns:64px 1fr 54px;align-items:center;gap:14px}
.yrow .yr{font-family:var(--f-display);font-weight:600;font-size:.95rem;color:var(--ink-2);
  font-variant-numeric:tabular-nums}
.yrow .track{height:26px;border-radius:8px;background:var(--surface-2);overflow:hidden;
  border:1px solid var(--line)}
/* display:block matters: the bar is a span, and an inline box ignores width,
   height and transform alike, so every bar in this chart was rendering at
   0x0 and the graph was eleven year labels with nothing between them */
.yrow .bar{display:block;height:100%;border-radius:7px;
  background:linear-gradient(90deg,var(--blue),var(--blue-deep));
  transform-origin:left;animation:grow .9s cubic-bezier(.2,.7,.2,1) both}
.yrow.peak .bar{background:linear-gradient(90deg,var(--saffron),var(--saffron-deep))}
.yrow .n{font-family:var(--f-display);font-weight:600;font-size:1.02rem;color:var(--ink);
  text-align:right;font-variant-numeric:tabular-nums}
@keyframes grow{from{transform:scaleX(0)}to{transform:scaleX(1)}}

/* Each row is a link down to its batch. The negative margin pulls the hover
   padding back out so the bars stay aligned with the heading above them
   rather than shifting inward once the rows became interactive. */
.yrow{text-decoration:none;border-radius:10px;padding:5px 7px;margin:0 -7px;
  transition:background .22s ease}
.yrow:hover{background:var(--surface-2)}
.yrow:hover .yr,.yrow:focus-visible .yr{color:var(--blue)}
.yrow:hover .n{color:var(--blue)}
.yrow:focus-visible{outline:2px solid var(--blue);outline-offset:1px;background:var(--surface-2)}
@media (hover:none){ .yrow:hover{background:transparent} }
/* 26px of content plus 5px padding is a 36px tap target, under the 44px
   minimum. It did not matter while these were plain rows; it does now. */
@media (max-width:767px){ .yrow{padding:9px 7px} }
.ytotal{display:flex;align-items:baseline;justify-content:space-between;gap:16px;margin-top:20px;
  padding-top:18px;border-top:2px solid var(--line);flex-wrap:wrap}
.ytotal b{font-family:var(--f-display);font-weight:600;font-size:2.2rem;color:var(--blue)}
.ytotal span{font-weight:600;color:var(--muted);font-size:.92rem}

/* graduate name lists */
.namelist{list-style:none;padding:0;margin:0;display:grid;
  grid-template-columns:1fr;gap:10px 22px;counter-reset:g}
.namelist li{counter-increment:g;display:flex;gap:11px;align-items:baseline;
  font-size:.96rem;color:var(--ink-2);font-weight:500}
.namelist li::before{content:counter(g,decimal-leading-zero);font-family:var(--f-display);
  font-weight:600;font-size:.8rem;color:var(--blue);min-width:20px;font-variant-numeric:tabular-nums}
.batch-head{display:flex;align-items:center;justify-content:space-between;gap:16px;
  flex-wrap:wrap;margin-bottom:20px}
.batch-head .count{display:inline-flex;align-items:center;gap:9px;background:var(--blue-soft);
  color:var(--blue);font-weight:700;font-size:.84rem;padding:8px 16px;border-radius:var(--pill)}

/* ------------------------------------------------------------
   DEGREE CLASSES.

   One place decides what each class looks like, because the same
   three words appear as a headline tile, as a badge beside a name
   and in a legend. Three separate colour choices would drift.

   Saffron is First Class and is used nowhere else on this page, so
   the eye can find it. Second Upper and Second Lower are two
   weights of the same blue rather than two different hues: they
   are neighbouring grades, and giving them unrelated colours would
   imply a bigger gap between them than there is.
   ------------------------------------------------------------ */
/* grid-auto-rows:1fr so every row is as tall as the tallest, which is what
   keeps the cards equal at two across: Second Lower carries three names and
   Gold medals carries one, so without it the two rows sit at different
   heights. At four across there is only one row and it costs nothing. */
.res-grid{display:grid;grid-template-columns:1fr;gap:16px;grid-auto-rows:1fr}

/* An award card, not a stat tile. The count alone was the least interesting
   thing on it: two First Classes in eleven years is a fact about two people,
   so the card names them. That also gives the number somewhere to lead to
   instead of sitting on its own in a box. */
.res{position:relative;overflow:hidden;background:var(--surface);
  border:1px solid var(--line);border-radius:var(--r-lg);padding:22px 20px 18px;
  box-shadow:var(--sh-sm)}
/* Set on .res.in, not .res: these cards are also .reveal, and .reveal's own
   transition is declared later in this file, so a transition on .res alone
   loses and the hover would move without its shadow. Opacity is kept in the
   list so the entrance still fades rather than snapping in. */
.res.in{transition:opacity .75s cubic-bezier(.2,.7,.2,1),
                   transform .45s cubic-bezier(.2,.7,.2,1),
                   box-shadow .45s ease,border-color .45s ease}
.res>*{position:relative;z-index:1}
.res:hover{transform:translateY(-6px);box-shadow:var(--sh);
  border-color:color-mix(in srgb,var(--rc) 38%,var(--line))}

/* ------------------------------------------------------------
   THE CLASS PALETTE, declared once for both places it is used:
   the results cards at the top of the page and the class groups
   inside every year's roll. Two declarations would drift, and a
   student badged saffron in one place and blue in the other is
   worse than no colour at all.

   --rc  the strong end of the strip and the rule
   --rc2 the light end, so the strip is a gradient rather than a
         flat band
   --rct text-safe: a rule may be as light as it likes, a label
         may not

   Second Lower mixes toward --muted rather than --surface-2.
   surface-2 is near-white in light and near-black in dark, so
   mixing into it made this colour vanish against the card in one
   theme; --muted is mid-tone in both.
   ------------------------------------------------------------ */
.res.first,.grp.first{--rc:var(--saffron-deep);--rct:var(--saffron-text)}
/* mixed toward --ink, which inverts with the theme, so this darkens on a pale
   band in light and lightens on a dark one in dark. Plain --blue sat at 4.44
   against the tinted band in dark mode, just under the 4.5 this size needs. */
.res.upper,.grp.upper{--rc:var(--blue-deep);
  --rct:color-mix(in srgb,var(--blue) 72%,var(--ink))}
.res.lower,.grp.lower{--rc:color-mix(in srgb,var(--blue) 55%,var(--muted));
  --rct:var(--ink-2)}
.res.medal{--rc:var(--saffron-deep);--rct:var(--saffron-text)}

/* A drawn edge, not a blurred disc. The soft blob behind the corner read as
   a rendering artefact rather than a decision, and at this card size there is
   no room for atmosphere: three crisp pixels say the same thing and stay
   sharp on every display. */
/* No strip and no texture. The gradient band faded to nothing at one end and
   the ruled corner read as a printing fault. What a result sheet actually
   does is head each column with its award, so the class name becomes a
   banded header across the top of the card: full width, tinted in the class
   colour, ruled off underneath. All of the colour, none of the ornament. */
.res{padding:0 0 18px;display:flex;flex-direction:column}
.res .lbl{order:-1;margin:0 0 16px;padding:11px 20px;
  background:color-mix(in srgb,var(--rc) 13%,transparent);
  border-bottom:1px solid color-mix(in srgb,var(--rc) 30%,var(--line));
  color:var(--rct)}
/* the 20px inset lives on each child, not on the card, because the header
   band has to run the full width while everything under it is indented */
.res .num{display:block;font-family:var(--f-display);font-weight:600;line-height:1;
  font-size:clamp(2.6rem,6vw,3.4rem);margin:2px 20px 6px;color:var(--ink);
  font-variant-numeric:tabular-nums}
.res .lbl{display:block;font-size:.67rem;font-weight:800;letter-spacing:.13em;
  text-transform:uppercase}
/* An unknown count is an em dash, never a zero. Zero claims nobody reached
   it; the dash says the number is not in yet. */
.res.tbd .num{color:var(--muted);font-size:clamp(1.6rem,4vw,2.1rem)}

.res .who{list-style:none;margin:16px 20px 0;padding:14px 0 0;display:grid;gap:7px;
  border-top:1px solid var(--line)}
.res .who li{display:flex;align-items:baseline;justify-content:space-between;gap:10px;
  font-size:.85rem;font-weight:600;color:var(--ink-2)}
.res .who i{font-style:normal;font-size:.78rem;color:var(--muted);
  font-variant-numeric:tabular-nums;flex:0 0 auto}
.res .note{margin:14px 20px 0;font-size:.8rem;font-weight:600;color:var(--muted)}

/* a single sweep of light when the card first arrives, keyed off .reveal so
   it fires with the entrance rather than on a timer of its own */
.res::after{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  transform:translateX(-130%);
  background:linear-gradient(105deg,transparent 42%,
    color-mix(in srgb,var(--rc) 26%,transparent) 50%,transparent 58%)}
.res.in::after{animation:sheen 1.15s cubic-bezier(.3,.6,.3,1) .25s both}
@keyframes sheen{to{transform:translateX(130%)}}

@media (hover:none){
  .res:hover{transform:none}
  .res:hover .aura{transform:none;opacity:.45}
  .res:hover .ic{transform:none}
}
@media (prefers-reduced-motion:reduce){
  .res,.res .aura,.res .ic{transition:none}
  .res.in::after{animation:none}
}

/* badge beside a name in the roll */
.gbadge{display:inline-flex;align-items:center;gap:5px;flex:0 0 auto;
  font-size:.68rem;font-weight:800;letter-spacing:.05em;text-transform:uppercase;
  padding:3px 9px;border-radius:var(--pill);white-space:nowrap;
  border:1px solid transparent}
.gbadge.first{background:var(--saffron-soft);color:var(--saffron-text);
  border-color:color-mix(in srgb,var(--saffron) 40%,var(--line))}
.gbadge.upper{background:var(--blue-soft);color:var(--blue);
  border-color:color-mix(in srgb,var(--blue) 32%,var(--line))}
.gbadge.lower{background:var(--surface-2);color:var(--ink-2);border-color:var(--line)}
/* the roll becomes name + badge, so the row has to be able to wrap */
.namelist li{flex-wrap:wrap}
.namelist li .nm{flex:1 1 auto;min-width:0}

.res-legend{display:flex;flex-wrap:wrap;gap:9px;margin-top:16px}

/* ------------------------------------------------------------
   BATCH CARD.

   Every year gets one, including the years with nothing in them
   yet. A year that has no poster still has a graduate count, and
   showing it as a quiet card keeps the run of years unbroken;
   dropping it would make the college look like it stopped
   graduating people between 2014 and 2021.
   ------------------------------------------------------------ */
/* One card per row, centred, all the same width. Inside, the sheet takes a
   fixed 300px column and the roll takes the rest, so every card is exactly
   as tall as a 3/4 poster at 300px and they come out identical without a
   magic height being set anywhere. */
.bgrid{display:flex;flex-direction:column;align-items:center;gap:20px;margin-top:26px}
.bcard{width:100%;max-width:940px;display:flex;flex-direction:column;
  border:1px solid var(--line);border-radius:var(--r-lg);background:var(--surface);
  box-shadow:var(--sh-sm);overflow:hidden;
  /* clears the sticky header when a year link lands here, otherwise the
     card's own heading arrives underneath it */
  scroll-margin-top:clamp(86px,13vh,132px);
  transition:transform .3s cubic-bezier(.2,.7,.2,1),box-shadow .3s ease,
             border-color .3s ease}
/* says "this is the one you asked for" without needing a script to say it */
.bcard:target{border-color:var(--blue);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--blue) 20%,transparent),var(--sh)}
.bcard:hover{transform:translateY(-3px);box-shadow:var(--sh)}
/* The convocation line is always in the markup, blank when the date is not
   known, so the six years missing a date sit level with the five that have
   one. Without it they came out 18px shorter, which is the sort of nearly
   aligned that reads worse than a real difference. */
.bcard .bhead{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;
  padding:16px 18px 0}
.bcard .yr{font-family:var(--f-display);font-weight:600;font-size:1.5rem;color:var(--ink);
  font-variant-numeric:tabular-nums}
.bcard .conv{font-size:.84rem;color:var(--muted);font-weight:600}
.bcard .n{display:inline-flex;align-items:center;gap:7px;background:var(--blue-soft);
  color:var(--blue);font-weight:700;font-size:.78rem;padding:5px 12px;border-radius:var(--pill)}
/* stacked on a phone, sheet beside roll from tablet up */
.bcard .bbody{padding:14px 18px 18px;display:grid;gap:18px;grid-template-columns:1fr;
  justify-items:center}
.bcard .bcar,.bcard .bwait{width:100%;max-width:340px}
/* the year still waiting on a poster holds exactly the space one would take,
   otherwise its card sits a third shorter than the other ten */
.bcard .bwait{aspect-ratio:17/25;min-height:0}

/* The roll is the only part that varies: nothing for 2014, fifteen names for
   2021. One fixed height that scrolls is what keeps every card the same size.
   A year with no names yet fills the same box with a line saying so, rather
   than leaving a hole the shape of a missing list. */
.bcard .roll{width:100%;height:200px;overflow-y:auto;padding-right:8px;
  scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--ink) 22%,transparent) transparent}
.bcard .roll::-webkit-scrollbar{width:6px}
.bcard .roll::-webkit-scrollbar-thumb{border-radius:3px;
  background:color-mix(in srgb,var(--ink) 22%,transparent)}
.bcard .roll .namelist{grid-template-columns:1fr;gap:10px}
.bcard .roll .namelist li{font-size:.92rem}

/* A class group is a bounded block, not a heading with a list loose beneath
   it. The tinted rule down the left is what stops a two-column list reading
   as though the heading above column one also covers column two. */
.grp{margin:0 0 14px;padding:0 0 0 12px;border-left:3px solid var(--line)}
.grp:last-child{margin-bottom:0}

/* A group that holds a class becomes a tinted panel, not just a tinted rule.
   These are the students the section exists to point at, so the panel gives
   them a boundary and their names take full ink at a heavier weight, while
   everyone else stays steady at --ink-2. The difference has to be visible at
   a glance down a card, which a coloured left edge alone was not. */
.grp.first,.grp.upper,.grp.lower{
  border-left-color:var(--rc);
  background:color-mix(in srgb,var(--rc) 9%,transparent);
  border-radius:0 var(--r-sm) var(--r-sm) 0;
  padding:11px 13px 12px 13px;margin-bottom:12px}
.grp.first li,.grp.upper li,.grp.lower li{color:var(--ink);font-weight:700;font-size:.95rem}

.grp h4{display:flex;align-items:center;gap:9px;margin:0 0 9px;
  font-family:var(--f-body);font-size:.7rem;font-weight:800;letter-spacing:.11em;
  text-transform:uppercase;color:var(--muted)}
.grp.first h4,.grp.upper h4,.grp.lower h4{color:var(--rct)}
.grp h4 b{font-weight:800;font-size:.68rem;letter-spacing:0;padding:1px 7px;
  border-radius:var(--pill);background:var(--surface-2);color:var(--ink-2)}
.grp.first h4 b,.grp.upper h4 b,.grp.lower h4 b{
  background:color-mix(in srgb,var(--rc) 22%,transparent);color:var(--rct)}
.grp ul{list-style:none;margin:0;padding:0;display:grid;gap:7px 20px;
  grid-template-columns:1fr}
.grp li{font-size:.92rem;font-weight:500;color:var(--ink-2)}
/* the named classes stay one column however wide the card gets: they hold a
   handful of people and splitting two names over two columns invites exactly
   the misreading this layout exists to prevent */
@media (min-width:768px){ .grp.none ul{grid-template-columns:1fr 1fr} }
.bcard .rollnote{width:100%;height:200px;margin:0;display:flex;align-items:center;
  justify-content:center;text-align:center;padding:0 10px;
  color:var(--muted);font-size:.9rem;font-weight:600}

@media (min-width:768px){
  .bcard .bbody{grid-template-columns:340px 1fr;align-items:start;justify-items:stretch;
    gap:24px;padding:16px 22px 22px}
  /* 340px at 17/25 is 500px of sheet, so the roll is given the same 500 and
     the card comes out the same height whether the year has fifteen names
     or none at all */
  .bcard .roll,.bcard .rollnote{height:500px}
  .bcard .roll .namelist{grid-template-columns:1fr 1fr;gap:10px 20px}
}

/* One poster at a time, the rest behind it.

   The sheets are not one shape: they run from 0.60 to 0.75 wide-to-tall
   across the years. A track that takes its height from the current slide
   would jump every time it advanced, and the arrows would move under the
   cursor. So the track is a fixed 3/4 band and the sheet is contained
   inside it, never cropped. Letterboxing a poster is the lesser evil;
   cropping one takes the names off. */
.bcar{position:relative}
/* 17/25 is 0.68, which sits in the middle of the range these sheets actually
   occupy (0.60 to 0.75). Matching the middle keeps the mat down to a couple
   of dozen pixels either way instead of a broad band on most of them. */
/* The slides are absolutely positioned, not stacked in one grid area. As grid
   items their row was auto-sized, so height:100% had no definite height to
   resolve against; the slide took its content's height, 702px inside a 500px
   band, and overflow:hidden simply cut the poster off at the bottom. Pinned
   to inset:0 they are the track's size by definition. */
.bcar .btrack{position:relative;aspect-ratio:17/25;overflow:hidden;
  border:1px solid var(--line);border-radius:var(--r);background:var(--surface-2)}
.bcar .bslide{position:absolute;inset:0;display:block;width:100%;height:100%;
  padding:0;border:0;background:none;cursor:zoom-in;opacity:0;pointer-events:none;
  transition:opacity .55s ease}
.bcar .bslide.on{opacity:1;pointer-events:auto}
.bcar .bslide:focus-visible{outline:3px solid var(--blue);outline-offset:-3px}
.bcar .bslide img{width:100%;height:100%;object-fit:contain;display:block}

.bcar .nav{position:absolute;top:50%;transform:translateY(-50%);z-index:2;
  width:38px;height:38px;border-radius:50%;display:grid;place-items:center;
  border:1px solid var(--line);background:color-mix(in srgb,var(--surface) 88%,transparent);
  color:var(--ink);cursor:pointer;box-shadow:var(--sh-sm);
  backdrop-filter:blur(6px);transition:background .2s ease,transform .2s ease}
.bcar .nav:hover{background:var(--surface);transform:translateY(-50%) scale(1.08)}
.bcar .nav:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
.bcar .prev{left:10px}
.bcar .next{right:10px}
/* a single sheet is not a carousel, so it loses the furniture entirely */
.bcar.one .nav,.bcar.one .dots{display:none}

.bcar .dots{position:absolute;left:50%;bottom:10px;transform:translateX(-50%);z-index:2;
  display:flex;gap:7px;padding:6px 10px;border-radius:var(--pill);
  background:color-mix(in srgb,var(--surface) 84%,transparent);
  border:1px solid var(--line);backdrop-filter:blur(6px)}
.bcar .dot{width:7px;height:7px;border-radius:50%;padding:0;border:0;cursor:pointer;
  background:color-mix(in srgb,var(--ink) 28%,transparent);transition:background .25s ease,width .25s ease}
.bcar .dot.on{width:18px;border-radius:var(--pill);background:var(--blue)}
.bcar .dot:focus-visible{outline:2px solid var(--blue);outline-offset:2px}

/* the waiting state: says what is missing and stays quiet about it */
.bwait{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;
  min-height:132px;padding:20px;text-align:center;border-radius:var(--r);
  border:1px dashed color-mix(in srgb,var(--blue) 26%,var(--line));
  background:var(--surface-2);color:var(--muted);font-size:.86rem;font-weight:600}
.bwait svg{color:color-mix(in srgb,var(--blue) 46%,var(--muted))}

/* Four result tiles go 2x2 on a phone rather than a single column: the
   numbers are meant to be compared, and comparing needs them in view
   together. They only earn a single row once there is width for it. */
/* two up before four: at 768 a four-across row leaves each card too narrow
   for a name and a year on one line, which is the whole point of the card */
@media (min-width:640px){ .res-grid{grid-template-columns:1fr 1fr} }
@media (min-width:1100px){ .res-grid{grid-template-columns:repeat(4,1fr)} }
@media (hover:none){
  .bcard:hover{transform:none}
  .bcar .nav:hover{transform:translateY(-50%)}
}
@media (prefers-reduced-motion:reduce){
  .bcard,.bcar .bslide,.bcar .nav,.bcar .dot{transition:none}
}

/* curriculum tables */
.tablewrap{overflow-x:auto;border:1px solid var(--line);border-radius:var(--r);background:var(--surface)}
table.cur{width:100%;border-collapse:collapse;min-width:520px;font-size:.93rem}
table.cur caption{text-align:left;padding:16px 18px 12px;font-family:var(--f-display);
  font-weight:600;font-size:1.05rem;color:var(--ink)}
table.cur th{text-align:left;padding:11px 18px;background:var(--surface-2);color:var(--muted);
  font-weight:800;font-size:.68rem;letter-spacing:.12em;text-transform:uppercase;
  border-block:1px solid var(--line)}
table.cur td{padding:12px 18px;border-bottom:1px solid var(--line);color:var(--ink-2)}
table.cur tr:last-child td{border-bottom:0}
table.cur td.code{font-family:ui-monospace,Consolas,monospace;font-weight:600;color:var(--blue);
  white-space:nowrap;font-size:.86rem}
table.cur td.cr{text-align:right;font-variant-numeric:tabular-nums;font-weight:600;color:var(--ink)}
table.cur tbody.sem th{background:var(--blue-soft);color:var(--blue);letter-spacing:.1em}
.opt{display:inline-block;margin-left:8px;font-size:.64rem;font-weight:800;letter-spacing:.08em;
  text-transform:uppercase;color:var(--saffron-text);background:var(--saffron-soft);
  padding:2px 8px;border-radius:var(--pill);vertical-align:middle}

/* segmented control, year switcher above the timetable */
.seg{display:inline-flex;gap:3px;padding:4px;border:1px solid var(--line);
  border-radius:var(--pill);background:var(--surface-2)}
.seg button{border:0;background:none;cursor:pointer;font-family:var(--f-body);
  font-weight:600;font-size:.87rem;color:var(--muted);padding:8px 17px;
  border-radius:var(--pill);white-space:nowrap;transition:background .2s,color .2s,box-shadow .2s}
.seg button:hover{color:var(--ink-2)}
.seg button[aria-selected="true"]{background:var(--surface);color:var(--blue);
  box-shadow:var(--sh-sm)}
.seg button:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
.ytt[hidden]{display:none}

/* class timetable */
table.tt{width:100%;border-collapse:collapse;min-width:460px;font-size:.94rem}
table.tt th{text-align:left;padding:12px 18px;background:var(--surface-2);color:var(--muted);
  font-weight:800;font-size:.68rem;letter-spacing:.12em;text-transform:uppercase;
  border-block:1px solid var(--line)}
table.tt td{padding:13px 18px;border-bottom:1px solid var(--line);color:var(--ink-2)}
table.tt td.day{font-weight:700;color:var(--ink);white-space:nowrap}
table.tt td.time{font-variant-numeric:tabular-nums;white-space:nowrap;color:var(--blue);font-weight:600}
table.tt tr:last-child td{border-bottom:0}
table.tt tr.sat td.day{color:var(--blue)}
table.tt tr.sun td.day{color:var(--saffron-text)}

/* ============================================================
   TABS, underline style, deliberately quiet. The bar sticks
   under the header so the section labels stay reachable while
   reading long content.
   ============================================================ */
.tabbar{position:sticky;top:68px;z-index:40;background:var(--bg);
  border-bottom:1px solid var(--line);margin-bottom:clamp(30px,4vw,46px)}
.tablist{display:flex;gap:2px;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}
.tablist::-webkit-scrollbar{display:none}
.tab{position:relative;flex:0 0 auto;border:0;background:none;cursor:pointer;
  font-family:var(--f-body);font-weight:600;font-size:.95rem;color:var(--muted);
  padding:16px 4px;margin-right:26px;white-space:nowrap;transition:color .2s}
.tab:last-child{margin-right:0}
.tab::after{content:"";position:absolute;inset:auto 0 -1px 0;height:2px;
  background:var(--blue);transform:scaleX(0);transform-origin:left;
  transition:transform .28s cubic-bezier(.2,.7,.2,1)}
.tab:hover{color:var(--ink-2)}
.tab[aria-selected="true"]{color:var(--blue)}
.tab[aria-selected="true"]::after{transform:scaleX(1)}
.tab:focus-visible{outline:2px solid var(--blue);outline-offset:-4px;border-radius:6px}
.tab .num{font-family:var(--f-display);font-size:.78rem;margin-right:7px;opacity:.65}
.tabpanel[hidden]{display:none}
.tabpanel{animation:tabIn .34s cubic-bezier(.2,.7,.2,1) both}
@keyframes tabIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
@media (min-width:768px){.tabbar{top:82px}.tab{font-size:1rem;margin-right:34px}}
@media (min-width:1024px){.tabbar{top:88px}}
@media (prefers-reduced-motion:reduce){
  .tabpanel{animation:none}
  .tab::after{transition:none}
}

/* ============================================================
   GPA CALCULATOR
   ============================================================ */
.gpa{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:clamp(20px,3vw,30px);box-shadow:var(--sh-sm)}
.gpa-rows{display:grid;gap:10px;margin:18px 0}
.gpa-row{display:grid;grid-template-columns:1fr 74px 92px 38px;gap:9px;align-items:center}
.gpa-row input,.gpa-row select{width:100%;padding:10px 11px;border:1.5px solid var(--line);
  border-radius:var(--r-sm);background:var(--surface-2);color:var(--ink);
  font-family:var(--f-body);font-size:.92rem}
.gpa-row input:focus,.gpa-row select:focus{outline:none;border-color:var(--blue);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--blue) 16%,transparent)}
.gpa-row .del{display:grid;place-items:center;height:38px;border-radius:var(--r-sm);
  border:1.5px solid var(--line);background:none;color:var(--muted);cursor:pointer;
  transition:.18s}
.gpa-row .del:hover{border-color:#d92d20;color:#d92d20;background:rgba(217,45,32,.06)}
.gpa-head{display:grid;grid-template-columns:1fr 74px 92px 38px;gap:9px;
  font-size:.64rem;font-weight:800;letter-spacing:.12em;text-transform:uppercase;
  color:var(--muted);padding-inline:2px}
.gpa-actions{display:flex;gap:10px;flex-wrap:wrap;align-items:center}

/* result readout */
.gpa-out{display:flex;align-items:center;gap:20px;flex-wrap:wrap;margin-top:22px;
  padding:20px 22px;border-radius:var(--r);background:var(--blue-ink);color:#fff}
.gpa-out .val{font-family:var(--f-display);font-weight:600;font-size:2.6rem;line-height:1;
  font-variant-numeric:tabular-nums}
.gpa-out .lbl{font-size:.64rem;font-weight:800;letter-spacing:.14em;
  text-transform:uppercase;color:rgba(255,255,255,.55);display:block;margin-bottom:5px}
.gpa-out .cls{font-family:var(--f-display);font-size:1.28rem;color:var(--saffron)}
.gpa-out .cr{margin-left:auto;text-align:right;font-size:.82rem;color:rgba(255,255,255,.7)}
/* the credits the level figure is averaged over; saffron while it is only
   part of the level, so a 4.00 from one subject cannot pass for a finished one */
.gpa-out .based{display:block;margin-top:6px;font-size:.76rem;font-weight:700;
  letter-spacing:.04em;color:rgba(255,255,255,.55);font-variant-numeric:tabular-nums}
.gpa-out .based.partial{color:var(--saffron)}
html[lang="ta"] .gpa-out .val,html[lang="ta"] .gpa-out .cls{font-family:"Noto Serif Tamil",var(--f-display)}

/* grade / class reference tables */
.scale{display:grid;grid-template-columns:repeat(2,1fr);gap:7px}
.scale div{display:flex;justify-content:space-between;gap:10px;padding:9px 13px;
  border:1px solid var(--line);border-radius:var(--r-sm);background:var(--surface-2);
  font-size:.88rem;font-weight:600;color:var(--ink-2)}
.scale div b{color:var(--blue);font-variant-numeric:tabular-nums}
@media (min-width:768px){.scale{grid-template-columns:repeat(3,1fr)}}

.classlist{display:grid;gap:9px}
.classrow{display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:13px 16px;border:1px solid var(--line);border-radius:var(--r-sm);
  background:var(--surface);font-weight:600}
.classrow.top{border-color:color-mix(in srgb,var(--saffron) 45%,var(--line));
  background:var(--saffron-soft)}
.classrow .gp{font-family:var(--f-display);color:var(--blue);font-variant-numeric:tabular-nums;
  white-space:nowrap}
.classrow.top .gp{color:var(--saffron-text)}
html[lang="ta"] .classrow .gp{font-family:"Noto Serif Tamil",var(--f-display)}

/* fee table */
table.fees{width:100%;border-collapse:collapse;min-width:520px;font-size:.93rem}
table.fees th{text-align:left;padding:11px 16px;background:var(--surface-2);color:var(--muted);
  font-weight:800;font-size:.66rem;letter-spacing:.12em;text-transform:uppercase;
  border-block:1px solid var(--line)}
table.fees th.r,table.fees td.r{text-align:right;font-variant-numeric:tabular-nums}
table.fees td{padding:12px 16px;border-bottom:1px solid var(--line);color:var(--ink-2)}
table.fees tr:last-child td{border-bottom:0}
table.fees tr.total td{font-weight:700;color:var(--ink);background:var(--surface-2)}

@media (max-width:560px){
  .gpa-row,.gpa-head{grid-template-columns:1fr 60px 78px 34px;gap:7px}
  .gpa-out .cr{margin-left:0;text-align:left;width:100%}
}

/* key-value fact strip */
.facts{display:grid;grid-template-columns:1fr 1fr;gap:16px}
/* A 1fr track will not shrink under its item's min-content width, so one long
   Tamil label was widening its column and scrolling the page. min-width:0 lets
   the track win; the wrap rule then breaks the label instead of the layout. */
.fact{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);padding:20px;
  box-shadow:var(--sh-sm);min-width:0;overflow-wrap:break-word}
.fact small{display:block;font-weight:800;font-size:.64rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--muted);margin-bottom:7px}
.fact b{font-family:var(--f-display);font-weight:600;font-size:1.2rem;color:var(--ink);line-height:1.2}

/* topic pills */
.topics{display:flex;flex-wrap:wrap;gap:10px}
.topic{display:inline-flex;align-items:center;gap:8px;background:var(--surface);
  border:1px solid var(--line);border-radius:var(--pill);padding:9px 16px;
  font-size:.89rem;font-weight:600;color:var(--ink-2);transition:.18s}
.topic:hover{border-color:var(--blue);color:var(--blue);transform:translateY(-2px)}
.topic .d{width:7px;height:7px;border-radius:50%;background:var(--saffron);flex:0 0 auto}

/* ============================================================
   EVENT TEMPLATE, student life / campus events
   Duplicate one <article class="event"> block per event.
   ============================================================ */
.event{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:clamp(24px,3vw,38px);box-shadow:var(--sh-sm);position:relative;overflow:hidden}
.event + .event{margin-top:26px}
.event::before{content:"";position:absolute;inset:0 0 auto 0;height:4px;
  background:linear-gradient(90deg,var(--saffron),var(--blue))}
.event-head{display:flex;align-items:flex-start;justify-content:space-between;
  gap:20px;flex-wrap:wrap;margin-bottom:8px}
/* The colour sits in the icon disc, not in the whole tag. A fully tinted pill
   competes with the heading beside it for the same glance; a neutral tag with
   one coloured mark reads as a category and then gets out of the way. Sentence
   case for the same reason, uppercase at .7rem was shouting a footnote. */
.event-label{display:inline-flex;align-items:center;gap:10px;
  background:var(--surface-2);color:var(--ink);border:1px solid var(--line);
  font-weight:700;font-size:.84rem;letter-spacing:.005em;text-transform:none;
  padding:5px 15px 5px 5px;border-radius:var(--pill);white-space:nowrap}
.event-label .ic{flex:0 0 auto;width:28px;height:28px;border-radius:50%;
  display:grid;place-items:center;background:var(--saffron-soft);color:var(--saffron-text)}
.event-label.blue .ic{background:var(--blue-soft);color:var(--blue)}
.event h3{font-size:clamp(1.4rem,2.8vw,2rem);margin:0}
.event .when{font-weight:600;font-size:.9rem;color:var(--muted);margin-top:7px;
  display:flex;align-items:center;gap:8px}
.event .when .d{width:6px;height:6px;border-radius:50%;background:var(--saffron)}
.event .lede{color:var(--muted);font-size:1.02rem;max-width:70ch;margin-top:14px}


/* portrait strip */
.faces{display:flex;flex-wrap:wrap;gap:12px}
.face{width:76px;text-align:center}
.face img{width:76px;height:76px;border-radius:50%;object-fit:cover;object-position:center 20%;
  border:2px solid var(--line);box-shadow:var(--sh-sm)}
.face span{display:block;font-size:.7rem;font-weight:600;color:var(--muted);margin-top:7px;line-height:1.3}

/* where a table came from, and which version of it. Quieter than the copy it
   sits under, because it is provenance rather than argument. */
.head .src{margin-top:12px;font-size:.86rem;line-height:1.6;color:var(--muted)}
.head .src a{color:var(--blue);font-weight:600;text-decoration:underline}

/* ---- elective baskets (Moratuwa) ----
   Twelve credits chosen from twenty-six modules is a real decision, and a flat
   list of twenty-six hides the shape of it. Grouped by specialisation the
   choice reads as six directions rather than one long menu. A handful of
   modules sit in more than one basket, so those are marked: picking one can
   count toward two directions at once, which is worth knowing before you
   choose. */
.baskets{display:grid;grid-template-columns:1fr;gap:18px}
.basket{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);
  padding:20px 22px 18px;box-shadow:var(--sh-sm)}
.basket h3{font-family:var(--f-display);font-weight:600;font-size:1.1rem;color:var(--ink);
  margin:0 0 13px;padding-bottom:11px;border-bottom:1px solid var(--line)}
.basket ul{list-style:none;padding:0;margin:0;display:grid;gap:9px}
.basket li{display:flex;flex-wrap:wrap;align-items:baseline;gap:9px;
  font-size:.92rem;color:var(--ink-2);line-height:1.5}
.basket li b{flex:0 0 auto;font-family:ui-monospace,Consolas,monospace;font-weight:600;
  font-size:.82rem;color:var(--blue);letter-spacing:-.01em}
.basket .shared{flex:0 0 auto;font-style:normal;font-weight:700;font-size:.62rem;
  letter-spacing:.1em;text-transform:uppercase;padding:2px 7px;border-radius:var(--pill);
  color:var(--saffron-text);background:var(--saffron-soft)}
html[lang="ta"] .basket .shared{letter-spacing:.02em;text-transform:none;font-size:.72rem}
@media (min-width:768px){.baskets{grid-template-columns:repeat(2,1fr)}}
@media (min-width:1280px){.baskets{grid-template-columns:repeat(3,1fr)}}

/* ---- student life galleries ----
   auto-fill rather than a column count, because the four events hold three,
   five, six and eight photos and a fixed count leaves an orphan on one of
   them at almost every width. The browser fits as many as the row takes and
   the last row simply fills. Square tiles, so a mixed set of portrait and
   landscape photographs still reads as one grid. */
/* 115px floor, not 150: the column inside an event card is about 259px on a
   360px phone, and a 150px floor only fits one tile there, which turns the
   eight-photo event into a very long scroll. 115 fits two. */
.egrid{display:grid;gap:clamp(8px,1.4vw,14px);margin-top:20px;
  grid-template-columns:repeat(auto-fill,minmax(min(115px,100%),1fr))}
.eshot{position:relative;padding:0;border:1px solid var(--line);cursor:pointer;
  border-radius:var(--r);overflow:hidden;background:var(--surface-2);
  aspect-ratio:1/1;display:block;
  transition:transform .3s cubic-bezier(.2,.7,.2,1),box-shadow .3s,border-color .3s}
.eshot img{width:100%;height:100%;object-fit:cover;display:block;
  transition:transform .55s cubic-bezier(.2,.7,.2,1)}
.eshot:hover{transform:translateY(-4px);z-index:1;
  border-color:color-mix(in srgb,var(--blue) 40%,var(--line));
  box-shadow:0 16px 34px -14px rgba(var(--cast),.45)}
.eshot:hover img{transform:scale(1.07)}
.eshot:focus-visible{outline:2px solid var(--blue);outline-offset:3px}
/* a corner glint so a tile reads as openable rather than decorative */
.eshot::after{content:"";position:absolute;right:9px;bottom:9px;width:22px;height:22px;
  border-radius:50%;opacity:0;transform:scale(.7);
  background:rgba(10,19,48,.55) url("data:image/svg+xml;charset=utf-8,\
%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' \
stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7'/%3E%3C/svg%3E") center/12px no-repeat;
  backdrop-filter:blur(3px);transition:opacity .25s,transform .25s}
.eshot:hover::after,.eshot:focus-visible::after{opacity:1;transform:none}
@media (min-width:768px){
  .egrid{grid-template-columns:repeat(auto-fill,minmax(min(190px,100%),1fr))}
}
@media (min-width:1280px){
  .egrid{grid-template-columns:repeat(auto-fill,minmax(min(215px,100%),1fr))}
}
@media (prefers-reduced-motion:reduce){
  .eshot,.eshot img,.eshot::after{transition:none}
  .eshot:hover{transform:none}
  .eshot:hover img{transform:none}
}

/* jump strip: one chip per event, with how many photos are in it */
.ev-jump{display:flex;flex-wrap:wrap;gap:9px}
.ev-chip{display:inline-flex;align-items:center;gap:8px;padding:8px 14px;
  border-radius:var(--pill);border:1px solid var(--line);background:var(--surface);
  font-size:.85rem;font-weight:600;color:var(--ink-2);
  transition:border-color .2s,color .2s,background .2s}
.ev-chip svg{color:var(--saffron-text);flex:0 0 auto}
.ev-chip b{font-weight:700;font-size:.74rem;padding:2px 7px;border-radius:99px;
  background:var(--blue-soft);color:var(--blue);font-variant-numeric:tabular-nums}
.ev-chip:hover{border-color:color-mix(in srgb,var(--blue) 45%,var(--line));
  color:var(--ink);background:var(--surface-2)}
.ev-chip:focus-visible{outline:2px solid var(--blue);outline-offset:2px}

/* ---- director's letter (about page) ----
   Set as a signed letter, not a testimonial. No oversized quote glyph and no
   circular avatar: both are the stock treatment and read as such. The portrait
   sits as an editorial plate, the prose holds a reading measure, and the block
   closes on a signature the way a letter closes. */
.dir{display:grid;grid-template-columns:1fr;gap:clamp(24px,4vw,38px);align-items:start}
.dir-plate{margin:0;border-radius:var(--r);overflow:hidden;border:1px solid var(--line);
  background:var(--surface);box-shadow:var(--sh-sm);max-width:250px}
.dir-plate img{width:100%;display:block;aspect-ratio:4/5;object-fit:cover;object-position:center 20%}
/* One measure for the whole letter. Capping the paragraphs in ch and leaving
   the lede uncapped gave them different right edges, because a ch is wider in
   the display face; bounding the column instead lines them up at any size. */
.dir-letter{max-width:600px}
.dir-lede{font-family:var(--f-display);font-weight:500;letter-spacing:-.012em;
  font-size:clamp(1.2rem,2.1vw,1.56rem);line-height:1.45;color:var(--ink);margin:0 0 20px}
.dir-body p{color:var(--ink-2);font-size:1.04rem;line-height:1.78;margin:0 0 14px}
.dir-body p:last-child{margin-bottom:0}
.dir-sign{margin-top:26px;padding-top:20px;border-top:1px solid var(--line)}
.dir-sign .nm{display:block;font-family:var(--f-display);font-weight:600;font-size:1.2rem;color:var(--ink)}
.dir-sign .rl{display:block;margin-top:6px;font-weight:700;font-size:.71rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--saffron-text)}
.dir-sign .qs{display:block;margin-top:5px;font-size:.87rem;color:var(--muted)}
/* Tamil needs more leading and reads badly in caps at label sizes */
html[lang="ta"] .dir-lede{line-height:1.62;letter-spacing:0}
html[lang="ta"] .dir-sign .rl{letter-spacing:.04em;text-transform:none;font-size:.83rem}
@media (min-width:768px){
  .dir{grid-template-columns:minmax(190px,240px) 1fr;gap:clamp(30px,4vw,46px)}
  .dir-plate{max-width:none}
}
@media (min-width:1280px){.dir{grid-template-columns:290px 1fr;gap:58px}}

/* gallery + placeholders */
/* ============================================================
   STUDENT LIFE PHOTO WALL, built by js/main.js.

   A hanging band, not a grid. Every tile has its own width,
   height and drop (--w/--h/--o, all multiples of --u), so
   neither the top nor the bottom edge lines up and the block
   has no rectangular outline to read as a frame.

   Tiles hold two stacked images so a change crossfades in
   place. Nothing ever moves position, so the wall never
   reflows under the reader.
   ============================================================ */
/* the wall section pulls its heading in close, so heading and photos clear a
   15in laptop together once the sticky header is allowed for */
.section.tight{padding-block:clamp(46px,6vw,72px)}
.section.tight .head.feature{margin-bottom:clamp(26px,3vw,34px)}

/* narrow screens: a fan has nowhere to open, so it becomes a plain two-up
   grid, the columns dissolve and every tile takes the same shape */
/* PHONE AND TABLET: a swipe rail, not a mosaic.
   Seven photos in a two-column grid is a wall you scroll past on a phone; one
   at a time, dragged, is something you actually look at. The next card is left
   peeking at the edge so the gesture is discoverable without a caption telling
   anyone to swipe. Snapping is native, so the momentum is the platform's. */
.wall{--g:clamp(10px,2.4vw,16px);--band:clamp(200px,23vw,308px);
  display:flex;gap:var(--g);
  overflow-x:auto;overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory;
  scrollbar-width:none;-ms-overflow-style:none;padding-bottom:4px}
.wall::-webkit-scrollbar{display:none}
.wall.empty{display:none}
.wcol{display:contents}

/* how far along the strip you are, and how much of it there is */
.wall-swipe{display:flex;align-items:center;gap:14px;margin-top:18px}
.wall-swipe[hidden]{display:none}
.wsw-count{flex:0 0 auto;font-family:var(--f-display);font-weight:600;
  font-size:.92rem;color:var(--muted);font-variant-numeric:tabular-nums}
.wsw-count b{color:var(--ink);font-weight:600}
.wsw-bar{position:relative;flex:1;height:3px;border-radius:99px;
  background:color-mix(in srgb,var(--line) 85%,transparent)}
/* sized to the share of the strip on screen and slid by the scroll, so it
   reads as the strip's own position rather than a decorative fill */
.wsw-bar i{position:absolute;top:-1px;bottom:-1px;left:0;width:20%;
  border-radius:99px;background:var(--blue);
  transition:left .12s linear,width .18s ease}
@media (prefers-reduced-motion:reduce){.wsw-bar i{transition:none}}
@media (min-width:1024px){
  /* back to the fan: no rail, no snapping, columns centred on one axis */
  .wall{display:flex;align-items:center;justify-content:center;
    overflow:visible;scroll-snap-type:none;padding-bottom:0}
  .wall-swipe{display:none}
  .wcol{display:flex;flex-direction:column;justify-content:center;
    flex:var(--w,1) 1 0;min-width:0;gap:var(--g)}
  .wt{flex:0 1 auto;scroll-snap-align:none;
    aspect-ratio:auto;height:calc(var(--h,1) * var(--band))}
}

.wt{position:relative;display:block;overflow:hidden;isolation:isolate;
  /* one card at a time on the rail, with the next just showing */
  flex:0 0 min(84%,380px);scroll-snap-align:center;
  aspect-ratio:4/3;padding:0;font:inherit;cursor:pointer;
  border-radius:clamp(16px,2.2vw,30px);
  border:1px solid color-mix(in srgb,var(--line) 70%,transparent);
  background:var(--surface-2);
  /* entrance: each tile rises to its own hanging depth, in sequence */
  opacity:0;transform:translateY(30px) scale(.96);
  transition:opacity .75s cubic-bezier(.2,.7,.2,1),transform .75s cubic-bezier(.2,.7,.2,1),
             box-shadow .4s,border-color .3s}
.wall.in .wt{opacity:1;transform:none;
  transition-delay:calc(var(--i,0) * 110ms),calc(var(--i,0) * 110ms),0s,0s}
/* a tile with no photo yet stays a quiet panel rather than a hole */
.wt:not(.has){background:
  repeating-linear-gradient(135deg,var(--surface-2) 0 9px,var(--surface) 9px 18px)}

/* Every tile drifts continuously, each on its own clock and heading, so the
   wall is never quite still even between changes. Transform only, so it stays
   on the compositor; paused while a layer is hidden. */
@keyframes wall-drift{
  from{transform:scale(1.06) translate(0,0)}
  to{transform:scale(1.17) translate(var(--kx,0),var(--ky,0))}
}
.wt img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity 1.1s ease;
  animation:wall-drift var(--kb,24s) var(--kd,0s) ease-in-out infinite alternate;
  animation-play-state:paused}
.wt img.on{opacity:1;animation-play-state:running}
/* first paint should not fade in from nothing */
.wt img.shim{transition:none}

.wt.has:hover{transform:translateY(-5px);z-index:2;
  box-shadow:0 14px 34px -12px rgba(var(--cast),.45);
  border-color:color-mix(in srgb,var(--blue) 38%,var(--line))}
.wt.has:hover img.on{animation-play-state:paused}

@media (prefers-reduced-motion:reduce){
  .wt{opacity:1;transform:none;transition:none}
  .wt img{animation:none;transition:opacity .3s linear}
  .wt.has:hover{transform:none}
}

/* ============================================================
   PHOTO VIEWER, opened from a wall tile. The stage animates out
   of the thumbnail that was clicked (js measures both rects and
   sets the starting transform), so the photo grows from where
   the reader was looking instead of appearing over it.
   ============================================================ */
body.lb-open{overflow:hidden}
/* anchored to the top of the frame rather than centred, so a landscape photo
   and a portrait one both open in the same place instead of the shorter one
   floating to the middle */
.lb{position:fixed;inset:0;z-index:120;display:grid;place-items:start center;
  padding:clamp(16px,3vw,34px) clamp(16px,4vw,48px)}
.lb[hidden]{display:none}
.lb-back{position:absolute;inset:0;background:rgba(4,8,22,.88);
  backdrop-filter:blur(12px) saturate(120%);
  opacity:0;transition:opacity .32s ease}
.lb.on .lb-back{opacity:1}

.lb-stage{position:relative;z-index:1;margin:0;overflow:hidden;
  border-radius:clamp(12px,1.6vw,22px);
  box-shadow:0 50px 110px -34px rgba(0,0,0,.85);
  opacity:0;transform-origin:50% 50%;
  transition:opacity .34s ease,transform .46s cubic-bezier(.2,.75,.25,1)}
.lb.on .lb-stage{opacity:1}
.lb-stage img{display:block;cursor:pointer;max-width:min(1180px,90vw);max-height:82vh;
  width:auto;height:auto;transition:opacity .22s ease}

.lb-btn{position:absolute;z-index:2;display:grid;place-items:center;
  width:46px;height:46px;border-radius:50%;cursor:pointer;
  color:#fff;background:rgba(255,255,255,.12);
  border:1px solid rgba(255,255,255,.22);backdrop-filter:blur(8px);
  transition:background .22s,transform .22s,border-color .22s}
.lb-btn:hover{background:rgba(255,255,255,.24);transform:scale(1.07)}
.lb-btn:focus-visible{outline:2px solid var(--saffron);outline-offset:3px}
.lb-x{top:clamp(14px,2.4vw,26px);right:clamp(14px,2.4vw,26px)}
.lb-prev{left:clamp(10px,2vw,26px);top:50%;margin-top:-23px}
.lb-next{right:clamp(10px,2vw,26px);top:50%;margin-top:-23px}
.lb-count{position:absolute;z-index:2;left:50%;transform:translateX(-50%);
  bottom:clamp(14px,2.4vw,26px);
  font-weight:700;font-size:.78rem;letter-spacing:.1em;
  color:rgba(255,255,255,.72);font-variant-numeric:tabular-nums}

@media (max-width:640px){
  .lb-btn{width:40px;height:40px}
  .lb-prev{left:8px;margin-top:-20px}
  .lb-next{right:8px;margin-top:-20px}
}
@media (prefers-reduced-motion:reduce){
  .lb-stage{transition:opacity .2s ease}
  .lb-btn:hover{transform:none}
}

.gal-grid{display:grid;grid-template-columns:1fr;gap:18px}
.gal-item{position:relative;border-radius:var(--r);overflow:hidden;border:1px solid var(--line);
  background:var(--surface-2);box-shadow:var(--sh-sm)}
.gal-item.tall{grid-row:auto}
.gal-item img{width:100%;height:100%;object-fit:cover;aspect-ratio:4/3;transition:transform .5s ease}
.gal-item.tall img{aspect-ratio:auto;min-height:100%}
.gal-item:hover img{transform:scale(1.045)}
.gal-item .cap{position:absolute;inset:auto 0 0 0;padding:36px 16px 13px;color:#fff;
  font-size:.85rem;font-weight:600;background:linear-gradient(180deg,transparent,rgba(6,11,28,.85))}

/* ------------------------------------------------------------
   POSTER.

   Not a .gal-item. A photograph survives being cropped to 4/3 and
   having a caption laid over its lower third; a poster does not,
   because on a poster the text is the content. Crop it and the
   names come off; overlay it and the college's own address block
   goes under a gradient.

   So: whole sheet, natural ratio, caption underneath, and a click
   to open it in the viewer. That is worth most on a tall screen,
   where a portrait sheet gains about half its width again; on a
   short landscape window the viewer is height-bound and gains
   little. The names are set as text beside it either way, so
   nothing depends on reading them off the sheet.
   ------------------------------------------------------------ */
.poster-fig{margin:0;display:flex;flex-direction:column;
  border:1px solid var(--line);border-radius:var(--r);overflow:hidden;
  background:var(--surface-2);box-shadow:var(--sh-sm)}
.poster-fig .sheet{display:block;width:100%;padding:0;border:0;background:none;
  cursor:zoom-in;line-height:0;
  transition:transform .4s cubic-bezier(.2,.7,.2,1)}
.poster-fig .sheet:hover{transform:scale(1.012)}
.poster-fig .sheet:focus-visible{outline:3px solid var(--blue);outline-offset:-3px}
.poster-fig img{width:100%;height:auto;display:block}
.poster-fig figcaption{padding:13px 16px 14px;background:var(--surface);
  border-top:3px solid var(--saffron);
  font-size:.86rem;font-weight:600;color:var(--ink);line-height:1.45}
.poster-fig figcaption .meta{display:block;margin-top:4px;
  font-size:.78rem;font-weight:600;color:var(--muted)}
/* a tall portrait sheet next to a short list unbalances the row, so the
   sheet is held to a readable width rather than filling its column */
.poster-hold{max-width:400px;margin-inline:auto;width:100%}
.ph{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;
  aspect-ratio:4/3;border:2px dashed color-mix(in srgb,var(--blue) 34%,var(--line));
  border-radius:var(--r);background:
    repeating-linear-gradient(45deg,transparent 0 14px,color-mix(in srgb,var(--blue) 4%,transparent) 14px 28px),
    var(--surface-2);
  color:var(--muted);text-align:center;padding:18px}
.ph svg{color:color-mix(in srgb,var(--blue) 55%,var(--muted))}
.ph b{font-weight:700;font-size:.9rem;color:var(--ink-2)}
.ph small{font-size:.74rem;font-weight:600}
.ph.wide{aspect-ratio:21/9}

/* Anything reachable by #hash has to clear the sticky header, which is 83px
   at its tallest; without this the landing section's eyebrow sits under it. */
[id]{scroll-margin-top:100px}

/* ---- staff ----
   The people are the content here, so the portraits lead and everything else
   sits under them. A previous pass set this as a 118px-thumbnail roster, which
   read as an internal directory: wrong for the one page a parent opens to see
   who will actually teach their son. Portraits are 4:5 rather than square,
   because a square crop of a head always wastes its corners.
   The two lecturers without an email were the reason for that roster. The fix
   is not to abandon cards, it is to stop the card depending on a field that
   may be absent: the mail line sits last and simply is not rendered. */
.lead-figure{display:grid;grid-template-columns:1fr;gap:clamp(24px,4vw,44px);align-items:center}
/* Uncapped below the tablet breakpoint on purpose: the lecturer cards go full
   width when they stack, so capping his portrait made the director the
   smallest face on his own page. The grid track takes over from 768 up. */
.lead-figure .portrait{margin:0;border-radius:var(--r);overflow:hidden;
  border:1px solid var(--line);background:var(--surface);box-shadow:var(--sh-sm)}
.lead-figure .portrait img{width:100%;display:block;aspect-ratio:4/5;
  object-fit:cover;object-position:center 18%}
.lead-figure .role{display:block;font-weight:800;font-size:.68rem;letter-spacing:.17em;
  text-transform:uppercase;color:var(--saffron-text)}
.lead-figure h3{font-family:var(--f-display);font-weight:600;margin:12px 0 0;
  font-size:clamp(1.7rem,3.6vw,2.5rem);letter-spacing:-.02em;line-height:1.1;color:var(--ink)}
.lead-figure .qual{margin:14px 0 0;color:var(--ink-2);font-size:1.06rem;line-height:1.65;max-width:42ch}
.lead-figure .mail{display:inline-flex;align-items:center;gap:8px;margin-top:16px;color:var(--blue);
  font-weight:600;font-size:.94rem;overflow-wrap:anywhere}
.lead-figure .mail:hover{text-decoration:underline}
/* the letter lives on the about page; this is the shortest route to it */
.lead-figure .letter-link{display:inline-flex;align-items:center;gap:9px;margin-top:22px;
  padding-top:20px;border-top:1px solid var(--line);width:100%;
  font-weight:700;font-size:.94rem;color:var(--blue)}
.lead-figure .letter-link svg{transition:transform .2s}
.lead-figure .letter-link:hover svg{transform:translateX(4px)}
.lead-figure .letter-link:hover{text-decoration:underline}

/* Perspective sits on the grid so the three cards tilt against one shared
   vanishing point instead of each having its own. Same idea as the stats
   tiles, which is where this site's motion vocabulary already lives. */
.faculty{display:grid;grid-template-columns:1fr;gap:clamp(20px,3vw,26px);perspective:1400px}
/* min-width:0 because a 1fr track will not shrink below its item's min-content
   width: the two lecturers with the longest qualification line were widening
   their own columns and the four cards came out 345, 322, 322, 345. */
.fcard{position:relative;background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-lg);overflow:hidden;display:flex;flex-direction:column;min-width:0;
  transform-style:preserve-3d;will-change:transform;
  box-shadow:
    0 1px 1px rgba(var(--cast),.05),
    0 4px 8px -3px rgba(var(--cast),.10),
    0 12px 22px -10px rgba(var(--cast),.16),
    inset 0 1px 0 var(--hi);
  transition:transform .42s cubic-bezier(.2,.7,.2,1),box-shadow .42s,border-color .3s}
/* .reveal is declared much later in the file at the same specificity, so it was
   winning the transition shorthand and putting the hover on the entrance's
   0.75s. Scoping under .faculty lifts this above it. opacity stays in the list
   because the shorthand replaces .reveal's entirely, and without it the cards
   would appear rather than fade. */
.faculty .fcard{transition:transform .5s cubic-bezier(.2,.7,.2,1),opacity .5s,
  box-shadow .42s,border-color .3s}
/* a rule that draws itself across the top edge, alternating blue and saffron
   the way the pillar numerals do. focus-within so it answers the keyboard
   too, not only the mouse. */
.fcard::after{content:"";position:absolute;inset:0 0 auto 0;height:3px;z-index:2;
  background:var(--blue);transform:scaleX(0);transform-origin:left;
  transition:transform .45s cubic-bezier(.2,.7,.2,1)}
.fcard:nth-child(even)::after{background:var(--saffron)}
.fcard:hover::after,.fcard:focus-within::after{transform:scaleX(1)}
.fcard:hover,.fcard:focus-within{
  transform:translateY(-10px) rotateX(5deg) scale(1.018);
  border-color:color-mix(in srgb,var(--blue) 30%,var(--line));
  box-shadow:
    0 2px 3px rgba(var(--cast),.06),
    0 12px 20px -7px rgba(var(--cast),.15),
    0 32px 56px -20px rgba(var(--cast),.30),
    inset 0 1px 0 var(--hi)}
.fcard .portrait{margin:0;overflow:hidden;background:var(--surface-2)}
/* MOBILE: square rather than 4:5. Three full-width 4:5 portraits stacked ran
   to 422px each and turned the section into a long scroll; a square keeps the
   face large and gives back 84px a card. */
.fcard .portrait img{width:100%;display:block;aspect-ratio:1/1;object-fit:cover;
  object-position:center 16%;transition:transform .7s cubic-bezier(.2,.7,.2,1)}
.fcard:hover .portrait img,.fcard:focus-within .portrait img{transform:scale(1.06)}
.fcard .info{padding:20px 22px 22px;display:flex;flex-direction:column;flex:1}
.fcard .role{display:block;font-weight:800;font-size:.64rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--saffron-text)}
.fcard h3{font-family:var(--f-display);font-weight:600;font-size:1.26rem;margin:7px 0 0;
  line-height:1.25;color:var(--ink)}
.fcard .qual{margin:9px 0 0;color:var(--muted);font-size:.92rem;line-height:1.6;flex:1}
/* the address settles up as the card lifts */
.fcard .mail{display:inline-flex;align-items:center;gap:7px;margin-top:14px;color:var(--blue);
  font-weight:600;font-size:.87rem;overflow-wrap:anywhere;
  transform:translateY(5px);opacity:.82;
  transition:transform .4s cubic-bezier(.2,.7,.2,1),opacity .4s}
.fcard:hover .mail,.fcard:focus-within .mail{transform:none;opacity:1}
.fcard .mail:hover{text-decoration:underline}
/* entrance: hinge up from the bottom edge, staggered by data-d */
.faculty .reveal{transform:translateY(36px) rotateX(-14deg);transform-origin:50% 100%}
.faculty .reveal.in{transform:none}

@media (min-width:768px){
  .lead-figure{grid-template-columns:minmax(240px,330px) 1fr;gap:clamp(36px,5vw,64px)}
  .lead-figure .portrait{max-width:none}
  /* TABLET: three across would squeeze every portrait to about 215px. The card
     turns on its side instead and spends the width on the face. */
  .fcard{flex-direction:row;align-items:stretch;min-height:270px}
  .fcard .portrait{flex:0 0 clamp(200px,33%,300px)}
  .fcard .portrait img{height:100%;min-height:100%;aspect-ratio:auto}
  .fcard .info{padding:28px 32px;justify-content:center}
  .fcard h3{font-size:1.42rem}
}
@media (min-width:1024px){
  /* LAPTOP: room for the row, so the portrait goes back on top and returns to
     4:5, which frames a head better than a square.
     Four across rather than three: with four lecturers, three would strand one
     on a row of its own. Two-by-two was the other option and it is worse here,
     because at this width it makes each lecturer's portrait larger than the
     director's and inverts the hierarchy the section headings set up. */
  .faculty{grid-template-columns:repeat(4,1fr)}
  .fcard{flex-direction:column;min-height:0}
  .fcard .portrait{flex:initial}
  .fcard .portrait img{aspect-ratio:4/5;height:auto;min-height:0}
  .fcard .info{padding:22px 24px 26px;justify-content:flex-start}
  .fcard h3{font-size:1.28rem}
}
@media (min-width:1440px){
  /* BIG: the same four, given air */
  .faculty{gap:30px}
  .fcard .info{padding:28px 30px 30px}
  .fcard h3{font-size:1.4rem}
  .fcard .qual{font-size:.96rem}
  .fcard .mail{font-size:.9rem}
}
@media (prefers-reduced-motion:reduce){
  .faculty .reveal,.faculty .reveal.in{transform:none}
  .fcard:hover,.fcard:focus-within{transform:none}
  .fcard:hover .portrait img,.fcard:focus-within .portrait img{transform:none}
  .fcard::after{transition:none}
  .fcard .mail{transform:none;opacity:1}
}
@media (min-width:1280px){
  /* Three lecturer cards across a wide row make each portrait bigger than the
     director's, which inverts the hierarchy the section headings set up. His
     grows past theirs rather than theirs being held back, so the row of three
     keeps its presence. */
  .lead-figure{grid-template-columns:minmax(320px,430px) 1fr}
}

/* contact */
.contact-band{background:var(--blue-ink);color:#fff;border-radius:var(--r-lg);
  padding:clamp(38px,6vw,64px);position:relative;overflow:hidden}
.contact-band::before{content:"";position:absolute;inset:0;
  background:radial-gradient(600px 400px at 90% 10%,rgba(239,157,42,.14),transparent 60%)}
.contact-grid{position:relative;z-index:1;display:grid;grid-template-columns:1fr;gap:48px}
/* the band is dark in both themes, so every eyebrow on it takes the light
   saffron. Scoped to .contact-grid before, which missed the one sitting
   directly on the band and left it dark blue on dark navy at 2.07:1. */
.contact-band .eyebrow,.contact-grid .eyebrow{color:var(--saffron)}
.contact-grid h2{color:#fff;font-size:clamp(1.9rem,3.8vw,2.7rem);margin:16px 0 14px}
.contact-grid .sub{color:rgba(255,255,255,.72);font-size:1.06rem}
.clist{display:grid;gap:13px;margin-top:28px}
.cline{display:flex;align-items:center;gap:15px;background:rgba(255,255,255,.06);
  border:1px solid rgba(255,255,255,.13);border-radius:var(--r);padding:15px 18px;transition:.2s}
a.cline:hover{background:rgba(255,255,255,.11);transform:translateX(4px)}
.cline .ic{width:44px;height:44px;border-radius:12px;flex:0 0 auto;display:grid;place-items:center;
  background:rgba(239,157,42,.14);color:var(--saffron)}
.cline small{font-weight:800;font-size:.62rem;letter-spacing:.15em;text-transform:uppercase;
  color:rgba(255,255,255,.5);display:block}
.cline b{font-weight:600;font-size:1rem}
/* This sits outside the dark band, on the page ground, so its old white-on-
   nothing styling was invisible in light mode. Same tokens as .ph, which
   already gets this right in both themes. */
/* The real map, in place of the dashed box that asked for one.

   The embed arrives with width="600" height="450" on it. Those attributes are
   left alone, because they are the ratio the browser lays out with before any
   CSS lands, but the frame is sized here instead: 100% of whatever column it
   is dropped into, with the height coming from an aspect ratio rather than a
   fixed number, so it neither overflows a phone nor leaves a letterbox on a
   wide screen. */
.map-embed{margin:0;border:1px solid var(--line);border-radius:var(--r);
  overflow:hidden;background:var(--surface-2);box-shadow:var(--sh-sm)}
.map-embed iframe{display:block;width:100%;height:auto;aspect-ratio:4/3;border:0}
.map-embed figcaption{display:flex;flex-wrap:wrap;align-items:baseline;
  justify-content:space-between;gap:8px 16px;
  padding:12px 16px 13px;background:var(--surface);
  border-top:1px solid var(--line);
  font-size:.86rem;font-weight:600;color:var(--ink-2)}
.map-embed figcaption a{color:var(--blue);font-weight:700;white-space:nowrap}

/* a 4/3 map is very tall on a phone and the pin is all anyone needs there */
@media (max-width:600px){ .map-embed iframe{aspect-ratio:1/1} }
@media (min-width:1024px){ .map-embed iframe{aspect-ratio:16/11} }

/* A field that failed says so under itself, in the saffron the site already
   uses for a caution rather than the red it uses for nothing else. The border
   carries the same signal, so the message is not the only thing marking it,
   which matters for a reader who cannot separate the two colours. */
.field.bad input,.field.bad select,.field.bad textarea{
  border-color:var(--saffron-text);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--saffron) 22%,transparent)}
.field .err{margin:7px 0 0;font-size:.84rem;font-weight:600;line-height:1.45;
  color:var(--saffron-text);display:flex;align-items:flex-start;gap:7px}
.field .err::before{content:"!";flex:0 0 auto;width:15px;height:15px;margin-top:1px;
  border-radius:50%;background:var(--saffron-text);color:var(--surface);
  font-size:.68rem;font-weight:800;display:grid;place-items:center}

/* form (contact page) */
.form-card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:32px;box-shadow:var(--sh);display:grid;gap:16px}
.field{display:grid;gap:7px}
.field label{font-weight:700;font-size:.72rem;letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}
.field input,.field select,.field textarea{width:100%;padding:13px 15px;border:1.5px solid var(--line);
  border-radius:var(--r-sm);background:var(--surface-2);color:var(--ink);
  font-family:var(--f-body);font-size:.96rem;transition:border-color .15s,box-shadow .15s}
.field input:focus,.field select:focus,.field textarea:focus{outline:none;border-color:var(--blue);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--blue) 18%,transparent)}
.frow{display:grid;grid-template-columns:1fr;gap:16px}

/* CTA band (shared) */
.cta-strip{background:linear-gradient(120deg,var(--blue-fill-2),var(--blue-fill));color:#fff;
  border-radius:var(--r-lg);padding:clamp(34px,5vw,54px);display:flex;align-items:center;
  justify-content:space-between;gap:26px;flex-wrap:wrap;position:relative;overflow:hidden;box-shadow:var(--sh-blue)}
.cta-strip::after{content:"";position:absolute;right:-70px;top:-70px;width:280px;height:280px;
  border-radius:50%;border:34px solid rgba(255,255,255,.09)}
.cta-strip h2{color:#fff;font-size:clamp(1.6rem,3.4vw,2.4rem);max-width:560px}
.cta-strip p{color:rgba(255,255,255,.78);margin-top:8px;max-width:520px}
.cta-strip .z{position:relative;z-index:1}

/* footer */
/* ============================================================
   FOOTER

   The previous one sat on the page background as three thin
   columns of small text and read as something left over. This is
   built as an ending: a dark ground that stops the page, one
   large closing line with the call to action beside it, then the
   practical detail underneath at a size you can actually read.

   The ground is --blue-ink, which is dark in both themes, so the
   footer looks the same either way and the text can simply be
   white rather than flipping with the palette.
   ============================================================ */
.ftr{background:var(--blue-ink);color:#fff;
  padding-block:clamp(40px,5.5vw,66px) 22px}


/* --- the three columns --- */
.ftr-cols{display:grid;grid-template-columns:1fr;
  gap:clamp(30px,4vw,46px);padding-bottom:clamp(32px,4vw,46px)}
.ftr h4{font-size:.68rem;font-family:var(--f-body);font-weight:800;letter-spacing:.18em;
  text-transform:uppercase;color:rgba(255,255,255,.5);margin:0 0 20px}
/* MOBILE: five links stacked one per line make the footer a long scroll for
   no reason, so they pair up until there is a column to put them in */
.ftr ul{list-style:none;padding:0;margin:0;display:grid;
  grid-template-columns:1fr 1fr;gap:14px 18px}
.ftr a{color:rgba(255,255,255,.82);font-weight:500;font-size:.97rem}
.ftr a:hover{color:#fff}

.ftr-brand .brand-txt{color:#fff}
.ftr-brand p{color:rgba(255,255,255,.62);font-size:.95rem;line-height:1.7;
  max-width:42ch;margin:18px 0 20px}
/* two of them now, so they sit as a row and wrap rather than collide in the
   narrower brand column at laptop width */
/* ============================================================
   GPA CALCULATOR, course-by-course.
   The old one asked the student to type each course in from
   memory. Every course of the degree is on screen now, so the
   only input is the grade, and the credits are pre-filled.
   ============================================================ */
/* which curriculum, then which level */
.syl-tabs{display:flex;gap:6px;margin:18px 0 0;flex-wrap:wrap}
.syl-tabs button{flex:1 1 auto;padding:8px 14px;border-radius:var(--r-sm);cursor:pointer;
  font-family:var(--f-body);font-weight:700;font-size:.8rem;
  background:none;color:var(--muted);border:1px solid var(--line);
  transition:background .2s,color .2s,border-color .2s}
.syl-tabs button[aria-selected="true"]{background:var(--saffron-soft);color:var(--saffron-text);
  border-color:color-mix(in srgb,var(--saffron) 40%,var(--line))}
.lvl-tabs{display:flex;gap:6px;margin:12px 0 14px;flex-wrap:wrap}
.lvl-tabs button{flex:1 1 auto;padding:9px 14px;border-radius:var(--pill);cursor:pointer;
  font-family:var(--f-body);font-weight:700;font-size:.85rem;
  background:var(--surface-2);color:var(--ink-2);border:1px solid var(--line);
  transition:background .2s,color .2s,border-color .2s}
.lvl-tabs button[aria-selected="true"]{background:var(--blue-fill);color:#fff;border-color:var(--blue-fill)}

.msem{margin-bottom:18px}
.msem h4{font-size:.68rem;font-weight:800;letter-spacing:.16em;text-transform:uppercase;
  color:var(--muted);margin:0 0 10px}
html[lang="ta"] .msem h4{letter-spacing:.03em;text-transform:none;font-size:.8rem}

.mrow{display:grid;grid-template-columns:64px 1fr 58px 82px;gap:10px;align-items:center;
  padding:7px 0;border-bottom:1px solid var(--line)}
.mrow:last-child{border-bottom:0}
.mcode{font-size:.74rem;font-weight:700;color:var(--muted);font-variant-numeric:tabular-nums}
.mname{font-size:.88rem;line-height:1.35;min-width:0}
.mtag{display:inline-block;font-style:normal;font-size:.6rem;font-weight:800;
  letter-spacing:.09em;text-transform:uppercase;padding:2px 7px;border-radius:var(--pill);
  background:var(--surface-2);color:var(--muted);margin-left:4px;white-space:nowrap}
.mtag.opt{background:var(--saffron-soft);color:var(--saffron-text)}
html[lang="ta"] .mtag{letter-spacing:.02em;text-transform:none;font-size:.66rem}
/* An EN course earns no grade point, so it used to sit greyed out at .55 with
   no control at all. It is not decoration: the level is not clear until every
   one of them is passed, and that is now a condition the check tests. So the
   row reads at full strength and its control looks as usable as any other.
   The NON-GPA tag is what marks it out, which is all it ever needed. */
.mrow.non .mcode,.mrow.non .mname{color:var(--muted)}
.mrow.non .mcr{font-size:.82rem;text-align:center}
span.mcr{font-size:.85rem;text-align:center;color:var(--muted);font-variant-numeric:tabular-nums}
select.mgrade{width:100%;padding:6px 8px;border-radius:var(--r-sm);
  border:1px solid var(--line);background:var(--surface);color:var(--ink);
  font-family:var(--f-body);font-size:.85rem}
select.mgrade{cursor:pointer}
select.mgrade:focus-visible{outline:2px solid var(--blue);outline-offset:1px}

@media (max-width:560px){
  .mrow{grid-template-columns:1fr 52px 76px;gap:8px}
  .mcode{grid-column:1/-1;margin-bottom:-4px}
}

/* a classification is a claim about a finished degree; while the student
   is still filling it in the field reports progress instead, and says so
   in a quieter voice than the real result gets */
/* A shown answer stays until it is reset or the level changes. Editing a grade
   afterwards does not wipe it, because a half-finished edit blanking the panel
   is worse than holding it; but a figure that no longer matches the grades on
   screen must not read as current either, so the whole answer greys back and
   says so. */
.gpa-result{transition:opacity .25s ease,filter .25s ease}
.gpa-result.stale{opacity:.5;filter:saturate(.4)}
.stale-note{display:none;margin:12px 0 0;font-size:.86rem;font-weight:600;
  color:var(--saffron-text);opacity:1}
.gpa-result.stale .stale-note{display:block}
@media (prefers-reduced-motion:reduce){.gpa-result{transition:none}}

/* ---- progression check ----
   Four separate tests, each with the figure that decided it. A student fails
   the year on any one of them regardless of the average, so they are listed
   rather than summed, and the verdict sits under them as a consequence of the
   list rather than as a headline of its own. */
.progress-check{margin-top:18px;padding:20px 22px;border-radius:var(--r);
  background:var(--surface-2);border:1px solid var(--line)}
.progress-check h4{display:flex;align-items:baseline;gap:9px;flex-wrap:wrap;
  margin:0 0 14px;font-family:var(--f-display);font-weight:600;font-size:1.06rem;color:var(--ink)}
.progress-check h4 b{font-weight:600;color:var(--blue)}
.crit{list-style:none;padding:0;margin:0;display:grid;gap:1px;
  background:var(--line);border:1px solid var(--line);border-radius:var(--r-sm);overflow:hidden}
.crit-row{display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:11px 14px;background:var(--surface);font-size:.92rem;color:var(--ink-2)}
.crit-row .ct{display:flex;align-items:baseline;gap:9px;min-width:0}
.crit-row .ct b{flex:0 0 auto;font-family:var(--f-display);font-weight:600;
  font-size:.86rem;color:var(--muted);font-variant-numeric:tabular-nums}
.crit-row .cv{flex:0 0 auto;font-weight:800;font-size:.78rem;letter-spacing:.08em;
  font-variant-numeric:tabular-nums}
.crit-row .cv i{font-style:normal;font-weight:600;opacity:.75;letter-spacing:0}
.crit-row .cv.yes{color:#0D8449}
.crit-row .cv.no{color:#c0392b}
/* not answerable yet: the row is present so a student can see what will be
   tested, but it makes no claim either way */
.crit-row .cv.wait{color:var(--muted);font-weight:600}
.crit-row.waiting .ct{color:var(--muted)}
:root[data-theme="dark"] .crit-row .cv.yes,
:root:not([data-theme="light"]) .crit-row .cv.yes{color:#4ade80}
:root[data-theme="dark"] .crit-row .cv.no,
:root:not([data-theme="light"]) .crit-row .cv.no{color:#f87171}
.verdict{margin-top:14px;font-family:var(--f-display);font-weight:600;font-size:1.1rem}
.verdict.pass{color:#0D8449}
.verdict.fail{color:#c0392b}
.verdict.idle{color:var(--muted);font-size:.98rem}
:root[data-theme="dark"] .verdict.pass,
:root:not([data-theme="light"]) .verdict.pass{color:#4ade80}
:root[data-theme="dark"] .verdict.fail,
:root:not([data-theme="light"]) .verdict.fail{color:#f87171}
.disclaim{margin:13px 0 0;font-size:.82rem;line-height:1.55;color:var(--muted)}
html[lang="ta"] .crit-row .cv{letter-spacing:.02em}

.gpa-out .cls.partial{color:rgba(255,255,255,.62);font-size:1rem;font-weight:600}
.gpa-out .cls.warn{color:var(--saffron);font-size:.95rem;font-weight:700;line-height:1.35}

.socials{display:flex;flex-wrap:wrap;gap:10px 22px}
.social{display:inline-flex;align-items:center;gap:9px;font-size:.9rem}
.social svg{flex:0 0 auto;color:var(--saffron)}

/* contact rows read as a list of ways in, each with its mark */
/* The Explore column is the one that only earns its place on a big screen.
   Hidden below 1440, where the footer runs three columns instead. Every page
   it lists is in the main menu, so nothing becomes unreachable. */
.ftr-links:not(.ftr-courses){display:none}
.ftr-reach{display:grid;gap:14px;align-content:start}
.ftr-reach a,.ftr-reach .addr{display:flex;align-items:flex-start;gap:11px;
  font-size:.97rem;line-height:1.5}
.ftr-reach svg{flex:0 0 auto;margin-top:2px;color:var(--saffron)}
.ftr-reach .addr{color:rgba(255,255,255,.62);max-width:26ch}

/* links slide their marker rather than changing colour alone */
.ftr-links a{position:relative;display:inline-block}
.ftr-links a::after{content:"";position:absolute;left:0;right:0;bottom:-4px;height:1.5px;
  border-radius:2px;background:var(--saffron);
  transform:scaleX(0);transform-origin:0 50%;
  transition:transform .32s cubic-bezier(.2,.7,.2,1)}
.ftr-links a:hover::after{transform:scaleX(1)}

.ftr-bar{display:flex;justify-content:space-between;gap:14px;flex-wrap:wrap;
  padding-top:20px;border-top:1px solid rgba(255,255,255,.13);
  color:rgba(255,255,255,.5);font-size:.83rem}
.ftr-bar a{color:rgba(255,255,255,.5);font-size:.83rem}
.ftr-bar a:hover{color:#fff}

@media (prefers-reduced-motion:reduce){
  .ftr-links a::after{transition:none}
}

/* recognition strip */
.strip{background:var(--surface);border-bottom:1px solid var(--line)}
.strip .wrap{display:flex;align-items:center;gap:34px;padding-block:22px;flex-wrap:wrap;justify-content:space-between}
.strip .lbl{font-weight:700;font-size:.7rem;letter-spacing:.16em;text-transform:uppercase;color:var(--muted)}
.strip .items{display:flex;align-items:center;gap:34px;flex-wrap:wrap}
.strip .items img{height:44px;width:auto;object-fit:contain}
.strip .items span{font-family:var(--f-display);font-weight:600;font-size:1rem;color:var(--ink-2)}

/* ============================================================
   PROGRAMME RAIL, self-scrolling card strip on a dark band.
   The track holds the same group twice and slides exactly -50%,
   so the loop is seamless. Edge masks fade cards in and out;
   hover (or keyboard focus) pauses it so cards can be clicked.
   Each card carries an --accent that tints its rule, category
   chip and watermark, so the four programme families read apart
   at a glance.
   ============================================================ */
.marquee{position:relative;overflow:hidden;padding-block:26px;
  background:var(--surface-2);border-block:1px solid var(--line);
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 6%,#000 94%,transparent 100%);
  mask-image:linear-gradient(90deg,transparent 0,#000 6%,#000 94%,transparent 100%)}
.mq-track{display:flex;width:max-content;animation:mqScroll 54s linear infinite}
.marquee:hover .mq-track,
.marquee:focus-within .mq-track{animation-play-state:paused}
.mq-group{display:flex;align-items:stretch;gap:16px;padding-right:16px}
@keyframes mqScroll{from{transform:translateX(0)}to{transform:translateX(-50%)}}

/* Cards sit up off the band: a light-to-shade surface gradient, a bright
   inner top edge where light catches, and a stacked shadow (tight contact
   shadow + wide soft cast). Hover lifts and tips them slightly in 3D. */
.mq-card{--accent:var(--blue);--accent-soft:var(--blue-soft);
  position:relative;display:flex;flex-direction:column;gap:7px;overflow:hidden;
  width:236px;flex:0 0 236px;padding:19px 18px 16px;border-radius:var(--r-lg);
  background:linear-gradient(178deg,var(--surface) 0%,
    color-mix(in srgb,var(--surface-2) 60%,var(--surface)) 100%);
  border:1px solid var(--line);color:var(--ink);text-decoration:none;
  transform-style:preserve-3d;
  box-shadow:
    0 1px 1px rgba(var(--cast),.05),
    0 4px 8px -3px rgba(var(--cast),.10),
    0 12px 22px -10px rgba(var(--cast),.18),
    inset 0 1px 0 var(--hi);
  transition:transform .38s cubic-bezier(.2,.7,.2,1),box-shadow .38s,border-color .3s}
.mq-card:hover{
  transform:perspective(900px) translateY(-9px) rotateX(5deg) scale(1.025);
  border-color:color-mix(in srgb,var(--accent) 40%,var(--line));
  box-shadow:
    0 2px 3px rgba(var(--cast),.06),
    0 10px 20px -6px rgba(var(--cast),.16),
    0 30px 50px -16px rgba(var(--cast),.30),
    inset 0 1px 0 var(--hi)}
.mq-card:focus-visible{outline:2px solid var(--accent);outline-offset:3px}

.mq-ic{display:grid;place-items:center;width:32px;height:32px;border-radius:10px;
  background:var(--accent-soft);color:var(--accent);flex:0 0 auto;margin-bottom:2px}
.mq-cat{font-size:.6rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted)}
.mq-title{font-family:var(--f-display);font-weight:600;font-size:1.2rem;
  line-height:1.22;color:var(--ink)}
.mq-sub{font-size:.83rem;line-height:1.5;color:var(--muted);flex:1}
.mq-meta{display:flex;align-items:center;justify-content:space-between;gap:8px;
  margin-top:6px;padding-top:11px;border-top:1px solid var(--line);
  font-size:.72rem;font-weight:700;color:var(--accent)}
.mq-meta svg{flex:0 0 auto;transition:transform .28s ease}
.mq-card:hover .mq-meta svg{transform:translateX(4px)}

/* Two accents only, and they mean something:
   blue  = awarded by a university   ·   saffron = run by the college */
.mq-card.uni{--accent:var(--blue);--accent-soft:var(--blue-soft)}
/* --accent is only ever text, border or focus ring here, never a fill under
   white, so it takes the readable saffron rather than the fill one */
.mq-card.cict{--accent:var(--saffron-text);--accent-soft:var(--saffron-soft)}

@media (min-width:768px){
  .marquee{padding-block:32px}
  .mq-card{width:252px;flex:0 0 252px;padding:22px 20px 18px}
  .mq-group{gap:18px;padding-right:18px}
}
/* no motion, static wrapped grid, duplicate copy removed */
@media (prefers-reduced-motion:reduce){
  .marquee{-webkit-mask-image:none;mask-image:none}
  .mq-track{animation:none;width:100%;flex-wrap:wrap;justify-content:center}
  .mq-group{flex-wrap:wrap;justify-content:center;padding-right:0}
  .mq-group[aria-hidden="true"]{display:none}
  .mq-card:hover{transform:none}
  .mq-card:hover .mq-meta svg{transform:none}
}

/* stats band */
/* ============================================================
   STATS BAND, a ruled ledger line.

   Two rewrites got this wrong before. The original was built on
   --blue-deep, which inverts in dark mode to #8aa2ff, a pale
   hover blue, so it rendered as a bright pastel slab. The second
   replaced that with a dark gradient carrying soft radial glows,
   which is the house style of every generated landing page.

   So: no gradient and no glow anywhere. The band is paper, ruled
   off from the sections either side, and each figure hangs from
   its own rule the way a column does in a printed table. Colour
   comes from the figures themselves, not from the ground, and it
   sits between the dark alumni panel above and the tinted gallery
   below so the three surfaces stay distinct.
   ============================================================ */
.stats-band{background:var(--surface);color:var(--ink);border-block:1px solid var(--line)}
/* the perspective lives on the container, so all four tiles tilt against one
   shared vanishing point instead of each having its own */
.stats-band .wrap{display:grid;grid-template-columns:1fr 1fr;gap:clamp(15px,2.2vw,20px);
  padding-block:clamp(46px,5.5vw,68px);text-align:left;perspective:1200px}

.stats-band .tile{position:relative;padding:26px 24px 24px;border-radius:var(--r-lg);
  border:1px solid var(--line);transform-style:preserve-3d;will-change:transform;
  background:linear-gradient(178deg,var(--surface) 0%,
    color-mix(in srgb,var(--surface-2) 60%,var(--surface)) 100%);
  box-shadow:
    0 1px 1px rgba(var(--cast),.05),
    0 4px 8px -3px rgba(var(--cast),.10),
    0 12px 22px -10px rgba(var(--cast),.18),
    inset 0 1px 0 var(--hi);
  transition:transform .42s cubic-bezier(.2,.7,.2,1),box-shadow .42s,border-color .3s}
.stats-band .tile:hover{transform:translateY(-9px) rotateX(6deg) scale(1.022);
  border-color:color-mix(in srgb,var(--blue) 30%,var(--line));
  box-shadow:
    0 2px 3px rgba(var(--cast),.06),
    0 12px 20px -7px rgba(var(--cast),.15),
    0 30px 54px -20px rgba(var(--cast),.30),
    inset 0 1px 0 var(--hi)}

/* entrance: the tiles hinge up from their bottom edge rather than sliding, so
   the stagger reads as depth. .reveal already carries the timing and the
   per-tile delay from data-d. */
.stats-band .reveal{transform:translateY(34px) rotateX(-18deg);transform-origin:50% 100%}
.stats-band .reveal.in{transform:none}

.stats-band b{display:block;font-family:var(--f-display);font-weight:600;
  font-size:clamp(2.6rem,5.6vw,3.7rem);line-height:.9;letter-spacing:-.03em;
  font-variant-numeric:tabular-nums;color:var(--blue)}
.stats-band span{display:block;margin-top:12px;max-width:15ch;
  font-size:.88rem;font-weight:600;line-height:1.45;color:var(--muted)}
/* One figure leads. The graduate count is the claim the other three exist to
   support, so it takes the warm side of the palette and the rest hold the blue.
   --saffron-deep alone measures 2.94:1 on the light ground, under the 3:1 floor
   even at this size; pulling it toward --ink clears 4.6:1, and because --ink
   flips with the theme the same mix stays legible on the dark ground too. */
.stats-band .tile:nth-child(3) b{color:var(--saffron-text)}

@media (prefers-reduced-motion:reduce){
  .stats-band .reveal,.stats-band .reveal.in{transform:none}
  .stats-band .tile:hover{transform:none}
}

/* ============================================================
   BACK TO TOP, a glass vessel that fills with rippling water as
   you scroll. Water level = scroll %, two sine waves drift across
   the surface at different speeds. Injected by js/main.js.
   ============================================================ */
/* Phone and tablet carry the bottom bar, so the button rides above it: the
   bar is 71px and the home indicator sits under that. It drops into the
   corner from 1024, where the bar gives way to the contact rail. */
.to-top{position:fixed;right:16px;z-index:80;
  bottom:calc(86px + env(safe-area-inset-bottom,0px));
  width:52px;height:52px;padding:0;border:0;border-radius:50%;cursor:pointer;
  display:grid;place-items:center;overflow:hidden;isolation:isolate;
  background:color-mix(in srgb,var(--surface) 90%,transparent);
  backdrop-filter:saturate(170%) blur(14px);
  box-shadow:0 4px 14px -4px rgba(10,22,51,.28),
             0 16px 38px -12px rgba(10,22,51,.36),
             inset 0 0 0 1.5px color-mix(in srgb,var(--blue) 18%,transparent);
  opacity:0;visibility:hidden;transform:translateY(18px) scale(.84);
  transition:opacity .38s cubic-bezier(.2,.7,.2,1),transform .38s cubic-bezier(.2,.7,.2,1),
             visibility .38s,box-shadow .28s}
.to-top.show{opacity:1;visibility:visible;transform:none}
.to-top:hover{box-shadow:0 6px 18px -4px rgba(10,22,51,.32),
             0 22px 50px -14px rgba(29,63,174,.45),
             inset 0 0 0 1.5px color-mix(in srgb,var(--blue) 34%,transparent)}
.to-top:hover .arw{transform:translateY(-3px)}
.to-top:active{transform:scale(.93)}
.to-top:focus-visible{outline:2px solid var(--blue);outline-offset:3px}

/* the water */
.to-top .liquid{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
.to-top .water{transform:translateY(var(--lvl,52px));
  transition:transform .45s cubic-bezier(.22,.68,.24,1)}
.to-top .wave{animation:ttWave 2.8s linear infinite}
.to-top .wave.front{fill:var(--blue)}
.to-top .wave.back{fill:color-mix(in srgb,var(--blue) 42%,transparent);
  animation-duration:4.6s;animation-direction:reverse}
@keyframes ttWave{to{transform:translateX(-28px)}}

/* highlight glint on the water surface */
.to-top .glint{fill:rgba(255,255,255,.34)}

/* arrow, flips to white once the water rises past it */
.to-top .arw{position:relative;z-index:1;color:var(--blue);
  transition:transform .28s ease,color .35s ease}
.to-top.deep .arw{color:#fff}

@media (min-width:768px){
  /* the bar is still here on tablet, so only the size and inset change */
  .to-top{right:20px;width:56px;height:56px}
}
@media (prefers-reduced-motion:reduce){
  .to-top{transition:opacity .2s,visibility .2s}
  .to-top.show{transform:none}
  .to-top .wave{animation:none}
  .to-top .water{transition:none}
  .to-top:hover .arw{transform:none}
}

/* Drives the light that travels around the border. Registering it as an
   angle is what lets it be animated at all: a plain custom property is a
   string to the interpolator and would jump from 0 to 360 rather than sweep.
   Chromium 85+, Safari 16.4+, Firefox 128+. Without it the ring simply sits
   still, which is a fine place to land. */
@property --crail-ang{syntax:"<angle>";initial-value:0deg;inherits:false}

/* ============================================================
   CONTACT RAIL, laptop and up.

   The dock gives a phone user Call and WhatsApp in the thumb
   zone, but it is hidden from 1024 up, and above that width the
   two were reachable only from the contact page. This puts them
   back. It stacks above .to-top rather than taking the corner:
   .to-top only appears once you scroll, so if the rail sat under
   it the column would jump. The cost is a little air below the
   rail at the top of the page, where nobody is looking.

   Each button is a circle that opens sideways into a pill on
   hover. The width is animated with a 0fr -> 1fr grid track
   rather than a guessed pixel value, so the pill stops exactly
   at the width of its own label. That matters here because the
   Tamil label is a different length from the English one and a
   fixed max-width would overshoot one or clip the other.
   ============================================================ */
.crail{position:fixed;right:14px;z-index:82;pointer-events:none;
  bottom:calc(16px + env(safe-area-inset-bottom,0px));
  display:none;flex-direction:column;align-items:flex-end;gap:10px;--ics:46px}
.crail a{pointer-events:auto;position:relative;isolation:isolate;
  display:flex;align-items:center;height:46px;width:46px;border-radius:99px;
  color:#fff;text-decoration:none;overflow:hidden;
  box-shadow:0 4px 14px -4px rgba(var(--cast),.34),
             0 16px 34px -14px rgba(var(--cast),.42),
             inset 0 0 0 1.5px rgba(255,255,255,.34);
  transition:width .42s cubic-bezier(.2,.7,.2,1),
             box-shadow .34s cubic-bezier(.2,.7,.2,1),
             transform .34s cubic-bezier(.2,.7,.2,1)}
/* --w is measured once the fonts are in, so the pill stops exactly at its own
   label in either language. A guessed max-width overshoots one and clips the
   other, and 0fr -> 1fr sizes to available space, which a flex item has none
   of. */
.crail a:hover,.crail a:focus-visible{width:var(--w,150px);transform:translateY(-2px)}
.crail a:active{transform:translateY(0) scale(.96)}
.crail a:focus-visible{outline:2px solid #fff;outline-offset:3px}

/* the icon keeps the circle's footprint whatever the pill does */
.crail .ic{flex:0 0 var(--ics);width:var(--ics);height:var(--ics);display:grid;place-items:center;
  transition:transform .42s cubic-bezier(.2,.7,.2,1)}
.crail a:hover .ic{transform:scale(1.08) rotate(-6deg)}
.crail .ic svg{width:22px;height:22px;fill:currentColor}

.crail .lw{display:block;overflow:hidden}
/* 14px of left padding is not decoration: it clears the gradient ramp, so the
   first letter lands on the deep green rather than on the 2:1 brand green. It
   also stops the text butting against the icon. */
.crail .lbl{display:block;white-space:nowrap;padding-left:14px;
  font-size:.86rem;font-weight:700;letter-spacing:.01em;
  opacity:0;transition:opacity .3s ease .06s}
.crail a:hover .lbl,.crail a:focus-visible .lbl{opacity:1;padding-right:20px}

/* The white ring travels once round on hover. transparent for most of the
   sweep so it reads as a light passing over the edge, not a spinning band. */
.crail a::before{content:"";position:absolute;inset:0;border-radius:inherit;
  z-index:-1;padding:1.5px;opacity:0;
  background:conic-gradient(from var(--crail-ang),
    transparent 0 62%,rgba(255,255,255,.35) 74%,#fff 82%,
    rgba(255,255,255,.35) 90%,transparent 100%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  mask-composite:exclude;
  transition:opacity .3s}
.crail a:hover::before,.crail a:focus-visible::before{
  opacity:1;animation:crailSweep 2.6s linear infinite}
@keyframes crailSweep{to{--crail-ang:360deg}}

/* The stops are in pixels, not percentages, so they are pinned to the icon
   rather than to the pill's changing width. WhatsApp's own #25D366 puts white
   at 1.98:1. That is fine under the glyph, which is a logotype and exempt, but
   the label beside it is ordinary 13.8px bold text and needs 4.5. So the brand
   green stays on the circle and the fill deepens to #0D8449 (4.76:1) exactly
   where the label begins. Collapsed, only the brand green is ever visible. */
.crail .call{background:linear-gradient(90deg,
  var(--blue) 0 var(--ics),var(--blue-fill-2) calc(var(--ics) + 14px))}
.crail .wa{background:linear-gradient(90deg,
  #25D366 0 var(--ics),#0D8449 calc(var(--ics) + 14px))}
/* Saffron cannot carry white at any usable ratio, so this one takes the dark
   ink the dock's Apply used, on both the glyph and the label. */
.crail .apply{color:#2b1a02;background:linear-gradient(90deg,
  var(--saffron) 0 var(--ics),var(--saffron-deep) calc(var(--ics) + 14px))}
.crail .apply:hover,.crail .apply:focus-visible{
  box-shadow:0 6px 18px -4px rgba(var(--cast),.4),
             0 20px 46px -14px rgba(239,157,42,.6),
             inset 0 0 0 1.5px rgba(255,255,255,.6)}
.crail .call:hover,.crail .call:focus-visible{
  box-shadow:0 6px 18px -4px rgba(var(--cast),.4),
             0 20px 46px -14px rgba(29,63,174,.6),
             inset 0 0 0 1.5px rgba(255,255,255,.6)}
.crail .wa:hover,.crail .wa:focus-visible{
  box-shadow:0 6px 18px -4px rgba(var(--cast),.4),
             0 20px 46px -14px rgba(18,164,92,.6),
             inset 0 0 0 1.5px rgba(255,255,255,.6)}

/* Phone and tablet keep the bottom bar; the rail is the laptop-up form of the
   same actions. From here .to-top reclaims the corner, so the rail sits well
   clear of it rather than stacked tight against it. The clamp lifts it a
   little on a tall screen without letting it climb toward the header on a
   short laptop. */
@media (min-width:1024px){.crail{display:flex;bottom:clamp(130px,16vh,200px)}}
/* No hover on a touch screen, so the pill would never open and a tap must
   just place the call. Held at the icon, which is the plain FAB it should be
   there. Keyboard focus still expands it, on any device. */
@media (hover:none){
  .crail a:hover{width:var(--ics);transform:none}
  .crail a:hover .lbl{opacity:0;padding-right:0}
  .crail a:hover .ic{transform:none}
  .crail a:hover::before{opacity:0;animation:none}
  /* A phone handset and the WhatsApp mark need no caption. A plus does, and
     Apply is the one thing on this rail a visitor is here to do, so it stays
     open. On a phone it is also the only Apply outside the drawer, which is
     the job the dock used to hold. */
  .crail .apply,.crail .apply:hover{width:var(--w,150px)}
  .crail .apply .lbl,.crail .apply:hover .lbl{opacity:1;padding-right:20px}
}
/* Below this the page has no side gutter to sit in, so the rail is kept small
   and tucked tight to the edge. Past 1400 the gutter opens up and it can take
   its full size without touching the content column at all. */
@media (min-width:1440px){
  .crail{right:24px;gap:12px}
  .crail{--ics:54px}
  .crail a{height:54px;width:54px}
  .crail .ic svg{width:25px;height:25px}
}
@media (prefers-reduced-motion:reduce){
  .crail a,.crail .ic,.crail .lw,.crail .lbl,.crail a::before{transition:none}
  .crail a:hover,.crail a:focus-visible{transform:none}
  .crail a:hover .ic{transform:none}
  .crail a:hover::before,.crail a:focus-visible::before{animation:none}
}

/* reveal */
.reveal{opacity:0;transform:translateY(26px);
  transition:opacity .75s cubic-bezier(.2,.7,.2,1),transform .75s cubic-bezier(.2,.7,.2,1)}
.reveal.in{opacity:1;transform:none}
.reveal[data-d="1"]{transition-delay:.09s}
.reveal[data-d="2"]{transition-delay:.18s}
.reveal[data-d="3"]{transition-delay:.27s}

/* ============================================================
   TAMIL TYPOGRAPHY
   No font-family overrides here on purpose, Tamil uses exactly the
   same stack as English (see --f-display / --f-body), so Latin text
   is identical in both languages and Tamil falls through to Noto.
   What DOES differ is metrics: Tamil glyphs are taller, so they need
   more leading, and Tamil has no case, so `text-transform` and wide
   `letter-spacing` are removed wherever the Latin design used them,
   because tracking pulls Tamil conjuncts apart.
   ============================================================ */
/* Tamil set at the same font-size reads noticeably larger, because Noto's
   glyphs fill more of the em: measured against Source Sans 3 its x-height runs
   1.14x and its total ink 1.23x. Every size on the site was tuned on the Latin,
   so the Tamil arrived oversized everywhere.

   font-size-adjust fixes it at the font level rather than the element level,
   which matters here: the stacks fall back per character, so "BIT பட்டம்" is
   Source Sans 3 for the Latin and Noto for the Tamil inside one element. A
   plain font-size reduction would shrink BIT and UCSC along with it. This
   scales each font to a shared cap-height instead, so the Latin is untouched
   and only the Tamil comes down: about 0.92 in the body face, 0.81 in the
   display face, resolved per stack by from-font rather than hardcoded. */
html[lang="ta"]{font-size-adjust:cap-height from-font}

/* Tamil has no hyphenation and no break point inside a word, so a break lands
   mid-cluster and orphans a syllable: யாழ்ப்பாணத்திலிருந் / து.
   The culprit is text-wrap:balance. body carries overflow-wrap:break-word so a
   long Latin URL cannot escape its column, and balance reads that as licence
   to split a heading word purely to even the line lengths, even when the word
   had room to stay whole. Dropping balance leaves break-word as what it was
   meant to be, a last resort for a word that genuinely cannot fit, rather than
   a tool for tidier ragging. Latin keeps balance. */
html[lang="ta"] h1,html[lang="ta"] h2,html[lang="ta"] h3,html[lang="ta"] h4{
  letter-spacing:0;line-height:1.3;text-wrap:wrap}
/* A phone column is about 324px, and Tamil builds long single words: at the
   40px the h1 clamp floors at, விண்ணப்பியுங்கள் or ஆரம்பமாகிவிட்டது is wider
   than the whole column, so break-word has no choice and snaps it mid-cluster.
   Headings take a smaller cap-height target here, which shrinks the Tamil
   glyphs without touching the clamp, the Latin, or the body copy. */
@media (max-width:767px){
  html[lang="ta"] h1,html[lang="ta"] h2,html[lang="ta"] h3{
    font-size-adjust:cap-height 0.565}
  /* பல்கலைக்கழகம் is thirteen characters and is simply the word for
     university, so no rewrite shortens it. The path card gives its title a
     171px column, which needs one more step down than the rest. */
  html[lang="ta"] .pathc h3{font-size:1.3rem}
}
html[lang="ta"] .page-hero h1,html[lang="ta"] .slide h1{line-height:1.32}
html[lang="ta"] .eyebrow,html[lang="ta"] .tier,html[lang="ta"] .field label,
html[lang="ta"] .fcard .role,html[lang="ta"] .lead-figure .role,
html[lang="ta"] .ftr h4,html[lang="ta"] .strip .lbl,
html[lang="ta"] .cline small,html[lang="ta"] .fact small,
html[lang="ta"] .notice .when{letter-spacing:.03em;text-transform:none}
html[lang="ta"] p,html[lang="ta"] li{line-height:1.75}

/* ------------------------------------------------------------
   TAMIL HEADER, the brand lockup and nav need their own sizing
   because Tamil labels run longer than their English equivalents.
   ------------------------------------------------------------ */
/* nothing in the lockup ever breaks, each of the three lines is its own row */
.brand-txt{white-space:nowrap}

html[lang="ta"] .topbar{font-size:.79rem}
html[lang="ta"] .brand-txt{font-size:1.02rem;line-height:1.3}
html[lang="ta"] .brand-txt small{
  text-transform:none;letter-spacing:0;font-size:.62rem;margin-top:4px;gap:0}
html[lang="ta"] .brand-txt small span{line-height:1.45}

/* Tamil words are ~40% longer than their English equivalents, so the nav is
   stepped down until the whole menu occupies about the same width as English.
   The action buttons keep identical dimensions in both languages, only the
   Apply label is scaled so it fits the shared 132px button. */
html[lang="ta"] .nav-links a.top,
html[lang="ta"] .nav-links button.top{font-size:.85rem;padding:10px 10px;line-height:1.7}
html[lang="ta"] .nav-links{gap:0}
html[lang="ta"] .nav-actions .btn-sm{font-size:.8rem;padding-inline:.9em}
html[lang="ta"] .dd a{line-height:1.6}
html[lang="ta"] .dd a small{line-height:1.5;margin-top:3px;letter-spacing:0}
html[lang="ta"] .dd .grp{letter-spacing:.05em;text-transform:none;font-size:.7rem}
html[lang="ta"] .mm-list a,
html[lang="ta"] .mm-list button.mm-t{font-size:1.24rem;line-height:1.5}
html[lang="ta"] .mm-sub a{font-size:.96rem;line-height:1.5}
html[lang="ta"] .btn{line-height:1.6}

/* ---------------- language button ---------------- */
.lang-btn{font-family:var(--f-body);font-weight:800;font-size:.95rem;
  color:var(--blue);border-color:color-mix(in srgb,var(--blue) 35%,var(--line))}
.lang-btn:hover{background:var(--blue-soft)}

/* ============================================================
   THEME TOGGLE, the sun's disc grows and a masked circle slides
   in to carve it into a crescent, while the rays retract and spin.
   One shape, two states, no icon swap.
   ============================================================ */
.theme-btn{overflow:hidden;position:relative;color:var(--saffron-text)}
.theme-btn::after{content:"";position:absolute;inset:0;border-radius:inherit;
  background:radial-gradient(circle at 50% 50%,
    color-mix(in srgb,var(--saffron) 30%,transparent),transparent 68%);
  opacity:0;transition:opacity .35s ease}
.theme-btn:hover{border-color:color-mix(in srgb,var(--saffron) 55%,var(--line))}
.theme-btn:hover::after{opacity:1}
.theme-btn:active{transform:scale(.92)}
.theme-btn .sunmoon{width:21px;height:21px;position:relative;z-index:1}

.theme-btn .orb{transition:r .45s cubic-bezier(.4,0,.2,1)}
.theme-btn .cut{transition:cx .45s cubic-bezier(.4,0,.2,1),cy .45s cubic-bezier(.4,0,.2,1)}
.theme-btn .rays{transform-origin:12px 12px;
  transition:transform .5s cubic-bezier(.4,0,.2,1),opacity .32s ease}

/* moon state, explicit dark, and system-dark when not overridden to light */
:root[data-theme="dark"] .theme-btn{color:var(--blue-deep)}
:root[data-theme="dark"] .theme-btn .orb{r:9}
:root[data-theme="dark"] .theme-btn .cut{cx:17;cy:6}
:root[data-theme="dark"] .theme-btn .rays{transform:rotate(45deg) scale(0);opacity:0}
:root[data-theme="dark"] .theme-btn::after{
  background:radial-gradient(circle at 50% 50%,
    color-mix(in srgb,var(--blue) 40%,transparent),transparent 68%)}
:root[data-theme="dark"] .theme-btn:hover{
  border-color:color-mix(in srgb,var(--blue) 60%,var(--line))}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .theme-btn{color:var(--blue-deep)}
  :root:not([data-theme="light"]) .theme-btn .orb{r:9}
  :root:not([data-theme="light"]) .theme-btn .cut{cx:17;cy:6}
  :root:not([data-theme="light"]) .theme-btn .rays{transform:rotate(45deg) scale(0);opacity:0}
}
@media (prefers-reduced-motion:reduce){
  .theme-btn .orb,.theme-btn .cut,.theme-btn .rays{transition:none}
  .theme-btn:active{transform:none}
}

/* ---------------- apply page ---------------- */
.apply-grid{display:grid;grid-template-columns:1fr;gap:32px;align-items:start}
.checkline{display:flex;align-items:center;gap:10px;font-size:.92rem;font-weight:600;
  color:var(--ink-2);cursor:pointer;user-select:none}
.checkline input{width:18px;height:18px;accent-color:var(--blue);cursor:pointer}
.hiw{display:grid;gap:0;position:relative}
.hiw .step{display:grid;grid-template-columns:46px 1fr;gap:16px;padding-bottom:26px;position:relative}
.hiw .step:last-child{padding-bottom:0}
.hiw .step::before{content:"";position:absolute;left:22px;top:50px;bottom:4px;width:2px;
  background:color-mix(in srgb,var(--blue) 25%,var(--line))}
.hiw .step:last-child::before{display:none}
.hiw .n{width:46px;height:46px;border-radius:14px;display:grid;place-items:center;
  background:var(--blue-soft);color:var(--blue);font-family:var(--f-display);font-weight:700}
.hiw p{color:var(--ink-2);font-weight:500;padding-top:10px}
.review-card{background:var(--blue-ink);color:#fff;border-radius:var(--r-lg);padding:30px;
  box-shadow:var(--sh);display:none}
.review-card.show{display:block}
.review-card h3{color:#fff;font-size:1.4rem}
.review-card .rp{color:rgba(255,255,255,.72);font-size:.94rem;margin:10px 0 18px}
.review-card pre{background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.15);
  border-radius:var(--r-sm);padding:18px;font-size:.86rem;line-height:1.7;color:#e8eefc;
  white-space:pre-wrap;word-break:break-word;margin:0 0 18px;max-height:340px;overflow-y:auto;
  font-family:ui-monospace,Consolas,monospace}
.send-row{display:flex;gap:10px;flex-wrap:wrap}
.send-row .btn{flex:1;min-width:150px}
.btn-wa{background:#1fae54;color:#fff}
.btn-wa:hover{background:#178a42;transform:translateY(-2px)}
.privnote{display:flex;gap:10px;align-items:flex-start;margin-top:16px;
  color:rgba(255,255,255,.55);font-size:.8rem}
.privnote svg{flex:0 0 auto;margin-top:2px}

/* ============================================================
   RESPONSIVE, mobile-first
   Base (above) = MOBILE.  Then two upward breakpoints:
     · 768px  → TABLET
     · 1024px → LAPTOP
     · 1440px → BIG SCREENS
   ============================================================ */

/* ============================================================
   PHONE AND TABLET LAYOUT

   Not the desktop page narrowed. Two structural changes below
   1024px:

   1. A fixed dock. Call, WhatsApp and Apply sit in the thumb
      zone on every page instead of being scrolled to.
   2. Card grids become swipe rails. A four-up grid restacked
      into one column turns a glanceable row into a long scroll;
      as a snapping rail the same cards stay one gesture apart
      and the page gets its vertical rhythm back.
   ============================================================ */
/* ---- the bottom bar, phone and tablet ----
   A full-width bar beats three circles in a corner here: the actions are
   labelled, they sit in the thumb zone, and none of them depends on a hover
   that a touch screen does not have. Same three actions as the laptop rail,
   and the same colours and marks, so it reads as one component in two forms
   rather than two components. */
.dock{position:fixed;left:0;right:0;bottom:0;z-index:85;
  display:grid;grid-template-columns:1fr 1fr 1.25fr;gap:8px;
  padding:9px 12px calc(9px + env(safe-area-inset-bottom,0px));
  background:color-mix(in srgb,var(--surface) 92%,transparent);
  backdrop-filter:blur(14px) saturate(160%);
  border-top:1px solid var(--line);
  box-shadow:0 -8px 24px -14px rgba(var(--cast),.5)}
.dock a{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;
  min-height:52px;border-radius:14px;font-weight:700;font-size:.7rem;
  color:#fff;letter-spacing:.01em;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.3),0 2px 8px -3px rgba(var(--cast),.4);
  transition:transform .18s ease,filter .18s ease}
.dock a:active{transform:scale(.96);filter:brightness(.94)}
.dock a svg{width:20px;height:20px;fill:currentColor;display:block}
.dock .call{background:var(--blue-fill)}
/* solid rather than the rail's gradient: at tile size a ramp just muddies it.
   White clears 4.76:1 on this green, where WhatsApp's own #25D366 gives 1.98. */
.dock .wa{background:#0D8449}
/* saffron cannot carry white, so Apply keeps the dark ink it has on the rail */
.dock .apply{background:var(--saffron-deep);color:#2b1a02;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.45),0 2px 8px -3px rgba(var(--cast),.4)}
html[lang="ta"] .dock a{font-size:.74rem}
/* the bar covers the foot of the page, so give the page its height back */
body{padding-bottom:80px}
/* Back-to-top sits above the bar here; see the .to-top block for the offsets. */

/* Rails run the full width of the screen. These elements are themselves the
   .wrap, so their own inline padding already insets the first and last card;
   adding a negative margin on top of that pushed the whole rail outside the
   viewport. Scrolling happens inside the wrap box, which is edge to edge on
   a phone, so the effect is full bleed without moving anything. */
/* the staff roster left this rail: rows stack on a phone, they do not swipe */
.sv-grid,.path-grid{
  display:flex;gap:14px;overflow-x:auto;overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory;scroll-padding-inline:18px;
  padding-bottom:4px;scrollbar-width:none;-ms-overflow-style:none}
.sv-grid::-webkit-scrollbar,.path-grid::-webkit-scrollbar{display:none}
.sv-grid>*,.path-grid>*{
  flex:0 0 min(78%,300px);scroll-snap-align:start}

@media (min-width:1024px){
  .dock{display:none}                 /* the rail takes over from here */
  body{padding-bottom:0}
  /* bar gone, so it takes the corner back at full size */
  .to-top{right:24px;bottom:16px;width:62px;height:62px}
  /* the bar carried Apply below this width; here the header takes it back,
     since the contact rail deliberately does not carry it */
  .nav-actions .btn-gold{display:inline-flex}
  /* back to real grids once there is width for them */
  .sv-grid,.path-grid{display:grid;overflow:visible;scroll-snap-type:none}
  .sv-grid>*,.path-grid>*{flex:initial}
}

/* ---------------- TABLET · 768px and up ---------------- */
@media (min-width:768px){
  .wrap{padding-inline:26px}
  body{font-size:16.5px}
  .topbar .wrap{justify-content:flex-start;gap:22px}
  .nav{height:82px;gap:18px}
  .brand img{width:46px;height:46px}
  .brand-txt small{display:flex}   /* room for the 3-line lockup */
  .nav-actions{gap:10px}

  .prog-grid,.why-grid,.gal-grid,
  .grid2c,.notice-grid{grid-template-columns:repeat(2,1fr)}
  /* rails stay rails on tablet, just with wider cards */
  .sv-grid>*,.path-grid>*{flex:0 0 min(46%,330px)}
  .facts,.stats-band .wrap{grid-template-columns:repeat(2,1fr)}
  /* TABLET: four blocks, so 2x2 rather than a row that would squeeze each
     column to ~180px */
  .ftr-cols{grid-template-columns:1.35fr 1fr 1.2fr}
  /* each column is narrow now, so links go back to one per line */
  .ftr ul{grid-template-columns:1fr}
  .ftr-brand p{max-width:34ch}
  .namelist{grid-template-columns:repeat(2,1fr)}
  .frow{grid-template-columns:1fr 1fr}
  .gal-item.tall{grid-row:span 2}

  .medal-card{max-width:620px}
  .slide{min-height:min(72vh,640px)}

  /* TABLET MENU: still the drawer, because six items plus the actions do not
     fit a 768 row, but a full-height single column of six links wastes the
     width. Two columns, and the sub-items stay open rather than needing a
     second tap on a screen with room to show them. */
  .mobile-menu{padding:30px 34px}
  .mm-list{display:grid;grid-template-columns:1fr 1fr;gap:0 34px;align-content:start}
  .mm-list>li{border-bottom:1px solid var(--line)}
  .mm-list a,.mm-list button.mm-t{padding-block:15px}
  .mm-sub{display:block;padding-bottom:14px}
  .mm-list .car{display:none}
}

/* ---------------- LAPTOP · 1024px and up ---------------- */
@media (min-width:1024px){
  body{font-size:17px}
  .nav{height:88px;gap:22px}
  .brand img{width:50px;height:50px}
  .topbar .addr{display:inline-flex}

  .prog-grid{grid-template-columns:repeat(3,1fr)}
  .sv-grid{grid-template-columns:repeat(3,1fr)}
  .why-grid{grid-template-columns:repeat(4,1fr)}
  .gal-grid{grid-template-columns:repeat(3,1fr)}
  .grid3c{grid-template-columns:repeat(3,1fr)}
  .notice-grid{grid-template-columns:repeat(3,1fr)}
  .facts{grid-template-columns:repeat(4,1fr)}
  .stats-band .wrap{grid-template-columns:repeat(4,1fr)}
  /* LAPTOP AND UP: same three columns as tablet, wider. The hole here was
     never the track sizing, it was the brand paragraph: capped at 34ch it
     wrapped at ~260px inside a 450px column and left the remainder empty.
     Uncapped, the text fills its track and the columns read as a set. */
  .ftr-cols{grid-template-columns:1.4fr 1fr 1.15fr}
  .ftr-brand{grid-column:auto}
  .ftr-brand p{max-width:none}
  .ftr-reach .addr{max-width:none}
  .ftr ul{grid-template-columns:1fr}

  .grid2c{grid-template-columns:1fr 1fr}
  .path-grid{grid-template-columns:1fr 1fr}
  .spot-grid{grid-template-columns:.92fr 1.08fr}
  .contact-grid{grid-template-columns:1.05fr .95fr}
  .apply-grid{grid-template-columns:1.08fr .92fr}

  /* hero poster only has room from desktop up */
  .slide-inner .grid2{grid-template-columns:1.15fr .85fr}
  .slide .poster{display:block}
  .slide{min-height:min(78vh,720px)}
}

/* ================================================================
   THE MENU AT EACH TIER

   Measured, not guessed. One row of the full nav costs 1007px in
   English and 1191px in தமிழ், because the Tamil labels are longer
   for the same six items. So the tiers are:

     mobile   the drawer, one column
     tablet   the drawer, two columns, phone and mail inside it
     laptop   full nav, condensed hard enough that தமிழ் also fits
              in 1024: smaller labels, no brand tagline, tight
              Apply button
     big      full nav at its natural size, everything restored

   Condensing at laptop rather than holding the burger to 1280 is
   what lets both languages switch to the real menu at the same
   width, which is the point: a burger on a 1200px laptop in one
   language and a menu in the other would be the inconsistency.
   ================================================================ */

/* ---------------- FULL NAV · 1280px and up ----------------
   Measured: one header row costs 1007px in English but 1191px in தமிழ்,
   because the same six labels are longer. Below this the burger holds, in
   BOTH languages, so the header never differs between them. Forcing the
   menu on earlier is what pushed the Tamil items out over the logo:
   .nav-links is justify-content:flex-end, so it overflows LEFTWARDS. */
@media (min-width:1280px){
  .nav-links{display:flex}
  .burger{display:none}
  .mobile-menu{display:none}
}

/* ---------------- BIG SCREENS · 1440px and up ---------------- */
@media (min-width:1440px){
  :root{--wrap:1300px}
  body{font-size:17.5px}
  /* the extra footer column, only here */
  .ftr-links:not(.ftr-courses){display:block}
  .ftr-cols{grid-template-columns:1.5fr .95fr .8fr 1.05fr}
  /* all five services fit on one row only once the wrap opens to 1300 */
  .sv-grid{grid-template-columns:repeat(5,1fr)}
}

/* ---------------- touch & reduced motion ---------------- */
@media (hover:none){
  .pcard:hover,.feat:hover,.pathc:hover,.notice:hover,.topic:hover{transform:none}
  .gal-item:hover img{transform:none}
}
@media (prefers-reduced-motion:reduce){
  *{scroll-behavior:auto!important}
  .reveal{opacity:1;transform:none;transition:none}
  .slides{transition:none}
  .slide-media img{transform:none;transition:none}
  .s-dot.on .fill{animation:none;width:100%}
  .yrow .bar{animation:none}
  .gal-item img,.medal-card{transition:none}
}
