/* ==========================================================================
   Swami Vivekananda University — main stylesheet
   Mobile-first. Breakpoints: 480 / 640 / 768 / 992 / 1200 / 1440
   No external fonts or CDN assets (keeps the strict CSP intact).
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Design tokens
   -------------------------------------------------------------------------- */
:root {
  --gold:            #e9cf52;   /* main navigation bar                */
  --gold-bright:     #f0d75f;
  --gold-mid:        #b09a3f;   /* marquee strip                      */
  --gold-dark:       #a4913c;   /* top utility bar                    */
  --gold-deep:       #c5ab48;   /* testimonial section background     */
  --accent:          #b8942e;   /* gold heading text                  */
  --accent-soft:     #d4b551;
  --brand:           #14463f;   /* dark teal of the SVU crest         */

  --ink:             #1f1f1f;
  --ink-soft:        #333333;
  --muted:           #6b6b6b;
  --line:            #e2e2e2;

  --paper:           #ffffff;
  --page:            #f1f1f1;
  --page-alt:        #f7f7f7;
  --dark:            #2b2b2b;
  --darker:          #212121;

  --red:             #d0021b;
  --blue:            #2d6cb5;
  --blue-dark:       #24578f;   /* tab pill hover, both tab panels */
  --whatsapp:        #25d366;

  --radius-sm: 3px;
  --radius:    6px;
  --radius-lg: 14px;
  --radius-pill: 999px;

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .06);
  --shadow-sm: 0 2px 6px rgba(0, 0, 0, .08);
  --shadow:    0 4px 16px rgba(0, 0, 0, .10);
  --shadow-lg: 0 12px 36px rgba(0, 0, 0, .16);

  --shell: 1240px;
  --gutter: 16px;

  --font-sans: "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --font-serif: Georgia, "Times New Roman", Times, serif;

  --header-h: 64px;
  --transition: .25s ease;
}

/* --------------------------------------------------------------------------
   2. Reset & base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 90px;
}

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.65;
  color: var(--ink-soft);
  background-color: var(--page);
  background-image: radial-gradient(rgba(0, 0, 0, .035) 1px, transparent 1px);
  background-size: 4px 4px;
}

/* Horizontal overflow is clipped on BOTH html and body, with clip rather than
   hidden. Two separate reasons, and the old rule - overflow-x:hidden on body
   alone - got both wrong.

   On body alone the value propagates to the viewport and body itself is then
   treated as visible, so an absolutely positioned descendant can still widen
   the scrollable area. That is what put a horizontal scrollbar under the site
   and slid the hero heading off the left: the Departments board hangs at
   left:100% of a dropdown and is only hidden with visibility, so it is laid
   out - and overflowing - even with every menu shut.

   clip rather than hidden because hidden makes the element a scroll container,
   and a scroll container breaks position:sticky inside it - which is exactly
   what .nav.is-stuck relies on to hold the menu bar at the top of the page. */
html,
body {
  overflow-x: clip;
}

img, svg, video, iframe { max-width: 100%; }
img, svg, video { height: auto; display: block; }

h1, h2, h3, h4, h5, h6 {
  margin: 0 0 .6em;
  color: var(--ink);
  line-height: 1.25;
  font-weight: 700;
}

p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

a { color: var(--accent); text-decoration: none; transition: color var(--transition); }
a:hover { color: var(--ink); }

ul, ol { margin: 0 0 1em; padding-left: 1.2em; }

hr { border: 0; border-top: 1px solid var(--line); margin: 1.5rem 0; }

table { border-collapse: collapse; width: 100%; }

/* Accessibility --------------------------------------------------------- */
:focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 2px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 999;
  background: var(--ink);
  color: #fff;
  padding: 12px 20px;
  border-radius: 0 0 var(--radius) 0;
}
.skip-link:focus {
  left: 0;
  color: #fff;
}

.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Honeypot — visually and programmatically hidden from real users */
.hp-field {
  position: absolute !important;
  left: -9999px !important;
  top: -9999px !important;
  width: 1px; height: 1px;
  opacity: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    /* Zero the delays too — the nav's hover-intent lead-in would otherwise
       survive here and read as lag rather than as motion. */
    transition-delay: 0s !important;
  }
}

/* --------------------------------------------------------------------------
   3. Layout primitives
   -------------------------------------------------------------------------- */
.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.shell--wide { max-width: 1440px; }
/* No ceiling at all, only the gutter: for a band meant to run the width of
   the viewport rather than line up with the rest of the page. */
.shell--full { max-width: none; }

.section { padding: 40px 0; }
.section--tight { padding: 26px 0; }
.section--alt { background: var(--page-alt); }
.section--white { background: var(--paper); }

.stack > * + * { margin-top: 1rem; }

.grid { display: grid; gap: 20px; }

/* Section headings — "SVU SCHOOLS" style two-tone titles */
.section-title {
  font-size: clamp(1.4rem, 1.1rem + 1.2vw, 1.9rem);
  letter-spacing: .5px;
  margin-bottom: 1.4rem;
  text-transform: uppercase;
}
/* A title that opens its section needs no space above it — the section padding
   already provides that.  One that follows body copy or a grid does, otherwise
   it runs straight into the block before it. */
.section-title:not(:first-child) { margin-top: clamp(2rem, 1.4rem + 1.4vw, 3rem); }
.section-title .h-dark  { color: var(--ink); }
.section-title .h-accent { color: var(--accent-soft); font-weight: 400; }

.section-title--underline { position: relative; padding-bottom: 12px; }
.section-title--underline::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 62px; height: 3px;
  background: var(--accent);
}
.section-title--center {
  text-align: center;
}
.section-title--center.section-title--underline::after {
  left: 50%;
  transform: translateX(-50%);
}

.section-subtitle {
  text-align: center;
  font-weight: 700;
  color: var(--ink);
  margin: -.6rem 0 1.8rem;
  font-size: clamp(.95rem, .85rem + .35vw, 1.15rem);
}

/* --------------------------------------------------------------------------
   4. Buttons
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 22px;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: .4px;
  text-transform: uppercase;
  cursor: pointer;
  transition: all var(--transition);
  text-align: center;
  line-height: 1.2;
  min-height: 42px;           /* comfortable touch target */
}

.btn--gold {
  background: var(--gold);
  color: var(--ink);
  box-shadow: 0 3px 0 rgba(0, 0, 0, .12);
}
.btn--gold:hover, .btn--gold:focus { background: var(--gold-bright); color: var(--ink); }

.btn--outline-gold {
  background: #fff;
  border-color: var(--gold);
  color: var(--ink);
  box-shadow: 3px 3px 0 var(--gold);
}
.btn--outline-gold:hover { background: var(--gold); color: var(--ink); }

.btn--dark { background: var(--dark); color: #fff; }
.btn--dark:hover { background: #000; color: #fff; }

.btn--blue { background: var(--blue); color: #fff; border-radius: var(--radius); }
.btn--blue:hover { background: #24578f; color: #fff; }

.btn--red { background: var(--red); color: #fff; }
.btn--red:hover { background: #a80217; color: #fff; }

.btn--block { width: 100%; }
.btn--sm { padding: 7px 16px; font-size: .74rem; min-height: 36px; }

/* --------------------------------------------------------------------------
   5. Top utility bar
   -------------------------------------------------------------------------- */
.topbar {
  background: var(--gold-dark);
  color: var(--ink);
  font-size: .82rem;
}
.topbar__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px 18px;
  padding-block: 8px;
}
.topbar__links {
  display: flex;
  align-items: center;
  gap: 10px;
  list-style: none;
  margin: 0; padding: 0;
  font-weight: 700;
}
.topbar__links a { color: var(--ink); }
.topbar__links a:hover { color: #fff; }
.topbar__links li + li::before { content: "|"; margin-right: 10px; opacity: .5; }

.topbar__social {
  display: flex;
  align-items: center;
  gap: 14px;
  list-style: none;
  margin: 0; padding: 0;
}
.topbar__social a {
  color: var(--ink);
  display: inline-flex;
  width: 24px; height: 24px;
  align-items: center;
  justify-content: center;
}
.topbar__social a:hover { color: #fff; }
.topbar__social svg { width: 15px; height: 15px; fill: currentColor; }

.topbar__search { margin-left: auto; }

/* From 768px up the bar becomes three columns so the social icons sit in the
   true centre of the viewport, independent of how wide the links or the search
   box are. Below that it stays a centred wrapping flex row. */
@media (min-width: 768px) {
  .topbar__inner {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 10px 18px;
  }
  /* Explicit columns, so the search stays right-aligned even when no social
     links are configured and the middle cell is empty. */
  .topbar__links  { grid-column: 1; justify-self: start; }
  .topbar__social { grid-column: 2; justify-self: center; }
  .topbar__search { grid-column: 3; justify-self: end; margin-left: 0; }
}
/* The WIDTH LIVES HERE, on the box, not on the input. The input then flexes
   to fill whatever is left over, which keeps the magnifier hard against the
   trailing edge at every viewport. Size the input instead and any rule that
   makes this box wider leaves the icon stranded mid-box with dead white space
   after it. */
.search-inline {
  display: flex;
  align-items: center;
  width: 225px;
  max-width: 100%;
  background: #fff;
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: 1px solid rgba(0, 0, 0, .15);
}
.search-inline input {
  flex: 1 1 auto;
  /* An input carries an automatic minimum width of about 20 characters, which
     would stop it shrinking inside a narrow box and push the button out. */
  min-width: 0;
  border: 0;
  padding: 6px 10px;
  font: inherit;
  font-size: .82rem;
  background: transparent;
  color: var(--ink);
}
.search-inline input:focus { outline: none; }
.search-inline button {
  flex: none;
  border: 0;
  background: transparent;
  padding: 6px 10px;
  cursor: pointer;
  color: var(--muted);
  display: inline-flex;
}
.search-inline button:hover { color: var(--ink); }
.search-inline svg { width: 15px; height: 15px; fill: currentColor; }

/* --------------------------------------------------------------------------
   6. Header
   -------------------------------------------------------------------------- */
.header { background: #fff; }
.header__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 26px;
  padding-block: 12px;
}

.header__brand {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}
.header__brand img { max-height: 56px; width: auto; }
.header__brand .brand-divider {
  width: 1px;
  height: 40px;
  background: var(--line);
}

/* Square university crest */
.header__crest {
  width: 52px;
  height: 52px;
  max-height: none;
  border-radius: 50%;
  object-fit: contain;
  flex: 0 0 auto;
}

/* Text lockup beside the crest */
.brand-lockup {
  display: flex;
  flex-direction: column;
  justify-content: center;
  line-height: 1.15;
}
.brand-lockup__name {
  font-weight: 800;
  /* Always one line — the size scales down instead of the words wrapping.
     nowrap cannot reflow, so the lower bound is set to clear the narrowest
     phone (320px) with room to spare rather than clipping. */
  white-space: nowrap;
  font-size: clamp(.50rem, .25rem + 1.3vw, .92rem);
  color: var(--brand);
  letter-spacing: .3px;
  text-transform: uppercase;
}
.brand-lockup small {
  display: block;
  font-size: .62rem;
  font-weight: 600;
  letter-spacing: .7px;
  color: var(--muted);
  text-transform: uppercase;
  margin-top: 2px;
}
/* The strapline used to be dropped on phones for width. It is kept now and
   sized to fit instead - see the phone block near the foot of this file. */

.header__info {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 14px 28px;
  flex: 1 1 auto;
}

.info-block { font-size: .8rem; line-height: 1.5; }
.info-block__label {
  display: inline-block;
  background: var(--gold);
  color: var(--ink);
  font-weight: 700;
  padding: 2px 10px;
  margin-bottom: 5px;
  font-size: .76rem;
  letter-spacing: .3px;
}
.info-block__body { color: var(--ink); font-weight: 600; }
.info-block__body a { color: var(--ink); display: block; }
.info-block__body a:hover { color: var(--accent); }
.info-block__body .toll-free { white-space: nowrap; }


.header__cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 18px;
  margin-left: auto;
}
.cta-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  border: 2px solid #444;
  border-radius: var(--radius-pill);
  padding: 5px 16px;
  font-weight: 800;
  font-size: .8rem;
  color: var(--ink);
  background: #fff;
  letter-spacing: .3px;
}
.cta-pill:hover { background: var(--gold); color: var(--ink); }

.cta-stack { text-align: center; line-height: 1.15; }
.cta-stack__title {
  display: block;
  font-weight: 800;
  font-size: .82rem;
  color: var(--ink);
  letter-spacing: .2px;
}
.cta-stack__sub { font-size: .72rem; color: var(--muted); }

.cta-apply { text-align: center; }
.cta-apply__label {
  display: block;
  font-weight: 800;
  font-size: .8rem;
  color: var(--ink);
  margin-bottom: 3px;
  letter-spacing: .2px;
}

.header__toggle {
  display: none;
  margin-left: auto;
  background: var(--gold);
  border: 0;
  border-radius: var(--radius-sm);
  padding: 9px 12px;
  cursor: pointer;
  color: var(--ink);
}
.header__toggle svg { width: 22px; height: 22px; fill: currentColor; }

/* --- Desktop: keep the whole header on ONE row -----------------------------
   Brand, contact blocks and the call-to-action group must never wrap to a
   second line. Type and spacing scale with the viewport, and a long address
   wraps inside its own column rather than stretching the row. */
@media (min-width: 992px) {
  .header__inner {
    flex-wrap: nowrap;
    gap: 10px clamp(10px, 1.2vw, 18px);
  }

  .header__brand { flex-shrink: 0; }
  .brand-lockup__name { font-size: clamp(.58rem, .30rem + .62vw, .88rem); }
  .brand-lockup small {
    font-size: clamp(.50rem, .38rem + .13vw, .60rem);
    letter-spacing: .3px;
  }

  .header__info {
    flex-wrap: nowrap;
    align-items: flex-start;
    gap: 0 clamp(10px, 1.1vw, 20px);
    min-width: 0;
  }
  .info-block {
    /* Tuned so the whole header still fits one line at 992px, where it is
       tightest. Wider screens get a bump further down. */
    font-size: clamp(.64rem, .50rem + .18vw, .76rem);
    line-height: 1.45;
    min-width: 0;
  }
  .info-block__label {
    font-size: clamp(.56rem, .46rem + .13vw, .68rem);
    padding: 2px 7px;
    margin-bottom: 3px;
    white-space: nowrap;
  }
  /* Wraps a long street address instead of forcing the row wider */
  .info-block__body { max-width: clamp(150px, 17vw, 250px); }
  .info-block__body a { white-space: nowrap; }

  .header__cta {
    flex-wrap: nowrap;
    align-items: center;
    gap: 0 clamp(8px, .9vw, 16px);
    flex: 0 0 auto;
  }
  .cta-pill {
    padding: 4px clamp(8px, .8vw, 14px);
    font-size: clamp(.60rem, .48rem + .14vw, .74rem);
    white-space: nowrap;
  }
  .cta-stack__title { font-size: clamp(.64rem, .52rem + .14vw, .78rem); }
  .cta-stack__sub   { font-size: clamp(.52rem, .44rem + .10vw, .66rem); }
  .cta-apply__label {
    font-size: clamp(.58rem, .48rem + .12vw, .72rem);
    margin-bottom: 2px;
    white-space: nowrap;
  }
  .cta-apply .btn {
    padding: 5px clamp(8px, .8vw, 14px);
    font-size: clamp(.58rem, .48rem + .12vw, .70rem);
    min-height: 30px;
    white-space: nowrap;
  }
}

/* Under 1200px the brand strapline is dropped to buy back horizontal room. */
@media (min-width: 992px) and (max-width: 1199.98px) {
  .brand-lockup small { display: none; }
}

/* --------------------------------------------------------------------------
   7. Marquee strip
   -------------------------------------------------------------------------- */
.marquee {
  background: var(--gold-mid);
  color: var(--ink);
  overflow: hidden;
  padding: 9px 0;
  border-block: 1px solid rgba(0, 0, 0, .08);
}
.marquee__track {
  display: inline-block;
  white-space: nowrap;
  font-weight: 700;
  font-size: .88rem;
  padding-left: 100%;
  /* The duration comes in on the element, worked out from how much text is on
     the strip, so the reading pace stays steady as announcements are added.
     The 32s here is only the fallback for a strip rendered without it. */
  animation: marquee-scroll var(--marquee-duration, 32s) linear infinite;
  will-change: transform;
}
/* Hovering stops it. The announcements are links, and no one should have to
   chase a moving target to click one. focus-within does the same for a
   visitor arriving by keyboard. */
.marquee:hover .marquee__track,
.marquee:focus-within .marquee__track { animation-play-state: paused; }

.marquee__item { color: inherit; }
/* Underlined only on hover: a strip of permanently underlined announcements
   reads as a link farm, but nothing should be clickable without saying so at
   the moment of clicking. */
.marquee__item--link:hover,
.marquee__item--link:focus-visible { text-decoration: underline; }

/* The divider between announcements, drawn rather than typed, so it is not in
   the text a screen reader reads out. Only between - never before the first. */
.marquee__item + .marquee__item::before {
  content: "\2022";
  margin: 0 14px;
  opacity: .55;
  font-weight: 400;
}

@keyframes marquee-scroll {
  0%   { transform: translateX(0); }
  100% { transform: translateX(-100%); }
}
@media (prefers-reduced-motion: reduce) {
  .marquee__track {
    animation: none;
    padding-left: 0;
    white-space: normal;
    display: block;
    text-align: center;
  }
}

/* --------------------------------------------------------------------------
   8. Main navigation
   -------------------------------------------------------------------------- */
.nav {
  background: var(--gold);
  position: relative;
  z-index: 60;
  box-shadow: var(--shadow-xs);
}
.nav.is-stuck {
  position: sticky;
  top: 0;
}

.nav__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  list-style: none;
  /* margin:0 and padding:0 used to be set here, which cancelled the .shell
     class this same element carries. The bar then sat hard against the left
     edge of the window while every other row on the page was centred - and
     because a centred flex row that overruns its box overflows BOTH sides
     equally, the Home icon was pushed off the left edge and clipped. Only the
     block axis is zeroed now, so .shell keeps centring it and keeps its
     gutters. */
  margin-block: 0;
  padding-block: 0;
  gap: 0;
}
.nav__item { position: relative; }

.nav__link {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 13px 11px;
  color: var(--ink);
  font-size: .78rem;
  font-weight: 800;
  letter-spacing: .2px;
  text-transform: uppercase;
  white-space: nowrap;
  border-bottom: 3px solid transparent;
  /* Without this the highlight and the underline snap on/off, which is what
     makes a hover read as mechanical. */
  transition: background .18s ease, border-bottom-color .18s ease, color .18s ease;
}
.nav__link:hover,
.nav__link:focus,
.nav__item.is-open > .nav__link,
.nav__link.is-current {
  background: rgba(255, 255, 255, .38);
  color: var(--ink);
  border-bottom-color: var(--ink);
}
.nav__link svg { width: 16px; height: 16px; fill: currentColor; flex-shrink: 0; }
/* Scoped, not a bare .nav__caret: the rule above is a class plus an element
   and so outranks a lone class, which quietly held every caret at the 16px
   meant for the home icon and left the sizes below doing nothing. */
.nav__link .nav__caret {
  width: 9px; height: 9px;
  fill: currentColor;
  opacity: .8;
  transition: transform .22s ease, opacity .18s ease;
}
.nav__item--has-children:hover > .nav__link .nav__caret,
.nav__item--has-children:focus-within > .nav__link .nav__caret {
  transform: rotate(180deg);
  opacity: 1;
}

.nav__submenu {
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 250px;
  background: #fff;
  list-style: none;
  margin: 0; padding: 6px 0;
  box-shadow: var(--shadow-lg);
  border-top: 3px solid var(--gold);
  border-radius: 0 0 var(--radius) var(--radius);
  opacity: 0;
  visibility: hidden;
  transform: translateY(6px);
  /* CLOSING. The delay is deliberate: it gives the pointer time to travel
     diagonally from the menu label down into the panel without the panel
     vanishing under it. visibility flips only after the fade has finished,
     so the panel is never clickable while invisible. */
  transition:
    opacity .2s ease .14s,
    transform .2s ease .14s,
    visibility 0s linear .34s;
  z-index: 70;
}
.nav__item:hover > .nav__submenu,
.nav__item:focus-within > .nav__submenu,
.nav__item.is-open > .nav__submenu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  /* OPENING. The 90ms lead-in is hover intent — sweeping the pointer across
     the bar to reach a far item no longer flickers every menu open on the
     way past.  Decelerating curve so it settles instead of stopping dead. */
  transition:
    opacity .24s cubic-bezier(.22, .61, .36, 1) .09s,
    transform .24s cubic-bezier(.22, .61, .36, 1) .09s,
    visibility 0s linear .09s;
}
/* Flip the last dropdowns so they never overflow the viewport */
.nav__item:nth-last-child(-n+3) .nav__submenu { left: auto; right: 0; }

.nav__submenu a {
  display: block;
  padding: 9px 18px;
  color: var(--ink-soft);
  font-size: .82rem;
  font-weight: 600;
  border-left: 3px solid transparent;
  transition: background .16s ease, color .16s ease, border-left-color .16s ease;
}
.nav__submenu a:hover,
.nav__submenu a:focus {
  background: #faf6e4;
  color: var(--accent);
  border-left-color: var(--gold);
}

/* Mega dropdown ---------------------------------------------------------
   For a menu with too many links to read as one tall column ("At a Glance").
   Turned on by 'columns' on the MAIN_NAV entry in data.py.

   It is centred on the viewport rather than hung off the item's left edge:
   a 1080px panel dropped from an item sitting two-thirds along the bar would
   otherwise run off the right of the screen. */
/* The item drops out of the positioning flow so the panel below anchors to
   .nav (full width of the bar) instead of to this one menu item — otherwise
   "centred" means centred on the word "At a Glance", which throws a 1080px
   panel off the left edge of the screen. */
.nav__item--mega { position: static; }

.nav__submenu--mega {
  left: 50%;
  right: auto;
  transform: translate(-50%, 6px);
  width: min(1080px, calc(100vw - 32px));
  min-width: 0;
  padding: 20px 22px;
  /* "At most 3 columns, each at least 260px", rather than a flat 3. Programs
     fills this panel with school headings and their departments, and names
     like "Department Of Advanced Networking & Cyber Security" need the room;
     below three columns' worth the browser drops one by itself. */
  columns: 260px 3;
  column-gap: 30px;
}

/* A panel of grouped content - Programs, with its schools.
   Newspaper columns rather than a grid, and the reason is the gaps. A grid
   aligns rows, so every row is as tall as its tallest group: School of
   Management, with one department, left a column of empty space beside School
   of Allied Health Services, with six. Columns pack each group directly under
   the last one and balance the total across the tracks, so there is no
   alignment to leave holes.
   No max-height here, and that matters: multi-column does not wrap under a
   height limit, it keeps adding columns sideways. Capping this panel is what
   previously gave it a horizontal scrollbar with five schools off-screen. Let
   it be as tall as it needs and it stays inside its box. */
.nav__submenu--groups {
  columns: 215px 4;
  column-gap: 30px;
}

/* Tighter than the sideways board, because this one shows all nine schools at
   once rather than a slice of them. At the board's 10px rows the panel came to
   roughly 900px and ran past the bottom of a laptop screen - and a panel that
   closes when the pointer leaves cannot be scrolled to, so anything below the
   fold is simply unreachable. At these values it lands near 600px. */
.nav__submenu--groups .nav__panel-group { margin-bottom: 18px; }
.nav__submenu--groups .nav__panel-list a { padding: 7px 0; }
.nav__submenu--groups a.nav__panel-heading::after { margin-top: 7px; }
/* Beats the "flip the last dropdowns" rule above, whose selector is more
   specific — without this a mega menu near the end of the bar jumps right. */
.nav__item:nth-last-child(-n+3) .nav__submenu--mega { left: 50%; right: auto; }

.nav__item:hover > .nav__submenu--mega,
.nav__item:focus-within > .nav__submenu--mega,
.nav__item.is-open > .nav__submenu--mega { transform: translate(-50%, 0); }

/* A two-line label must not be split down the middle by the column break. */
.nav__submenu--mega li { break-inside: avoid; page-break-inside: avoid; }
.nav__submenu--mega a {
  padding: 8px 10px;
  border-left: 0;
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-size: .84rem;
}
.nav__submenu--mega a:hover,
.nav__submenu--mega a:focus { background: #faf6e4; color: var(--accent); }

@media (max-width: 1199.98px) {
  /* Less room, so two columns rather than three squeezed ones. */
  .nav__submenu--mega { column-count: 2; width: min(760px, calc(100vw - 32px)); }
}

/* Third level: the Departments board ------------------------------------
   A dropdown child carrying 'panel' in data.py flies its own board out to the
   side - school headings, each with its departments beneath, read down in
   columns.  The contents come from context_processors.py, so the board lists
   whatever DEPARTMENTS holds and never needs editing here.

   The row is the positioning context and the board hangs off its right edge.
   That only fits because Programs sits near the left of the bar; a panel on a
   menu further along would run off the screen and would need the mega menu's
   centring instead.  The width subtracts the whole distance already spent -
   the item's offset plus the 250px dropdown - so the board cannot reach past
   the right edge on a narrow laptop. */
.nav__subitem--has-panel { position: relative; }
.nav__subitem--has-panel > a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
/* The shared caret points down; on a sideways flyout it points the way the
   board opens.

   It needs its own size: the rule that sizes every other caret is scoped to
   '.nav__link .nav__caret', and this one hangs off an ordinary dropdown
   anchor instead. Left unsized, an <svg> with a viewBox and no dimensions
   stretches to fill the flex row - which is exactly what it did, blowing the
   Departments row up to the height of the arrow. */
.nav__submenu .nav__caret--side {
  width: 9px;
  height: 9px;
  flex: none;
  opacity: .8;
  transform: rotate(-90deg);
  transition: transform .22s ease, opacity .18s ease;
}
.nav__subitem--has-panel:hover > a .nav__caret--side,
.nav__subitem--has-panel:focus-within > a .nav__caret--side { opacity: 1; }

.nav__panel {
  position: absolute;
  top: -6px;
  left: 100%;
  /* The board starts at shellLeft + the nav offset of Programs + the 250px
     dropdown, so the room left for it is not a fixed subtraction from 100vw.
     The old calc(100vw - 470px) assumed the board began 470px in; once the
     shell stops being full-bleed that is optimistic by the whole left margin,
     and on a 1280px laptop it asked for 810px into about 690px of space - the
     last column of schools ran off the right-hand edge.
     Two bounds instead, whichever is smaller:
       50vw - 10px    once the shell is centred and gains a margin each side
       100vw - 640px  while the shell still spans the viewport
     Both allow for the widest the Programs label can sit, so the board stays
     inside the window at every width rather than at the one it was measured
     on. */
  width: min(920px, calc(50vw - 10px), calc(100vw - 640px));
  padding: 22px 26px 4px;
  background: #fff;
  box-shadow: var(--shadow-lg);
  border-top: 3px solid var(--gold);
  border-radius: 0 var(--radius) var(--radius) 0;
  /* "At most 3 columns, each at least 240px" rather than a flat count of 3.
     The board narrows with the viewport under the width rule above, and a
     fixed 3 columns then squeezed each to about 160px on a small laptop -
     enough to break "Department Of Advanced Networking & Cyber Security"
     across four lines. The browser drops to two columns, then one, on its own,
     which is one fewer breakpoint to keep in step with the width. */
  columns: 240px 3;
  column-gap: 34px;
  opacity: 0;
  visibility: hidden;
  transform: translateX(6px);
  /* Same closing delay as the dropdown above it: the pointer has to cross
     from the label into the board without it vanishing on the way. */
  transition:
    opacity .2s ease .14s,
    transform .2s ease .14s,
    visibility 0s linear .34s;
  z-index: 80;
}
.nav__subitem--has-panel:hover > .nav__panel,
.nav__subitem--has-panel:focus-within > .nav__panel,
.nav__subitem--has-panel.is-open > .nav__panel {
  opacity: 1;
  visibility: visible;
  transform: translateX(0);
  transition:
    opacity .22s cubic-bezier(.22, .61, .36, 1) .06s,
    transform .22s cubic-bezier(.22, .61, .36, 1) .06s,
    visibility 0s linear .06s;
}

/* A school and the departments under it must never be split by a column
   break, or a heading ends up stranded at the foot of one column. */
.nav__panel-group {
  break-inside: avoid;
  page-break-inside: avoid;
  margin-bottom: 20px;
}

/* Scoped past '.nav__submenu a', which otherwise lands its own padding and
   left border on both of these - it matches every anchor in the dropdown,
   and this board sits inside one.
   Both contexts are listed because the schools are drawn two ways now: as the
   sideways board for a plain dropdown, and inline inside the columned panel
   that Programs and At a Glance use. Same markup, same styling, different
   parent. */
.nav__submenu--mega a.nav__panel-heading,
.nav__panel a.nav__panel-heading {
  display: block;
  padding: 0;
  color: var(--ink);
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
  line-height: 1.3;
  border-left: 0;
  background: none;
}
.nav__submenu--mega a.nav__panel-heading::after,
.nav__panel a.nav__panel-heading::after {
  content: "";
  display: block;
  width: 108px;
  height: 3px;
  margin-top: 9px;
  background: var(--red);
}
.nav__submenu--mega a.nav__panel-heading:hover,
.nav__submenu--mega a.nav__panel-heading:focus,
.nav__panel a.nav__panel-heading:hover,
.nav__panel a.nav__panel-heading:focus { color: var(--accent); background: none; }

.nav__panel-list { list-style: none; margin: 4px 0 0; padding: 0; }
.nav__panel-list li + li { border-top: 1px solid var(--line); }
.nav__submenu--mega .nav__panel-list a,
.nav__panel .nav__panel-list a {
  display: block;
  padding: 10px 0;
  color: var(--ink-soft);
  font-size: .8rem;
  font-weight: 500;
  line-height: 1.45;
  border-left: 0;
  background: none;
}
.nav__submenu--mega .nav__panel-list a:hover,
.nav__submenu--mega .nav__panel-list a:focus,
.nav__panel .nav__panel-list a:hover,
.nav__panel .nav__panel-list a:focus { color: var(--accent); background: none; }

@media (max-width: 1399.98px) {
  /* Two columns rather than three squeezed ones, same as the mega menu. */
  .nav__panel { column-count: 2; }
}
@media (max-width: 1149.98px) {
  /* Below this the board is too narrow to column at all, so it becomes one
     list and scrolls inside itself rather than running off the screen. */
  .nav__panel { column-count: 1; max-height: 72vh; overflow-y: auto; }
}

.nav__close, .nav__mobile-head { display: none; }
.nav-scrim { display: none; }

/* --------------------------------------------------------------------------
   9. Hero carousel
   -------------------------------------------------------------------------- */
.hero { position: relative; background: #111; overflow: hidden; }

/* Full-height stage: the clip fills the viewport. `svh` keeps it honest on
   mobile, where the address bar makes `vh` overshoot; `vh` is the fallback. */
.hero__media {
  position: relative;
  width: 100%;
  height: 100vh;
  height: 100svh;
  min-height: 420px;
  overflow: hidden;
  background: #111;
}

/* Local MP4 — fills the stage and crops, never letterboxes. */
.hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
}

/* YouTube fallback: a 16:9 frame blown up until it covers the wider stage.
   pointer-events are off so the hero never swallows a click. */
.hero__frame {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100vw;
  height: 56.25vw;           /* 16:9 of the viewport width  */
  min-width: 177.78vh;       /* …and tall enough when short */
  min-height: 100%;
  border: 0;
  pointer-events: none;
}

/* Darkens the footage just enough for the caption to stay readable. */
.hero__overlay {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: linear-gradient(to top,
              rgba(0, 0, 0, .74) 0%,
              rgba(0, 0, 0, .18) 55%,
              rgba(0, 0, 0, .38) 100%);
}

/* Sit the caption on the same left edge as the rest of the page, and give it
   room to breathe now that the stage is a full screen tall.

   These deliberately out-specify the >=768px rules further down, which turn the
   caption into a light panel with dark text.  That reads well over the old
   bright banner stills but disappears against video footage, so the video hero
   keeps white text sitting on the dark end of the overlay gradient. */
.hero--video .hero__caption {
  z-index: 3;
  top: auto;
  bottom: 0;
  height: auto;
  width: 100%;
  display: block;
  background: none;              /* .hero__overlay provides the scrim */
  color: #fff;
  padding: 44px max(var(--gutter), calc((100% - var(--shell)) / 2)) 58px;
}
.hero--video .hero__caption h2 {
  color: #fff;
  max-width: 26ch;
  text-shadow: 0 2px 14px rgba(0, 0, 0, .55);
}
.hero--video .hero__caption p {
  color: #f1f1f1;
  text-shadow: 0 1px 10px rgba(0, 0, 0, .5);
}

.hero__caption {
  position: absolute;
  inset-inline-start: 0;
  bottom: 0;
  width: 100%;
  padding: 16px var(--gutter);
  background: linear-gradient(to top, rgba(0, 0, 0, .68), rgba(0, 0, 0, 0));
  color: #fff;
}
.hero__caption h2 {
  color: #fff;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: clamp(1rem, .7rem + 2vw, 2.4rem);
  margin-bottom: .2em;
  max-width: 22ch;
}
.hero__caption p {
  font-size: clamp(.78rem, .7rem + .4vw, 1rem);
  max-width: 46ch;
  margin-bottom: .6em;
}

.hero__arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: rgba(255, 255, 255, .55);
  border: 0;
  width: 40px; height: 54px;
  cursor: pointer;
  display: grid;
  place-items: center;
  color: var(--ink);
  transition: background var(--transition);
  z-index: 3;
}
.hero__arrow:hover { background: var(--gold); }
.hero__arrow--prev { left: 0; border-radius: 0 var(--radius) var(--radius) 0; }
.hero__arrow--next { right: 0; border-radius: var(--radius) 0 0 var(--radius); }
.hero__arrow svg { width: 20px; height: 20px; fill: currentColor; }

.hero__dots {
  position: absolute;
  bottom: 10px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 8px;
  z-index: 3;
}

.dots { display: flex; gap: 8px; justify-content: center; }
.dots button {
  width: 11px; height: 11px;
  border-radius: 50%;
  border: 2px solid #fff;
  background: transparent;
  cursor: pointer;
  padding: 0;
  transition: background var(--transition);
}
.dots button.is-active { background: var(--gold); border-color: var(--gold); }

.dots--dark button { border-color: var(--muted); }
.dots--dark button.is-active { background: var(--accent); border-color: var(--accent); }

/* --------------------------------------------------------------------------
   10. Home: three-column band (notices / welcome / enquiry)
   -------------------------------------------------------------------------- */
.home-band { padding: 30px 0; }
.home-band__grid {
  display: grid;
  gap: 22px;
  grid-template-columns: 1fr;
}

.panel {
  background: #fff;
  border: 2px solid var(--gold);
  box-shadow: var(--shadow-sm);
}
.panel__head {
  background: var(--gold);
  color: var(--ink);
  text-align: center;
  font-size: 1.25rem;
  font-weight: 800;
  padding: 10px 14px;
  margin: 0;
}
.panel__body { padding: 14px; }

/* Notice board ---------------------------------------------------------- */
.notice-board__list {
  list-style: none;
  margin: 0; padding: 0;
  max-height: 330px;
  overflow-y: auto;
  /* Scrollbar hidden, exactly as on .enlist-strip: the motion is the
     affordance, and a thumb next to a list that is already drifting on its own
     jitters the whole time it is on screen. Hidden, not disabled - a wheel or
     a drag still works, which is what someone reaching for a notice needs. */
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* The ticker moves this by setting scrollTop every frame; smooth scrolling
     would queue an animation per frame and turn the drift into a stutter. */
  scroll-behavior: auto;
}
.notice-board__list::-webkit-scrollbar { display: none; }

.notice-item { padding: 12px 4px; border-bottom: 1px solid var(--line); }
.notice-item:last-child { border-bottom: 0; }
.notice-item__date {
  display: block;
  font-size: .82rem;
  color: var(--ink);
  font-weight: 600;
  margin-bottom: 2px;
}
.notice-item__title {
  color: var(--ink-soft);
  font-size: .88rem;
  display: block;
}
.notice-item__title:hover { color: var(--accent); }
.notice-item.is-important .notice-item__title { font-weight: 700; }
/* Same shape as the NEW flag beside it, in the neutral ink rather than red -
   an attachment is information, not urgency, and two red badges on one row
   would compete. */
.notice-item__doc {
  display: inline-block;
  background: var(--ink);
  color: #fff;
  font-size: .6rem;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: var(--radius-sm);
  margin-left: 6px;
  vertical-align: middle;
  letter-spacing: .04em;
}

.notice-item__flag {
  display: inline-block;
  background: var(--red);
  color: #fff;
  font-size: .6rem;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: var(--radius-sm);
  margin-left: 6px;
  vertical-align: middle;
  text-transform: uppercase;
}
.notice-board__more { margin-top: 14px; text-align: center; }

/* Welcome column -------------------------------------------------------- */
.welcome__title {
  font-size: clamp(1.5rem, 1.1rem + 1.8vw, 2.3rem);
  text-transform: uppercase;
  margin-bottom: .5em;
  line-height: 1.15;
}
.welcome__text {
  text-align: justify;
  color: var(--ink-soft);
  margin-bottom: 1.6rem;
}

.quicklinks {
  display: grid;
  grid-template-columns: 1fr;
  gap: 18px 26px;
}
.quicklink { display: flex; gap: 14px; align-items: flex-start; }
.quicklink__badge {
  flex: 0 0 auto;
  width: 42px; height: 42px;
  border-radius: 50%;
  background: var(--gold);
  display: grid;
  place-items: center;
  color: var(--ink);
  transition: transform var(--transition), background var(--transition);
}
.quicklink:hover .quicklink__badge { transform: translateX(4px); background: var(--gold-bright); }
.quicklink__badge svg { width: 18px; height: 18px; fill: currentColor; }
.quicklink__title {
  display: inline-block;
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--accent);
  border-bottom: 1px solid var(--accent-soft);
  margin-bottom: 5px;
}
.quicklink:hover .quicklink__title { color: var(--ink); }
.quicklink__desc { font-size: .85rem; color: var(--ink-soft); margin: 0; }

/* --------------------------------------------------------------------------
   11. Forms
   -------------------------------------------------------------------------- */
.form-field { margin-bottom: 12px; }

.form-field > label {
  display: block;
  font-size: .78rem;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 4px;
}

.form-control,
.form-field input[type="text"],
.form-field input[type="email"],
.form-field input[type="tel"],
.form-field input[type="search"],
.form-field input[type="number"],
.form-field select,
.form-field textarea {
  width: 100%;
  padding: 11px 12px;
  border: 1px solid #d5d5d5;
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: .88rem;
  color: var(--ink);
  background: #fff;
  transition: border-color var(--transition), box-shadow var(--transition);
  min-height: 44px;          /* touch target */
}
.form-field textarea { min-height: 110px; resize: vertical; }

.form-field select {
  appearance: none;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%23555' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 11px;
  padding-right: 32px;
}

.form-control:focus,
.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(233, 207, 82, .35);
}

.form-field.has-error input,
.form-field.has-error select,
.form-field.has-error textarea { border-color: var(--red); }

.field-error {
  display: block;
  color: var(--red);
  font-size: .76rem;
  margin-top: 4px;
  font-weight: 600;
}

.form-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

.phone-group { display: flex; gap: 0; }
.phone-group__code {
  display: inline-flex;
  align-items: center;
  padding: 0 12px;
  background: #f2f2f2;
  border: 1px solid #d5d5d5;
  border-right: 0;
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
  font-size: .88rem;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}
.phone-group input { border-radius: 0 var(--radius-sm) var(--radius-sm) 0 !important; }

.consent {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  font-size: .76rem;
  line-height: 1.45;
  color: var(--ink-soft);
  margin: 6px 0 14px;
}
.consent input[type="checkbox"] {
  flex: 0 0 auto;
  width: 17px; height: 17px;
  margin-top: 2px;
  accent-color: var(--accent);
}

/* CAPTCHA --------------------------------------------------------------- */
.captcha-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  align-items: start;
}
.captcha-box {
  display: flex;
  align-items: center;
  gap: 6px;
  border: 1px solid #d5d5d5;
  border-radius: var(--radius-sm);
  padding: 4px 6px;
  background: #fff;
  min-height: 44px;
}
.captcha-box img {
  height: 34px;
  width: auto;
  border-radius: 2px;
  flex: 1 1 auto;
  object-fit: contain;
}
.captcha-refresh {
  border: 0;
  background: transparent;
  cursor: pointer;
  color: var(--muted);
  padding: 4px;
  display: inline-flex;
  border-radius: 50%;
  flex: 0 0 auto;
}
.captcha-refresh:hover { color: var(--accent); background: #f5f5f5; }
.captcha-refresh svg { width: 15px; height: 15px; fill: currentColor; }

/* Enquiry panel --------------------------------------------------------- */
.enquiry-panel__scroll {
  max-height: 430px;
  overflow-y: auto;
  padding-right: 6px;
  scrollbar-width: thin;
}
.enquiry-panel__scroll::-webkit-scrollbar { width: 6px; }
.enquiry-panel__scroll::-webkit-scrollbar-thumb { background: #ccc; border-radius: 3px; }

.enquiry-ad { margin-top: 16px; border: 1px solid var(--line); background: #fff; }
.enquiry-ad img { width: 100%; }

/* Alerts ---------------------------------------------------------------- */
.alert {
  padding: 12px 16px;
  border-radius: var(--radius);
  border-left: 4px solid;
  margin-bottom: 16px;
  font-size: .88rem;
}
.alert--success { background: #eaf7ee; border-color: #2e7d32; color: #1b5e20; }
.alert--error   { background: #fdecea; border-color: var(--red); color: #8e0011; }
.alert--info    { background: #eef4fb; border-color: var(--blue); color: #1d4e85; }
.alert--warning { background: #fff8e1; border-color: #f0a500; color: #7a5200; }

.form-status { margin-bottom: 12px; }

/* --------------------------------------------------------------------------
   12. "We are now enlisted"
   -------------------------------------------------------------------------- */
.enlist-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
  text-align: center;
}

/* Logo strip ------------------------------------------------------------
   Same cards as the grid above, laid in a row that drifts sideways under
   main.js (data-autoscroll-axis="x"). The scrollbar is hidden â€” the motion
   is the affordance, and dragging still works. */
.enlist-strip {
  display: flex;
  gap: 24px;
  overflow-x: auto;
  text-align: center;
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* The ticker writes scrollLeft every frame; smooth scrolling would queue an
     animation per frame and turn the drift into a stutter. */
  scroll-behavior: auto;
}
.enlist-strip::-webkit-scrollbar { display: none; }
.enlist-strip .enlist-card { flex: 0 0 calc(100% - 12px); }

/* The tinted second card is a grid-position rule; in a looping strip the
   clones would re-tint at the wrong place, so every card sits on the same
   neutral panel and the logos themselves carry the colour. */
.enlist-strip .enlist-card:nth-child(2) .enlist-card__media { background: #eee; }
.enlist-card__media {
  background: #eee;
  aspect-ratio: 16 / 11;
  display: grid;
  place-items: center;
  padding: 22px;
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.enlist-card:nth-child(2) .enlist-card__media { background: #fdf3dc; }
.enlist-card__media img { max-height: 100%; width: auto; object-fit: contain; }
.enlist-card__title {
  margin-top: 14px;
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   12b. Affiliation & Recognitions  —  home page
   The same drifting strip as .enlist-strip above, on a dark ground with the
   marks in white discs.

   Its own classes rather than a modifier on the enlist ones: that band is
   white with rectangular panels and this is the opposite on both counts, so
   sharing would mean overriding nearly every rule.
   -------------------------------------------------------------------------- */
/* A near-white field with a fine dot grid drawn in CSS rather than shipped as
   an image: one gradient, no extra request, and it stays crisp at any zoom or
   pixel density. background-size is the spacing of the dots — raise it for a
   looser weave, lower it for a tighter one.

   The heading and captions keep the site's normal dark-on-light colours here,
   so nothing needs inverting. */
.affil-band {
  background-color: #f2f2f4;
  background-image: radial-gradient(circle, rgba(31, 31, 31, .09) 1px, transparent 1px);
  background-size: 13px 13px;
}

.affil-strip {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  text-align: center;
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* The ticker writes scrollLeft every frame; smooth scrolling would queue an
     animation per frame and turn the drift into a stutter. */
  scroll-behavior: auto;
}
.affil-strip::-webkit-scrollbar { display: none; }

/* ---- HOW MANY DISCS SIT ACROSS ------------------------------------------
   The space between two marks is mostly NOT the `gap` above. A card is much
   wider than the 150px disc inside it, and that leftover is what reads as
   space: at four across on a wide screen each card was 280px holding a 150px
   disc, which left 154px between neighbouring marks and only 24px of it was
   the gap. So the way to close it is to fit MORE cards across, not to shrink
   the gap further.

   Raising a count narrows the cards, so CHECK THE CARD NEVER GETS NARROWER
   THAN THE DISC or the two collide. At each step the tightest case is the
   first pixel of its range:

     step    card at its narrowest    disc    left between marks
     2 up    136px  (at 320px wide)   112px    40px
     3 up    139px  (at 480px wide)   132px    23px
     4 up    170px  (at 768px wide)   150px    36px
     5 up    176px  (at 992px wide)   150px    42px
     6 up    179px  (at 1200px wide)  150px    45px                        */
.affil-strip .affil-card   { flex: 0 0 calc((100% - 16px) / 2); }
@media (min-width:  480px) { .affil-strip .affil-card { flex: 0 0 calc((100% - 32px) / 3); } }
@media (min-width:  768px) { .affil-strip .affil-card { flex: 0 0 calc((100% - 48px) / 4); } }
@media (min-width:  992px) { .affil-strip .affil-card { flex: 0 0 calc((100% - 64px) / 5); } }
@media (min-width: 1200px) { .affil-strip .affil-card { flex: 0 0 calc((100% - 80px) / 6); } }

.affil-card__link { display: block; color: inherit; }

/* A fixed circle, not an aspect-ratio: aspect-ratio only sets a PREFERRED
   size and a flex item's automatic minimum still lets a tall mark push past
   it, which is what left the Collaboration grid ragged. A fixed width and
   height cannot be pushed, so every disc is identical. */
.affil-card__disc {
  display: grid;
  place-items: center;
  width: 150px;
  height: 150px;
  margin: 0 auto;
  /* Barely any padding: these marks are square and already sit inside their
     own generous whitespace, so the disc adding more would shrink them to
     nothing. A mark that arrives tight to its edges is the case to raise
     this for. */
  padding: 8px;
  background: #fff;
  border-radius: 50%;
  /* On the near-white field a plain white disc would have no edge, so it gets
     a hairline and a soft lift to sit above the dots. */
  box-shadow: 0 0 0 1px rgba(31, 31, 31, .07), 0 4px 14px rgba(31, 31, 31, .08);
  overflow: hidden;
}
/* min-width/min-height:0 is what makes the max-* caps bite: without them the
   image keeps its own intrinsic size as a minimum and ignores them. */
.affil-card__disc img {
  min-width: 0;
  min-height: 0;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}
/* Two across on a phone leaves a card narrower than a full-size disc, so the
   disc steps down with it rather than forcing the card wider than its share.

   THESE MUST STAY BELOW the width/height above, not above them: same
   specificity, so whichever comes last in the file wins. Placed before it,
   they were silently overridden and the disc stayed 150px on a phone. */
@media (max-width: 575.98px) { .affil-card__disc { width: 132px; height: 132px; } }
@media (max-width: 379.98px) { .affil-card__disc { width: 112px; height: 112px; } }

/* Shown inside the disc until a mark is uploaded — see the note in data.py. */
.affil-card__fallback {
  color: var(--ink);
  font-size: .74rem;
  font-weight: 700;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.affil-card__title {
  margin: 14px auto 0;
  max-width: 22ch;
  color: var(--ink);
  font-size: .93rem;
  font-weight: 500;
  line-height: 1.55;
  /* A card is 136px at its narrowest; a long name has to break inside the
     word rather than set a floor the phone cannot meet. */
  overflow-wrap: anywhere;
}
.affil-card__link:hover .affil-card__title { text-decoration: underline; }
.affil-card__link:focus-visible { outline: 3px solid var(--red); outline-offset: 4px; }

/* --------------------------------------------------------------------------
   Scroll reveal
   A block marked data-reveal fades up as it scrolls into view. initReveal in
   main.js adds .is-revealed to it, and data-reveal-delay="120" staggers a row.

   NOTE THE .js-reveal GUARD ON <html>. Nothing is hidden until the script has
   run far enough to add that class, so a browser with JS off - or a script
   that throws before it gets there - shows every block normally instead of a
   page of invisible content. That is the whole reason the guard exists;
   hiding these in plain CSS would be one broken script away from an empty
   page.
   -------------------------------------------------------------------------- */
.js-reveal [data-reveal] {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity .55s ease-out, transform .55s ease-out;
  /* Tells the browser what is about to change so it can promote the layer
     once, instead of repainting the block on every frame of the fade. */
  will-change: opacity, transform;
}
.js-reveal [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* Motion is the thing being asked about here, so the whole effect goes - the
   blocks are simply present. initReveal makes the same check and stops
   observing, so this is belt and braces rather than the only guard. */
@media (prefers-reduced-motion: reduce) {
  .js-reveal [data-reveal],
  .js-reveal [data-reveal].is-revealed {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   13. Life at SVU
   -------------------------------------------------------------------------- */
/* Red rather than the site's gold accent, matching the heading in the design
   this section was built from. Scoped, so no other section title moves. */
.life-at .section-title .h-accent { color: var(--red); }

.life-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 18px;
}

/* aspect-ratio rather than a fixed height: the tile keeps the same shape at
   every column count, so portrait, landscape and square originals all crop to
   one grid instead of setting the height of their own row. */
.life-tile {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: 10px;
  background: var(--page);
  aspect-ratio: 4 / 3;
  text-decoration: none;
}
.life-tile:focus-visible { outline: 3px solid var(--red); outline-offset: 3px; }

/* The name sits over the photograph, behind a scrim, and comes up on hover or
   keyboard focus. Covering the whole tile rather than a strip along the bottom
   keeps it legible whatever the photograph is doing in that corner. */
.life-tile__label {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 12px;
  background: rgba(14, 20, 30, .46);
  color: #fff;
  font-size: clamp(.95rem, .88rem + .32vw, 1.18rem);
  font-weight: 700;
  line-height: 1.3;
  text-align: center;
  text-shadow: 0 2px 10px rgba(0, 0, 0, .55);
  opacity: 0;
  transition: opacity .3s ease;
}
.life-tile:hover .life-tile__label,
.life-tile:focus-visible .life-tile__label { opacity: 1; }

/* Nothing to hover with on a touch screen, so the names stay up there. */
@media (hover: none) {
  .life-tile__label { opacity: 1; }
}
.life-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform .5s ease;
}
.life-tile:hover img { transform: scale(1.05); }

/* 4 -> 3 -> 2 -> 1. Four columns are unreadable well before the phone: at the
   tablet breakpoint each tile is about 170px across, which is too small to
   make out a room in. */
@media (max-width: 1199.98px) {
  .life-grid { grid-template-columns: repeat(3, 1fr); gap: 16px; }
}
@media (max-width: 767.98px) {
  .life-grid { grid-template-columns: repeat(2, 1fr); gap: 14px; }
}
@media (max-width: 479.98px) {
  .life-grid { grid-template-columns: 1fr; gap: 14px; }
  /* One per row can afford to be wider than it is tall. */
  .life-tile { aspect-ratio: 3 / 2; }
}

/* --------------------------------------------------------------------------
   14. Explore our offerings
   -------------------------------------------------------------------------- */
.offerings { background: var(--gold); position: relative; }
.offerings__grid {
  display: grid;
  grid-template-columns: 1fr;
}
.offerings__media { position: relative; min-height: 220px; }
.offerings__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  position: absolute;
  inset: 0;
}
.offerings__media-title {
  position: absolute;
  left: 24px; bottom: 24px;
  color: #fff;
  font-size: clamp(1.3rem, 1rem + 1.4vw, 2rem);
  font-weight: 800;
  text-transform: uppercase;
  text-shadow: 0 2px 12px rgba(0, 0, 0, .6);
  line-height: 1.15;
  margin: 0;
  z-index: 2;
}
.offerings__media::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(0, 0, 0, .5), rgba(0, 0, 0, .05));
}

.offerings__panel {
  background: #fff;
  padding: 28px 22px;
  display: grid;
  grid-template-columns: 1fr;
  gap: 26px 34px;
  border-radius: var(--radius-lg);
  margin: 0;
}

.offering { position: relative; padding-left: 56px; }
.offering__icon {
  position: absolute;
  left: 0; top: 0;
  width: 42px; height: 42px;
  color: var(--accent-soft);
}
.offering__icon svg { width: 100%; height: 100%; fill: currentColor; }
.offering__num {
  position: absolute;
  left: 2px; top: 40px;
  font-size: 1.5rem;
  font-weight: 700;
  color: rgba(0, 0, 0, .10);
  line-height: 1;
}
.offering__title {
  font-size: 1.05rem;
  font-weight: 400;
  letter-spacing: 1.5px;
  color: var(--accent-soft);
  text-transform: uppercase;
  margin-bottom: .35em;
}
.offering__text { font-size: .86rem; color: var(--ink-soft); margin: 0; }

/* --------------------------------------------------------------------------
   15. Carousels (schools / events / testimonials)
   -------------------------------------------------------------------------- */
/* The track below is its own scroll container, so its slides ought to be its
   own business - but the browser still counts them in the scrollable overflow
   of everything above, and the page ended up 1700px wide inside a 390px phone.
   body{overflow-x:hidden} hid the scrollbar, which made it look fine on
   desktop while a phone could still pan sideways onto blank background. Worse,
   panning moves the layout viewport, and position:fixed is anchored to THAT -
   so the WhatsApp button went off-screen with it.

   'clip' rather than 'hidden' on purpose: hidden would make this a scroll
   container too, and the clip-margin keeps the nav arrows visible where they
   sit 8px outside the box. */
.carousel {
  position: relative;
  overflow-x: clip;
  /* Nothing bleeds out by default. The arrows only sit outside the box on a
     window wide enough to have room for them, and the margin is opened up
     there and only there - see the rule further down. Left open at every
     width it let ~11px of slide through at 1280px, which is a scrollbar the
     page does not need. */
  overflow-clip-margin: 0;
}

.carousel__track {
  display: flex;
  gap: 18px;
  overflow-x: auto;
  /* Spelled out because a lone overflow-x: auto computes overflow-y to auto
     as well, and the track then scrolls vertically - invisibly, since the
     scrollbar below is hidden - whenever a card overflows it by a pixel. */
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  padding-bottom: 6px;
  /* Hide the native scrollbar; dots + arrows drive navigation */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.carousel__track::-webkit-scrollbar { display: none; }

.carousel__item {
  flex: 0 0 100%;
  scroll-snap-align: start;
  min-width: 0;
}

.carousel__nav {
  position: absolute;
  top: 40%;
  transform: translateY(-50%);
  width: 38px; height: 38px;
  border-radius: 50%;
  border: 0;
  background: rgba(255, 255, 255, .95);
  box-shadow: var(--shadow);
  cursor: pointer;
  display: none;
  place-items: center;
  color: var(--ink);
  z-index: 4;
}
.carousel__nav:hover { background: var(--gold); }
.carousel__nav svg { width: 16px; height: 16px; fill: currentColor; }
.carousel__nav--prev { left: -8px; }
.carousel__nav--next { right: -8px; }
.carousel__nav[disabled] { opacity: .35; cursor: default; }

.carousel__dots { margin-top: 18px; }

/* School card ----------------------------------------------------------- */
.school-card {
  background: #fff;
  box-shadow: var(--shadow-sm);
  display: flex;
  flex-direction: column;
  height: 100%;
  border-bottom: 4px solid var(--gold);
  transition: transform var(--transition), box-shadow var(--transition);
}
.school-card:hover { transform: translateY(-4px); box-shadow: var(--shadow); }
.school-card__media { aspect-ratio: 16 / 9; overflow: hidden; background: #1f6b5c; }
.school-card__media img { width: 100%; height: 100%; object-fit: cover; }
.school-card__body {
  padding: 18px;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  gap: 10px;
}
.school-card__title {
  font-size: 1rem;
  text-transform: uppercase;
  margin: 0;
  position: relative;
  padding-bottom: 10px;
}
.school-card__title::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 46px; height: 2px;
  background: var(--accent);
}
.school-card__desc { font-size: .84rem; color: var(--muted); margin: 0; }
.school-card__footer { margin-top: auto; padding-top: 8px; }

/* Department ------------------------------------------------------------ */
/* One block per department on a school page: the department heads it, its
   courses sit inside — school > department > course, read top to bottom. */
.dept-block {
  background: #fff;
  box-shadow: var(--shadow-sm);
  border-left: 4px solid var(--gold);
  padding: 20px;
  margin-bottom: 22px;
}
.dept-block__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 14px;
  margin-bottom: 6px;
}
.dept-block__title { font-size: 1.02rem; margin: 0; }
.dept-block__title a { color: var(--ink); }
.dept-block__title a:hover { color: var(--accent); }
.dept-block__count {
  font-size: .72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink-soft);
  background: var(--page-alt);
  border-radius: var(--radius-pill);
  padding: 3px 10px;
}
.dept-block__desc { font-size: .84rem; color: var(--muted); margin: 0 0 16px; }

.dept-card {
  background: #fff;
  box-shadow: var(--shadow-sm);
  border-bottom: 3px solid var(--gold);
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  height: 100%;
  transition: transform var(--transition), box-shadow var(--transition);
}
.dept-card:hover { transform: translateY(-4px); box-shadow: var(--shadow); }
.dept-card__school {
  font-size: .7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--accent);
}
.dept-card__title { font-size: .98rem; margin: 0; }
.dept-card__desc { font-size: .84rem; color: var(--muted); margin: 0; }
.dept-card__footer { margin-top: auto; padding-top: 6px; }

/* Video cards ----------------------------------------------------------- */
.video-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
}

/* Video carousel --------------------------------------------------------
   The generic carousel steps up to 3 and then 4 items across; a video
   thumbnail that narrow is unreadable, so this one holds at two. */
.video-carousel .carousel__item { flex-basis: 100%; }
@media (min-width: 768px) {
  .video-carousel .carousel__item { flex-basis: calc((100% - 18px) / 2); }
}
.video-carousel .video-card { height: 100%; }
.video-card__title {
  font-size: .95rem;
  text-transform: uppercase;
  margin-bottom: 10px;
}
.video-card__title .h-accent { color: var(--accent-soft); }

.video-embed {
  position: relative;
  aspect-ratio: 16 / 9;
  background: #000;
  overflow: hidden;
  border-radius: var(--radius-sm);
}
.video-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  border: 0;
}
/* Click-to-load facade: no YouTube request until the visitor asks for it */
.video-facade {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  border: 0;
  padding: 0;
  cursor: pointer;
  background: #000;
}
.video-facade img { width: 100%; height: 100%; object-fit: cover; opacity: .85; }
.video-facade__play {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 68px; height: 48px;
  border-radius: 12px;
  background: var(--red);
  display: grid;
  place-items: center;
  transition: background var(--transition);
}
.video-facade:hover .video-facade__play { background: #f00; }
.video-facade__play svg { width: 24px; height: 24px; fill: #fff; }
.video-facade__hint {
  position: absolute;
  bottom: 8px; left: 0; right: 0;
  text-align: center;
  color: #fff;
  font-size: .7rem;
  opacity: .85;
}

/* Event card ------------------------------------------------------------ */
.event-card {
  background: #fff;
  box-shadow: var(--shadow-sm);
  display: flex;
  flex-direction: column;
  height: 100%;
  transition: transform var(--transition), box-shadow var(--transition);
}
.event-card:hover { transform: translateY(-4px); box-shadow: var(--shadow); }
.event-card__media { aspect-ratio: 4 / 3; overflow: hidden; background: #ddd; }
.event-card__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform .4s ease;
}
.event-card:hover .event-card__media img { transform: scale(1.05); }
.event-card__body {
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1 1 auto;
}
.event-card__date {
  font-size: .74rem;
  color: var(--accent);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .5px;
}
.event-card__title {
  font-size: .86rem;
  font-weight: 700;
  text-transform: uppercase;
  line-height: 1.5;
  margin: 0;
  color: var(--ink);
  text-align: justify;
}
.event-card__footer { margin-top: auto; padding-top: 6px; }

/* --------------------------------------------------------------------------
   16. Counting figures
   -------------------------------------------------------------------------- */
/* One row, however many figures there are. grid-auto-flow: column means the
   count is not written into the CSS at all: a sixth figure added in data.py
   takes its place on the same line rather than dropping to a second row. */
.stats-grid {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 18px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.stat { text-align: center; }

/* tabular-nums is not decoration here: with proportional digits a 1 is
   narrower than a 4, so the figure would jitter sideways the whole way up the
   count and drag the label under it along. */
.stat__figure {
  margin: 0 0 8px;
  color: var(--ink);
  font-size: clamp(1.35rem, 1rem + 1.3vw, 2.1rem);
  font-weight: 800;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.stat__label {
  margin: 0;
  color: var(--muted);
  font-size: clamp(.7rem, .66rem + .18vw, .84rem);
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: .05em;
  text-transform: uppercase;
}

/* Dividers only while they are all on one row. Once the list wraps, a rule
   down the left edge of the first figure in a new row points at nothing. */
@media (min-width: 992px) {
  .stat + .stat { border-left: 1px solid var(--line); }
}

/* One line is a desktop idea. Below it the row has to wrap, which means
   switching the flow back to rows - grid-auto-flow: column would otherwise
   keep packing everything into one line however narrow it gets. */
@media (max-width: 991.98px) {
  .stats-grid {
    grid-auto-flow: row;
    grid-template-columns: repeat(3, 1fr);
    gap: 24px 14px;
  }
}
/* Two across on a phone, all the way down - no single-column step. */
@media (max-width: 767.98px) {
  .stats-grid { grid-template-columns: repeat(2, 1fr); gap: 22px 12px; }
  /* An odd count leaves the last figure alone on its row. Span it so it
     centres under the pair above rather than sitting in the left column with
     a hole beside it. Five figures give 2, 2, then 1 centred; an even count
     never matches this and is left alone. */
  .stat:last-child:nth-child(odd) { grid-column: 1 / -1; }
}

/* --------------------------------------------------------------------------
   17. Chancellor + centres
   -------------------------------------------------------------------------- */
.chancellor-band { padding: 0 0 40px; }
.chancellor-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
}

.chancellor {
  position: relative;
  background: #1b1b1b;
  border: 12px solid var(--gold);
  padding: 30px 24px;
  color: #fff;
  overflow: hidden;
}
.chancellor__bg {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: .30;
}
.chancellor__inner { position: relative; z-index: 2; }
.chancellor__title {
  color: #fff;
  font-size: clamp(1.4rem, 1.1rem + 1.6vw, 2.2rem);
  text-transform: uppercase;
  margin-bottom: 1.2rem;
}
.chancellor__quote-mark {
  font-family: var(--font-serif);
  font-size: 2.4rem;
  color: var(--gold);
  line-height: .5;
  display: block;
  margin-bottom: 10px;
}
.chancellor__text {
  text-align: justify;
  font-size: .92rem;
  color: #ececec;
  margin-bottom: 1.2rem;
}
.chancellor__divider { border-top: 1px solid rgba(255, 255, 255, .25); margin: 20px 0; }
.chancellor__person { display: flex; align-items: center; gap: 16px; }
.chancellor__photo {
  width: 82px; height: 82px;
  object-fit: cover;
  border: 3px solid var(--gold);
  flex: 0 0 auto;
  background: #333;
}
.chancellor__name { font-size: 1.15rem; color: #fff; margin: 0 0 2px; font-weight: 500; }
.chancellor__role {
  font-size: .74rem;
  color: #ddd;
  text-transform: uppercase;
  letter-spacing: .6px;
  line-height: 1.6;
}

/* Two by two rather than a single column, so the picture on each centre is
   the size of a picture instead of a bullet beside a paragraph. */
.centres {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 24px 20px;
  padding: 30px 20px;
}
.centre {
  position: relative;
  text-align: center;
}
/* The connector line down the left belonged to the single column this used to
   be. Two columns have nothing for it to join up. */
.centre:not(:last-child)::before { content: none; }
.centre__icon {
  margin: 0 auto 14px;
  width: 62px; height: 62px;
  border-radius: 50%;
  background: var(--gold);
  border: 5px solid #f4f4f4;
  box-shadow: 0 0 0 1px var(--line);
  display: grid;
  place-items: center;
  color: var(--ink);
  position: relative;
  z-index: 2;
}
.centre__icon svg { width: 24px; height: 24px; fill: currentColor; }

/* A photograph is not a badge, so it stops being a 62px disc and becomes the
   picture at the head of the card. The gold ground and the disc border both go
   with it; what is left is the picture and a soft edge. */
.centre__icon--photo {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  margin: 0 0 14px;
  border: 0;
  border-radius: var(--radius);
  background: var(--page);
  box-shadow: var(--shadow-xs);
  overflow: hidden;
}
.centre__icon--photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius);
  display: block;
  transition: transform .5s ease;
}
/* Same slow push-in the gallery cells use. It hangs off .centre rather than
   the picture, so the whole card is the target - the medallion is aria-hidden
   and nothing in it is focusable, so there is no keyboard state to match. The
   overflow: hidden above is what crops the growth back to the frame. */
.centre:hover .centre__icon--photo img { transform: scale(1.05); }

@media (prefers-reduced-motion: reduce) {
  .centre__icon--photo img { transition: none; }
  .centre:hover .centre__icon--photo img { transform: none; }
}
.centre__title {
  font-size: 1.05rem;
  font-weight: 400;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: .4em;
}
.centre__text { font-size: .85rem; color: var(--muted); margin: 0; }

/* One column on a phone: two columns of a paragraph this long leave a measure
   of about twenty characters. */
@media (max-width: 575.98px) {
  .centres { grid-template-columns: 1fr; }
}

/* ---- Appreciations: the vertical slider beside the Chancellor ---------- */
/* One card's height. The track is two of these plus the gap, so the two
   numbers cannot drift apart.

   Sized for the picture rather than to match the Chancellor block: tying it to
   whatever height was left over gave cards of about 126px, and a 1.55:1 image
   fitted into that is barely 160px wide inside a 536px card. The band no
   longer stretches its cells, so the two columns can differ in height without
   either of them growing empty space inside. */
.appreciations {
  display: flex;
  flex-direction: column;
  padding: 30px 20px;
}
/* Centred over the slider. text-align rather than align-items, so the heading
   still spans the column and only its words move - centring the box itself
   would shrink-wrap it and leave the underline, if one is ever added, the
   width of the word instead of the slider. */
.appreciations__title {
  margin: 0 0 16px;
  color: var(--ink);
  font-size: 1.05rem;
  font-weight: 800;
  letter-spacing: 1px;
  text-transform: uppercase;
  text-align: center;
}

/* The shared carousel turned on its side. Only the track changes axis - the
   controller reads position through accessors that already know which one. */
/* The height comes from the shape of the pictures, not from a number. They run
   between 1.55:1 and 1.68:1, so 8/5 sits in the middle of them, and the track
   is then as tall as one picture needs to be at whatever width the column
   happens to have - no breakpoint has to be told again. */
.carousel--vertical .carousel__track {
  flex-direction: column;
  /* No gap: one picture fills the slot, and a gap would only show as a strip
     of page sliding through between them. */
  gap: 0;
  overflow-x: hidden;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  height: auto;
  aspect-ratio: 8 / 5;
  padding-bottom: 0;
}
/* One at a time, filling the slot. */
.carousel--vertical .carousel__item {
  flex: 0 0 100%;
  scroll-snap-align: start;
}

/* A frame the image sits inside rather than fills. These are certificates and
   portraits of every shape, and cover would crop a tall one down to a band
   across its middle - which is what made them look stretched. */
/* Just the picture: no border, no panel behind it, nothing but the image
   fitted and centred in the space it has. Fitted rather than cropped, because
   these run about 1.55:1 and cover was slicing each into a letterbox. */
.appreciation {
  display: grid;
  place-items: center;
  margin: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
/* auto width AND auto height, capped by the frame: the image keeps its own
   proportions and simply stops growing at whichever edge it meets first. */
.appreciation img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
}

.appreciations__foot { margin-top: 18px; text-align: center; }

@media (max-width: 575.98px) {
  .appreciations { padding: 26px 16px; }
}

/* --------------------------------------------------------------------------
   18. Testimonials
   --------------------------------------------------------------------------
   Navy-and-ivory, unlike anything else on the site. The colours are declared
   as custom properties on the section rather than in :root, because nothing
   outside this block uses them and :root is where every component looks first.

   Both watermarks are drawn SVG carried as base64 data URIs. The CSP is locked
   to 'self' and the site deliberately ships no icon font, and base64 rather
   than percent-encoding because the drawings contain "(" and ")" in their
   transforms, which close a CSS url() token early and break collectstatic.
   -------------------------------------------------------------------------- */
.testimonials {
  --tst-navy: #1b3055;
  --tst-navy-deep: #16294a;
  --tst-ivory: #f7f5ef;
  --tst-page: #fbfaf6;
  --tst-gold: #c2a13c;
  --tst-gold-line: rgba(194, 161, 60, .45);
  --tst-body: #33415c;
  --tst-meta: #55617a;

  padding: 56px 0 46px;
  background-color: var(--tst-page);
  /* A dot screen, barely there — it stops the ivory reading as a flat fill. */
  background-image: radial-gradient(circle, rgba(27, 48, 85, .055) 1.1px, transparent 1.5px);
  background-size: 15px 15px;
}

/* ---- heading with the rules either side ---- */
.testimonials .section-title {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 22px;
  margin: 0 0 38px;
  color: var(--tst-navy);
  font-size: clamp(1.45rem, 1rem + 2.2vw, 2.5rem);
  font-weight: 800;
  letter-spacing: .01em;
  text-transform: uppercase;
  text-align: center;
}
.testimonials .section-title .h-dark { color: var(--tst-navy); font-weight: 800; }
.testimonials .section-title .h-accent { color: var(--tst-gold); font-weight: 800; }

/* Each rule is two background layers on one empty box — a line that fades out
   towards the edge of the section, and the dot that caps it next to the words.
   Drawing it this way keeps two purely decorative elements out of the markup. */
.testimonials .section-title::before,
.testimonials .section-title::after {
  content: "";
  flex: 1 1 0;
  max-width: 260px;
  height: 9px;
  background-repeat: no-repeat;
}
.testimonials .section-title::before {
  background-image:
    radial-gradient(circle, var(--tst-gold) 3px, transparent 3.5px),
    linear-gradient(90deg, transparent, var(--tst-gold));
  background-position: right center, left center;
  background-size: 9px 9px, calc(100% - 14px) 1px;
}
.testimonials .section-title::after {
  background-image:
    radial-gradient(circle, var(--tst-gold) 3px, transparent 3.5px),
    linear-gradient(270deg, transparent, var(--tst-gold));
  background-position: left center, right center;
  background-size: 9px 9px, calc(100% - 14px) 1px;
}

/* ---- the card ----
   No grid: the navy panel is a painted layer rather than a column, and the
   portrait is positioned on the seam between the two. Laying it out as
   columns would make the straddling portrait fight the track that holds it. */
.testimonial {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  justify-content: center;
  height: 100%;
  min-height: 306px;
  /* The left inset clears the panel AND the half of the portrait that hangs
     past it, which is why it is a calc and not a flat value. */
  padding: 28px 32px 22px 46%;
  border-radius: 22px;
  background-color: var(--tst-ivory);
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxMjAgMTIwJyBmaWxsPSdjdXJyZW50Q29sb3InIGNvbG9yPScjZTZlMmQ0Jz48ZWxsaXBzZSBjeD0nMCcgY3k9JzAnIHJ4PSc3LjQnIHJ5PSczLjQnIHRyYW5zZm9ybT0ndHJhbnNsYXRlKDYwLjAgMTA3LjApIHJvdGF0ZSgxODAuMCknLz48ZWxsaXBzZSBjeD0nMCcgY3k9JzAnIHJ4PSc3LjEnIHJ5PSczLjInIHRyYW5zZm9ybT0ndHJhbnNsYXRlKDcyLjMgMTA1LjIpIHJvdGF0ZSgtMTYzLjMpJy8+PGVsbGlwc2UgY3g9JzAnIGN5PScwJyByeD0nNi43JyByeT0nMy4xJyB0cmFuc2Zvcm09J3RyYW5zbGF0ZSg4My42IDk5LjkpIHJvdGF0ZSgtMTQ2LjcpJy8+PGVsbGlwc2UgY3g9JzAnIGN5PScwJyByeD0nNi40JyByeT0nMi45JyB0cmFuc2Zvcm09J3RyYW5zbGF0ZSg5Mi45IDkxLjYpIHJvdGF0ZSgtMTMwLjApJy8+PGVsbGlwc2UgY3g9JzAnIGN5PScwJyByeD0nNi4wJyByeT0nMi44JyB0cmFuc2Zvcm09J3RyYW5zbGF0ZSg5OS41IDgxLjApIHJvdGF0ZSgtMTEzLjMpJy8+PGVsbGlwc2UgY3g9JzAnIGN5PScwJyByeD0nNS43JyByeT0nMi42JyB0cmFuc2Zvcm09J3RyYW5zbGF0ZSgxMDIuNyA2OS4wKSByb3RhdGUoLTk2LjcpJy8+PGVsbGlwc2UgY3g9JzAnIGN5PScwJyByeD0nNS4zJyByeT0nMi40JyB0cmFuc2Zvcm09J3RyYW5zbGF0ZSgxMDIuMyA1Ni41KSByb3RhdGUoLTgwLjApJy8+PGVsbGlwc2UgY3g9JzAnIGN5PScwJyByeD0nNS4wJyByeT0nMi4zJyB0cmFuc2Zvcm09J3RyYW5zbGF0ZSg5OC40IDQ0LjcpIHJvdGF0ZSgtNjMuMyknLz48ZWxsaXBzZSBjeD0nMCcgY3k9JzAnIHJ4PSc0LjYnIHJ5PScyLjEnIHRyYW5zZm9ybT0ndHJhbnNsYXRlKDkxLjMgMzQuNSkgcm90YXRlKC00Ni43KScvPjxlbGxpcHNlIGN4PScwJyBjeT0nMCcgcng9JzQuMycgcnk9JzIuMCcgdHJhbnNmb3JtPSd0cmFuc2xhdGUoODEuNSAyNi44KSByb3RhdGUoLTMwLjApJy8+PGVsbGlwc2UgY3g9JzAnIGN5PScwJyByeD0nNy40JyByeT0nMy40JyB0cmFuc2Zvcm09J3RyYW5zbGF0ZSg2MC4wIDEwNy4wKSByb3RhdGUoMTgwLjApJy8+PGVsbGlwc2UgY3g9JzAnIGN5PScwJyByeD0nNy4xJyByeT0nMy4yJyB0cmFuc2Zvcm09J3RyYW5zbGF0ZSg0Ny43IDEwNS4yKSByb3RhdGUoMTYzLjMpJy8+PGVsbGlwc2UgY3g9JzAnIGN5PScwJyByeD0nNi43JyByeT0nMy4xJyB0cmFuc2Zvcm09J3RyYW5zbGF0ZSgzNi40IDk5LjkpIHJvdGF0ZSgxNDYuNyknLz48ZWxsaXBzZSBjeD0nMCcgY3k9JzAnIHJ4PSc2LjQnIHJ5PScyLjknIHRyYW5zZm9ybT0ndHJhbnNsYXRlKDI3LjEgOTEuNikgcm90YXRlKDEzMC4wKScvPjxlbGxpcHNlIGN4PScwJyBjeT0nMCcgcng9JzYuMCcgcnk9JzIuOCcgdHJhbnNmb3JtPSd0cmFuc2xhdGUoMjAuNSA4MS4wKSByb3RhdGUoMTEzLjMpJy8+PGVsbGlwc2UgY3g9JzAnIGN5PScwJyByeD0nNS43JyByeT0nMi42JyB0cmFuc2Zvcm09J3RyYW5zbGF0ZSgxNy4zIDY5LjApIHJvdGF0ZSg5Ni43KScvPjxlbGxpcHNlIGN4PScwJyBjeT0nMCcgcng9JzUuMycgcnk9JzIuNCcgdHJhbnNmb3JtPSd0cmFuc2xhdGUoMTcuNyA1Ni41KSByb3RhdGUoODAuMCknLz48ZWxsaXBzZSBjeD0nMCcgY3k9JzAnIHJ4PSc1LjAnIHJ5PScyLjMnIHRyYW5zZm9ybT0ndHJhbnNsYXRlKDIxLjYgNDQuNykgcm90YXRlKDYzLjMpJy8+PGVsbGlwc2UgY3g9JzAnIGN5PScwJyByeD0nNC42JyByeT0nMi4xJyB0cmFuc2Zvcm09J3RyYW5zbGF0ZSgyOC43IDM0LjUpIHJvdGF0ZSg0Ni43KScvPjxlbGxpcHNlIGN4PScwJyBjeT0nMCcgcng9JzQuMycgcnk9JzIuMCcgdHJhbnNmb3JtPSd0cmFuc2xhdGUoMzguNSAyNi44KSByb3RhdGUoMzAuMCknLz48cGF0aCBkPSdNNjAuMCAxMDcuMCBBIDQzLjAgNDMuMCAwIDAgMSA4MS41IDI2LjgnIGZpbGw9J25vbmUnIHN0cm9rZT0nY3VycmVudENvbG9yJyBzdHJva2Utd2lkdGg9JzIuMicgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJy8+PHBhdGggZD0nTTYwLjAgMTA3LjAgQSA0My4wIDQzLjAgMCAwIDAgMzguNSAyNi44JyBmaWxsPSdub25lJyBzdHJva2U9J2N1cnJlbnRDb2xvcicgc3Ryb2tlLXdpZHRoPScyLjInIHN0cm9rZS1saW5lY2FwPSdyb3VuZCcvPjxwb2x5Z29uIHBvaW50cz0nNjAuMCw1LjAgNjEuNyw5LjYgNjYuNyw5LjggNjIuOCwxMi45IDY0LjEsMTcuNyA2MC4wLDE0LjkgNTUuOSwxNy43IDU3LjIsMTIuOSA1My4zLDkuOCA1OC4zLDkuNicvPjxwb2x5Z29uIHBvaW50cz0nNDAuMCwxNS4wIDQxLjIsMTguMyA0NC44LDE4LjUgNDIuMCwyMC42IDQyLjksMjQuMCA0MC4wLDIyLjEgMzcuMSwyNC4wIDM4LjAsMjAuNiAzNS4yLDE4LjUgMzguOCwxOC4zJy8+PHBvbHlnb24gcG9pbnRzPSc4MC4wLDE1LjAgODEuMiwxOC4zIDg0LjgsMTguNSA4Mi4wLDIwLjYgODIuOSwyNC4wIDgwLjAsMjIuMSA3Ny4xLDI0LjAgNzguMCwyMC42IDc1LjIsMTguNSA3OC44LDE4LjMnLz48cGF0aCBkPSdNNjAgNDIgTDEwMCA1NyBMNjAgNzIgTDIwIDU3IFonLz48cGF0aCBkPSdNMzkgNjIgTDM5IDc4IEw4MSA3OCBMODEgNjIgTDYwIDcwIFonLz48cGF0aCBkPSdNOTggNTggTDk4IDgwJyBmaWxsPSdub25lJyBzdHJva2U9J2N1cnJlbnRDb2xvcicgc3Ryb2tlLXdpZHRoPScyLjQnIHN0cm9rZS1saW5lY2FwPSdyb3VuZCcvPjxjaXJjbGUgY3g9Jzk4JyBjeT0nODQnIHI9JzMuNicvPjwvc3ZnPg==");
  background-repeat: no-repeat;
  background-position: right 30px bottom 24px;
  background-size: 74px 78px;
  box-shadow: 0 10px 30px rgba(20, 30, 50, .10);
  overflow: hidden;
}
/* The navy panel, with the campus silhouette standing on its bottom edge.
   z-index: -1 against the card's own stacking context (isolation: isolate)
   keeps it behind the text without escaping to sit behind the card itself. */
.testimonial::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 26.5%;
  z-index: -1;
  background-color: var(--tst-navy);
  background-image:
    url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyMDAgMTUwJyBmaWxsPScjMjg0MDZiJz48cmVjdCB4PSc0JyB5PScxMzgnIHdpZHRoPScxOTInIGhlaWdodD0nMTInLz48cmVjdCB4PScxNCcgeT0nMTMwJyB3aWR0aD0nMTcyJyBoZWlnaHQ9JzgnLz48cmVjdCB4PScyNCcgeT0nMTI0JyB3aWR0aD0nMTUyJyBoZWlnaHQ9JzYnLz48cmVjdCB4PScyNCcgeT0nOTInIHdpZHRoPSczMCcgaGVpZ2h0PSczMicvPjxyZWN0IHg9JzE0NicgeT0nOTInIHdpZHRoPSczMCcgaGVpZ2h0PSczMicvPjxyZWN0IHg9JzI5JyB5PSc5Nicgd2lkdGg9JzUnIGhlaWdodD0nMjAnLz48cmVjdCB4PScxNTEnIHk9Jzk2JyB3aWR0aD0nNScgaGVpZ2h0PScyMCcvPjxyZWN0IHg9JzM5JyB5PSc5Nicgd2lkdGg9JzUnIGhlaWdodD0nMjAnLz48cmVjdCB4PScxNjEnIHk9Jzk2JyB3aWR0aD0nNScgaGVpZ2h0PScyMCcvPjxyZWN0IHg9JzQ5JyB5PSc5Nicgd2lkdGg9JzUnIGhlaWdodD0nMjAnLz48cmVjdCB4PScxNzEnIHk9Jzk2JyB3aWR0aD0nNScgaGVpZ2h0PScyMCcvPjxyZWN0IHg9JzU4JyB5PSc4NCcgd2lkdGg9Jzg0JyBoZWlnaHQ9JzQwJy8+PHJlY3QgeD0nNjMnIHk9JzkwJyB3aWR0aD0nNycgaGVpZ2h0PSczMCcvPjxyZWN0IHg9Jzc2JyB5PSc5MCcgd2lkdGg9JzcnIGhlaWdodD0nMzAnLz48cmVjdCB4PSc4OScgeT0nOTAnIHdpZHRoPSc3JyBoZWlnaHQ9JzMwJy8+PHJlY3QgeD0nMTAyJyB5PSc5MCcgd2lkdGg9JzcnIGhlaWdodD0nMzAnLz48cmVjdCB4PScxMTUnIHk9JzkwJyB3aWR0aD0nNycgaGVpZ2h0PSczMCcvPjxyZWN0IHg9JzEyOCcgeT0nOTAnIHdpZHRoPSc3JyBoZWlnaHQ9JzMwJy8+PHJlY3QgeD0nNTQnIHk9Jzc0JyB3aWR0aD0nOTInIGhlaWdodD0nMTAnLz48cG9seWdvbiBwb2ludHM9JzEwMCw1NCAxNDgsNzQgNTIsNzQnLz48cmVjdCB4PSc4NCcgeT0nMzQnIHdpZHRoPSczMicgaGVpZ2h0PScyMCcvPjxwYXRoIGQ9J004NCAzNiBhMTYgMTggMCAwIDEgMzIgMCBaJy8+PHJlY3QgeD0nOTYnIHk9JzIwJyB3aWR0aD0nOCcgaGVpZ2h0PSc4Jy8+PGNpcmNsZSBjeD0nMTAwJyBjeT0nMTUnIHI9JzQnLz48cmVjdCB4PSc5OScgeT0nNCcgd2lkdGg9JzInIGhlaWdodD0nOScvPjwvc3ZnPg=="),
    linear-gradient(160deg, var(--tst-navy), var(--tst-navy-deep));
  background-repeat: no-repeat, no-repeat;
  background-position: center bottom, center;
  background-size: 152px auto, cover;
}
/* Hairline gold frame, inset from the card edge. */
.testimonial::after {
  content: "";
  position: absolute;
  inset: 8px;
  border: 1px solid var(--tst-gold-line);
  border-radius: 16px;
  pointer-events: none;
}

/* Centred on the panel's right edge, so it reads as one portrait bridging the
   two halves rather than a photograph dropped on either of them. */
.testimonial__photo {
  position: absolute;
  left: 22.6%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 31%;
  aspect-ratio: 1;
  border-radius: 50%;
  object-fit: cover;
  border: 7px solid #fff;
  outline: 2px solid var(--tst-gold);
  outline-offset: 2px;
  background: #e7e7e7;
  box-shadow: 0 8px 22px rgba(20, 30, 50, .18);
}

.testimonial__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* Sized off its own line-height: the glyph carries very deep internal leading
   and would otherwise push the quote down the card. */
.testimonial__body::before {
  content: "\201C";
  font-family: var(--font-serif);
  font-size: clamp(2.1rem, 1.8rem + .9vw, 2.6rem);
  line-height: .64;
  color: var(--tst-gold);
}
.testimonial__quote {
  margin: 8px 0 14px;
  color: var(--tst-body);
  /* Fluid rather than fixed: the quote column is a percentage of the card, so
     a flat size reads small on a wide screen and cramped on a narrow one. */
  font-size: clamp(.85rem, .8rem + .28vw, 1.06rem);
  line-height: 1.42;
}
/* margin-top: auto pins the name block to the foot, so two cards holding
   quotes of different lengths still line their names up with each other. */
.testimonial__caption { margin-top: auto; }
.testimonial__caption::before {
  content: "";
  display: block;
  width: 50px;
  height: 2px;
  margin-bottom: 12px;
  background: var(--tst-gold);
}
.testimonial__name {
  margin: 0 0 4px;
  color: var(--tst-navy);
  font-weight: 800;
  font-size: clamp(1rem, .95rem + .28vw, 1.18rem);
  letter-spacing: .02em;
  text-transform: uppercase;
}
.testimonial__meta {
  color: var(--tst-meta);
  font-size: clamp(.85rem, .83rem + .12vw, .95rem);
  font-weight: 500;
  line-height: 1.62;
}
.testimonial__footer { padding-top: 14px; }

/* The shared arrows are built for a dark ground; on ivory they need their own
   weight and a navy chevron. */
.testimonials .carousel__nav {
  width: 46px; height: 46px;
  background: #fff;
  color: var(--tst-navy);
  box-shadow: 0 6px 18px rgba(20, 30, 50, .16);
}
.testimonials .carousel__nav:hover { background: var(--tst-gold); color: #fff; }
.testimonials .carousel__nav svg { width: 18px; height: 18px; }
.testimonials .carousel__dots { margin-top: 26px; }

/* ---- stacked ----
   Side by side stops working long before the phone: the quote column collapses
   while the portrait keeps its diameter. The panel becomes a band across the
   top and the portrait sits on its lower edge, which is the same idea turned
   ninety degrees. */
@media (max-width: 991.98px) {
  .testimonial {
    min-height: 0;
    padding: 164px 24px 20px;
    min-height: 0;
    text-align: center;
    align-items: center;
    background-position: right 14px bottom 14px;
    background-size: 74px 74px;
  }
  .testimonial::before {
    width: 100%;
    height: 100px;
    bottom: auto;
    background-size: 118px auto, cover;
  }
  .testimonial__photo {
    left: 50%;
    top: 100px;
    width: 120px; height: 120px;
    aspect-ratio: auto;
    border-width: 6px;
  }
  .testimonial__body { align-items: center; }
  .testimonial__caption::before { margin-left: auto; margin-right: auto; }
}

@media (max-width: 575.98px) {
  .testimonial { padding: 148px 18px 18px; border-radius: 18px; }
  .testimonial::before { height: 88px; }
  .testimonial__photo { top: 88px; width: 108px; height: 108px; aspect-ratio: auto; }
  .testimonial::after { inset: 7px; border-radius: 14px; }
  .testimonial__quote { line-height: 1.55; }
}

/* --------------------------------------------------------------------------
   19. Page shell (inner pages)
   -------------------------------------------------------------------------- */
.page-hero {
  background: var(--dark);
  color: #fff;
  padding: 34px 0;
  position: relative;
  overflow: hidden;
}
.page-hero__bg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: .32;
}
/* Centred, which is how .page-banner in page-head.css already sets its own
   title — the two are the site's inner-page banners and reading one centred
   and the other ranged left made them look like different components. */
.page-hero__inner { position: relative; z-index: 2; text-align: center; }
.page-hero__title {
  color: #fff;
  margin: 0 0 6px;
  font-size: clamp(1.4rem, 1.1rem + 1.6vw, 2.2rem);
  text-transform: uppercase;
}
.page-hero__subtitle { color: #eee; margin: 0; font-size: .92rem; }

.breadcrumbs {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: center;
  margin: 8px 0 0;
  padding: 0;
  font-size: .78rem;
  color: #ddd;
}
.breadcrumbs a { color: var(--gold); }
.breadcrumbs li + li::before { content: "/"; margin-right: 6px; opacity: .6; }

.prose { max-width: 78ch; }
/* Drops the reading-width cap — for a short intro that should span the shell. */
.prose--full { max-width: none; }
.prose h2 { font-size: 1.3rem; margin-top: 1.6em; }
.prose h3 { font-size: 1.1rem; margin-top: 1.4em; }
.prose ul, .prose ol { padding-left: 1.4em; }
.prose li { margin-bottom: .4em; }
.prose table { margin: 1.2em 0; font-size: .88rem; }
.prose th, .prose td { border: 1px solid var(--line); padding: 8px 10px; text-align: left; }
.prose th { background: var(--page-alt); }
.prose img { border-radius: var(--radius); margin: 1em 0; }

.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.card {
  background: #fff;
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 20px;
}

.auto-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  gap: 20px;
}

.chip-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; }
.chip {
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  background: #fff;
  border: 1px solid var(--line);
  font-size: .8rem;
  color: var(--ink-soft);
  font-weight: 600;
}
.chip:hover, .chip.is-active { background: var(--gold); border-color: var(--gold); color: var(--ink); }

.meta-list { list-style: none; padding: 0; margin: 0; }
.meta-list li {
  display: flex;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
  font-size: .88rem;
}
.meta-list li:last-child { border-bottom: 0; }
.meta-list dt, .meta-list .k { font-weight: 700; color: var(--ink); min-width: 130px; }

.empty-state {
  text-align: center;
  padding: 46px 20px;
  color: var(--muted);
}
.empty-state svg { width: 54px; height: 54px; fill: var(--line); margin: 0 auto 14px; }

/* Pagination ------------------------------------------------------------ */
.pagination {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  list-style: none;
  padding: 0;
  margin: 28px 0 0;
}
.pagination a, .pagination span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px; min-height: 40px;
  padding: 0 10px;
  border: 1px solid var(--line);
  background: #fff;
  border-radius: var(--radius-sm);
  font-size: .84rem;
  color: var(--ink-soft);
}
.pagination a:hover { background: var(--gold); border-color: var(--gold); color: var(--ink); }
.pagination .is-active { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 700; }
.pagination .is-disabled { opacity: .45; }

/* FAQ ------------------------------------------------------------------- */
.faq-item {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin-bottom: 10px;
  overflow: hidden;
}
.faq-item__q {
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  padding: 15px 46px 15px 16px;
  font: inherit;
  font-weight: 700;
  color: var(--ink);
  cursor: pointer;
  position: relative;
  min-height: 50px;
}
.faq-item__q::after {
  content: "+";
  position: absolute;
  right: 16px; top: 50%;
  transform: translateY(-50%);
  font-size: 1.3rem;
  color: var(--accent);
  line-height: 1;
}
.faq-item__q[aria-expanded="true"]::after { content: "–"; }
.faq-item__q:hover { background: #fcfaf2; }
.faq-item__a { padding: 0 16px 16px; font-size: .9rem; color: var(--ink-soft); }
.faq-item__a[hidden] { display: none; }

/* --------------------------------------------------------------------------
   20. Footer
   -------------------------------------------------------------------------- */
.footer {
  background: var(--darker);
  color: #cfcfcf;
  padding: 42px 0 26px;
  font-size: .88rem;
}
.footer__grid {
  display: grid;
  /* Two across from the narrowest screen up. One column made the footer a very
     long scroll on a phone: six blocks, each the full width, one under another. */
  grid-template-columns: repeat(2, 1fr);
  gap: 28px 18px;
}
/* On a phone only the four link columns pair up. The brand block carries a
   crest, the name, a paragraph and the social row; Contact Us carries an email
   address, four numbers and the postal address. Neither reads in half a phone
   width, so both hold the full row and the link columns do the 2x2. */
.footer__brand,
.footer__contact-col { grid-column: 1 / -1; }
/* A grid child's default minimum is its content, so one long unbroken word -
   an email address, a course title - would widen its column and squeeze the
   one beside it. */
.footer__grid > * { min-width: 0; }

/* Brand column ---------------------------------------------------------- */
.footer__brand-head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}
.footer__logo {
  width: 56px; height: 56px;
  border-radius: 50%;
  object-fit: cover;
  background: #fff;
  flex: none;
}
.footer__brand-name {
  margin: 0;
  color: #fff;
  font-size: 1rem;
  font-weight: 800;
  line-height: 1.3;
  text-transform: uppercase;
}
.footer__tagline {
  margin: 0 0 14px;
  font-size: .82rem;
  color: var(--gold);
}
.footer__about {
  margin: 0 0 18px;
  font-size: .84rem;
  line-height: 1.75;
  color: #bdbdbd;
  text-align: justify;
}
.footer__title {
  color: #fff;
  font-size: .95rem;
  text-transform: uppercase;
  letter-spacing: .6px;
  margin-bottom: 16px;
  font-weight: 700;
}
.footer__list { list-style: none; margin: 0; padding: 0; }
.footer__list li { border-bottom: 1px dashed rgba(255, 255, 255, .16); }
.footer__list li:last-child { border-bottom: 0; }
.footer__list a {
  display: block;
  padding: 9px 0 9px 14px;
  color: #cfcfcf;
  font-size: .86rem;
}
.footer__list a:hover { color: var(--gold); padding-left: 18px; }

.footer__contact { list-style: none; margin: 0; padding: 0; }
.footer__contact li {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin-bottom: 9px;
  line-height: 1.5;
}
.footer__contact svg { width: 15px; height: 15px; fill: var(--gold); flex: 0 0 auto; margin-top: 4px; }
.footer__contact a { color: #cfcfcf; }
.footer__contact a:hover { color: var(--gold); }

.footer__social { display: flex; gap: 10px; margin-top: 18px; }
.footer__social a {
  width: 34px; height: 34px;
  border: 1px solid rgba(255, 255, 255, .35);
  border-radius: var(--radius-sm);
  display: grid;
  place-items: center;
  color: #cfcfcf;
}
.footer__social a:hover { background: var(--gold); border-color: var(--gold); color: var(--ink); }
.footer__social svg { width: 15px; height: 15px; fill: currentColor; }

.footer__social-embed {
  background: #fff;
  border-radius: var(--radius-sm);
  padding: 14px;
  color: var(--ink-soft);
}
.footer__social-embed .fb-card {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 14px;
  text-align: center;
}
.footer__social-embed .fb-card__name { font-weight: 700; color: var(--ink); }
.footer__social-embed .fb-card__meta { font-size: .78rem; color: var(--muted); margin-bottom: 10px; }

.subfooter {
  background: #171717;
  color: #9a9a9a;
  font-size: .78rem;
  padding: 12px 0;
}
.subfooter__inner {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  align-items: center;
  justify-content: center;
  text-align: center;
}
.subfooter a { color: #bdbdbd; }
.subfooter a:hover { color: var(--gold); }
.subfooter__links { display: flex; gap: 14px; flex-wrap: wrap; justify-content: center; }

/* --------------------------------------------------------------------------
   21. Floating actions
   -------------------------------------------------------------------------- */
.float-actions {
  position: fixed;
  right: 14px;
  bottom: 16px;
  z-index: 90;
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: flex-end;
}

.float-whatsapp {
  width: 52px; height: 52px;
  border-radius: 50%;
  background: var(--whatsapp);
  display: grid;
  place-items: center;
  box-shadow: var(--shadow);
  color: #fff;
  transition: transform var(--transition);
}
.float-whatsapp:hover { transform: scale(1.08); color: #fff; }
.float-whatsapp svg { width: 28px; height: 28px; fill: currentColor; }

.float-top {
  width: 42px; height: 42px;
  border: 0;
  border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, .35);
  color: #fff;
  cursor: pointer;
  display: grid;
  place-items: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition), visibility var(--transition), background var(--transition);
}
.float-top.is-visible { opacity: 1; visibility: visible; }
.float-top:hover { background: var(--accent); }
.float-top svg { width: 18px; height: 18px; fill: currentColor; }

/* --------------------------------------------------------------------------
   22. Responsive — progressive enhancement upward
   -------------------------------------------------------------------------- */

/* ---- Mobile navigation (below 992px) ---- */
@media (max-width: 991.98px) {
  .header__toggle { display: inline-flex; }

  .nav {
    position: fixed;
    top: 0;
    right: 0;
    width: min(86vw, 340px);
    height: 100%;
    height: 100dvh;
    background: #fff;
    box-shadow: var(--shadow-lg);
    transform: translateX(100%);
    transition: transform .3s ease;
    overflow-y: auto;
    z-index: 200;
    -webkit-overflow-scrolling: touch;
  }
  .nav.is-open { transform: translateX(0); }
  /* The drawer and the sticky top bar are the same element, and .nav.is-stuck
     outranks .nav on specificity, so it would apply here too. Pinned back to
     fixed: a class left over from a wider viewport must not drop the drawer
     into the flow as a full-height block covering the page. */
  .nav.is-stuck { position: fixed; }

  .nav__mobile-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 16px;
    background: var(--gold);
    position: sticky;
    top: 0;
    z-index: 3;
  }
  .nav__mobile-head strong { font-size: .9rem; text-transform: uppercase; }

  .nav__close {
    display: inline-flex;
    border: 0;
    background: transparent;
    cursor: pointer;
    padding: 6px;
    color: var(--ink);
  }
  .nav__close svg { width: 20px; height: 20px; fill: currentColor; }

  /* The list keeps the .shell class, which is wanted on the bar but not in
     the drawer: its 16px inline padding would indent every row a second time
     on top of the padding .nav__link already carries, and narrow the tap
     target by the same amount on the right. */
  .nav__list {
    flex-direction: column;
    align-items: stretch;
    padding-inline: 0;
    max-width: none;
  }
  .nav__item { border-bottom: 1px solid var(--line); }

  .nav__link {
    padding: 14px 16px;
    justify-content: space-between;
    border-bottom: 0;
    font-size: .82rem;
    min-height: 48px;
  }
  .nav__link:hover, .nav__item.is-open > .nav__link { background: #faf6e4; }
  .nav__caret { transition: transform var(--transition); }
  .nav__item.is-open > .nav__link .nav__caret { transform: rotate(180deg); }
  /* The caret opens and closes the submenu, so it needs a finger-sized target
     of its own rather than the few pixels the glyph occupies. The padding
     grows the hit area and the negative margin takes that growth back out of
     the layout, so the arrow does not move. Shrinking the glyph therefore
     costs nothing in tappability - the target stays 38px either way.
     Slightly larger than the desktop caret because here it sits alone at the
     end of a 48px row rather than tucked against a label. */
  .nav__link .nav__caret {
    width: 10px; height: 10px;
    box-sizing: content-box;
    padding: 14px;
    margin: -14px -14px -14px 0;
  }

  .nav__submenu {
    position: static;
    opacity: 1;
    visibility: visible;
    transform: none;
    /* The drawer opens by display, not by fade — carrying the desktop
       hover-intent delays over would just make the tap feel unresponsive. */
    transition: none;
    box-shadow: none;
    border-top: 0;
    border-radius: 0;
    background: #fbfbfb;
    padding: 0;
    display: none;
    min-width: 0;
  }
  .nav__item.is-open > .nav__submenu { display: block; }
  .nav__submenu a { padding: 12px 16px 12px 30px; min-height: 44px; }
  .nav__item:nth-last-child(-n+3) .nav__submenu { right: auto; }

  /* In the drawer the mega menu is just a long accordion list like any other
     — columns and the centring transform would both fight the off-canvas. */
  .nav__submenu--mega,
  .nav__item:nth-last-child(-n+3) .nav__submenu--mega {
    /* columns, not column-count: the desktop rule is the shorthand
       "columns: 260px 3", and overriding only the count would leave the
       260px column-width in force. */
    columns: auto;
    width: auto;
    left: auto;
    transform: none;
    padding: 0;
    /* The desktop panel caps its height and scrolls inside itself. In the
       drawer that would be a scroll area nested in a scroll area - the whole
       drawer already scrolls. */
    max-height: none;
    overflow-y: visible;
  }
  /* One stacked list off-canvas. columns:auto rather than column-count:1,
     because the shorthand above sets a column-width too and overriding only
     the count would leave the 215px in force. */
  .nav__submenu--groups { columns: auto; }
  .nav__item.is-open > .nav__submenu--mega { transform: none; }
  .nav__submenu--mega a {
    padding: 12px 16px 12px 30px;
    border-radius: 0;
    font-size: .82rem;
  }

  /* Schools inside a columned panel - Programs. On desktop these sit in
     columns; here they stack, so they need the same indenting treatment the
     sideways board gets below, or the headings and their departments read as
     one flat list. */
  .nav__submenu--mega .nav__panel-group { margin: 0; padding: 2px 16px 8px 30px; }
  .nav__submenu--mega a.nav__panel-heading { padding: 12px 0 0; font-size: .74rem; }
  .nav__submenu--mega a.nav__panel-heading::after { width: 84px; margin-top: 7px; }
  .nav__submenu--mega .nav__panel-list a {
    padding: 11px 0;
    min-height: 44px;
  }

  /* Third level in the drawer is one more accordion step, indented a notch
     further - a board flying out of a 320px panel has nowhere to go. */
  .nav__submenu .nav__caret--side { transform: none; }
  .nav__subitem--has-panel.is-open > a .nav__caret--side { transform: rotate(180deg); }
  .nav__submenu .nav__subitem--has-panel > a .nav__caret--side {
    width: 10px; height: 10px;
    box-sizing: content-box;
    padding: 12px;
    margin: -12px -12px -12px 0;
  }
  .nav__subitem--has-panel > a { min-height: 44px; }

  .nav__panel {
    position: static;
    display: none;
    width: auto;
    max-height: none;
    overflow: visible;
    column-count: 1;
    padding: 0;
    background: #f4f4f4;
    box-shadow: none;
    border-top: 0;
    border-radius: 0;
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: none;
  }
  .nav__subitem--has-panel.is-open > .nav__panel { display: block; }

  .nav__panel-group { margin: 0; padding: 2px 16px 8px 30px; }
  .nav__panel a.nav__panel-heading { padding: 12px 0 0; font-size: .74rem; }
  .nav__panel a.nav__panel-heading::after { width: 84px; margin-top: 7px; }
  .nav__panel .nav__panel-list a { padding: 11px 0; min-height: 44px; }


  .nav-scrim {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, .5);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition), visibility var(--transition);
    z-index: 150;
  }
  .nav-scrim.is-visible { opacity: 1; visibility: visible; }

  body.nav-open { overflow: hidden; }
}

/* ---- Centred header stack on tablet & phone (below 992px) ----
   The brand, contact blocks and call-to-actions each take a full-width row and
   centre themselves; the menu button is pulled out of the flow to the top-right
   so the logo can sit dead centre. */
@media (max-width: 991.98px) {
  .header__inner {
    position: relative;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: 18px;
    padding-block: 16px;
  }

  /* The menu button is out of the flow at the right, so the brand reserves its
     footprint as padding and centres in whatever is left - which sits the
     lockup a little left of the page centre, clear of the button.

     A max-width cap was doing this before and could not: a cap is symmetric
     and the button is on one side only, so a centred lockup at the cap still
     reached about 16px under it. Padding is one-sided, like the problem. */
  .header__brand {
    justify-content: center;
    width: 100%;
    max-width: none;
    /* gutter + the button: 22px icon inside 12px of side padding, plus a
       little air so the descender of the name never touches it. */
    padding-right: calc(var(--gutter) + 54px);
  }

  .header__info {
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 16px;
    width: 100%;
  }
  .info-block { text-align: center; width: 100%; }

  .header__cta {
    flex-direction: column;
    align-items: center;
    justify-content: center;
    margin-left: 0;
    gap: 14px;
    width: 100%;
  }

  .header__toggle {
    position: absolute;
    top: 16px;
    right: var(--gutter);
    margin-left: 0;
  }
}

/* ---- Phone: centre the utility bar too ---- */
@media (max-width: 767.98px) {
  .topbar__inner { flex-direction: column; justify-content: center; gap: 10px; }
  .topbar__links, .topbar__social { justify-content: center; }
  .topbar__search { margin-left: 0; width: 100%; display: flex; justify-content: center; }
  .search-inline { width: min(100%, 320px); }
}

/* ---- >= 400px ---- */
@media (min-width: 400px) {
  .form-row { grid-template-columns: 1fr 1fr; }
}

/* ---- >= 576px ---- */
@media (min-width: 576px) {
  :root { --gutter: 20px; }
  body { font-size: 15.5px; }

  .quicklinks { grid-template-columns: 1fr 1fr; }
  .enlist-grid { grid-template-columns: repeat(2, 1fr); }
  .enlist-strip .enlist-card { flex: 0 0 calc((100% - 24px) / 2); }
  .offerings__panel { grid-template-columns: 1fr 1fr; padding: 32px 28px; }
  .video-grid { grid-template-columns: 1fr 1fr; }
  .carousel__item { flex-basis: calc((100% - 18px) / 2); }
  /* Columns and the brand span are the base now; only the gap opens up. */
  .footer__grid { gap: 30px 24px; }
  /* Past the phone there is width for Contact Us beside a link column. */
  .footer__contact-col { grid-column: auto; }
  .subfooter__inner { justify-content: space-between; text-align: left; }
  .float-actions { right: 20px; bottom: 22px; }
}

/* ---- >= 768px ---- */
@media (min-width: 768px) {
  .section { padding: 52px 0; }
  .enlist-grid { grid-template-columns: repeat(3, 1fr); }
  .enlist-strip .enlist-card { flex: 0 0 calc((100% - 48px) / 3); }
  /* Three across, matching the grid this replaced. */
  /* Three link columns beside each other; brand still spans the full row. */
  .footer__grid { grid-template-columns: repeat(3, 1fr); }
  .carousel__nav { display: grid; }
  .carousel__item { flex-basis: calc((100% - 36px) / 3); }
  .hero__caption {
    top: 0;
    bottom: auto;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    width: 55%;
    background: linear-gradient(to right, rgba(255, 255, 255, .0), rgba(255, 255, 255, 0));
    padding-left: 5%;
  }
  .hero__caption h2 { color: var(--ink); text-shadow: 0 1px 10px rgba(255, 255, 255, .8); }
  .hero__caption p { color: var(--ink-soft); }
  .hero__arrow { width: 46px; height: 62px; }
  .chancellor { padding: 40px 36px; }
  .centres { padding: 40px 30px; }
}

/* ---- >= 992px ---- */
@media (min-width: 992px) {
  body { font-size: 16px; }
  :root { --gutter: 24px; }

  .home-band__grid {
    grid-template-columns: minmax(0, 250px) minmax(0, 1fr) minmax(0, 320px);
    align-items: start;
  }
  .quicklinks { grid-template-columns: 1fr 1fr; gap: 22px 30px; }

  .offerings__grid { grid-template-columns: 40% 60%; align-items: stretch; }
  .offerings__panel {
    margin: 40px 0 40px -60px;
    padding: 40px 44px;
    z-index: 2;
    position: relative;
  }

  /* start, not stretch. Stretch made both cells as tall as the taller one, so
     whichever was shorter carried the difference as empty space inside itself -
     which is what put a gap under the Chancellor's message. Each sizes to its
     own content now. */
  .chancellor-grid { grid-template-columns: 52% 48%; align-items: start; }
  .carousel__item { flex-basis: calc((100% - 54px) / 4); }
  .carousel--triple .carousel__item { flex-basis: calc((100% - 36px) / 3); }
  .carousel--double .carousel__item { flex-basis: calc((100% - 18px) / 2); }

  /* Six columns: brand, contact, then the four from FOOTER_LINKS.  The brand
     and contact columns get more room because their content is prose and
     addresses rather than short link labels. */
  .footer__grid { grid-template-columns: 1.5fr 1.2fr repeat(4, 1fr); gap: 26px; }
  /* Back to a single column of the six — the full-width span is only for the
     narrower layouts above, where the blurb would be squeezed into a half. */
  .footer__brand { grid-column: auto; }
  .carousel__nav--prev { left: -18px; }
  .carousel__nav--next { right: -18px; }

  .content-grid { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 34px; }

  /* Main navigation aims to sit on a single line — padding and type scale
     with the viewport so the top-level items fit across the shell.

     It used to be pinned there with flex-wrap: nowrap, sized for roughly
     110-115 characters of labels. That held until the labels grew past it,
     and then it failed in the worst way available: a centred flex row that
     overruns its box overflows BOTH edges, so the Home icon was clipped off
     the left of the window rather than anything wrapping. Wrapping is allowed
     again, so a bar that no longer fits takes a second row instead of hiding
     its first item. Tighter padding below keeps it to one row at the widths
     where it can still manage one. */
  /* The upper bounds matter as much as the lower ones. Both values grow with
     the VIEWPORT while the bar itself stops growing at the shell's 1320px, so
     an uncapped clamp made the labels widest exactly where there was no more
     room for them - one item wrapped at 1920px while 1366px fitted fine. The
     maxima are set to what still fits 1320px. */
  .nav__link {
    padding: 13px clamp(2px, .26vw, 4px);
    font-size: clamp(.56rem, .38rem + .31vw, .64rem);
    letter-spacing: 0;
  }
  .nav__link .nav__caret { width: 8px; height: 8px; }
}

/* --- Carousel arrows: outside the cards only when there is room ---------
   From 992px up the rule above hangs the arrows 18px outside the carousel.
   That assumes space beside the shell, and there is none until the window is
   wider than the shell itself: at 1024px the left arrow landed 6px from the
   edge of the screen, half of it over the first card. Below that point they
   sit just inside instead, which is where a carousel arrow usually lives.

   1340px is where the 1240px shell, its gutters and an arrow either side stop
   competing for the same pixels. */
@media (max-width: 1339.98px) {
  .carousel__nav--prev { left: 6px; }
  .carousel__nav--next { right: 6px; }
}
/* Wide enough for the arrows to stand outside the cards, so let them out of
   the clip. 24px clears the 18px offset with room to spare, and the shell is
   nowhere near the window edge at this width. */
@media (min-width: 1340px) {
  .carousel { overflow-clip-margin: 24px; }
}

/* The arrows read as controls rather than smudges: a hairline so they hold
   their shape against a white card behind them, and the icon in the ink
   colour instead of inheriting whatever the surrounding text is. */
.carousel__nav {
  border: 1px solid var(--line);
  color: var(--ink);
}
.carousel__nav:hover { border-color: var(--gold); color: var(--ink); }


/* Twelve top-level labels on one line is a tight fit just above the drawer
   breakpoint: at 992px the last item ("Examination") spilled ~10px past the
   viewport. Trimming the horizontal padding across this band buys it back. */
@media (min-width: 992px) and (max-width: 1199.98px) {
  .nav__link { padding-inline: clamp(2px, .34vw, 6px); }
}

/* ---- >= 1200px ---- */
@media (min-width: 1200px) {
  .section { padding: 60px 0; }
}

/* ---- Very small phones ---- */
@media (max-width: 380px) {
  .header__brand img { max-height: 44px; }
  .header__crest { width: 42px; height: 42px; }
  /* No max-width and no font-size here: both are set in the phone block below,
     which comes later in the file and would win anyway. A 12ch cap on a
     28-character name only ever cut it off. */
  .btn { padding: 9px 16px; font-size: .76rem; }
  .panel__head { font-size: 1.05rem; }
  .topbar__inner { gap: 8px 12px; }
  /* No width override here. The box is already capped to the space available
     by .search-inline above, and pinning the input instead leaves the
     magnifier stranded mid-box with dead white space after it. */
}

/* ---- Desktop: full width, and the crop taken off the bottom ----
   The clip is 1280x720, so 16:9, and a desktop viewport is usually wider than
   that - 1920x950 is about 2.0. cover fills the width and loses the difference
   in height: about 130px at 1920x950.

   The default object-position centres that, which halves the loss top and
   bottom and takes the logo at the top of the frame with it. Anchoring to the
   top spends the whole crop on the bottom instead, so the logo stays and the
   video still reaches both edges of the screen.

   Only from 992px up. Below that the viewport is narrower than 16:9 rather
   than wider, so cover crops the sides and not the height, and where that crop
   falls vertically stops mattering. */
@media (min-width: 992px) {
  .hero__video { object-position: center top; }
}

/* ---- Portrait phones and tablets: the hero should not be the whole screen ----
   At 100svh the clip fills the viewport, so the first thing anyone on a phone
   can do is scroll past a full screen of video before reaching a word of the
   page. Shorter here, so the section below is already showing underneath it
   and the hero reads as a header rather than a splash screen. These sit above
   the landscape rule on purpose: a phone held sideways has so little height
   that it needs the opposite treatment, and that rule still wins there. */
@media (max-width: 991.98px) {
  .hero__media { height: 68vh; height: 68svh; min-height: 360px; }
}
@media (max-width: 767.98px) {
  .hero__media { height: 58vh; height: 58svh; min-height: 300px; }
}
@media (max-width: 479.98px) {
  .hero__media { height: 52vh; height: 52svh; min-height: 260px; }
}

/* ---- Landscape phones: keep the hero from eating the screen ---- */
@media (max-width: 900px) and (orientation: landscape) {
  .hero__media { height: 78vh; height: 78svh; min-height: 260px; }
}

/* --------------------------------------------------------------------------
   20b. Narrow phones: reflow instead of clipping
   Verified at 320 / 375 / 425 / 768 / 1440.
   -------------------------------------------------------------------------- */

/* The crest and the university name share the bar with the menu button.  Held
   on one line the name cannot fit 320px — it clamps to ~8px type and is still
   too wide, so it gets cut off.  Two readable lines beat one unreadable one. */
@media (max-width: 767.98px) {
  .header__brand { min-width: 0; }
  .brand-lockup { min-width: 0; }
  /* One line each, name and strapline both. The size is what gives, not the
     wrapping: 2.6vw carries the name from about 10px at 320px up to its cap,
     and the letter-spacing goes because 27 gaps at .3px is most of a
     character's worth of width on the narrowest screen. */
  .brand-lockup__name {
    white-space: nowrap;
    font-size: clamp(.55rem, .10rem + 2.35vw, .95rem);
    line-height: 1.2;
    letter-spacing: 0;
  }
  .brand-lockup small {
    display: block;
    white-space: nowrap;
    font-size: clamp(.46rem, .04rem + 2.15vw, .70rem);
    letter-spacing: .1px;
    margin-top: 1px;
  }

}

/* Below 992px the stock header stacks every block into its own full-width row
   and runs ~470px tall — the whole first screen on a phone.  Nothing is
   hidden to fix that: every helpline number, the address, the fee link, the
   UGC link and APPLY NOW all stay on the page.  They are laid out densely
   instead — the two info blocks share a row, the four numbers run in two
   columns, and the three calls to action sit on one wrapping line.
   992px is the same breakpoint the drawer nav uses. */
@media (max-width: 991.98px) {
  .header__inner { padding-block: 10px; gap: 10px 14px; }

  .header__info {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: center;
    gap: 10px 20px;
    width: 100%;
  }
  .info-block {
    width: auto;
    flex: 1 1 250px;
    max-width: 360px;
    text-align: center;
  }
  .info-block__label { font-size: .62rem; padding: 2px 8px; margin-bottom: 4px; }
  .info-block__body { font-size: .8rem; line-height: 1.5; max-width: none; }

  /* The four helpline numbers as two columns: two rows instead of four, which
     is where most of the old header height went. */
  .header__info .info-block:first-child .info-block__body {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1px 12px;
    justify-items: center;
  }
  .info-block__body a { white-space: nowrap; }
  /* Toll-free is one long string, so it takes the full width under the grid. */
  .info-block__body .toll-free { grid-column: 1 / -1; }

  /* Fee link, UGC and APPLY NOW on one wrapping row rather than three stacked
     full-width blocks. */
  .header__cta {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 10px 16px;
    width: 100%;
    margin-left: 0;
  }
  .cta-pill { font-size: .74rem; padding: 5px 13px; }
  .cta-stack__title { font-size: .76rem; }
  .cta-stack__sub { font-size: .66rem; }
  .cta-apply__label { font-size: .72rem; margin-bottom: 2px; }
}

/* Phones: everything in the header runs in one column, read straight down.
   The two-column tricks above earn their keep on a tablet, where there is
   width to spend; on a phone they only make a number share a line with the
   number beside it and a link share one with the next link. */
@media (max-width: 575.98px) {
  /* One helpline number per line, in the order they are written in data.py. */
  .header__info .info-block:first-child .info-block__body {
    grid-template-columns: 1fr;
    gap: 2px;
  }
  /* PAY FEE ONLINE, then UGC, then ADMISSION OPEN - each on its own row
     rather than wrapping onto a shared line in whatever order they fit. */
  .header__cta {
    flex-direction: column;
    align-items: center;
    gap: 12px;
  }
}

/* Very small phones: the two info blocks no longer fit side by side, so they
   stack — but every value is still on the page. */
@media (max-width: 479.98px) {
  .info-block { flex: 1 1 100%; max-width: none; }
  .header__info { gap: 8px; }
  .info-block__body { font-size: .76rem; }
  .cta-pill { font-size: .7rem; padding: 4px 11px; }
}

/* Email addresses and URLs are single unbreakable tokens. Without a break
   opportunity they set a floor on the page width that no viewport can meet. */
.footer__contact a,
.footer__contact span,
.meta-list span,
.search-results__excerpt,
.prose a { overflow-wrap: anywhere; }

.meta-list li, .meta-list span { min-width: 0; }

/* Below 576px, stack the label above its value so a long URL gets the
   full column width rather than the ~190px left beside a 130px label. */
@media (max-width: 575.98px) {
  .meta-list li { flex-direction: column; gap: 2px; }
  .meta-list dt, .meta-list .k { min-width: 0; }
}

/* ---- Large desktops ---- */
@media (min-width: 1440px) {
  :root { --shell: 1320px; }
}

/* From 1280px there is slack in the header row, so the helpline and address
   stop shrinking — the curve above bottoms out near 10.6px, which is too
   small to read comfortably on a large screen. */
@media (min-width: 1280px) {
  .info-block { font-size: .78rem; }
  .info-block__label { font-size: .68rem; }
}

/* --------------------------------------------------------------------------
   23. Print
   -------------------------------------------------------------------------- */
@media print {
  .topbar, .nav, .marquee, .hero, .float-actions, .header__cta,
  .carousel__nav, .carousel__dots, .footer, .subfooter,
  .enquiry-panel, .course-panel__aside { display: none !important; }
  body { background: #fff; font-size: 11pt; color: #000; }
  .shell { max-width: none; padding: 0; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; color: #555; }
  .card, .panel { box-shadow: none; border: 1px solid #ccc; }
}
