/* ═══════════════════════════════════════════════════════════════════════════
   PUBLIC SITE — CORE
   Theme-agnostic structure: reset, layout, rhythm, components, motion, a11y.

   This file must NEVER hardcode a colour, font family, radius, or shadow.
   Every visual value comes from a custom property that the active theme
   defines. That is the whole contract: swap the theme stylesheet and the look
   changes completely without a single markup edit.

   THEME TOKEN CONTRACT — every theme file must define all of these.
   ─────────────────────────────────────────────────────────────────────────
   Colour    --c-bg --c-surface --c-surface-2 --c-surface-3 --c-ink
             --c-ink-strong --c-muted --c-border --c-brand --c-brand-ink
             --c-accent --c-accent-ink --c-scrim --c-hero-ink
   Type      --f-display --f-body --fw-display --display-tracking
             --eyebrow-transform --eyebrow-tracking
   Shape     --r-sm --r-md --r-lg --r-pill --card-bg --card-border
             --card-blur --card-pad
   Depth     --sh-card --sh-hover --sh-nav
   Layout    --container --section-pad --measure --gap
   Nav       --nav-h --nav-h-scrolled --nav-bg --nav-ink --nav-blur

   THEME CONSTRAINT — do not position `body > *`.
   The preview banner and the JS-injected lightbox are both direct children of
   <body> and rely on their own position:fixed. A theme rule matching
   `body[data-theme=...] > *` outranks their class selectors and drops them
   into the document flow. Name the structural elements (.pub-nav, main,
   .pub-footer) instead.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Reset ────────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

/* ── The size of everything ───────────────────────────────────────────────────
   ONE NUMBER controls how large the public site renders. Lower it and the
   whole site gets smaller together; raise it and it grows back.

   HOW IT WORKS
   `--ui-scale` multiplies the ROOT font size, and 196 of the 199 font-size
   declarations across the public CSS and templates are written in `rem` — so
   they all follow from here. That includes the ones no token could reach: the
   .8125rem eyebrow, the .6875rem table label, the sixty-five sizes set inline
   in section templates. A per-token multiplier would have had to name each of
   them and would have missed the ones added next month.

   It is a PERCENTAGE, never a pixel value. 100% is whatever the visitor has
   set as their browser's default text size, so someone who raised theirs to
   20px still gets text 11% smaller than before rather than being overridden
   back down to our idea of normal. A `font-size: 14px` here would silently
   throw that preference away.

   WHY 0.89
   Body copy was 18px, two points above what almost every site sets. 0.89 puts
   it at 16px, which is the size a visitor's eye is calibrated for, and brings
   everything else down with it in proportion.

   WHAT IT DOES NOT REACH, and is handled separately
     · px tokens — --container, --measure, --nav-h. They pass through the same
       --ui-scale in each theme file, so images and line length come down too.
     · Hero heights, which are in svh. A viewport unit does not care how large
       the type is; those are cut explicitly below.
     · Hairlines, radii and the responsive breakpoints, all in px on purpose.
       A 1px rule is 1px at any text size, and a breakpoint that moved with the
       type scale would make the layout ladder unpredictable.

   THE PHONE IS SCALED TOO, at .94. It used to sit at 1 while the desktop ran
   at .89 — so phone text rendered LARGER than desktop text, on the device with
   the least room for it.

   The old note here argued that shrinking phone type would leave it smaller
   than the 44px tap targets around it. That was true, and it was a bug in the
   targets rather than a reason: they were written `min-height: 2.75rem`, in
   rem, so they followed this number down. They are literal px now — a tap
   target is 44px whatever size the type is — which is what makes this scale
   safe to move. See the POINTER block at the foot of this file.

   Data tables do NOT come from here. The evidence table on an area page sets
   its own sizes: prose is read and a table is scanned, and the two want
   different floors. One number cannot serve both.
   ───────────────────────────────────────────────────────────────────────── */
:root { --ui-scale: .94; }
@media (min-width: 769px) {
  :root { --ui-scale: .89; }
}


/* ════════════════════════════════════════════════════════════════════════════
   LAYOUT TOKENS

   Container width, section rhythm, measure and nav height. These arrived here
   from themes/prestige.css when the other four layouts were removed — they are
   the layout, and there is one, so a separate file for them was a level of
   indirection with nothing on the other side of it. See pages/themes.py.

   --nav-h is the one that has to move with the viewport. It was a flat 92px,
   which is a quarter of a small phone's screen spent on a bar with a logo and
   a hamburger in it — and the hero, the mobile nav panel and every sticky
   offset are measured from it, so the cost compounded down the page.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  --container:   calc(1320px * var(--ui-scale));
  --section-pad: clamp(4.5rem, calc(9vw * var(--ui-scale)), 8.75rem);
  --measure:     calc(700px * var(--ui-scale));
  --gap:         clamp(1.5rem, calc(2.5vw * var(--ui-scale)), 2rem);
  --card-pad:    clamp(1.75rem, calc(3vw * var(--ui-scale)), 2.5rem);

  --nav-h:          64px;
  --nav-h-scrolled: 56px;
}

@media (min-width: 769px) {
  :root {
    --nav-h:          calc(80px * var(--ui-scale));
    --nav-h-scrolled: calc(64px * var(--ui-scale));
  }
}

@media (min-width: 993px) {
  :root {
    --nav-h:          calc(92px * var(--ui-scale));
    --nav-h-scrolled: calc(70px * var(--ui-scale));
  }
}

/* Long-form reading gets slightly looser leading. */
.prose { line-height: 1.8; }

/* Cards breathe — this layout never crowds a grid. */
.pub-grid.cols-3 { gap: clamp(1.75rem, calc(3vw * var(--ui-scale)), 2.5rem); }

/* Pull quotes are set in italic display type, the one editorial flourish. */
.pull-quote { font-style: italic; }

html {
  font-size: calc(100% * var(--ui-scale));
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body.pub {
  margin: 0;
  background: var(--c-bg);
  color: var(--c-ink);
  font-family: var(--f-body);
  font-size: 1.0625rem;             /* phone base; 1.125rem from 769 up */
  line-height: 1.7;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { max-width: 100%; height: auto; display: block; }

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

/* ── Narrow-screen robustness ─────────────────────────────────────────────────
   Three guards that stop a phone layout being broken by its content rather
   than by its rules.

   1. Long words wrap. An area name, an uploaded filename or a pasted URL is
      otherwise a single unbreakable box wider than the screen, and body's
      overflow-x:hidden would hide it rather than fix it.
   2. Grid and flex children default to min-width:auto, which is the intrinsic
      width of their content — that is what actually pushes a page sideways.
      Zeroing it lets a column be narrower than the longest thing inside it.
   3. .break-any is for the slots that hold machine-generated strings, where
      breaking mid-word is preferable to overflowing.
   ───────────────────────────────────────────────────────────────────────────── */
body.pub { overflow-wrap: break-word; }

.pub-split > *,
.pub-grid > *,
.subnav-inner > *,
.footer-grid > * { min-width: 0; }

.break-any { overflow-wrap: anywhere; }

/* ── Typography scale ─────────────────────────────────────────────────────── */
.pub h1, .pub h2, .pub h3, .pub h4 {
  font-family: var(--f-display);
  font-weight: var(--fw-display);
  letter-spacing: var(--display-tracking);
  color: var(--c-ink-strong);
  margin: 0;
  line-height: 1.15;
}

.t-hero    { font-size: clamp(2.75rem, calc(6.5vw * var(--ui-scale)), 5rem);  line-height: 1.05; }
.t-section { font-size: clamp(2rem, calc(4vw * var(--ui-scale)), 3rem);       line-height: 1.15; }
.t-sub     { font-size: clamp(1.5rem, calc(2.4vw * var(--ui-scale)), 2rem);   line-height: 1.25; }
.t-card    { font-size: 1.5rem;                       line-height: 1.3; }
.t-body    { font-size: 1.125rem; }
.t-small   { font-size: 0.875rem; }

.lead {
  font-size: 1.25rem;
  line-height: 1.65;
  color: var(--c-muted);
  max-width: var(--measure);
}

.measure  { max-width: var(--measure); }
.prose    { max-width: var(--measure); }
.prose p  { margin: 0 0 1.4em; }
.prose p:last-child { margin-bottom: 0; }

/* ── Justified prose, landing page only ─────────────────────────────────────
   Body copy on the landing page sets flush to both margins, so the sections
   read as one document rather than as five blocks with five different ragged
   edges.

   PROSE ONLY. Justification needs several lines and a comfortable measure to
   do anything but open gaps, so it is listed against the paragraph classes
   rather than applied to `p`. Headings, eyebrows, labels, captions, buttons,
   stat figures, table cells and nav keep their own alignment — a justified
   two-word label is just a stretched one.

   `hyphens: auto` is not optional here. Without it the browser can only pad
   the word spaces, and a long word arriving on a short line opens a river
   straight down the column; with it the word breaks and the line closes up.
   It works because <html lang="en"> is set — hyphenation is dictionary-based
   and does nothing without a language.

   `text-wrap: pretty` asks the browser not to leave a single word alone on the
   last line, which is the one place justification cannot help: the last line
   of a justified paragraph is set ragged by definition.

   NOT the area-card blurbs. From 993 up `.area-desc` is `-webkit-line-clamp`ed
   (two lines, one at 1201) — so there it is a truncated label ending in an
   ellipsis, not a paragraph. Below 993 it is whole, and still not justified:
   a card blurb is not body copy. Justification skips the LAST line of a block, but a clamped line
   is not the last line as far as the browser is concerned, so it gets
   justified: "Community    engagement,    partnerships,    technolo…" with the
   word spaces stretched to the card edge. One line has nothing to gain from
   being flush on both sides and everything to lose.

   VMGO sets its own justification in its section template, alongside the type
   scale it had to unify first.
   ───────────────────────────────────────────────────────────────────────── */
body.is-landing .lead,
body.is-landing .prose p {
  /* Ragged-right on a phone. The measure there is too narrow to justify into:
     four or five words to a line cannot absorb the slack, so every line is
     padded to breaking point and the column fills with holes. Justification
     arrives at 481, where there is enough line to distribute across.

     Hyphenation stays on at every width — it shortens the rag, which is the
     one thing that still helps on a narrow measure. */
  text-align: left;
  -webkit-hyphens: auto;
  hyphens: auto;
  text-wrap: pretty;
}

@media (min-width: 481px) {
  body.is-landing .lead,
  body.is-landing .prose p { text-align: justify; }
}

.muted    { color: var(--c-muted); }
.ink      { color: var(--c-ink-strong); }

/* Eyebrow — the small label above a section heading */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  font-family: var(--f-body);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: var(--eyebrow-tracking);
  text-transform: var(--eyebrow-transform);
  color: var(--c-accent);
  margin-bottom: 1rem;
}
.eyebrow::before {
  content: '';
  width: 2rem;
  height: 2px;
  background: var(--c-accent);
  flex: none;
}
.eyebrow.no-rule::before { display: none; }

/* ── Layout ───────────────────────────────────────────────────────────────── */
.pub-container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, calc(4vw * var(--ui-scale)), 2.5rem);
}

.pub-section { padding-block: clamp(2rem, calc(8vw * var(--ui-scale)), 3rem); position: relative; }
.pub-section.tight  { padding-block: 1.75rem; }
.pub-section.flush-top    { padding-top: 0; }
.pub-section.flush-bottom { padding-bottom: 0; }
.pub-section.alt    { background: var(--c-surface-2); }
.pub-section.invert { background: var(--c-brand); color: var(--c-brand-ink); }
.pub-section.invert h1, .pub-section.invert h2,
.pub-section.invert h3, .pub-section.invert h4 { color: var(--c-brand-ink); }
.pub-section.invert .muted,
.pub-section.invert .lead { color: color-mix(in srgb, var(--c-brand-ink) 72%, transparent); }

/* Centred, with air above and below. */
.section-head { margin-bottom: clamp(3rem, calc(6vw * var(--ui-scale)), 4.5rem); }
.section-head.center { text-align: center; }
.section-head.center .lead { margin-inline: auto; }
.section-head.center .eyebrow::before { display: none; }

/* Generic responsive grid — themes restyle the cards, not the grid */
.pub-grid { display: grid; gap: var(--gap); }
/* One column is the base. Every widening is a min-width tier at the foot of
   this file — a grid that has to be un-columned for a phone is a grid written
   for the wrong device. */
.pub-grid.cols-2,
.pub-grid.cols-3,
.pub-grid.cols-4 { grid-template-columns: 1fr; }
.pub-grid.auto   { grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr)); }
.pub-grid.auto-sm{ grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr)); }

/* Split — image one side, text the other. `.reverse` flips it. */
.pub-split {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.5rem;
  align-items: center;
}
/* Stacked, the reverse flip has nothing to flip — it would only push the image
   below its own text. Restored with the two columns at 993. */
.pub-split.reverse > *:first-child { order: 0; }
.pub-split .split-media img { width: 100%; border-radius: var(--r-lg); }

/* ── Surfaces ─────────────────────────────────────────────────────────────── */
.pub-card {
  background: var(--card-bg);
  border: var(--card-border);
  border-radius: var(--r-lg);
  padding: var(--card-pad);
  box-shadow: var(--sh-card);
  backdrop-filter: var(--card-blur);
  -webkit-backdrop-filter: var(--card-blur);
  transition: transform .35s cubic-bezier(.2,.7,.3,1), box-shadow .35s ease;
  height: 100%;
}
.pub-card.hoverable:hover {
  transform: translateY(-6px);
  box-shadow: var(--sh-hover);
}
.pub-card.flush { padding: 0; overflow: hidden; }

.card-icon {
  display: inline-grid;
  place-items: center;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--c-accent) 12%, transparent);
  color: var(--c-accent);
  font-size: 1.5rem;
  margin-bottom: 1.25rem;
}

/* Media card — image on top, body beneath */
.media-card { overflow: hidden; display: flex; flex-direction: column; }
.media-card .media-thumb {
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--c-surface-2);
}
.media-card .media-thumb img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform 1.2s cubic-bezier(.2,.7,.3,1);
}
.media-card:hover .media-thumb img { transform: scale(1.06); }
.media-card .media-body { padding: var(--card-pad); flex: 1; display: flex; flex-direction: column; }

.tag {
  display: inline-block;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--c-accent);
  margin-bottom: .625rem;
}

/* ── Buttons ──────────────────────────────────────────────────────────────── */
.pub-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .55rem;
  /* Full width is the phone default; 481 up returns them to their content. */
  width: 100%;
  padding: .875rem 1.25rem;
  border-radius: var(--r-pill);
  border: 1.5px solid transparent;
  font-family: var(--f-body);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  transition: transform .25s ease, box-shadow .25s ease,
              background-color .25s ease, color .25s ease, border-color .25s ease;
}
.pub-btn:hover { transform: translateY(-2px); box-shadow: var(--sh-hover); }
.pub-btn:active { transform: translateY(0); }

.pub-btn.primary {
  background: var(--c-accent);
  color: var(--c-accent-ink);
  border-color: var(--c-accent);
}
.pub-btn.primary:hover { color: var(--c-accent-ink); }

.pub-btn.outline {
  background: transparent;
  color: var(--c-ink-strong);
  border-color: var(--c-border);
}
.pub-btn.outline:hover { border-color: var(--c-accent); color: var(--c-accent); }

.pub-btn.on-dark {
  background: transparent;
  color: #fff;
  border-color: color-mix(in srgb, #fff 55%, transparent);
}
.pub-btn.on-dark:hover { background: color-mix(in srgb, #fff 14%, transparent); color: #fff; }

.pub-btn.ghost { width: auto; background: none; border-color: transparent; padding-inline: 0; color: var(--c-accent); }
.pub-btn.ghost:hover { box-shadow: none; transform: none; }
.pub-btn.lg { padding: 1rem 1.5rem; font-size: 1rem; }
.pub-btn.sm { padding: .7rem 1.25rem; font-size: .9rem; }

/* The hero's calls to action stack full-width until 769. They are the widest
   labels on the page — "Explore Accreditation Areas" beside "About the
   Institution" — and side by side below that they wrap inside the button. */
.hero-actions .pub-btn { width: 100%; }

/* Arrow link with slide-on-hover */
.arrow-link {
  display: inline-flex; align-items: center; gap: .5rem;
  font-weight: 600; font-size: .9375rem; color: var(--c-accent);
}
.arrow-link i { transition: transform .3s ease; }
.arrow-link:hover i { transform: translateX(4px); }

/* ── Navbar ───────────────────────────────────────────────────────────────── */
.pub-nav {
  position: fixed;
  inset: 0 0 auto 0;
  height: var(--nav-h);
  z-index: 1030;
  display: flex;
  align-items: center;
  transition: height .35s ease, background-color .35s ease,
              box-shadow .35s ease, border-color .35s ease;
  background: transparent;
  border-bottom: 1px solid transparent;
}
/* Over a hero the nav is transparent; once scrolled — or on any page without a
   hero — it takes its solid/glass treatment. */
.pub-nav.is-solid,
.pub-nav.scrolled {
  height: var(--nav-h-scrolled);
  background: var(--nav-bg);
  backdrop-filter: var(--nav-blur);
  -webkit-backdrop-filter: var(--nav-blur);
  box-shadow: var(--sh-nav);
  border-bottom-color: color-mix(in srgb, var(--nav-ink) 12%, transparent);
}

.pub-nav .nav-inner {
  width: 100%; max-width: var(--container); margin-inline: auto;
  padding-inline: clamp(1.25rem, calc(4vw * var(--ui-scale)), 2.5rem);
  display: flex; align-items: center; gap: 2rem;
}

.nav-brand {
  display: flex; align-items: center; gap: .875rem;
  color: var(--nav-ink); text-decoration: none; margin-right: auto;
}
.nav-brand:hover { color: var(--nav-ink); }
.nav-brand .brand-mark {
  display: grid; place-items: center;
  width: 2.5rem; height: 2.5rem; flex: none;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--c-accent) 90%, transparent);
  color: var(--c-accent-ink);
  font-size: 1.25rem;
}
.nav-brand .brand-text { display: flex; flex-direction: column; line-height: 1.15; }
.nav-brand .brand-name {
  font-family: var(--f-display); font-weight: var(--fw-display);
  font-size: 1.125rem; letter-spacing: .01em;
}
.nav-brand .brand-sub { font-size: .6875rem; letter-spacing: .14em; text-transform: uppercase; opacity: .72; }

/* THE PANEL IS THE BASE. A row of links that has to be re-flowed into a panel
   for a phone is a nav written for a desktop; this one starts as the panel and
   is re-flowed into a row at 993, which is the width that can hold one. */
.nav-links {
  position: fixed; inset: 0 0 auto 0;
  display: flex; flex-direction: column; align-items: stretch; gap: 0;
  padding: calc(var(--nav-h) + 1rem) 1.5rem 2rem;
  background: var(--nav-bg);
  backdrop-filter: var(--nav-blur); -webkit-backdrop-filter: var(--nav-blur);
  transform: translateY(-100%); transition: transform .4s cubic-bezier(.2,.7,.3,1);
  box-shadow: var(--sh-nav);
}
.nav-links.open { transform: translateY(0); }
.nav-links .pub-btn { margin-top: 1.25rem; }
.nav-link {
  position: relative;
  padding: 1rem .25rem;
  color: var(--nav-ink);
  font-size: 1.0625rem; font-weight: 500;
  border-bottom: 1px solid color-mix(in srgb, var(--nav-ink) 12%, transparent);
  opacity: .85;
  transition: opacity .2s ease;
}
.nav-link:hover { opacity: 1; color: var(--nav-ink); }
/* Underline grows from the left on hover */
.nav-link::after {
  content: ''; display: none; position: absolute; left: .875rem; right: .875rem; bottom: .25rem;
  height: 1.5px; background: var(--c-accent);
  transform: scaleX(0); transform-origin: left;
  transition: transform .3s cubic-bezier(.2,.7,.3,1);
}
.nav-link:hover::after, .nav-link.active::after { transform: scaleX(1); }

/* Shown by default and hidden from 993 up, where the links become a bar. The
   size is stated here, next to the control, so the 44px minimum is written once.

   `position: relative` is load-bearing: .nav-links is `position: fixed` when
   open and comes AFTER this button in the markup, so it painted straight over
   it and the X that closes the menu was invisible. A positioned button paints
   in the same pass and the z-index settles it. */
.nav-toggle {
  display: grid;
  position: relative;
  z-index: 1;
  place-items: center;
  /* px, not rem: a tap target is 44px whatever --ui-scale does to the type. */
  min-width: 44px; min-height: 44px;
  background: none; border: 0; color: var(--nav-ink);
  font-size: 1.5rem; padding: .25rem; cursor: pointer;
  /* The bar is only --nav-h tall; without this the target would push it open. */
  margin-inline-end: -.25rem;
}

/* ── Hero ─────────────────────────────────────────────────────────────────────
   HEIGHT IS DELIBERATELY UNDER ONE SCREEN. A hero at 100svh, plus a nav on top
   of it, meant a visitor arrived at the portal and saw a photograph and
   nothing else — no heading, no section, no indication there was a page below.
   At 62 the section beneath breaks the fold, which is the whole job: the
   banner still leads, but it no longer hides the site.

   These are in svh, so they do NOT follow --ui-scale — a viewport unit does
   not care how large the type is. They are cut by hand instead. */
.pub-hero {
  position: relative;
  min-height: 62svh;
  display: flex; align-items: center;
  overflow: hidden;
  color: var(--c-hero-ink);
}
.pub-hero .hero-media { position: absolute; inset: 0; z-index: 0; }
.pub-hero .hero-media img,
.pub-hero .hero-media video { width: 100%; height: 100%; object-fit: cover; }
.pub-hero .hero-scrim { position: absolute; inset: 0; z-index: 1; background: var(--c-scrim); }
.pub-hero .hero-inner { position: relative; z-index: 2; width: 100%; padding-block: calc(var(--nav-h) + 3rem) 6rem; }
.pub-hero h1 { color: var(--c-hero-ink); }
.pub-hero .eyebrow { color: var(--c-accent); }
.pub-hero .eyebrow::before { background: var(--c-accent); }
.pub-hero .lead { color: color-mix(in srgb, var(--c-hero-ink) 82%, transparent); }

.hero-actions { display: flex; flex-wrap: wrap; gap: 1rem; margin-top: 2.5rem; }

/* Credential strip — status pill + survey date beneath the hero copy */
.hero-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 1.25rem;
  margin-top: 3rem; padding-top: 2rem;
  border-top: 1px solid color-mix(in srgb, var(--c-hero-ink) 22%, transparent);
  font-size: .9375rem;
  color: color-mix(in srgb, var(--c-hero-ink) 80%, transparent);
}
.hero-meta .meta-item { display: inline-flex; align-items: center; gap: .5rem; }


/* Status pill */
.status-pill {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .4rem 1rem; border-radius: var(--r-pill);
  font-size: .8125rem; font-weight: 600; letter-spacing: .02em;
  background: color-mix(in srgb, var(--c-accent) 18%, transparent);
  color: var(--c-accent);
  border: 1px solid color-mix(in srgb, var(--c-accent) 40%, transparent);
}
/* Status is never conveyed by colour alone — each carries its own icon. */
.status-pill::before { font-family: 'bootstrap-icons'; font-size: .8em; }
.status-pill.preparation::before { content: '\F4C3'; }
.status-pill.ongoing::before     { content: '\F4F7'; }
.status-pill.submitted::before   { content: '\F26B'; }
.status-pill.completed::before   { content: '\F26E'; }

/* ── Statistics ───────────────────────────────────────────────────────────── */
.stat { text-align: center; }

/* The three counters on an area card. They are two or three characters wide, so
   stacking them one per row would spend a whole screen on three numbers — they
   stay side by side at every width and shrink to fit instead. This is why the
   base has to restate cols-3: the generic grid is one column on a phone. */
.area-figures .pub-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.area-figures .stat { padding-inline: .5rem; }
.area-figures .stat-value { font-size: clamp(1.5rem, calc(7vw * var(--ui-scale)), 2.25rem); }
/* "Requirements" is one word and must stay one word: body.pub's break-word
   would otherwise split it as "Requirem / ents" in a 118px column. Small enough
   to fit is the fix; hyphenating a label is not. */
.area-figures .stat-label {
  font-size: .625rem;
  letter-spacing: .04em;
  overflow-wrap: normal;
  hyphens: none;
}
.stat .stat-value {
  font-family: var(--f-display);
  font-weight: var(--fw-display);
  font-size: clamp(2.75rem, calc(5vw * var(--ui-scale)), 4rem);
  line-height: 1;
  color: var(--c-accent);
  display: flex; align-items: baseline; justify-content: center; gap: .05em;
}
.stat .stat-label {
  margin-top: .875rem; font-size: .9375rem; font-weight: 600;
  color: var(--c-ink-strong); letter-spacing: .01em;
}
.stat .stat-note { margin-top: .3rem; font-size: .8125rem; color: var(--c-muted); }
.stat .stat-icon { font-size: 1.5rem; color: var(--c-accent); opacity: .6; margin-bottom: .75rem; display: block; }

/* ── The nine areas — a 3x3 menu ──────────────────────────────────────────────
   THREE ACROSS AT EVERY WIDTH. Nine things that divide by three want to be seen
   as a square, and the shape is the same on a phone as on a desktop — only the
   air inside each tile changes. A menu that reflows from three to two to one is
   a different object at each width and has to be re-learned at each.

   Whitespace is the whole composition here: there is no photograph, no rule and
   no border doing the work, so the tile is defined by the space around its three
   marks. Padding scales with the viewport, the gap stays a hair tighter than the
   padding so the tiles read as a group rather than nine separate things, and the
   icon gets room above it rather than sitting on the text.
   ───────────────────────────────────────────────────────────────────────────── */
.area-menu {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(.5rem, 1.5vw, 1rem);
}

/* Each tile is a BOX, in the card language the rest of the site uses:
   --card-bg on --card-border at --r-md, a soft green-tinted shadow, and a lift
   on hover. It was a tinted plate with a transparent border that only became
   visible on hover — nine shapes a reader had to infer from their spacing. A
   menu of nine targets should look like nine targets standing still.

   --r-md rather than the 24px card standard: a 108px tile at 24px radius is
   most of its own corner. The shadow does the separating instead.

   The top edge takes the area's own colour, which is the one place the kind is
   readable without the icon — a 3px rule the card language already uses, tinted
   by family rather than always green. */
.area-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: .5rem;
  padding: clamp(1rem, 2.5vw, 1.75rem) clamp(.375rem, 1.5vw, 1rem);
  text-align: center;
  border-radius: var(--r-md);
  background: var(--card-bg);
  border: var(--card-border);
  border-top: 3px solid var(--kind);
  box-shadow: var(--sh-card);
  transition: box-shadow .25s ease, transform .25s ease, border-color .25s ease;
}
.area-tile:hover {
  transform: translateY(-3px);
  box-shadow: var(--sh-hover);
  border-color: color-mix(in srgb, var(--kind) 40%, var(--c-border));
  border-top-color: var(--kind);
}
.area-tile:focus-visible { outline: 2.5px solid var(--c-accent); outline-offset: 2px; }

/* A tap leaves :hover stuck on the element until the next tap elsewhere, so the
   lift would read as a rendering fault rather than a response. */
@media (hover: none) {
  .area-tile:hover { transform: none; box-shadow: var(--sh-card); }
}

/* THE ICON, AND ITS COLOUR.

   DESIGN_SYSTEM.md says green dominates, gold accents, and never a third hue.
   This is a deliberate exception and the only one on the site: four hues, one
   per area FAMILY, so the colour carries a grouping rather than decorating nine
   tiles differently for the sake of it. Confined to a 24px mark on a tinted
   plate — no text takes these colours, and nothing else on the page does.

   Every value clears 4.5:1 on white, so the marks stay legible for a reader who
   cannot separate the hues and is relying on shape alone. */
.at-icon {
  display: grid;
  place-items: center;
  width: clamp(2.25rem, 6vw, 3rem);
  height: clamp(2.25rem, 6vw, 3rem);
  border-radius: var(--r-sm);
  color: var(--kind);
  background: color-mix(in srgb, var(--kind) 11%, transparent);
  transition: background .25s ease;
}
.at-icon svg { width: 58%; height: 58%; display: block; }
.area-tile:hover .at-icon { background: color-mix(in srgb, var(--kind) 18%, transparent); }

.area-tile[data-kind="governance"]  { --kind: var(--c-brand); }   /* the seal green */
.area-tile[data-kind="academic"]    { --kind: #245E92; }
.area-tile[data-kind="scholarship"] { --kind: #A8700F; }
.area-tile[data-kind="support"]     { --kind: #6A4C86; }

.at-num {
  font-family: var(--f-body);
  font-size: .625rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--c-muted);
}
.at-name {
  font-family: var(--f-display);
  font-weight: var(--fw-display);
  font-size: .8125rem;
  line-height: 1.25;
  color: var(--c-ink-strong);
  /* "Teaching & Learning" is two words the browser would rather break than
     wrap; a tile is 108px at the narrowest and every character counts. */
  overflow-wrap: normal;
  hyphens: none;
}

@media (min-width: 481px) {
  .at-num  { font-size: .6875rem; }
  .at-name { font-size: .9375rem; }
}
@media (min-width: 769px) {
  .area-tile { gap: .75rem; }
  .at-name { font-size: 1.0625rem; }
}
@media (min-width: 993px) {
  .area-menu { gap: clamp(1rem, 1.6vw, 1.5rem); }
  .at-name { font-size: 1.125rem; }
}


/* ── Timeline (milestones) ────────────────────────────────────────────────── */
.timeline { position: relative; display: grid; gap: 2.5rem; }
.timeline::before {
  content: ''; position: absolute; left: 0; top: .5rem; bottom: .5rem;
  width: 1px; background: var(--c-border);
}
.timeline-item { display: grid; grid-template-columns: 1fr; gap: .5rem; padding-left: 1.75rem; position: relative; }
.timeline-year {
  font-family: var(--f-display); font-weight: var(--fw-display);
  font-size: 1.25rem; color: var(--c-accent); text-align: left; padding-top: .1rem;
}
.timeline-item::before {
  content: ''; position: absolute; left: 0; top: .6rem;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--c-accent); transform: translateX(-4px);
  box-shadow: 0 0 0 4px var(--c-bg);
}

/* ── Quote / president ────────────────────────────────────────────────────── */
.quote-mark {
  font-family: var(--f-display); font-size: 5rem; line-height: 1;
  color: var(--c-accent); opacity: .3; display: block; margin-bottom: -1.5rem;
}
.pull-quote {
  font-family: var(--f-display); font-weight: var(--fw-display);
  font-size: clamp(1.5rem, calc(2.6vw * var(--ui-scale)), 2rem); line-height: 1.4;
  color: var(--c-ink-strong);
}
.signature { margin-top: 2rem; }
.signature .sig-name { font-family: var(--f-display); font-weight: var(--fw-display); font-size: 1.125rem; color: var(--c-ink-strong); }
.signature .sig-title { font-size: .875rem; color: var(--c-muted); }

/* ── Gallery — masonry via CSS columns ────────────────────────────────────── */
.masonry { columns: 1; column-gap: var(--gap); }
.masonry .masonry-item {
  break-inside: avoid; margin-bottom: var(--gap);
  position: relative; overflow: hidden; border-radius: var(--r-md);
  cursor: zoom-in; display: block; width: 100%; padding: 0; border: 0; background: none;
}
.masonry .masonry-item img { width: 100%; transition: transform 1s cubic-bezier(.2,.7,.3,1); }
.masonry .masonry-item:hover img { transform: scale(1.05); }
.masonry .masonry-cap {
  position: absolute; inset-inline: 0; bottom: 0;
  padding: 2.5rem .875rem .75rem;
  background: linear-gradient(to top, rgba(0,0,0,.75), transparent);
  color: #fff; font-size: .8125rem; text-align: left;
  opacity: 0; transition: opacity .3s ease;
}
.masonry .masonry-item:hover .masonry-cap,
.masonry .masonry-item:focus-visible .masonry-cap { opacity: 1; }

/* Uniform grid gallery */
.tile-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(16rem,100%),1fr)); gap: var(--gap); }
.tile-grid .masonry-item { aspect-ratio: 4/3; }
.tile-grid .masonry-item img { height: 100%; object-fit: cover; }

/* ── Modal ────────────────────────────────────────────────────────────────────
   Used for the privacy policy. `hidden` keeps it out of the accessibility tree
   until opened; core.js removes the attribute and adds .open.
   ───────────────────────────────────────────────────────────────────────────── */
.pub-modal {
  position: fixed; inset: 0; z-index: 2100;
  display: grid; place-items: center;
  /* Full-bleed by default: 44rem of panel inside a 360px screen was mostly
     margin. The inset returns at 481. */
  padding: 0;
}
.pub-modal[hidden] { display: none; }

.pub-modal-backdrop {
  position: absolute; inset: 0;
  background: rgba(11, 44, 37, .72);
  backdrop-filter: blur(3px);
  opacity: 0; transition: opacity .25s ease;
}
.pub-modal.open .pub-modal-backdrop { opacity: 1; }

.pub-modal-panel {
  position: relative;
  width: 100%; max-width: none;
  max-height: 100svh;
  display: flex; flex-direction: column;
  background: var(--c-surface);
  border-radius: 0;
  box-shadow: 0 32px 80px rgba(6, 26, 21, .45);
  overflow: hidden;
  opacity: 0; transform: translateY(12px) scale(.99);
  transition: opacity .25s ease, transform .25s cubic-bezier(.2,.7,.3,1);
}
.pub-modal.open .pub-modal-panel { opacity: 1; transform: none; }

.pub-modal-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem;
  padding: clamp(1.25rem, calc(2.5vw * var(--ui-scale)), 1.75rem);
  border-bottom: 1px solid var(--c-border);
  background: var(--c-brand); color: #fff;
}
.pub-modal-head h2 {
  margin: 0; color: #fff;
  font-family: var(--f-display); font-weight: 700;
  font-size: clamp(1.125rem, calc(2vw * var(--ui-scale)), 1.375rem);
}
.pub-modal-meta {
  margin: .3rem 0 0; font-size: .75rem;
  color: rgba(255,255,255,.72); letter-spacing: .04em;
}
.pub-modal-close {
  flex: none; width: 2.75rem; height: 2.75rem;
  display: grid; place-items: center;
  border: 0; border-radius: 50%;
  background: rgba(255,255,255,.14); color: #fff;
  cursor: pointer; font-size: .875rem;
  transition: background .2s ease;
}
.pub-modal-close:hover { background: rgba(255,255,255,.26); }

.pub-modal-body {
  padding: clamp(1.25rem, calc(2.5vw * var(--ui-scale)), 1.75rem);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.pub-modal-intro {
  margin: 0 0 1.5rem; font-size: 1rem; line-height: 1.7;
  color: var(--c-ink-strong); font-weight: 500;
}
.pub-modal-section + .pub-modal-section { margin-top: 1.5rem; }
.pub-modal-section h3 {
  margin: 0 0 .5rem;
  font-family: var(--f-display); font-weight: 700;
  font-size: 1rem; color: var(--c-ink-strong);
}
.pub-modal-section p {
  margin: 0; font-size: .9375rem; line-height: 1.7; color: var(--c-ink);
}

.pub-modal-foot {
  padding: 1rem clamp(1.25rem, calc(2.5vw * var(--ui-scale)), 1.75rem);
  border-top: 1px solid var(--c-border);
  display: flex; justify-content: flex-end;
  background: var(--c-surface-2);
}

body[data-motion="none"] .pub-modal-backdrop,
body[data-motion="none"] .pub-modal-panel { transition: none; }

/* ── Lightbox ─────────────────────────────────────────────────────────────── */
.lightbox {
  position: fixed; inset: 0; z-index: 2000;
  background: rgba(8,10,14,.94);
  display: none; align-items: center; justify-content: center;
  padding: clamp(1rem, calc(5vw * var(--ui-scale)), 4rem);
  opacity: 0; transition: opacity .3s ease;
}
.lightbox.open { display: flex; }
.lightbox.visible { opacity: 1; }
.lightbox img { max-width: 100%; max-height: 78vh; border-radius: var(--r-md); object-fit: contain; }
.lightbox .lb-cap { color: rgba(255,255,255,.75); font-size: .875rem; text-align: center; margin-top: 1rem; }
.lightbox .lb-count { color: rgba(255,255,255,.45); font-size: .8125rem; text-align: center; margin-top: .35rem; }
.lightbox button {
  position: absolute; background: rgba(255,255,255,.1); border: 0; color: #fff;
  width: 2.75rem; height: 2.75rem; border-radius: 50%; cursor: pointer;
  display: grid; place-items: center; font-size: 1.25rem;
  transition: background .2s ease;
}
.lightbox button:hover { background: rgba(255,255,255,.22); }
.lightbox .lb-close { top: .75rem; right: .75rem; }
.lightbox .lb-prev  { left: .5rem;  top: 50%; transform: translateY(-50%); }
.lightbox .lb-next  { right: .5rem; top: 50%; transform: translateY(-50%); }
.lightbox figure { margin: 0; max-width: 100%; }

/* ── Video ────────────────────────────────────────────────────────────────── */
.video-frame {
  position: relative; aspect-ratio: 16/9; overflow: hidden;
  border-radius: var(--r-lg); box-shadow: var(--sh-card);
  background: var(--c-surface-2);
}
.video-frame iframe { width: 100%; height: 100%; border: 0; display: block; }
/* Click-to-load poster — no third-party JS until the visitor asks for it */
.video-frame .video-poster {
  position: absolute; inset: 0; width: 100%; height: 100%;
  border: 0; padding: 0; cursor: pointer; background: none; display: block;
}
.video-frame .video-poster img { width: 100%; height: 100%; object-fit: cover; }
.video-frame .video-poster::after {
  content: ''; position: absolute; inset: 0; background: rgba(0,0,0,.25);
  transition: background .3s ease;
}
.video-frame .video-poster:hover::after { background: rgba(0,0,0,.4); }
.video-frame .play-btn {
  position: absolute; z-index: 2; inset: 50% auto auto 50%;
  transform: translate(-50%,-50%);
  width: 5rem; height: 5rem; border-radius: 50%;
  background: var(--c-accent); color: var(--c-accent-ink);
  display: grid; place-items: center; font-size: 1.75rem;
  pointer-events: none;
  transition: transform .3s cubic-bezier(.2,.7,.3,1);
}
.video-frame .video-poster:hover ~ .play-btn,
.video-frame .video-poster:focus-visible ~ .play-btn { transform: translate(-50%,-50%) scale(1.1); }

/* Thumbnail strip beneath the featured player */
.video-strip {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--gap);
  margin-top: var(--gap);
}
.video-thumb {
  position: relative; display: block; width: 100%; padding: 0;
  border: 1px solid var(--c-border); border-radius: var(--r-md);
  overflow: hidden; background: var(--c-surface-2); cursor: pointer;
  text-align: left;
  transition: border-color .3s ease, transform .3s ease;
}
.video-thumb:hover { transform: translateY(-3px); border-color: var(--c-accent); }
.video-thumb.active { border-color: var(--c-accent); border-width: 2px; }
.video-thumb .vt-media { aspect-ratio: 16/9; overflow: hidden; }
.video-thumb .vt-media img { width: 100%; height: 100%; object-fit: cover; }
.video-thumb .vt-media::after {
  content: ''; position: absolute; inset: 0 0 auto 0; aspect-ratio: 16/9;
  background: rgba(0,0,0,.2); transition: background .3s ease;
}
.video-thumb:hover .vt-media::after { background: rgba(0,0,0,.05); }
.video-thumb .vt-play {
  position: absolute; z-index: 2; top: 50%; left: 50%;
  transform: translate(-50%,-50%);
  width: 2.5rem; height: 2.5rem; border-radius: 50%;
  background: var(--c-accent); color: var(--c-accent-ink);
  display: grid; place-items: center; font-size: .875rem;
}
.video-thumb .vt-body { padding: .875rem 1rem 1rem; }
.video-thumb .vt-title { font-size: .875rem; font-weight: 600; color: var(--c-ink-strong); line-height: 1.35; }
.video-thumb .vt-time { font-size: .75rem; color: var(--c-muted); margin-top: .25rem; display: block; }
.video-thumb.active .vt-title { color: var(--c-accent); }

/* ── Value / partner chips ────────────────────────────────────────────────── */
.chip-row { display: flex; flex-wrap: wrap; gap: .75rem; }
.chip {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .625rem 1.125rem;
  border: 1px solid var(--c-border); border-radius: var(--r-pill);
  font-size: .9375rem; font-weight: 500; color: var(--c-ink-strong);
  background: var(--c-surface);
}
.chip i { color: var(--c-accent); }

.partner-card {
  display: flex; align-items: center; gap: 1rem;
  padding: 1.375rem 1.5rem;
  background: var(--card-bg); border: var(--card-border);
  border-radius: var(--r-md); box-shadow: var(--sh-card);
  transition: transform .3s ease, border-color .3s ease;
  height: 100%;
}
.partner-card:hover { transform: translateY(-4px); border-color: var(--c-accent); }
.partner-card i { font-size: 1.5rem; color: var(--c-accent); flex: none; }
.partner-name { font-weight: 600; font-size: .9375rem; color: var(--c-ink-strong); line-height: 1.35; }
.partner-short { font-size: .8125rem; color: var(--c-muted); margin-top: .15rem; }

/* ── Call to action band ──────────────────────────────────────────────────── */
.cta-band { position: relative; overflow: hidden; text-align: center; }
.cta-band .cta-media { position: absolute; inset: 0; z-index: 0; }
.cta-band .cta-media img { width: 100%; height: 100%; object-fit: cover; }
.cta-band .cta-scrim { position: absolute; inset: 0; z-index: 1; background: var(--c-scrim); }
.cta-band .pub-container { position: relative; z-index: 2; }
.cta-band h2, .cta-band p { color: var(--c-hero-ink); }
.cta-band p { color: color-mix(in srgb, var(--c-hero-ink) 82%, transparent); margin-inline: auto; }

/* ── Research / extension card meta ───────────────────────────────────────── */
.card-meta { display: grid; gap: .5rem; margin: 1rem 0 1.25rem; font-size: .875rem; }
.card-meta div { display: flex; gap: .5rem; align-items: flex-start; color: var(--c-muted); }
.card-meta i { color: var(--c-accent); flex: none; margin-top: .15rem; }
.card-meta b { color: var(--c-ink-strong); font-weight: 600; }

/* ── Area page ────────────────────────────────────────────────────────────── */
.area-hero {
  position: relative; min-height: 52svh; display: flex; align-items: flex-end;
  overflow: hidden; color: var(--c-hero-ink);
}
.area-hero .hero-media { position: absolute; inset: 0; }
.area-hero .hero-media img { width: 100%; height: 100%; object-fit: cover; }
.area-hero .hero-scrim { position: absolute; inset: 0; background: var(--c-scrim); }
.area-hero .hero-inner { position: relative; z-index: 2; width: 100%; padding-block: calc(var(--nav-h) + 1.5rem) 2.5rem; }
.area-hero h1 { color: var(--c-hero-ink); }
/* The default eyebrow colour is the brand green, which on the hero's own dark
   green scrim is barely a shade apart — around 1.6:1. Gold is what the landing
   hero already uses over the same scrim. */
.area-hero .eyebrow { color: var(--c-gold); }
.area-hero .eyebrow::before { background: var(--c-gold); }
.area-hero .no-media { position: absolute; inset: 0; background: var(--c-brand); }

.pub-crumb {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  font-size: .875rem; margin-bottom: 1.25rem;
  color: color-mix(in srgb, var(--c-hero-ink) 70%, transparent);
}
.pub-crumb a { color: color-mix(in srgb, var(--c-hero-ink) 85%, transparent); }
.pub-crumb a:hover { color: var(--c-hero-ink); text-decoration: underline; }

/* Sticky in-page navigation */
.subnav {
  position: sticky; top: var(--nav-h-scrolled); z-index: 900;
  background: var(--c-surface);
  border-bottom: 1px solid var(--c-border);
  overflow-x: auto; -webkit-overflow-scrolling: touch;
}
.subnav-inner { display: flex; gap: .25rem; max-width: var(--container); margin-inline: auto; padding-inline: clamp(1.25rem,calc(4vw * var(--ui-scale)),2.5rem); }
.subnav a {
  padding: 1.125rem .875rem; font-size: .9375rem; font-weight: 500;
  color: var(--c-muted); white-space: nowrap;
  border-bottom: 2px solid transparent; transition: color .2s ease, border-color .2s ease;
}
.subnav a:hover, .subnav a.active { color: var(--c-accent); border-bottom-color: var(--c-accent); }

/* Parameters */
.param-card {
  display: flex; gap: 1.25rem; align-items: flex-start;
  padding: var(--card-pad);
  background: var(--card-bg); border: var(--card-border);
  border-radius: var(--r-md); box-shadow: var(--sh-card);
  transition: transform .3s ease, box-shadow .3s ease;
  height: 100%;
}
.param-card:hover { transform: translateY(-4px); box-shadow: var(--sh-hover); }
.param-letter {
  flex: none; width: 3rem; height: 3rem; border-radius: var(--r-sm);
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--c-accent) 12%, transparent);
  color: var(--c-accent);
  font-family: var(--f-display); font-weight: var(--fw-display); font-size: 1.25rem;
}
.param-name { font-weight: 600; color: var(--c-ink-strong); font-size: 1.0625rem; line-height: 1.4; }
.param-meta { font-size: .8125rem; color: var(--c-muted); margin-top: .35rem; }

/* Author-published component blocks */
.comp-block { padding-block: calc(var(--section-pad) * .55); }
.comp-block + .comp-block { border-top: 1px solid var(--c-border); }

.doc-item {
  display: flex; align-items: center; gap: 1rem;
  padding: 1.25rem var(--card-pad);
  background: var(--card-bg); border: var(--card-border);
  border-radius: var(--r-md);
  transition: transform .3s ease, box-shadow .3s ease;
}
.doc-item:hover { transform: translateY(-3px); box-shadow: var(--sh-hover); }
.doc-icon { font-size: 1.75rem; color: var(--c-accent); flex: none; }
.doc-name { font-weight: 600; color: var(--c-ink-strong); font-size: 1rem; }

.person-card { text-align: center; }
.person-card img,
.person-card .person-ph {
  width: 8rem; height: 8rem; border-radius: 50%; object-fit: cover;
  margin: 0 auto 1.25rem; display: block;
}
.person-card .person-ph {
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--c-accent) 12%, transparent);
  color: var(--c-accent);
  font-family: var(--f-display); font-size: 2.25rem; font-weight: var(--fw-display);
}
.person-name { font-weight: 700; color: var(--c-ink-strong); font-size: 1.0625rem; }
.person-role { font-size: .875rem; color: var(--c-muted); margin-top: .2rem; }

/* ── Footer ───────────────────────────────────────────────────────────────── */
.pub-footer { background: var(--c-brand); color: color-mix(in srgb, var(--c-brand-ink) 70%, transparent); }
.pub-footer a { color: color-mix(in srgb, var(--c-brand-ink) 70%, transparent); }
.pub-footer a:hover { color: var(--c-accent); }
.footer-grid {
  display: grid;
  /* Three columns now that the areas list is gone: identity, quick links,
     contact. The identity block takes the extra width so the columns do not
     stretch into over-wide, half-empty measures. */
  grid-template-columns: 1fr;
  gap: 1.75rem;
  /* Tightened — the tallest column is now five list items, so the previous
     3-4.5rem block padding left a large void above and below. */
  padding-block: clamp(2.25rem, calc(4vw * var(--ui-scale)), 3rem);
  align-items: start;
}

.footer-title {
  font-size: .75rem; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--c-brand-ink); margin-bottom: .875rem;
}
.footer-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; font-size: .875rem; }
.footer-list i { margin-right: .5rem; }

/* ── The fold ────────────────────────────────────────────────────────────────
   Quick Links, Contact and the social row: in the document at every width, on
   screen from 769 up. Stacked they ran roughly 500px of the footer's 800, and
   Quick Links alone repeated four of the five destinations already in the
   hamburger — a menu underneath a menu.

   `display: none` rather than removed from the template: at 769 the three-column
   footer is the right amount of chrome for the width, and one markup path is
   what keeps them from drifting apart. */
.footer-fold { display: none; }

.footer-brand {
  display: flex; align-items: center; gap: 1rem;
  margin-bottom: 1rem;
  flex-wrap: wrap;
}
.fb-acronym {
  display: block;
  font-family: var(--f-display); font-weight: 600;
  font-size: 1.125rem; color: #fff;
}
.fb-sub {
  display: block;
  font-size: .6875rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--c-gold);
}
.footer-name { max-width: 34ch; }

/* One copyright at a time — the four-word form on a phone, where the full
   sentence wrapped to three lines to say the same thing. */
.fc-long { display: none; }
.footer-legal { display: flex; flex-wrap: wrap; gap: .35rem .75rem; }
/* Separators drawn between, not after: a trailing dot reads as a truncation. */
.footer-legal a + a::before { content: '\00b7'; margin-right: .75rem; color: inherit; }
.footer-bottom {
  border-top: 1px solid color-mix(in srgb, var(--c-brand-ink) 15%, transparent);
  padding-block: 1.125rem;
  display: flex; flex-direction: column; flex-wrap: wrap; gap: 1rem;
  align-items: flex-start; justify-content: space-between;
  font-size: .75rem;
}
.social-row { display: flex; gap: .75rem; }
.social-row a {
  width: 2.5rem; height: 2.5rem; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid color-mix(in srgb, var(--c-brand-ink) 22%, transparent);
  transition: background .25s ease, border-color .25s ease, color .25s ease;
}
.social-row a:hover { background: var(--c-accent); border-color: var(--c-accent); color: var(--c-accent-ink); }

/* ── Empty state ──────────────────────────────────────────────────────────── */
.empty-state { text-align: center; padding-block: clamp(4rem, calc(10vw * var(--ui-scale)), 8rem); }
.empty-state i { font-size: 3.5rem; color: color-mix(in srgb, var(--c-muted) 40%, transparent); display: block; margin-bottom: 1.5rem; }
.empty-state h2 { margin-bottom: .75rem; }
.empty-state p { color: var(--c-muted); max-width: 34rem; margin-inline: auto; }

/* ── Scroll reveal ──────────────────────────────────────────────────────────
   VISIBLE IS THE DEFAULT. Every revealed block used to start at `opacity: 0`
   and wait for a class that only JavaScript applies — with the fallback that
   restores it living inside the very script that might not run, and no
   <noscript> anywhere. A public record that renders blank when a script does
   not arrive is not a public record.

   The hiding is now scoped to `.js`, which the inline script in the <head> of
   public/shared/base.html sets before first paint. No script, no class, no
   hidden content. That head script also carries a timeout that drops the class
   again if core.js never boots, so a 404 or a dropped connection costs a few
   seconds rather than the page.
   ───────────────────────────────────────────────────────────────────────── */
[data-reveal] { opacity: 1; transform: none; }

.js [data-reveal] {
  opacity: 0; transform: translateY(24px);
  transition: opacity .8s cubic-bezier(.2,.7,.3,1), transform .8s cubic-bezier(.2,.7,.3,1);
  will-change: opacity, transform;
}
.js [data-reveal].revealed { opacity: 1; transform: none; }

/* A layout that opts out of motion gets everything visible immediately. */
body[data-motion="none"] [data-reveal],
.js body[data-motion="none"] [data-reveal] { opacity: 1; transform: none; transition: none; }

/* ── Accessibility ────────────────────────────────────────────────────────── */
:where(a, button, [tabindex]):focus-visible {
  outline: 2.5px solid var(--c-accent);
  outline-offset: 3px;
  border-radius: 3px;
}
.skip-link {
  position: absolute; left: 1rem; top: -4rem; z-index: 2100;
  background: var(--c-accent); color: var(--c-accent-ink);
  padding: .875rem 1.5rem; border-radius: var(--r-sm); font-weight: 600;
  transition: top .2s ease;
}
.skip-link:focus { top: 1rem; color: var(--c-accent-ink); }
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
  /* `.js` guards the hiding rule, so the override has to carry it too or it
     loses on specificity and a reader who asked for less motion gets the
     animation anyway. */
  [data-reveal],
  .js [data-reveal] { opacity: 1; transform: none; }
}

/* ════════════════════════════════════════════════════════════════════════════
   RESPONSIVE — MOBILE FIRST, three tiers

     >=  481   phone landscape     buttons and dialogs stop being full-bleed
     >=  769   tablet              two-column grids, desktop type scale
     >=  993   laptop              the nav becomes a bar, splits go side by side
     >= 1201   wide                the widest grids reach their full count

   Everything ABOVE this block is the phone. These tiers only ever ADD width —
   a rule here should read as "now that there is room, do this", never as
   "undo what was written for a desktop".

   It used to be the other way round: four max-width tiers subtracting from a
   desktop base, 38 of them against a single min-width. The layout a phone got
   was therefore whatever was left after the overrides, which is why a phone
   ended up with a 92px nav, four-column grids to un-column, and a dialog it
   had to have its margins taken back off.

   These four widths are the ONLY breakpoints the public site uses. Every
   stylesheet and every inline <style> in a section template picks one of them
   — a fifth value scattered somewhere means two layouts that change at almost,
   but not quite, the same moment, which is impossible to reason about and
   worse to test. They are the old ladder read upward: 481/769/993/1201 are
   the widths one past 480/768/992/1200.
   ═══════════════════════════════════════════════════════════════════════════ */

@media (min-width: 481px) {
  /* A button no longer has to span the screen to be comfortably tappable. */
  .pub-btn { width: auto; padding: 1rem 1.75rem; }
  .pub-btn.lg { padding: 1.15rem 2.25rem; font-size: 1.0625rem; }

  .pub-section { padding-block: var(--section-pad); }
  .pub-section.tight { padding-block: calc(var(--section-pad) * .6); }

  .pub-modal { padding: clamp(1rem, calc(4vw * var(--ui-scale)), 2.5rem); }
  .pub-modal-panel {
    max-width: 44rem;
    max-height: min(84svh, 46rem);
    border-radius: var(--r-lg);
  }

  .lightbox .lb-cap { font-size: .9375rem; }
  .lightbox button { width: 3rem; height: 3rem; }
  .lightbox .lb-close { top: 1.5rem; right: 1.5rem; }
  .lightbox .lb-prev { left: 1.5rem; }
  .lightbox .lb-next { right: 1.5rem; }
  .lightbox .lb-count { font-size: .8125rem; }

  .footer-bottom { flex-direction: row; align-items: center; font-size: .8125rem; }
}

@media (min-width: 769px) {
  /* Room for the full three-column footer. */
  .footer-fold { display: block; }
  .social-row.footer-fold { display: flex; margin-top: 1.75rem; }
  .footer-brand { margin-bottom: 1.5rem; }
  .fc-short { display: none; }
  .fc-long { display: inline; }
  .footer-legal { gap: 1.25rem; }
  .footer-legal a + a::before { content: none; margin-right: 0; }
}

@media (min-width: 769px) {
  body.pub { font-size: 1.125rem; }

  .hero-actions .pub-btn { width: auto; }

  .pub-grid.cols-2,
  .pub-grid.cols-3,
  .pub-grid.cols-4 { grid-template-columns: repeat(2, 1fr); }
  .masonry { columns: 2; }

  /* Room for the counters to stop being cramped — back to the shared .stat
     sizing they share with the landing page. */
  .area-figures .stat { padding-inline: 1rem; }
  .area-figures .stat-value { font-size: clamp(2.75rem, calc(5vw * var(--ui-scale)), 4rem); }
  .area-figures .stat-label { font-size: .9375rem; letter-spacing: .01em; }
}

@media (min-width: 993px) {
  /* THE NAV BECOMES A BAR. Everything here undoes the panel, which is the one
     place a min-width tier legitimately reverses the base: the two are
     genuinely different components sharing one list of links. */
  .nav-links {
    position: static;
    flex-direction: row; align-items: center; gap: .5rem;
    padding: 0;
    background: none;
    backdrop-filter: none; -webkit-backdrop-filter: none;
    transform: none;
    box-shadow: none;
  }
  .nav-links .pub-btn { margin-top: 0; }
  .nav-link { padding: .5rem .875rem; font-size: .9375rem; border-bottom: 0; }
  .nav-link::after { display: block; }
  .nav-toggle { display: none; }

  .pub-split { grid-template-columns: 1fr 1fr; gap: clamp(2rem, calc(5vw * var(--ui-scale)), 5rem); }
  .pub-split.reverse > *:first-child { order: 2; }

  .pub-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
  .video-strip { grid-template-columns: repeat(4, 1fr); }
  .area-hero .hero-inner { padding-block: calc(var(--nav-h) + 2rem) 4rem; }
  .masonry { columns: 3; }

  .footer-grid {
    grid-template-columns: 1.5fr 1fr 1.2fr;
    gap: clamp(1.5rem, calc(3vw * var(--ui-scale)), 3rem);
  }

  .timeline::before { left: 5.5rem; }
  .timeline-item { grid-template-columns: 5.5rem 1fr; gap: 2.5rem; padding-left: 0; }
  .timeline-item::before { left: 5.5rem; }
  .timeline-year { text-align: right; }
}

@media (min-width: 1201px) {
  .pub-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
  .footer-grid { grid-template-columns: 1.7fr 1fr 1.2fr; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   POINTER — what changes when there is no mouse

   Width is a poor proxy for input: a 1024px tablet is touch, and a 700px
   desktop window is not. These rules key off the pointer itself.
   ═══════════════════════════════════════════════════════════════════════════ */

@media (hover: none) {
  /* 44x44 minimums, applied through padding so nothing looks heavier on a
     desktop that happens to be narrow.

     IN PX, DELIBERATELY. These were 2.75rem, which followed --ui-scale — so
     the moment the phone scale moved off 1 they silently became 41px. A tap
     target does not get smaller because the type did; it belongs with the
     hairlines and breakpoints this file already keeps in px on purpose. */
  .pub-btn,
  .nav-link,
  .arrow-link,
  .subnav a,
  .pub-crumb a { min-height: 44px; display: inline-flex; align-items: center; }
  .arrow-link:hover i { transform: none; }
}
