/* ===========================================================================
   Public school website — design system.

   Every selector the Razor views use is defined here; the markup is unchanged.
   Institutional colour comes from the tenant's branding (--brand / --accent are
   set inline per site), so the system is built on neutrals and lets those two
   values carry the identity.
   =========================================================================== */

:root {
  /* Set per site by the layout; these are safe fallbacks. */
  --brand: #0f2d5c;
  --accent: #c9a227;

  --ink: #131b2b;
  --ink-soft: #47536b;
  --muted: #667287;
  --faint: #8b95a7;

  --canvas: #fbfaf8;
  --surface: #ffffff;
  --surface-soft: #f5f4f1;
  --line: rgba(19, 27, 43, .11);
  --line-strong: rgba(19, 27, 43, .18);

  --shadow-xs: 0 1px 2px rgba(16, 24, 40, .05);
  --shadow-sm: 0 2px 8px rgba(16, 24, 40, .06);
  --shadow-md: 0 12px 28px rgba(16, 24, 40, .09);
  --shadow-lg: 0 28px 64px rgba(16, 24, 40, .14);

  --r-xs: .5rem;
  --r-sm: .75rem;
  --r-md: 1rem;
  --r-lg: 1.375rem;
  --r-xl: 1.875rem;

  --gutter: clamp(1.125rem, 4vw, 2.5rem);
  /* Both of these used to hold a floor far above what a phone needs — the section rhythm
     never came below 52px top and bottom, so two headings on a 375px screen were 104px
     apart and a page was mostly air. The floors are now what a phone should actually get
     and the vw term carries the growth, so the same rule serves 360px and 1600px. */
  --section-y: clamp(2.125rem, 5vw + 1rem, 5.5rem);
  --ease: cubic-bezier(.22, .61, .36, 1);

  --font-display: "Cormorant Garamond", Georgia, serif;
  --font-body: "Source Sans 3", "Segoe UI", system-ui, sans-serif;
}

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
html, body { margin: 0; padding: 0; }

body {
  min-height: 100vh;
  color: var(--ink);
  background: var(--canvas);
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }
img, iframe { display: block; max-width: 100%; border: 0; }
::selection { background: color-mix(in srgb, var(--accent) 32%, transparent); }

:where(a, button, input, textarea, select, [tabindex]):focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
  border-radius: 4px;
}

/* The content band. It held at 1200px however wide the screen was, so on a large monitor the
   whole site ran down the middle third with the notice strip and the banner going edge to edge
   either side of it — the header, the menu and every section boxed into a strip while the
   picture above them filled the glass.
 
   It now grows with the window past about 1540px, up to 1520px. Below that nothing moves, so
   laptops are unchanged; and the cap keeps a line of text a readable length on a very wide
   display. Body copy has its own narrower measure regardless. */
.shell {
  width: min(clamp(1200px, 88vw, 1520px), calc(100% - var(--gutter) * 2));
  margin-inline: auto;
}

/* Typography --------------------------------------------------------------- */

h1, h2, h3 {
  margin: 0 0 .6rem;
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -.015em;
  text-wrap: balance;
}
/* The floors matter more than the ceilings. A 2.5rem h1 minimum put a 40px headline on a
   375px screen, which is three or four words a line; the vw term never engaged because it
   was below the floor at every phone width. Each of these now starts where a phone wants
   it and grows on a slope that reaches the old desktop size by about 1440px. */
h1 { font-size: clamp(1.875rem, 3.4vw + 1.05rem, 4.1rem); line-height: 1.06; letter-spacing: -.025em; }
h2 { font-size: clamp(1.375rem, 1.7vw + .98rem, 2.5rem); }
h3 { font-size: clamp(1.05rem, .5vw + .87rem, 1.35rem); font-family: var(--font-body); font-weight: 650; letter-spacing: -.01em; }

p { margin: 0 0 1rem; color: var(--ink-soft); line-height: 1.7; }
p:last-child { margin-bottom: 0; }

.eyebrow {
  /* Inline, not block: a flex box fills its container and then ignores the text-align that
     centres everything around it, which left the eyebrow hard against the left margin of every
     centred section while its own heading sat in the middle. As an inline box it goes wherever
     the surrounding text is aligned, and is unchanged in left-aligned sections. */
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  margin: 0 0 .85rem;
  color: var(--accent);
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  /* An eyebrow is a label, and a label that wraps is no longer one. */
  text-wrap: balance;
}
.eyebrow::before {
  width: 1.6rem;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
  content: "";
}

.lede { max-width: 44rem; color: var(--ink-soft); font-size: clamp(1.05rem, 1.4vw, 1.2rem); line-height: 1.62; }
.muted { color: var(--muted); }
.name { margin: 0; color: var(--brand); font-weight: 700; }

.rich-text { max-width: 46rem; }
.rich-text h2, .rich-text h3 { margin-top: 2rem; }
.rich-text li { margin-bottom: .4rem; }

/* A list in prose belongs to the paragraph above it, so its markers sit just
   inside the text rather than at the browser's default 40px — which on a 339px
   phone column is an eighth of the width, and leaves the bullets lined up with
   nothing on the page. .rich-text had this; every other place prose is shown
   did not, so the same list was indented differently on the About page and on
   the home page. */
:is(.rich-text,
    .section-body,
    .message-block,
    .panel,
    .campus-panels article,
    .prestige-row > *,
    .card-grid article,
    .split-cards article) :is(ul, ol) {
  margin: 0 0 1rem;
  padding-inline-start: 1.35rem;
  color: var(--ink-soft);
}
.rich-text a { color: var(--brand); text-decoration: underline; text-underline-offset: 2px; }

/* Announcement bar --------------------------------------------------------- */

.notice-bar {
  color: #fff;
  background: var(--brand);
  font-size: .875rem;
}
/* A notice that does not scroll is a label and a line of text. Both are blocks, so they were
   stacking — the label on one line and the notice under it — rather than sitting together on
   one. One centred row, and the label keeps its place at the head of it. */
.notice-bar .shell {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: .25rem .9rem;
  padding-block: .6rem;
  text-align: center;
}
.notice-bar span { display: block; font-weight: 600; letter-spacing: .005em; }

/* Header ------------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 30;
  /* Solid, and with no rule under it.
     It used to be 88% opaque over a blur, which let the banner underneath show through as a
     milky ghost with a hairline drawn across the bottom of it — a bar over the top of the
     picture, and the one thing keeping the header and the banner from reading as one piece. */
  background: var(--surface);
}
.site-header .shell {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  min-height: 76px;
  /* The brand, the contact blocks, the button and the menu are four things on one row. When
     they do not fit — a long school name, ten menu items, a laptop rather than a desktop — the
     menu used to run off the side of the page instead of moving, so the last item sat under
     the edge of the window and the whole page scrolled sideways to reach it. It wraps now. */
  flex-wrap: wrap;
}
.site-header nav { flex-wrap: wrap; }

.brand { display: flex; align-items: center; gap: .8rem; min-width: 0; }
.brand img {
  /* Schools send crests of wildly different proportions, so the height is theirs to set from
     the console. The width follows so nothing is squashed.

     The negative margin absorbs whatever the crest gains over the default, so a taller logo
     hangs past the bar instead of pushing it open: a bigger crest was the ask, a fatter header
     that shoves the whole page down was not. */
  width: auto;
  /* Resolved once so the cap below only has to move one value. */
  --logo-h: var(--logo-size, 46px);
  height: var(--logo-h);
  margin-block: calc((var(--logo-h) - 46px) / -2);
  max-width: min(220px, 40vw);
  flex: none;
  object-fit: contain;
}
/* The name is set to truncate rather than wrap, but an ellipsis only appears if the box is
   allowed to be narrower than the text. Without this the name kept its full width and ran
   straight over the phone number beside it. */
.brand > span { min-width: 0; }
.brand strong {
  display: block;
  overflow: hidden;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(1.2rem, 1.9vw, 1.5rem);
  font-weight: 600;
  letter-spacing: -.015em;
  line-height: 1.15;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.brand small {
  display: block;
  overflow: hidden;
  color: var(--faint);
  font-size: .76rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.site-header nav { display: flex; align-items: center; gap: .1rem; justify-content: flex-end; }
.site-header nav a {
  position: relative;
  padding: .55rem .75rem;
  color: var(--ink-soft);
  font-size: .93rem;
  font-weight: 600;
  transition: color .18s var(--ease);
}
.site-header nav a::after {
  position: absolute;
  inset: auto .75rem .3rem;
  height: 2px;
  background: var(--accent);
  border-radius: 2px;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .22s var(--ease);
  content: "";
}
.site-header nav a:hover { color: var(--brand); }
.site-header nav a:hover::after, .site-header nav a.is-active::after { transform: scaleX(1); }
.site-header nav a.is-active { color: var(--brand); }

.nav-toggle {
  display: none;
  padding: .55rem .85rem;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-xs);
  font: inherit;
  font-size: .88rem;
  font-weight: 650;
  cursor: pointer;
}

/* Buttons ------------------------------------------------------------------ */

.btn, .contact-form button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  padding: .8rem 1.4rem;
  /* Worked out from the school's own colour rather than assumed. A school whose accent is
     navy or maroon used to get dark text on a dark button — a solid black bar with the words
     invisible inside it. */
  color: var(--on-accent, #131b2b);
  background: var(--accent);
  border: 1px solid transparent;
  border-radius: var(--r-xs);
  box-shadow: var(--shadow-xs);
  font: inherit;
  font-size: .95rem;
  font-weight: 700;
  letter-spacing: .005em;
  cursor: pointer;
  transition: transform .18s var(--ease), box-shadow .18s var(--ease), filter .18s var(--ease);
}
.btn:hover, .contact-form button:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-md);
  filter: brightness(1.04);
}
.btn:active, .contact-form button:active { transform: none; }

.btn-ghost {
  color: #fff;
  background: transparent;
  border-color: rgba(255, 255, 255, .5);
  box-shadow: none;
}
.btn-ghost:hover { background: rgba(255, 255, 255, .1); box-shadow: none; filter: none; }

.text-link {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  margin-top: 1rem;
  color: var(--brand);
  font-weight: 700;
  font-size: .95rem;
}
.text-link::after { transition: transform .18s var(--ease); content: "→"; }
.text-link:hover::after { transform: translateX(3px); }

.cta-row { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 1.75rem; }

.tag {
  display: inline-flex;
  align-items: center;
  padding: .2rem .65rem;
  color: var(--brand);
  background: color-mix(in srgb, var(--brand) 8%, transparent);
  border-radius: 999px;
  font-size: .69rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
}

/* Hero --------------------------------------------------------------------- */

.hero {
  position: relative;
  display: grid;
  align-items: end;
  /* A banner, not a page. At 78vh a hero took 84% of a 1440x900 laptop screen once the
     header was counted, and nothing below it was visible — the site opened as a picture
     with no evidence there was anything under it. The start of the next section now
     always shows, which is what tells a visitor to scroll. */
  min-height: min(66vh, 640px);
  /* The ceilings are reached around 1370px, which is where a 768-tall laptop sits — so on
     exactly that machine the hero was paying its maximum padding on its shortest screen.
     Lower ceilings; the fluid range below them is unchanged. */
  padding: clamp(3.5rem, 7vw + 1rem, 5.5rem) 0 clamp(2.25rem, 5vw + .75rem, 3.5rem);
  overflow: hidden;
  color: #fff;
  /* One shade, low and behind the copy only.
     There used to be two, each covering the whole frame — a dark wash from the bottom and a
     second from the left — and together they greyed out the entire banner. Since the slideshow
     started working they were painted over the pictures as well, so a school's own banner
     arrived on the page dimmed end to end. The top three fifths are untouched now, and the
     shade only deepens where the heading and the buttons actually sit. */
  background:
    linear-gradient(to top, rgba(7, 16, 33, .72) 0%, rgba(7, 16, 33, .34) 22%, transparent 58%),
    var(--hero) center / cover no-repeat;
}
/* There used to be a 70px band across the bottom of the hero, meant to fade the picture into
   the page. --canvas is not opaque, so the two stops resolved to the same translucent white and
   it painted as a flat grey bar over the bottom of the banner instead of fading anything. The
   banner ends where it ends. */
/* The heading carries its own contrast now that the picture behind it is not dimmed. */
.hero h1 { text-shadow: 0 2px 10px rgba(7, 16, 33, .55), 0 1px 32px rgba(7, 16, 33, .45); }
.hero-copy p, .hero .shell p { text-shadow: 0 1px 8px rgba(7, 16, 33, .6); }
.hero-copy, .hero .shell { position: relative; z-index: 1; max-width: 46rem; }
.hero h1 { color: #fff; }
/* :not(.eyebrow), because the eyebrow in a hero is a <p> too — and this rule outranks
   .eyebrow's own font-size, so every hero label was being set at body size with an
   eyebrow's tracking on it. At 375px that is what pushed a one-line label onto two. */
.hero p:not(.eyebrow) { color: rgba(255, 255, 255, .88); font-size: clamp(.95rem, .6vw + .8rem, 1.2rem); }
.hero p.eyebrow { color: var(--accent); }
.hero .eyebrow { color: var(--accent); }

.hero-campus { min-height: min(64vh, 600px); }
/* Prestige is the formal one and carries a framed panel, so it keeps a little more height
   than the rest — but not a screenful more. */
.hero-prestige { align-items: center; min-height: min(70vh, 680px); }

.prestige-frame {
  max-width: 46rem;
  /* 48px of inset on every side of the panel is generous on a wide screen and is the
     difference between the next section peeking above the fold and not, on a 1366x768
     laptop. The fluid range below the ceiling is unchanged. */
  padding: clamp(1.5rem, 4vw, 2.5rem);
  background: rgba(10, 16, 28, .42);
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: var(--r-lg);
  backdrop-filter: blur(10px);
}

/* Modern variant: light, split hero */
/* Statistics band ---------------------------------------------------------- */

.stats-grid {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  width: min(clamp(1200px, 88vw, 1520px), calc(100% - var(--gutter) * 2));
  margin-inline: auto;
  overflow: hidden;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
}
.stats-grid .stat { padding: 1.6rem 1rem; background: var(--surface); text-align: center; }
.stats-grid .stat strong {
  display: block;
  margin-bottom: .15rem;
  color: var(--brand);
  font-family: var(--font-display);
  font-size: clamp(1.9rem, 3vw, 2.5rem);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.stats-grid .stat span { color: var(--muted); font-size: .85rem; font-weight: 600; letter-spacing: .02em; }

/* Sections ----------------------------------------------------------------- */

/* --- How much room a section gets ------------------------------------------
   Every section used to take a whole rhythm unit above it and another below, so
   two of them put two units between their content no matter what was in them: a
   gallery and a one-line "Director's message" were separated by the same gap,
   and the message section was more air than message.

   Two changes. A section pays half the gap and its neighbour pays the other
   half, so adjacent sections share one unit instead of stacking two. And the
   unit is scaled by what the section actually holds, because that is what the
   gap has to separate — pictures need to stand clear of the text around them, a
   grid of cards needs less, prose less again, and a heading with nothing under
   it needs almost none.

   The two variables stay separate on purpose. --rhythm is the design's own
   measure, which is why Bulletin is tighter than Atrium; --weight is what this
   particular section contains. Setting them separately means a design can be
   airier without flattening the difference between its full sections and its
   empty ones, which is what a single padding value on .section could not do. */

:is(.section,
    .campus-panels, .prestige-row, .split-cards,
    .bulletin-welcome, .atrium-statement, .atrium-pillars, .atrium-wall,
    .stats-grid) {
  --weight: 1;
  padding-block: calc(var(--rhythm, var(--section-y)) * var(--weight) / 2);
}

/* Three of the design blocks are coloured bands, and a band's padding has to sit
   on the element the colour is painted on — the inner shell — or the colour ends
   at the text. Same measure, one level down. */
.bulletin-figures > .shell,
.bulletin-cta > .shell,
.atrium-message > .shell {
  padding-block: calc(var(--rhythm, var(--section-y)) * var(--weight, 1) / 2);
}

/* Each design lays part of its page out in blocks of its own rather than in a
   plain .section, and those carried fixed padding — so a design could look tidy
   through its .section runs and still have a 128px band sitting in the middle of
   them. They are on the same measure now. These weights are written rather than
   detected because, unlike a .section, what each block holds is fixed by the
   design: a wall is always pictures, a figures strip is always figures, a call
   to action is always a line and a button. */
.atrium-wall { --weight: 1.15; }
.atrium-statement,
.bulletin-welcome { --weight: .8; }
.bulletin-cta { --weight: .5; }

/* Prose under a heading: less than a grid needs, more than a label. */
.section:has(> .section-body),
.section:has(> .message-block) { --weight: .8; }

/* Pictures and film. A photograph butted close to a paragraph reads as an
   illustration of that paragraph, so a section built around one is the only
   kind that gets more than the standard measure rather than less. */
.section:has(> .gallery-grid),
.section:has(> .section-image),
.section:has(> .video-grid),
.section:has(> .map),
.section:has(> figure) { --weight: 1.15; }

/* A heading and a button, or a heading and nothing at all: the section is a
   label, and should cost about what a label costs. Last, and most specific, so
   it wins over the rules above — a section with no body is a section with no
   body whatever else it was going to be classed as. */
.section:has(> .section-head:last-child),
.section:has(> .section-head + .section-more:last-child) { --weight: .5; }

/* A section-head with nothing under it is not introducing anything. */
.section > .section-head:last-child { margin-bottom: 0; }

/* --- Where the heading sits --------------------------------------------------
   Three of the designs centre their section headings. Over a grid of cards or a
   wall of pictures that reads as intended: the block underneath is symmetric, so
   a centred heading sits on its axis. Over a paragraph it does not — body copy
   ranges left, and a centred heading above it shares no edge with anything, which
   is the misalignment you see rather than a centred heading.

   So the heading follows what it introduces. Prose takes the heading with it;
   grids, galleries and tables leave it where the design put it. */
.section:has(> .section-body) > .section-head,
.section:has(> .rich-text) > .section-head {
  margin-inline: 0;
  text-align: start;
}
.section:has(> .section-body) > .section-head .eyebrow,
.section:has(> .rich-text) > .section-head .eyebrow { justify-content: flex-start; }

/* A leadership message is the exception the rule above allows for: the block is a portrait
   beside a message, bounded and centred on the page, so it is symmetric and a centred heading
   sits on its axis. The prose inside still ranges left, because prose has to. */
.section:has(> .message-block) > .section-head { margin-inline: auto; }

/* A section that has both — a picture and the prose beside it — keeps the prose
   rule: the text is what the reader is aligning to. */
.section:has(> .section-body) > .section-head p,
.section:has(> .message-block) > .section-head p { margin-inline: 0; }

/* A band with its own background needs its edges to clear the content inside
   it, so it keeps the full measure on both sides rather than half. */
.section.muted {
  padding-block: calc(var(--rhythm, var(--section-y)) * var(--weight));
  background: var(--surface-soft);
  border-block: 1px solid var(--line);
}
.section-head { max-width: 42rem; margin-bottom: clamp(1.15rem, 2.4vw, 2rem); }
.section-image {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 7;
  max-height: 380px;
  object-fit: cover;
  border-radius: var(--r-sm);
}
.section-head p { margin-top: .5rem; }

/* Shared card surface. Borders carry the structure; shadow only on lift. */
.panel,
.split-cards article,
.card-grid article,
.message-list article,
.person-card,
.campus-panels article,
.people-card,
.news-item,
.event-card,
.steps li {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-xs);
}

.split-cards article,
.card-grid article,
.message-list article,
.campus-panels article,
.people-card,
.news-item,
.event-card {
  transition: transform .2s var(--ease), box-shadow .2s var(--ease), border-color .2s var(--ease);
}
.card-grid article:hover,
.people-card:hover,
.news-item:hover,
.event-card:hover,
.campus-panels article:hover,
.panel { padding: clamp(1.4rem, 2.5vw, 2rem); }

.campus-panels,
.prestige-row,
.split-cards,
.card-grid,
.contact-layout {
  display: grid;
  gap: 1.25rem;
}
.campus-panels,
.prestige-row,
.split-cards {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  width: min(clamp(1200px, 88vw, 1520px), calc(100% - var(--gutter) * 2));
  margin-inline: auto;
}

/* Only under the banner. The overlap is what makes the figures read as part of the
   hero; anywhere else in the page it is the band climbing into whatever is above it. */
.hero + .stats-grid { margin-top: -2.75rem; }

/* These blocks self-centre when used at page level (home variants). Inside a
   .shell they must not centre again, or they indent away from the body text. */
.shell .campus-panels,
.shell .prestige-row,
.shell .split-cards,
.shell .cta-band,
.shell .stats-grid {
  width: 100%;
  margin-inline: 0;
}
.campus-panels article,
.prestige-row > *,
.split-cards article,
.card-grid article,
.message-list article {
  padding: clamp(1.25rem, 2vw, 1.75rem);
}
.split-cards .wide { grid-column: 1 / -1; }

/* Headings inside a card are subheads, not section titles. */
.split-cards article h2,
.campus-panels article h2,
.panel h2,
.message-list article h2 {
  font-size: clamp(1.2rem, 1.7vw, 1.45rem);
}

.card-grid { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.card-grid article h3 { margin-bottom: .4rem; }
.card-grid img {
  width: 100%;
  height: 190px;
  margin-bottom: 1rem;
  object-fit: cover;
  border-radius: var(--r-sm);
  background: var(--surface-soft);
}

.prestige-row blockquote {
  margin: 0;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 2.4vw, 1.85rem);
  line-height: 1.35;
}
.prestige-row cite {
  display: block;
  margin-top: 1rem;
  color: var(--muted);
  font-family: var(--font-body);
  font-style: normal;
  font-weight: 650;
}

/* Call-to-action band ------------------------------------------------------ */

.cta-band {
  width: min(clamp(1200px, 88vw, 1520px), calc(100% - var(--gutter) * 2));
  margin: 1rem auto 2.5rem;
  padding: clamp(2rem, 4vw, 3.25rem);
  overflow: hidden;
  color: #fff;
  background:
    radial-gradient(circle at 88% 0%, color-mix(in srgb, var(--accent) 34%, transparent), transparent 46%),
    linear-gradient(135deg, var(--brand), color-mix(in srgb, var(--brand) 72%, #ffffff));
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
}
.cta-band h2 { color: #fff; }
.cta-band p { color: rgba(255, 255, 255, .88); }

/* Inner page header -------------------------------------------------------- */

.page-shell { display: block; min-height: 55vh; }
.page-hero {
  padding: clamp(2.25rem, 4vw, 3.25rem) 0 clamp(1.5rem, 2.5vw, 2.25rem);
  background: linear-gradient(180deg, var(--surface-soft), var(--canvas));
  border-bottom: 1px solid var(--line);
}
/* An inner page is a document, not a landing page: its title sits a step below
   the homepage hero so the two never compete. */
.page-hero h1 { font-size: clamp(2rem, 3.6vw, 3rem); }
.page-hero .lede { margin-top: .5rem; }
/* The hero above already pays its own bottom padding and draws a rule, so a second full
   measure here stacked into a band of empty page between the title and the first thing on it. */
.page-body { padding: clamp(1.25rem, 2.2vw, 1.75rem) 0 var(--section-y); }

/* An inner page runs on margins between its blocks rather than padding inside
   them, so a block that also belongs to the page-level rhythm drops its padding
   here and takes the margin instead — otherwise it pays both and a gallery on
   the About page sits twice as far down as the same gallery on the home page.

   Named rather than "every child": a .panel draws a box and its padding is the
   inside of that box, not page rhythm, so taking it away would close the box up
   on its own text. */
.page-body > :is(.section, .campus-panels, .prestige-row, .split-cards, .stats-grid) {
  padding-block: 0;
}

/* And the margin is chosen by what comes next, on the same reasoning as the home
   page: a paragraph following a paragraph needs a line's worth of separation, a
   block of cards needs to be clearly its own thing, and a picture needs to clear
   the text above it or it reads as an illustration of that sentence. */
.page-body > * + * { margin-top: calc(var(--section-y) * .6); }
.page-body > * + :is(.gallery-block, .split-cards, .card-grid, .contact-layout,
                     .steps, .disclosure-table, .message-list, .panel, .people-block,
                     .event-list, .table-scroll) { margin-top: calc(var(--section-y) * .85); }
.page-body > * + :is(figure, img, .section-image, iframe, video) { margin-top: var(--section-y); }

/* Admission steps ---------------------------------------------------------- */

.steps ol { margin: 0; padding: 0; list-style: none; display: grid; gap: .75rem; }
.steps li {
  display: grid;
  gap: .3rem;
  padding: 1.1rem 1.25rem;
  counter-increment: step;
}
.steps ol { counter-reset: step; }
.steps li strong { color: var(--brand); font-weight: 700; }
.steps li strong::before {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.55rem;
  height: 1.55rem;
  margin-right: .6rem;
  color: var(--brand);
  background: color-mix(in srgb, var(--brand) 9%, transparent);
  border-radius: 50%;
  font-size: .8rem;
  font-variant-numeric: tabular-nums;
  content: counter(step);
}
.steps li span { color: var(--ink-soft); }

/* Gallery ------------------------------------------------------------------ */

.gallery-block { margin-bottom: 2.5rem; }
/* A gallery that lines up, without cutting anybody out of the picture.
   Two wrong answers were tried before this one. Filling a fixed box cropped every photograph —
   a tall one lost its top and bottom, a group photograph lost the people at the edges. Letting
   each photograph keep its own height lined nothing up: the rows ended at six different places
   and it read as a mistake.

   So: every cell the same size, and the photograph fitted inside its cell rather than cropped
   to fill it. The rows line up, nothing is cut off, and the space a photograph does not fill
   is the page's own colour, so there is no box or frame around it — the picture is all you
   see. Whatever an administrator uploads, in whatever shape, it lands square with the rest. */
.gallery-grid {
  display: grid;
  /* Four across on a full-width screen, so the cells stay a sensible shape rather than
     stretching wide and shallow. Fewer as the screen narrows, down to one on a phone. */
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr));
  gap: 1.25rem;
}
@media (min-width: 1024px) { .gallery-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (min-width: 700px) and (max-width: 1023px) { .gallery-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.gallery-grid figure {
  margin: 0;
  overflow: hidden;
  border-radius: var(--r-md);
}
.gallery-grid img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: contain;
  transition: transform .4s var(--ease);
}
.gallery-grid figure:hover img { transform: scale(1.045); }
/* A film has no natural height in a column, so it keeps the shape films come in. */
.gallery-grid iframe,
.gallery-grid video { display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; }
.gallery-grid figcaption {
  padding: .7rem .9rem;
  color: var(--muted);
  font-size: .88rem;
}

/* Mandatory disclosure table ----------------------------------------------- */

.disclosure-table {
  overflow: auto;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-xs);
}
.disclosure-table table { width: 100%; border-collapse: collapse; }
.disclosure-table th, .disclosure-table td {
  padding: .95rem 1.15rem;
  border-bottom: 1px solid var(--line);
  text-align: left;
}
.disclosure-table th {
  color: var(--brand);
  background: var(--surface-soft);
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  white-space: nowrap;
}
.disclosure-table tbody tr:last-child td { border-bottom: 0; }
.disclosure-table tbody tr:hover { background: var(--surface-soft); }
.disclosure-table a { color: var(--brand); font-weight: 650; text-decoration: underline; text-underline-offset: 2px; }

/* Leadership messages ------------------------------------------------------ */

.message-list { display: grid; gap: 1rem; }
.message-list article { display: grid; gap: .35rem; border-left: 3px solid var(--accent); }
.message-list blockquote {
  margin: .5rem 0 0;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(1.2rem, 1.9vw, 1.45rem);
  line-height: 1.45;
}

/* Committee ---------------------------------------------------------------- */

.person-card { padding: 1.35rem; text-align: center; }
.person-card h3 { margin-bottom: .2rem; }
.person-card p { color: var(--muted); font-size: .92rem; }

/* Contact ------------------------------------------------------------------ */

.contact-layout { grid-template-columns: 1.05fr .95fr; align-items: start; }
.contact-meta {
  display: grid;
  margin: 0 0 1.75rem;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--line);
}
.contact-meta li {
  padding: .8rem .1rem;
  color: var(--ink-soft);
  border-bottom: 1px solid var(--line);
  font-size: .95rem;
}
.contact-meta li strong { color: var(--ink); font-weight: 650; }

.contact-form { display: grid; grid-template-columns: 1fr 1fr; gap: .85rem; margin-top: 1.25rem; }
/* One column on a phone. Two columns of inputs plus their padding are wider than the screen,
   so the fields ran off the right-hand edge and the page scrolled sideways to reach them. */
@media (max-width: 700px) { .contact-form { grid-template-columns: 1fr; } }
.contact-form label { display: grid; gap: .35rem; color: var(--ink); font-size: .88rem; font-weight: 650; }
.contact-form .full, .contact-form button { grid-column: 1 / -1; }
.contact-form input, .contact-form textarea, .contact-form select {
  padding: .8rem .95rem;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-xs);
  font: inherit;
  transition: border-color .16s var(--ease), box-shadow .16s var(--ease);
}
.contact-form input:focus, .contact-form textarea:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 14%, transparent);
}
.contact-form textarea { resize: vertical; }

.map {
  width: 100%;
  min-height: 440px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-xs);
}

.notice {
  margin-bottom: 1.25rem;
  padding: .9rem 1.1rem;
  color: #0f6340;
  background: #eaf7f0;
  border: 1px solid #b9e3cd;
  border-radius: var(--r-xs);
  font-size: .93rem;
}
.validation-summary { margin-bottom: .85rem; color: #a3232f; font-size: .9rem; }
.validation-summary ul { margin: 0; padding-left: 1.1rem; }
.validation-summary:empty, .validation-summary-valid { display: none; }

/* News --------------------------------------------------------------------- */

.article-image {
  width: 100%;
  max-height: 420px;
  margin-bottom: 1.75rem;
  object-fit: cover;
  border-radius: var(--r-md);
}

.news-list { display: grid; gap: 1rem; }
.news-item {
  display: grid;
  grid-template-columns: minmax(0, 270px) 1fr;
  gap: 1.5rem;
  align-items: start;
  padding: 1.35rem;
}
.news-item--text { grid-template-columns: 1fr; }
.news-item img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; border-radius: var(--r-sm); }
.news-item.is-featured { border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
.news-item h2 { margin: .4rem 0 .5rem; font-size: clamp(1.15rem, 1.8vw, 1.4rem); }
.news-item h2 a:hover { color: var(--brand); }
.news-item__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6rem;
  margin: 0;
  color: var(--faint);
  font-size: .82rem;
}
.news-item__actions { display: flex; flex-wrap: wrap; gap: 1.1rem; margin-top: .75rem; }
.news-item__actions .text-link { margin-top: 0; }

/* Events ------------------------------------------------------------------- */

.event-list { display: grid; gap: .85rem; margin-bottom: 2.5rem; }
.event-card { display: grid; grid-template-columns: 76px 1fr; gap: 1.25rem; align-items: start; padding: 1.25rem 1.4rem; }
.event-card__date {
  display: grid;
  padding: .65rem 0;
  color: #fff;
  background: var(--brand);
  border-radius: var(--r-sm);
  line-height: 1.15;
  text-align: center;
}
.event-card__date strong { font-size: 1.5rem; font-variant-numeric: tabular-nums; }
.event-card__date span { font-size: .67rem; letter-spacing: .05em; text-transform: uppercase; opacity: .85; }
.event-card h3 { margin: 0 0 .3rem; }
.event-card h3 a:hover { color: var(--brand); }
.event-card__meta { margin: 0 0 .5rem; color: var(--muted); font-size: .88rem; }
.event-list.is-past .event-card { opacity: .68; }
.event-list.is-past .event-card__date { background: var(--faint); }

/* Faculty ------------------------------------------------------------------ */

.people-block { margin-bottom: 2.75rem; }
.people-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 1rem; }
.people-card { padding: 1.5rem 1.25rem; text-align: center; }
.people-card img, .people-card__initial {
  width: 88px;
  height: 88px;
  margin: 0 auto 1rem;
  object-fit: cover;
  border-radius: 50%;
}
.people-card__initial {
  display: grid;
  place-items: center;
  color: var(--brand);
  background: color-mix(in srgb, var(--brand) 8%, transparent);
  font-family: var(--font-display);
  font-size: 1.85rem;
  font-weight: 600;
  letter-spacing: .02em;
}
.people-card h3 { margin: 0 0 .15rem; font-size: 1.05rem; }
.people-card__role { margin: 0 0 .5rem; color: var(--brand); font-size: .88rem; font-weight: 650; }
.people-card__meta { margin: 0 0 .25rem; color: var(--muted); font-size: .82rem; }
.people-card__contact { margin: .75rem 0 0; font-size: .82rem; word-break: break-word; }
.people-card__contact a { color: var(--brand); text-decoration: underline; text-underline-offset: 2px; }

/* Footer ------------------------------------------------------------------- */

.site-footer {
  margin-top: var(--section-y);
  padding: clamp(2.5rem, 5vw, 3.5rem) 0 2.25rem;
  color: rgba(255, 255, 255, .84);
  background:
    radial-gradient(circle at 6% 0%, color-mix(in srgb, var(--accent) 18%, transparent), transparent 38%),
    linear-gradient(158deg, color-mix(in srgb, var(--brand) 88%, #000), var(--brand));
}
.site-footer a { color: #fff; }
.site-footer a:hover { color: var(--accent); }
.footer-grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: 2rem; align-items: start; }
.footer-grid strong {
  display: block;
  margin-bottom: .6rem;
  color: #fff;
  font-family: var(--font-display);
  font-size: 1.3rem;
  font-weight: 600;
}
.footer-grid p { margin-bottom: .4rem; color: rgba(255, 255, 255, .78); font-size: .92rem; line-height: 1.6; }
.social-links { display: flex; flex-wrap: wrap; gap: .9rem; }
.social-links a { font-size: .9rem; font-weight: 600; }

/* Variant accents ---------------------------------------------------------- */

/* Responsive --------------------------------------------------------------- */

@media (max-width: 1000px) {
  .nav-toggle { display: inline-flex; }
  .site-header .shell { flex-wrap: wrap; min-height: 68px; padding-block: .8rem; }
  .site-header nav {
    display: none;
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    padding-bottom: .5rem;
  }
  .site-header nav.is-open { display: flex; }
  .site-header nav a { padding: .7rem .25rem; border-bottom: 1px solid var(--line); }
  .site-header nav a::after { display: none; }

  .campus-panels,
  .prestige-row,
  .split-cards,
  .contact-layout,
  .contact-form,
  .footer-grid {
    grid-template-columns: 1fr;
  }
  .stats-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .hero + .stats-grid { margin-top: -1.5rem; }
}

@media (max-width: 720px) {
  body { font-size: 1rem; }
  .hero { min-height: min(72vh, 560px); padding: 5rem 0 3.5rem; }
  .news-item, .event-card { grid-template-columns: 1fr; }
  .event-card__date { width: 76px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
  .card-grid article:hover, .people-card:hover, .news-item:hover, .event-card:hover { transform: none; }
  /* Reveals are never applied when motion is reduced, but guard anyway so content
     can never be left invisible. */
  .reveal { opacity: 1 !important; transform: none !important; }
}

@media print {
  body { background: #fff; }
}

.programme-list {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  margin: .85rem 0 0;
  padding: 0;
  list-style: none;
}
.programme-list li {
  padding: .2rem .6rem;
  color: var(--ink-soft);
  background: var(--surface-soft);
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: .78rem;
  font-weight: 600;
}

/* ===========================================================================
   Motion, press feedback and scroll affordances.
   Every effect here is additive: with JavaScript disabled nothing is hidden and
   nothing moves — the reveal classes are only ever applied by site.js.
   =========================================================================== */

/* The hidden state must NOT transition: the class is applied by script after the
   element has painted, so a transition here would fade content out and back in. */
.reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: none;
  will-change: opacity, transform;
}
.reveal.is-revealed {
  opacity: 1;
  transform: none;
  transition:
    opacity .55s var(--ease) var(--reveal-delay, 0ms),
    transform .55s var(--ease) var(--reveal-delay, 0ms);
}

/* Images fade in rather than snapping, which hides decode jank on slow connections. */
img { opacity: 1; }
.card-grid img,
.news-item img,
.people-card img,
.gallery-grid img,
.article-image {
  transition: opacity .4s var(--ease);
}
.card-grid img:not(.is-loaded), .news-item img:not(.is-loaded),
.people-card img:not(.is-loaded), .gallery-grid img:not(.is-loaded),
.article-image:not(.is-loaded) { opacity: 0; }

/* Press feedback ----------------------------------------------------------- */

.btn:active, .contact-form button:active, .nav-toggle:active { transform: translateY(1px) scale(.985); }
.nav-toggle { transition: transform .12s var(--ease), border-color .16s var(--ease), background .16s var(--ease); }
.nav-toggle:hover { border-color: var(--brand); }

.card-grid article:active, .people-card:active, .news-item:active, .event-card:active { transform: translateY(0); }

/* Header condenses once the page scrolls, giving content more room ---------- */

.site-header { transition: box-shadow .25s var(--ease), background .25s var(--ease); }
.site-header.is-scrolled {
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
/* The bar keeps its height and the crest keeps its size and its place, whatever the page is
   doing. Shrinking both on scroll moved the crest and the name a few pixels down and left the
   moment a visitor touched the wheel, and a school's crest is the one thing on the page that
   should sit still. Scrolling changes the bar's weight — its ground and its shadow — and
   nothing that has a position. */

/* Back to top -------------------------------------------------------------- */

.to-top {
  position: fixed;
  right: clamp(1rem, 3vw, 2rem);
  bottom: clamp(1rem, 3vw, 2rem);
  z-index: 40;
  display: grid;
  width: 46px;
  height: 46px;
  place-items: center;
  color: #fff;
  background: var(--brand);
  border: 0;
  border-radius: 50%;
  box-shadow: var(--shadow-md);
  opacity: 0;
  transform: translateY(12px) scale(.9);
  pointer-events: none;
  cursor: pointer;
  transition: opacity .25s var(--ease), transform .25s var(--ease), background .2s var(--ease);
}
.to-top.is-visible { opacity: 1; transform: none; pointer-events: auto; }
.to-top:hover { background: color-mix(in srgb, var(--brand) 86%, #000); }
.to-top:active { transform: scale(.94); }
.to-top svg { width: 20px; height: 20px; }

/* Mobile navigation drawer -------------------------------------------------- */

body.nav-locked { overflow: hidden; }

@media (max-width: 1000px) {
  .site-header nav {
    gap: 0;
    max-height: min(70vh, 26rem);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .site-header nav.is-open { animation: nav-in .22s var(--ease); }
  .site-header nav a.is-active { color: var(--brand); background: var(--surface-soft); }
}

@keyframes nav-in {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: none; }
}

/* Additional breakpoints ---------------------------------------------------- */

@media (max-width: 1280px) {
  .site-header nav a { padding-inline: .55rem; font-size: .89rem; }
}

@media (max-width: 880px) {
  .stats-grid { border-radius: var(--r-md); }
  .people-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
  .gallery-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: .75rem; }
  .cta-band { padding: 1.75rem; }
}

@media (max-width: 560px) {
  .stats-grid { grid-template-columns: 1fr 1fr; }
  .stats-grid .stat { padding: 1.15rem .75rem; }
  .people-grid { grid-template-columns: 1fr 1fr; }
  .people-card { padding: 1.1rem .85rem; }
  .people-card img, .people-card__initial { width: 68px; height: 68px; }
  .disclosure-table th, .disclosure-table td { padding: .75rem .8rem; font-size: .88rem; }
  .to-top { width: 44px; height: 44px; }
}

/* Touch devices get no hover lift — it sticks after tapping ------------------ */

@media (hover: none) {
  .card-grid article:hover, .people-card:hover, .news-item:hover,
.event-card:hover,
.campus-panels article:hover {
    transform: none;
    box-shadow: var(--shadow-xs);
  }
  .gallery-grid figure:hover img { transform: none; }
}

/* Hero slideshow -----------------------------------------------------------
   The school's own pictures, filling the hero. The scrim is repeated over the
   images so headings stay legible whatever photograph is showing. */
.hero-slides { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
.hero-slides__item {
  position: absolute;
  inset: 0;
  background-position: center;
  background-size: cover;
  opacity: 0;
  transform: scale(1.04);
  transition: opacity .9s ease, transform 7s ease;
}
.hero-slides__item.is-active { opacity: 1; transform: scale(1); }
/* The same shade as the hero's own, because the slides cover the hero's background and would
   otherwise leave the copy standing on bare photograph. Confined to the bottom for the same
   reason: the picture is the point of the section. */
.hero-slides::after {
  position: absolute;
  inset: 0;
  content: "";
  background: linear-gradient(to top, rgba(7, 16, 33, .72) 0%, rgba(7, 16, 33, .34) 22%, transparent 58%);
}
/* Direct children only: with a space this also matched the arrows and dots inside the
   slideshow, turning them relative and dropping them into the top-left corner. */
.hero > *:not(.hero-slides) { position: relative; z-index: 2; }
.hero-slides__nav {
  position: absolute;
  top: 50%;
  z-index: 3;
  display: grid;
  width: 46px;
  height: 46px;
  margin-top: -23px;
  padding: 0;
  color: #fff;
  background: rgba(9, 18, 36, .42);
  border: 1px solid rgba(255, 255, 255, .35);
  border-radius: 50%;
  cursor: pointer;
  place-items: center;
  transition: background .18s ease, transform .18s ease;
}
.hero-slides__nav:hover { background: rgba(9, 18, 36, .72); transform: scale(1.06); }
.hero-slides__nav svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2; }
.hero-slides__nav--prev { left: clamp(10px, 3vw, 28px); }
.hero-slides__nav--next { right: clamp(10px, 3vw, 28px); }
/* The hit area, not the bar. Inset horizontally by half the 8px gap so neighbouring
   targets meet without overlapping, and 20px top and bottom for a 44px-tall target. */
.hero-slides__dots button::before {
  position: absolute;
  inset: -20px -4px;
  content: "";
}
.hero-slides__dots {
  position: absolute;
  bottom: 22px;
  left: 50%;
  z-index: 3;
  display: flex;
  gap: 8px;
  transform: translateX(-50%);
}
.hero-slides__dots button {
  position: relative;
  width: 26px;
  height: 4px;
  padding: 0;
  background: rgba(255, 255, 255, .45);
  border: 0;
  border-radius: 3px;
  cursor: pointer;
  transition: background .2s ease, width .2s ease;
}
.hero-slides__dots button.is-active { width: 40px; background: #fff; }

@media (prefers-reduced-motion: reduce) {
  .hero-slides__item { transition: opacity .01ms; transform: none; }
}

/* The header settles into a compact bar once the page is scrolled, so the school's name stays
   present without taking a fifth of the screen. Motion is skipped for anyone who asks for less. */
.site-header {
  transition: box-shadow .28s ease, background-color .28s ease;
}
.site-header.is-condensed {
  background: var(--surface);
  box-shadow: 0 10px 30px -22px rgba(9, 18, 36, .7);
}

@media (prefers-reduced-motion: reduce) {
  .site-header { transition: none; }
}

/* ===========================================================================
   Scrolling notice strip, opening popup and per-section reveal styles.
   Everything here is additive: with the settings off, or with JavaScript
   disabled, the page is exactly what it was before.
   =========================================================================== */

/* --- Notice strip that moves ---------------------------------------------- */

.notice-bar--scrolling { overflow: hidden; padding-block: .6rem; }
.notice-marquee {
  display: flex;
  width: max-content;
  animation: notice-scroll var(--notice-speed, 26s) linear infinite;
}
/* One set is the school's notices end to end. The track holds as many identical sets as it
   takes to outrun the screen, and travels exactly one set's width — so the loop closes with no
   gap and no visible repeat, whatever the notice says. */
.notice-marquee__set {
  display: flex;
  gap: 3.5rem;
  padding-right: 3.5rem;
  font-weight: 600;
  letter-spacing: .005em;
  white-space: nowrap;
}
.notice-marquee__set > span { display: block; }
/* Reading stops while the visitor is trying to read it. */
.notice-bar--scrolling:hover .notice-marquee,
.notice-bar--scrolling:focus-within .notice-marquee { animation-play-state: paused; }

@keyframes notice-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(calc(var(--notice-set-width, 50%) * -1)); }
}

/* --- Opening popup -------------------------------------------------------- */

.site-popup {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: clamp(.75rem, 3vw, 2rem);
}
.site-popup[hidden] { display: none; }
.site-popup__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(8, 17, 31, .72);
  backdrop-filter: blur(2px);
}
.site-popup__panel {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  width: min(960px, 100%);
  max-height: 90vh;
  overflow: auto;
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 30px 80px rgba(8, 17, 31, .45);
  animation: popup-in .32s var(--ease, cubic-bezier(.22, .61, .36, 1)) both;
}
@keyframes popup-in {
  from { opacity: 0; transform: translateY(18px) scale(.98); }
  to { opacity: 1; transform: none; }
}
.site-popup__close {
  position: absolute;
  top: .5rem;
  right: .6rem;
  z-index: 2;
  width: 38px;
  height: 38px;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, .92);
  color: #08111f;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(8, 17, 31, .25);
}
.site-popup__close:hover { background: #fff; }
.site-popup__poster { flex: 1 1 320px; min-width: 0; }
.site-popup__poster img { display: block; width: 100%; height: auto; }
.site-popup__form {
  flex: 1 1 300px;
  min-width: 0;
  padding: clamp(1.25rem, 3vw, 2rem);
}
.site-popup__form h2 { margin: 0 0 .25rem; font-size: 1.3rem; }
.site-popup__form h3 { margin: 0 0 1rem; font-size: 1rem; color: var(--brand); }
.site-popup__form label { display: block; margin-bottom: .7rem; }
.site-popup__form label span { display: block; margin-bottom: .25rem; font-size: .82rem; font-weight: 600; }
.site-popup__form input,
.site-popup__form textarea {
  width: 100%;
  padding: .55rem .7rem;
  border: 1px solid #d7dde6;
  border-radius: 8px;
  font: inherit;
}
.site-popup__form button {
  width: 100%;
  padding: .7rem 1rem;
  border: 0;
  border-radius: 8px;
  background: var(--brand);
  color: #fff;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.site-popup__status {
  margin: 0 0 .9rem;
  padding: .6rem .75rem;
  border-radius: 8px;
  background: #eef7ee;
  color: #205c2a;
  font-size: .9rem;
}
/* A poster on its own should not be stretched to a form's width. */

body.popup-open { overflow: hidden; }

/* --- Per-section reveal styles -------------------------------------------- */

[data-animate="none"].reveal,
[data-animate="none"] .reveal { opacity: 1 !important; transform: none !important; }

[data-animate="fade"].reveal { transform: none; }
[data-animate="zoom"].reveal { transform: scale(.965); }
[data-animate="slide-left"].reveal { transform: translateX(-28px); }
[data-animate="slide-right"].reveal { transform: translateX(28px); }
[data-animate="fade-down"].reveal { transform: translateY(-18px); }

@media (prefers-reduced-motion: reduce) {
  /* Standing still, one set is enough and the copies would only read as stutter. */
  .notice-marquee { animation: none; width: 100%; justify-content: center; }
  .notice-marquee__set[aria-hidden="true"] { display: none; }
  .notice-marquee__set { padding-right: 0; }
  .site-popup__panel { animation: none; }
}

/* ===========================================================================
   Lightbox, reading progress and the extra entrance styles.
   =========================================================================== */

.is-zoomable { cursor: zoom-in; }
.is-zoomable:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 210;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: clamp(.25rem, 2vw, 1rem);
  padding: clamp(.75rem, 4vw, 2.5rem);
  background: rgba(6, 12, 22, .93);
  animation: lightbox-in .22s ease both;
}
.lightbox[hidden] { display: none; }
@keyframes lightbox-in { from { opacity: 0; } to { opacity: 1; } }

.lightbox__stage {
  display: grid;
  place-items: center;
  gap: .75rem;
  margin: 0;
  min-width: 0;
  max-height: 100%;
}
.lightbox__stage img {
  max-width: 100%;
  max-height: 78vh;
  border-radius: 8px;
  box-shadow: 0 24px 70px rgba(0, 0, 0, .6);
  animation: lightbox-zoom .28s var(--ease, cubic-bezier(.22, .61, .36, 1)) both;
}
@keyframes lightbox-zoom {
  from { opacity: 0; transform: scale(.94); }
  to { opacity: 1; transform: none; }
}
.lightbox__stage figcaption {
  max-width: 68ch;
  color: rgba(255, 255, 255, .88);
  font-size: .95rem;
  text-align: center;
}
.lightbox__stage figcaption[hidden] { display: none; }

.lightbox__close,
.lightbox__nav {
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, .12);
  color: #fff;
  cursor: pointer;
  transition: background .18s ease, transform .18s ease;
}
.lightbox__close:hover,
.lightbox__nav:hover { background: rgba(255, 255, 255, .26); transform: scale(1.06); }
.lightbox__close:focus-visible,
.lightbox__nav:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

.lightbox__close {
  position: absolute;
  top: clamp(.5rem, 2vw, 1.25rem);
  right: clamp(.5rem, 2vw, 1.25rem);
  width: 44px;
  height: 44px;
  font-size: 1.8rem;
  line-height: 1;
}
.lightbox__nav {
  width: clamp(38px, 6vw, 54px);
  height: clamp(38px, 6vw, 54px);
  font-size: 2rem;
  line-height: 1;
}
.lightbox--single .lightbox__nav { visibility: hidden; }

/* --- Reading progress ------------------------------------------------------ */

.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 220;
  width: 100%;
  height: 3px;
  background: linear-gradient(90deg, var(--brand), var(--accent));
  transform: scaleX(0);
  transform-origin: 0 50%;
  pointer-events: none;
}

/* --- Two more ways for a section to arrive --------------------------------- */

[data-animate="blur"].reveal { transform: none; filter: blur(9px); }
[data-animate="blur"].reveal.is-revealed { filter: none; transition: opacity .6s var(--ease), filter .6s var(--ease); }

[data-animate="rise"].reveal { transform: translateY(40px) scale(.97); }

@media (prefers-reduced-motion: reduce) {
  .lightbox, .lightbox__stage img { animation: none; }
  .scroll-progress { display: none; }
}

@media print {
  .lightbox, .scroll-progress, .site-popup { display: none !important; }
}

/* ===========================================================================
   Section backdrops — a photograph, a drawn pattern, or both.
   The layers below are inert until the home page emits a rule naming a section;
   nothing here applies to a section that was never given a backdrop.
   =========================================================================== */

[data-section] { position: relative; }

/* Behind the section's own content, never over it. */
[data-section]::before,
[data-section]::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-position: center;
  background-repeat: no-repeat;
  opacity: 0;
}
[data-section]::before { background-size: cover; }
[data-section]::after { background-size: 22px 22px; color: var(--ink); }
/* Lifts a section's own content above the background layer drawn in ::before. The hero's
   slideshow is not content — it is the background — and it is positioned to fill the hero, so
   turning it relative collapsed it to nothing and no template with banner pictures ever showed
   them. It keeps its own positioning. */
[data-section] > *:not(.hero-slides) { position: relative; z-index: 1; }

/* --- Popup posters that take turns ---------------------------------------- */

.site-popup__poster { position: relative; }
.site-popup__slide {
  margin: 0;
  opacity: 0;
  transition: opacity .45s var(--ease, ease);
}
.site-popup__slide:not(:first-child) { position: absolute; inset: 0; }
.site-popup__slide.is-active { opacity: 1; }
.site-popup__slide img { display: block; width: 100%; height: 100%; object-fit: cover; }

.site-popup__dots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: .7rem;
  display: flex;
  justify-content: center;
  gap: .4rem;
}
.site-popup__dots button {
  width: 8px;
  height: 8px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, .5);
  cursor: pointer;
  transition: background .2s ease, transform .2s ease;
}
.site-popup__dots button.is-active { background: #fff; transform: scale(1.35); }
.site-popup__dots button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  .site-popup__slide { transition: none; }
}

/* --- Moving backdrops ------------------------------------------------------
   Drawn with gradients, so they cost no request and no image. The section's
   own text always sits above them, and a visitor who asked for less motion —
   or a school that switched animations off — gets the still version. */

@keyframes backdrop-drift {
  from { background-position: 0 0; }
  to { background-position: 260px 260px; }
}
@keyframes backdrop-rise {
  from { background-position: 0 0, 0 0, 0 0; }
  to { background-position: 0 -540px, 0 -700px, 0 -440px; }
}
@keyframes backdrop-sweep {
  0%, 100% { background-position: 200% 0; }
  50% { background-position: -100% 0; }
}
@keyframes backdrop-twinkle {
  0%, 100% { opacity: .05; }
  50% { opacity: .13; }
}

body[data-scroll-animations="off"] [data-section]::after { animation: none !important; }

@media (prefers-reduced-motion: reduce) {
  [data-section]::after { animation: none !important; }
}

/* ===========================================================================
   Button and card treatments a school picks in the console.
   Each is a class on <body>, so a design's own look is what shows until one
   is chosen — nothing here applies by default.
   =========================================================================== */

/* --- Button shape --------------------------------------------------------- */

.btn-shape-rounded .btn, .btn-shape-rounded .contact-form button,
.btn-shape-rounded .site-popup__form button { border-radius: 10px; }
.btn-shape-pill .btn, .btn-shape-pill .contact-form button,
.btn-shape-pill .site-popup__form button { border-radius: 999px; }
.btn-shape-square .btn, .btn-shape-square .contact-form button,
.btn-shape-square .site-popup__form button { border-radius: 0; }

/* --- Button style --------------------------------------------------------- */

.btn-style-solid .btn { color: #fff; background: var(--brand); }

.btn-style-outline .btn {
  color: var(--brand);
  background: transparent;
  border-color: currentColor;
  box-shadow: none;
}

.btn-style-soft .btn {
  color: var(--brand);
  background: color-mix(in srgb, var(--brand) 14%, transparent);
  border-color: transparent;
  box-shadow: none;
}

.btn-style-gradient .btn {
  color: #fff;
  background: linear-gradient(120deg, var(--brand), var(--accent));
  border-color: transparent;
}

/* --- Button hover --------------------------------------------------------- */

/* Each replaces the default lift rather than stacking on it, or two effects fight. */
.btn-hover-lift .btn:hover { transform: translateY(-3px); filter: none; }

.btn-hover-fill .btn {
  position: relative;
  overflow: hidden;
  z-index: 0;
}
.btn-hover-fill .btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--brand);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .32s var(--ease);
}
.btn-hover-fill .btn:hover { color: #fff; transform: none; filter: none; }
.btn-hover-fill .btn:hover::before { transform: scaleX(1); }

.btn-hover-glow .btn:hover {
  transform: none;
  filter: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 45%, transparent),
              0 10px 26px color-mix(in srgb, var(--accent) 35%, transparent);
}

/* A sweep of light crossing the button, rather than the button moving. */
.btn-hover-slide .btn { position: relative; overflow: hidden; }
.btn-hover-slide .btn::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -60%;
  width: 45%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .55), transparent);
  transform: skewX(-18deg);
  transition: left .5s var(--ease);
}
.btn-hover-slide .btn:hover { transform: none; filter: none; }
.btn-hover-slide .btn:hover::after { left: 120%; }

/* --- Card and tile hover -------------------------------------------------- */

.card-hover-lift :is(.people-card, .news-item, .event-card, .card-grid > article, .gallery-grid figure):hover {
  transform: translateY(-6px);
  box-shadow: var(--shadow-md);
}
.card-hover-zoom :is(.people-card, .news-item, .event-card, .card-grid > article, .gallery-grid figure):hover {
  transform: scale(1.025);
}
.card-hover-glow :is(.people-card, .news-item, .event-card, .card-grid > article, .gallery-grid figure):hover {
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent),
              0 14px 34px color-mix(in srgb, var(--brand) 22%, transparent);
}
.card-hover-tilt :is(.people-card, .news-item, .event-card, .card-grid > article, .gallery-grid figure) {
  transform-style: preserve-3d;
}
.card-hover-tilt :is(.people-card, .news-item, .event-card, .card-grid > article, .gallery-grid figure):hover {
  transform: perspective(700px) rotateX(3deg) rotateY(-3deg) translateY(-4px);
}

/* A pointer that cannot hover has nothing to show, and a visitor who asked for
   less motion should not be moved at. */
@media (hover: none), (prefers-reduced-motion: reduce) {
  .btn-hover-lift .btn:hover,
  .btn-hover-fill .btn:hover,
  .btn-hover-glow .btn:hover,
  .btn-hover-slide .btn:hover { transform: none; box-shadow: var(--shadow-xs); }
  .btn-hover-fill .btn:hover::before { transform: scaleX(0); }
  .btn-hover-slide .btn:hover::after { left: -60%; }
  .card-hover-lift :is(.people-card, .news-item, .event-card, .card-grid > article, .gallery-grid figure):hover,
  .card-hover-zoom :is(.people-card, .news-item, .event-card, .card-grid > article, .gallery-grid figure):hover,
  .card-hover-tilt :is(.people-card, .news-item, .event-card, .card-grid > article, .gallery-grid figure):hover {
    transform: none;
  }
}

/* --- Notice strip treatments ---------------------------------------------- */

.notice-bar--gradient { background: linear-gradient(100deg, var(--brand), var(--accent)); }
.notice-bar--dark { background: #10192b; }
.notice-bar--outline {
  color: var(--brand);
  background: transparent;
  border-block: 1px solid currentColor;
}

/* --- Buttons take the school's own colour --------------------------------- */

/* The ink follows whichever colour actually ends up behind the words. */
.btn-style-solid .btn,
.btn-style-solid .contact-form button {
  background: var(--btn-color, var(--brand));
  color: var(--on-btn, var(--on-brand, #fff));
}
.btn-style-outline .btn { color: var(--btn-color, var(--brand)); }
.btn-style-soft .btn {
  color: var(--btn-color, var(--brand));
  background: color-mix(in srgb, var(--btn-color, var(--brand)) 14%, transparent);
}
.btn-style-gradient .btn {
  background: linear-gradient(120deg, var(--btn-color, var(--brand)), var(--accent));
}
.btn-hover-fill .btn::before { background: var(--btn-color, var(--brand)); }

/* A colour chosen for the buttons applies even when no style was picked. */
body:not([class*="btn-style-"]) .btn { background: var(--btn-color, var(--accent)); }

/* A section's own hover beats the site-wide one: it is on the section, so it is nearer. */
[data-section].card-hover-lift :is(.people-card, .news-item, .event-card, .card-grid > article, .gallery-grid figure):hover {
  transform: translateY(-6px);
}
[data-section].card-hover-zoom :is(.people-card, .news-item, .event-card, .card-grid > article, .gallery-grid figure):hover {
  transform: scale(1.025);
}
[data-section].card-hover-tilt :is(.people-card, .news-item, .event-card, .card-grid > article, .gallery-grid figure):hover {
  transform: perspective(700px) rotateX(3deg) rotateY(-3deg) translateY(-4px);
}

/* One copy crossing the strip: it begins off the right edge, so there is a gap behind it. */
.notice-marquee--single { padding-left: 100%; }
.notice-marquee--single .notice-marquee__set { padding-right: 0; }

/* ===========================================================================
   Hover colour, and the treatments for pictures and links.
   Everything below reads --hover-color, which falls back to the accent when
   the school has not chosen one.
   =========================================================================== */

.btn-hover-glow .btn:hover {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--hover-color, var(--accent)) 45%, transparent),
              0 10px 26px color-mix(in srgb, var(--hover-color, var(--accent)) 35%, transparent);
}
.card-hover-glow :is(.people-card, .news-item, .event-card, .card-grid > article, .gallery-grid figure):hover {
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--hover-color, var(--accent)) 55%, transparent),
              0 14px 34px color-mix(in srgb, var(--brand) 22%, transparent);
}

/* --- Pictures ------------------------------------------------------------- */

/* The wrapper has to clip, or a zooming picture spills over the card's corner. */
.img-hover-zoom :is(.card-grid > article, .news-item, .people-card, .event-card, .gallery-grid figure) {
  overflow: hidden;
}
.img-hover-zoom :is(.card-grid > article, .news-item, .people-card, .event-card, .gallery-grid figure) img {
  transition: transform .45s var(--ease);
}
.img-hover-zoom :is(.card-grid > article, .news-item, .people-card, .event-card, .gallery-grid figure):hover img {
  transform: scale(1.06);
}

.img-hover-lift :is(.card-grid > article, .news-item, .people-card, .event-card, .gallery-grid figure) img {
  transition: transform .35s var(--ease), box-shadow .35s var(--ease);
}
.img-hover-lift :is(.card-grid > article, .news-item, .people-card, .event-card, .gallery-grid figure):hover img {
  transform: translateY(-5px);
  box-shadow: 0 12px 28px rgba(8, 17, 31, .22);
}

.img-hover-tint :is(.card-grid > article, .news-item, .people-card, .event-card, .gallery-grid figure) img {
  transition: filter .35s var(--ease);
}
.img-hover-tint :is(.card-grid > article, .news-item, .people-card, .event-card, .gallery-grid figure):hover img {
  filter: saturate(1.25) contrast(1.05);
}

/* --- Links in body copy --------------------------------------------------- */

/* Never the navigation or a button: those already say what they are, and a second
   treatment on them reads as a mistake. */
.link-hover-underline :is(.section-body, .page-body, .prose) a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}
.link-hover-color :is(.section-body, .page-body, .prose) a {
  transition: color .2s var(--ease);
}
.link-hover-color :is(.section-body, .page-body, .prose) a:hover {
  color: var(--hover-color, var(--accent));
}

@media (hover: none), (prefers-reduced-motion: reduce) {
  .img-hover-zoom :is(.card-grid > article, .news-item, .people-card, .event-card, .gallery-grid figure):hover img,
  .img-hover-lift :is(.card-grid > article, .news-item, .people-card, .event-card, .gallery-grid figure):hover img {
    transform: none;
  }
}

/* Arrows on the poster, so a visitor can move through them rather than only wait. */
.site-popup__arrow {
  position: absolute;
  top: 50%;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  transform: translateY(-50%);
  border: 0;
  border-radius: 50%;
  background: rgba(8, 17, 31, .45);
  color: #fff;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  transition: background .18s ease;
}
.site-popup__arrow:hover { background: rgba(8, 17, 31, .7); }
.site-popup__arrow:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.site-popup__arrow--prev { left: .5rem; }
.site-popup__arrow--next { right: .5rem; }

/* ===========================================================================
   Design identities.

   Until now every variant shared one typeface pairing, one card shape and one
   spacing rhythm, so the templates were the same page with the blocks in a
   different order — which is exactly how they read. Type comes from the
   layout, which loads only the families the chosen design needs; the rest is
   here. Each block changes shape, density and colour weighting, not just hue.
   =========================================================================== */

/* --- Campus: soft, warm, generous ----------------------------------------- */

.variant-campus {
  --r-xs: 1rem;
  --r-sm: 1.5rem;
  --r-md: 2rem;
  --surface: #fffdf9;
}
.variant-campus h1,
.variant-campus h2 { letter-spacing: -.01em; }
.variant-campus .btn { border-radius: 999px; padding-inline: 1.8rem; }
.variant-campus :is(.card-grid > article, .news-item, .people-card, .event-card) {
  border: 0;
  box-shadow: 0 10px 30px rgba(60, 40, 20, .09);
}
.variant-campus img { border-radius: 1rem; }

/* --- Prestige: formal, spaced, gold --------------------------------------- */

.variant-prestige {
  --r-xs: 0;
  --r-sm: 0;
  --ink: #1a1712;
}
.variant-prestige h1,
.variant-prestige h2 { font-weight: 500; letter-spacing: .01em; }
.variant-prestige .eyebrow {
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--accent);
}
.variant-prestige .section-head h2::after {
  content: "";
  display: block;
  width: 54px;
  height: 2px;
  margin: .9rem auto 0;
  background: var(--accent);
}
.variant-prestige .section-head { text-align: center; }
.variant-prestige .btn { border-radius: 0; letter-spacing: .1em; text-transform: uppercase; }

/* --- Bulletin: dense, utilitarian, notice-led ----------------------------- */

.variant-bulletin {
  --r-xs: 3px;
  --r-sm: 3px;
  --r-md: 3px;
  --shell: 1240px;
  font-size: 15px;
}
.variant-bulletin h1 { letter-spacing: .01em; text-transform: uppercase; }
.variant-bulletin h2 { font-size: clamp(1.3rem, 2.2vw, 1.7rem); text-transform: uppercase; letter-spacing: .02em; }
.variant-bulletin { --rhythm: clamp(1.5rem, 3vw + .6rem, 3.25rem); }
.variant-bulletin .btn { border-radius: 3px; text-transform: uppercase; letter-spacing: .05em; }

.bulletin-pillars > .shell {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1px;
  background: var(--line);
  border-block: 1px solid var(--line);
}
.bulletin-pillars article { padding: 1.2rem 1.1rem; background: var(--surface); }
.bulletin-pillars h3 { margin: 0 0 .3rem; font-size: .98rem; color: var(--brand); }
.bulletin-pillars p { margin: 0; color: var(--muted); font-size: .88rem; }

.bulletin-welcome {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3rem);
}
@media (max-width: 860px) { .bulletin-welcome { grid-template-columns: 1fr; } }

.bulletin-news { border: 1px solid var(--line); border-radius: 3px; padding: 1.1rem 1.2rem; }
.bulletin-news h3 {
  margin: 0 0 .8rem;
  padding-bottom: .6rem;
  border-bottom: 2px solid var(--brand);
  font-size: .95rem;
  text-transform: uppercase;
  letter-spacing: .06em;
}
.bulletin-news ul { margin: 0; padding: 0; list-style: none; }
.bulletin-news li { padding: .7rem 0; border-bottom: 1px dashed var(--line); }
.bulletin-news li:last-child { border-bottom: 0; }
.bulletin-news li a { font-weight: 600; }
.bulletin-news li p { margin: .2rem 0 .25rem; color: var(--muted); font-size: .85rem; }
.bulletin-news time { color: var(--accent); font-size: .78rem; font-weight: 600; }

.bulletin-figures { background: var(--brand); color: #fff; }
.bulletin-figures > .shell {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  text-align: center;
}
.bulletin-figures strong { display: block; font-family: var(--font-display); font-size: 1.9rem; }
.bulletin-figures span { font-size: .78rem; letter-spacing: .1em; text-transform: uppercase; opacity: .85; }

.bulletin-cta { background: var(--sunken, #f1f4f8); }
.bulletin-cta > .shell {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.bulletin-cta h2 { margin: 0 0 .25rem; }
.bulletin-cta p { margin: 0; color: var(--muted); }

/* --- Atrium: spacious, editorial ------------------------------------------ */

.variant-atrium {
  --r-xs: 0;
  --r-sm: 0;
}
.variant-atrium h1 { font-size: clamp(2.6rem, 6vw, 5rem); font-weight: 300; letter-spacing: -.02em; }
.variant-atrium h2 { font-size: clamp(1.8rem, 3.4vw, 2.9rem); font-weight: 300; }
.variant-atrium .eyebrow { letter-spacing: .28em; font-weight: 400; }
.variant-atrium .btn { border-radius: 0; font-weight: 400; letter-spacing: .08em; }

.atrium-statement { text-align: center; }
.atrium-statement .section-body { max-width: 62ch; margin-inline: auto; font-size: 1.08rem; }

.atrium-message > .shell {
  display: grid;
  grid-template-columns: minmax(0, 380px) minmax(0, 1fr);
  align-items: center;
  gap: clamp(2rem, 6vw, 5rem);
}
@media (max-width: 820px) {
  /* Stacked, the portrait's column is the full width of the page — and a 4:5 ratio then
     makes it 707x883, a whole tablet screen of one face. It keeps a portrait's size and
     sits above the quote, which is what the two columns were doing side by side. */
  .atrium-message > .shell { grid-template-columns: 1fr; }
  .atrium-message figure { max-width: 320px; }
}
.atrium-message figure { margin: 0; }
/* Atrium gives the principal a full-height portrait on purpose — it is the design's
   one big photograph. Cropped rather than left at whatever shape the school uploaded,
   so a tall picture does not make the row twice the height of the quote beside it. */
.atrium-message img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
}
.atrium-message blockquote {
  margin: .5rem 0 1.2rem;
  font-family: var(--font-display);
  font-size: clamp(1.3rem, 2.4vw, 1.85rem);
  font-weight: 300;
  line-height: 1.45;
}
.atrium-message__by strong { display: block; }
.atrium-message__by span { color: var(--muted); font-size: .9rem; }

.atrium-pillars ol { margin: 2rem 0 0; padding: 0; list-style: none; counter-reset: pillar; }
.atrium-pillars li {
  counter-increment: pillar;
  display: grid;
  grid-template-columns: 4rem minmax(0, 1fr);
  gap: 1.5rem;
  padding: clamp(1.5rem, 3vw, 2.25rem) 0;
  border-top: 1px solid var(--line);
}
.atrium-pillars li::before {
  content: counter(pillar, decimal-leading-zero);
  font-family: var(--font-display);
  font-size: 1.6rem;
  color: var(--accent);
}
/* The number is the first grid item and takes the narrow column; without saying so, the
   heading lands beside it and the description falls to the next row's narrow column, where it
   sets one word per line and the section runs for several screens. Both belong in the wide
   column, under one another. */
.atrium-pillars li > * { grid-column: 2; }
.atrium-pillars li::before { grid-column: 1; grid-row: 1; }
.atrium-pillars li { row-gap: .4rem; }

.atrium-pillars h3 { margin: 0 0 .4rem; font-family: var(--font-display); font-size: 1.4rem; font-weight: 500; }
.atrium-pillars p { margin: 0; max-width: 60ch; color: var(--muted); }

.atrium-wall { padding-bottom: 0; }
.atrium-wall__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 2px;
  margin-top: 2rem;
}
.atrium-wall figure { margin: 0; overflow: hidden; }
.atrium-wall img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: contain; }

/* ===========================================================================
   Sections a school actually needs, and no design had.
   Tables stay tables — a timetable read as cards is four screens of scrolling
   and cannot be searched with the browser's own find.
   =========================================================================== */

.table-scroll,
.disclosure-table table,
.custom-html table {
  /* A sideways-scrolling table with no affordance reads as a table whose last column has
     been cut off. The fade on the right edge is what says there is more, and it is painted
     only while there is: scroll to the end and it goes. */
  background:
    linear-gradient(to right, var(--surface) 30%, transparent) left / 28px 100% no-repeat local,
    linear-gradient(to left, var(--surface) 30%, transparent) right / 28px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, rgba(19, 27, 43, .16), transparent) left / 12px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgba(19, 27, 43, .16), transparent) right / 12px 100% no-repeat scroll;
}
.table-scroll { overflow-x: auto; }
.table-scroll table {
  width: 100%;
  border-collapse: collapse;
  font-size: .94rem;
}
.table-scroll :is(th, td) {
  padding: .7rem .85rem;
  text-align: left;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.table-scroll thead th {
  background: color-mix(in srgb, var(--brand) 8%, transparent);
  color: var(--brand);
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.table-scroll tbody th { font-weight: 600; }
.table-scroll tbody tr:last-child :is(th, td) { border-bottom: 0; }
.table-note { margin-top: .8rem; color: var(--muted); font-size: .86rem; }

/* Timings and staff read as columns of the same kind of thing, so the digits line up. */
.timings-table td,
.staff-table td:first-child { font-variant-numeric: tabular-nums; }
.staff-table td:first-child { width: 3.5rem; color: var(--muted); }

.downloads-table td:last-child { text-align: right; white-space: nowrap; }
.btn-sm { padding: .4rem .9rem; font-size: .82rem; }

/* --- Crest ---------------------------------------------------------------- */

/* The motto over the top, the crest beside its own reading underneath. The grid is on the
   inner element, so a design can place this block but cannot take it apart. */
.crest-section__inner { display: grid; gap: clamp(1.25rem, 3vw, 2rem); }

.crest-section__head { max-width: 56rem; margin-inline: auto; text-align: center; }
.crest-section__head .eyebrow { justify-content: center; }
.crest-section .motto {
  margin: .4rem 0 0;
  color: var(--muted);
  font-size: .84rem;
  font-style: normal;
  font-weight: 600;
  letter-spacing: .1em;
  line-height: 1.6;
  text-transform: uppercase;
}

.crest-layout {
  display: grid;
  grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
  align-items: center;
  gap: clamp(1.5rem, 5vw, 4rem);
}
.crest-layout:not(:has(> .crest-section__mark)) { grid-template-columns: minmax(0, 1fr); }
/* A crest with no readings beside it has nothing to sit beside. Left in the first column of a
   two-column grid it hung against the left edge with the rest of the section empty, which is
   the crest looking dropped rather than placed. Alone, it is the thing on show: centred, and
   large enough to be looked at. */
.crest-layout:not(:has(> .crest-meanings)) {
  grid-template-columns: minmax(0, 320px);
  justify-content: center;
}
.crest-layout:not(:has(> .crest-meanings)) .crest-section__mark { max-width: 320px; }
@media (max-width: 760px) {
  .crest-layout { grid-template-columns: 1fr; justify-items: center; text-align: start; }
}

.crest-section__mark { margin: 0; width: 100%; max-width: 260px; }
.crest-section__mark img { width: 100%; aspect-ratio: 1; object-fit: contain; }

/* One symbol per line, its badge beside it — read in order, which is the point of a crest. */
.crest-meanings { margin: 0; padding: 0; list-style: none; display: grid; gap: 1.1rem; }
.crest-meanings li { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 1rem; align-items: start; }
.crest-badge {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  background: var(--brand);
  border-radius: 50%;
  overflow: hidden;
}
.crest-badge img { width: 72%; height: 72%; object-fit: contain; }
/* No picture supplied: a plain mark, so the row still reads as one of a set. */
.crest-badge:empty::after,
.crest-badge:not(:has(img))::after {
  width: 12px;
  height: 12px;
  background: var(--surface);
  border-radius: 50%;
  content: "";
}
.crest-meanings__text { display: grid; gap: .15rem; }
.crest-meanings__text strong { color: var(--ink); }
.crest-meanings__text span { color: var(--muted); }

/* --- Alumni --------------------------------------------------------------- */

.alumni-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 1.5rem;
}
.alumni-grid article { text-align: center; }
.alumni-grid img {
  width: 128px;
  height: 128px;
  margin: 0 auto .8rem;
  display: block;
  border-radius: 50%;
  object-fit: cover;
}
.alumni-grid h3 { margin: 0 0 .2rem; font-size: 1.02rem; }
.alumni-role { margin: 0; color: var(--muted); font-size: .88rem; }
.alumni-batch { margin: .25rem 0 0; color: var(--accent); font-size: .8rem; font-weight: 600; }

/* --- Facilities ----------------------------------------------------------- */

.facility-list { display: grid; gap: 1.25rem; }
.facility-list article {
  display: grid;
  grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
  gap: 1.5rem;
  align-items: center;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid var(--line);
}
.facility-list article:last-child { border-bottom: 0; padding-bottom: 0; }
.facility-list article:only-child,
.facility-list article:not(:has(img)) { grid-template-columns: 1fr; }
@media (max-width: 700px) { .facility-list article { grid-template-columns: 1fr; } }
.facility-list img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--r-sm); }
.facility-list h3 { margin: 0 0 .35rem; }
.facility-list p { margin: 0; color: var(--muted); }

/* --- Founder and history -------------------------------------------------- */

.founder-section {
  display: grid;
  grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
  gap: clamp(1.5rem, 5vw, 4rem);
  align-items: start;
}
@media (max-width: 820px) { .founder-section { grid-template-columns: 1fr; } }
/* --- Leadership portraits ---------------------------------------------------
   The principal's, chairman's, director's and manager's messages each carry a
   photograph, and nothing here sized it: the only rule that reached it was the
   blanket "img { max-width: 100% }", so the portrait took the full width of the
   section and stood as tall as its own aspect ratio made it — a head-and-
   shoulders shot the size of the message it was meant to illustrate.

   A portrait sits beside its words at the size a portrait wants: square, capped,
   and cropped to the face rather than letterboxed. Below the point where two
   columns stop fitting it goes above the text and keeps the same size. */

.message-block {
  display: grid;
  grid-template-columns: minmax(0, 210px) minmax(0, 1fr);
  gap: clamp(1.25rem, 3vw, 2.25rem);
  align-items: start;
  /* Bounded and centred. Run across the full width of a wide screen it read as a stray
     paragraph rather than as the head of the school addressing you. */
  max-width: 64rem;
  margin-inline: auto;
}
.message-block:not(:has(> .message-portrait)) { grid-template-columns: minmax(0, 1fr); }
/* Two classes deep on purpose. "​.page-body img" and "​.section img" are both more
   specific than a lone class, so a bare .message-portrait cap loses the cascade — which
   is what left the four portraits on the Messages page at the full width of the column
   and, with a square aspect ratio, as tall as the page was wide. */
.message-block .message-portrait,
.message-list .message-portrait {
  width: 100%;
  max-width: 210px;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: var(--r-md);
}
@media (max-width: 560px) {
  .message-block { grid-template-columns: minmax(0, 120px) minmax(0, 1fr); gap: 1rem; }
}

/* The founder's portrait is the one place a larger picture is the point, but a
   320px column at a portrait aspect is still 400px of face. Cropped and capped. */
/* The cap goes on the wrapper, not the picture. "​.section img { max-width: 100% }" is a
   more specific selector than anything that names this image, so capping the image itself
   lost the cascade — which only showed on Bulletin, where the section is its own two-column
   grid and the founder's column is the full width of the page: a 1128px portrait. */
.founder-section__who { max-width: 260px; }
.founder-section__who img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--r-sm);
  margin-bottom: 1rem;
}
.founder-name { margin: 0; font-weight: 700; }
.founder-dates { margin: .1rem 0 1rem; color: var(--muted); font-size: .9rem; }

.founder-timeline { margin: 0; padding: 0; list-style: none; }
.founder-timeline li {
  display: grid;
  grid-template-columns: minmax(0, 5.5rem) minmax(0, 1fr);
  gap: 1.25rem;
  padding: 0 0 1.4rem 0;
  position: relative;
}
.founder-timeline li::before {
  content: "";
  position: absolute;
  left: 5.5rem;
  top: .55rem;
  bottom: -.4rem;
  width: 1px;
  background: var(--line);
}
.founder-timeline li:last-child { padding-bottom: 0; }
.founder-timeline li:last-child::before { display: none; }
.founder-timeline time {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.founder-timeline h3 { margin: 0 0 .2rem; font-size: 1.02rem; }
.founder-timeline p { margin: 0; color: var(--muted); }

/* ===========================================================================
   Where the designs actually diverge.

   Changing the hero and a handful of owned sections was not enough: roughly
   twenty-three of every page's twenty-five sections fall through to one
   renderer, and the header, top strip and footer are shared outright. So the
   page had the same shape in every design and only the typeface moved.

   The markup is consistent, so the shape can be rearranged from here — where
   the heading sits, how a card is drawn, how the navigation stacks, how much
   air a section gets. That is what makes one design unlike another.
   =========================================================================== */

/* --- Bulletin: a directory. Heading left, content right, rules everywhere. -- */

.variant-bulletin .site-header .shell {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  grid-template-areas: "brand brand" "nav nav";
  gap: 0;
  min-height: 0;
  padding-block: .6rem 0;
}
@media (min-width: 900px) {
  .variant-bulletin .site-header .shell { grid-template-areas: "brand nav"; align-items: center; padding-block: .5rem; }
}
.variant-bulletin .brand { grid-area: brand; }
.variant-bulletin .site-header nav {
  grid-area: nav;
  justify-content: flex-start;
  gap: 0;
  margin-top: .5rem;
  border-top: 1px solid var(--line);
  overflow-x: auto;
}
@media (min-width: 900px) {
  .variant-bulletin .site-header nav { justify-content: flex-end; margin-top: 0; border-top: 0; }
}
.variant-bulletin .site-header nav a {
  padding: .7rem .85rem;
  border-right: 1px solid var(--line);
  font-size: .82rem;
  text-transform: uppercase;
  letter-spacing: .03em;
  white-space: nowrap;
}
.variant-bulletin .site-header nav a::after { display: none; }
.variant-bulletin .site-header nav a.is-active { background: var(--brand); color: #fff; }

/* Heading in a left column, the section's content beside it. */
.variant-bulletin .section {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
  gap: 1.5rem 2.5rem;
  align-items: start;
  border-top: 2px solid var(--line);
}
.variant-bulletin .section > * { grid-column: 2; }

/* Bulletin only. The left column holds each section's heading; for a message it holds the
   person as well — the portrait directly under the heading, and the letter down the right.

   The block steps aside so the portrait and the letter become cells of the section's own grid.
   The letter spans both rows, and the second row is the flexible one: a spanning item that
   crosses a flexible track does not size the fixed tracks, so the first row stays exactly the
   heading's height and the portrait starts right under it rather than halfway down a row
   stretched by a long letter. */
@media (min-width: 861px) {
  .variant-bulletin .message-section { grid-template-rows: auto 1fr; }
  .variant-bulletin .message-section > .message-block { display: contents; }
  .variant-bulletin .message-block > .message-portrait {
    grid-column: 1;
    grid-row: 2;
    align-self: start;
    max-width: none;
  }
  .variant-bulletin .message-block > div { grid-column: 2; grid-row: 1 / span 2; }
  /* The heading starts at the rail's left edge like every other Bulletin heading, rather than
     being centred in it, and hands the gap under it to the grid, so the portrait sits one row
     gap below the heading instead of a heading margin and a row gap below it. */
  .variant-bulletin .message-section > .section-head { margin: 0; }
}
/* Row 1, not "rows 1 to 20". Spanning twenty rows does not reserve twenty rows of
   nothing — it creates them, and every one of the nineteen gaps between them is real:
   19 × 24px of empty space inside every section, which is why a principal's message
   with a portrait and four lines of text stood 711px tall. The heading sits at the top
   of its column, which is where it was being drawn anyway. */
.variant-bulletin .section-head {
  grid-column: 1;
  grid-row: 1;
  align-self: start;
  text-align: left;
}

/* The same fault, and the same fix. */
@media (max-width: 860px) {
  .variant-bulletin .section { grid-template-columns: 1fr; }
  .variant-bulletin .section > *,
  .variant-bulletin .section-head { grid-column: 1; }
  .variant-bulletin .section-head { grid-row: auto; }
}
.variant-bulletin .section-head h2 { font-size: 1.2rem; }
.variant-bulletin .section-head p { font-size: .86rem; }

.variant-bulletin .card-grid { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 1px; background: var(--line); }
.variant-bulletin .card-grid > article {
  padding: .95rem 1rem;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  background: var(--surface);
}
.variant-bulletin .card-grid h3 { font-size: .95rem; }
.variant-bulletin .card-grid p { font-size: .85rem; }

.variant-bulletin .site-footer .footer-grid { gap: 1.25rem 2rem; }
.variant-bulletin .site-footer strong { text-transform: uppercase; letter-spacing: .06em; font-size: .82rem; }

/* --- Atrium: air. Centred heads, hairline cards, one column of reading. ---- */

.variant-atrium .site-header .shell {
  display: grid;
  justify-items: center;
  gap: .6rem;
  padding-block: 1.4rem;
  min-height: 0;
}
.variant-atrium .brand { flex-direction: column; text-align: center; gap: .4rem; }
.variant-atrium .site-header nav { justify-content: center; flex-wrap: wrap; gap: .2rem 1.4rem; }
.variant-atrium .site-header nav a { font-size: .82rem; letter-spacing: .12em; text-transform: uppercase; }

.variant-atrium { --rhythm: clamp(2.75rem, 6vw + 1.25rem, 7rem); }
.variant-atrium .section-head { text-align: center; margin-bottom: 2.5rem; }
.variant-atrium .section-head p { max-width: 58ch; margin-inline: auto; }
.variant-atrium .section-body { max-width: 64ch; margin-inline: auto; }

.variant-atrium .card-grid { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 3rem 3.5rem; }
.variant-atrium .card-grid > article {
  padding: 0;
  border: 0;
  border-top: 1px solid var(--line);
  padding-top: 1.4rem;
  border-radius: 0;
  box-shadow: none;
  background: transparent;
}
.variant-atrium .card-grid h3 { font-family: var(--font-display); font-size: 1.35rem; font-weight: 500; }

.variant-atrium .site-footer { text-align: center; }
.variant-atrium .site-footer .footer-grid { justify-items: center; gap: 2rem; }

/* --- Modern: left-ruled, two up, nothing centred ---------------------------- */

/* --- Campus: rounded, three up, centred heads ------------------------------- */

.variant-campus .site-header nav a { border-radius: 999px; padding-inline: .9rem; }
.variant-campus .site-header nav a.is-active { background: color-mix(in srgb, var(--brand) 12%, transparent); }
.variant-campus .site-header nav a::after { display: none; }
.variant-campus .section-head { text-align: center; }
.variant-campus .section-head p { max-width: 56ch; margin-inline: auto; }
.variant-campus .card-grid { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1.75rem; }

/* --- Academic: ruled columns, dense, nothing rounded ------------------------ */

/* On a phone the two columns become one. Setting the template alone was not enough: every
   child is pinned to column 2, and a child pinned to a column the template does not have makes
   the browser invent one — so the section stayed two columns wide and ran 136px off the side of
   a 390px screen. The children have to come back to column 1 with it. */
@media (max-width: 820px) {
}
/* --- Prestige: centred, spaced, a rule under every heading ------------------ */

.variant-prestige .site-header .shell { justify-content: center; gap: 2rem; }
.variant-prestige .site-header nav { justify-content: center; }
.variant-prestige .site-header nav a { letter-spacing: .14em; text-transform: uppercase; font-size: .76rem; }
.variant-prestige .card-grid { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 2.5rem; }
.variant-prestige .card-grid > article {
  border: 0;
  border-top: 2px solid var(--accent);
  border-radius: 0;
  box-shadow: none;
  background: transparent;
  padding: 1.2rem 0 0;
  text-align: center;
}
.variant-prestige .site-footer { text-align: center; }

/* --- Notice label, and contact in the header ------------------------------- */

/* Written as a descendant so it outranks `.notice-bar span { display: block }`, which is one
   class and one element to this rule's one class. Losing that contest cost the label its
   `display: inline-flex`, so it was not a flex container, its `align-items: center` did
   nothing, and the word sat at the top of a box stretched to the full height of the bar —
   eleven pixels above the notice beside it, which is the label sitting off-centre. */
.notice-bar .notice-bar__tag {
  /* The notice scrolls past underneath. Both are in normal flow and the marquee comes second,
     so without this it paints over the label and the two read as one smeared line. */
  position: relative;
  z-index: 1;
  flex: none;
  align-self: stretch;
  display: inline-flex;
  align-items: center;
  padding: 0 1rem;
  background: var(--accent);
  color: #fff;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
}
.notice-bar--scrolling { display: flex; align-items: stretch; padding-block: 0; }
.notice-bar--scrolling .notice-marquee { align-self: center; padding-block: .6rem; }
@media (prefers-reduced-motion: reduce) {
  /* Standing still the marquee is sized to the whole bar, which with a tag beside it would
     centre the notice against a width the tag has already taken part of. It takes what is
     left instead. */
  .notice-bar--scrolling .notice-marquee { flex: 1 1 auto; width: auto; min-width: 0; }
}

.header-contact { display: none; align-items: center; }
.header-contact a { display: flex; align-items: center; gap: .6rem; color: var(--ink); }
.header-contact .icon { width: 26px; height: 26px; color: var(--accent); }
.header-contact small {
  display: block;
  color: var(--muted);
  font-size: .72rem;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.header-contact strong { display: block; font-size: .92rem; white-space: nowrap; }
/* A phone number broken across two lines stops being a phone number, so the block keeps its
   own width and the row gives way around it rather than through it. */
.header-contact a { flex: none; }

.header-cta { flex: none; }
@media (max-width: 700px) { .header-cta { display: none; } }

/* Bulletin puts the contact blocks and the call to action on the brand row, with the
   navigation on its own bar underneath — the way both reference sites are built. */
@media (min-width: 1080px) {
  .variant-bulletin .site-header .shell {
    grid-template-columns: minmax(0, auto) minmax(0, 1fr) auto auto;
    grid-template-areas: "brand contact contact cta" "nav nav nav nav";
    gap: .75rem 1.5rem;
    align-items: center;
  }
  .variant-bulletin .header-contact { grid-area: contact; justify-self: end; }
  .variant-bulletin .header-cta { grid-area: cta; }
  .variant-bulletin .site-header nav {
    grid-area: nav;
    justify-content: center;
    margin-top: .35rem;
    border-top: 0;
    background: var(--brand);
    border-radius: 999px;
  }
  .variant-bulletin .site-header nav a { color: #fff; border-right: 0; }
  .variant-bulletin .site-header nav a:hover { color: var(--accent); }
  .variant-bulletin .site-header nav a.is-active { background: transparent; color: var(--accent); }
}

/* Atrium's own arrangement: the brand at the left, contact and the call to action at the
   right, and the navigation centred on a rule below — the same three rows as Bulletin, read
   quietly rather than boxed. The two stay apart in weight and colour, not in structure,
   because that is how the sites they follow are actually built. */
@media (min-width: 1080px) {
  .variant-atrium .site-header .shell {
    grid-template-columns: minmax(0, auto) minmax(0, 1fr) auto;
    grid-template-areas: "brand contact cta" "nav nav nav";
    justify-items: stretch;
    align-items: center;
    gap: .9rem 2rem;
  }
  .variant-atrium .brand { grid-area: brand; flex-direction: row; text-align: left; gap: .9rem; }
  .variant-atrium .header-contact { grid-area: contact; justify-self: end; gap: 2.25rem; }
  .variant-atrium .header-cta { grid-area: cta; }
  .variant-atrium .site-header nav {
    grid-area: nav;
    border-top: 1px solid var(--line);
    padding-top: .7rem;
  }
}
/* No panel behind the icons here, and the labels carry the weight instead. */
.variant-atrium .header-contact .icon { width: 22px; height: 22px; color: var(--brand); }
.variant-atrium .header-contact small { letter-spacing: .14em; }
.variant-atrium .header-contact strong { font-weight: 600; }

/* --- The opening poster, shown whole ---------------------------------------
   A school designs an admissions poster as a whole picture: the dates are at
   the bottom and the phone number is in a corner, so cropping it to fill a box
   is how the useful part gets cut off. The frame takes the poster's own shape
   and the picture is fitted inside it, never trimmed.

   640x480 is the size the reference site opens at, and it is what the frame
   falls back to until the first poster reports its own proportions. */

/* The frame's own size is measured against the poster and the window, in script; until that
   runs it is 640x480, the size the reference site opens at. The transition is so a change of
   poster shape reads as the next poster arriving rather than as the popup breaking. */
.site-popup__poster {
  flex: 0 0 auto;
  width: min(640px, 100%);
  max-width: 100%;
  aspect-ratio: 4 / 3;
  background: #eef0f3;
  transition: width .35s var(--ease, ease), height .35s var(--ease, ease);
}
/* Once script has measured it, the box has both dimensions and the fallback shape must not
   fight them. */
.site-popup__poster[style*="height"] { aspect-ratio: auto; }
.site-popup__slide img { object-fit: contain; }
/* A poster alone: the panel is whatever the poster measured, so a tall narrow poster is not
   left sitting against a white margin the width of a landscape one. With an enquiry form
   beside it the panel takes a settled width instead, because a form that changes width as the
   posters change shape is unreadable. */
.site-popup__panel:not(:has(.site-popup__form)) { width: auto; max-width: min(1040px, 100%); }
.site-popup__panel { align-items: stretch; }
.site-popup__panel:has(.site-popup__form) { width: min(1040px, 100%); max-width: 100%; }
.site-popup__form { flex: 1 1 340px; align-self: center; }

@media (max-width: 720px) {
  /* Stacked, the poster keeps its measured shape and the form follows underneath. */
  .site-popup__panel { flex-direction: column; align-items: center; }
}

/* --- Banners that carry their own text -------------------------------------
   A school's banner is often a finished poster: headline, dates and a button
   already drawn into the picture. Laying our heading across it gives the frame
   two headlines, and the scrim that keeps ours readable dims theirs. Here the
   picture is the whole hero — shown entire, nothing over it, nothing cut off. */

body.hero-plain .hero {
  min-height: 0;
  padding: 0;
  color: inherit;
  background: #0b1220;
}
body.hero-plain .hero::after { display: none; }
body.hero-plain .hero-copy,
body.hero-plain .hero > .shell { display: none; }

/* Full width, and as tall as the picture's own proportions make it — measured in script,
   because a max-height on a box with an aspect-ratio is transferred back into a max-width and
   narrows the hero instead of shortening it. 16:9 until that measurement lands. */
body.hero-plain .hero > .hero-slides {
  position: relative;
  inset: auto;
  width: 100%;
  aspect-ratio: 16 / 9;
}
body.hero-plain .hero > .hero-slides[style*="height"] { aspect-ratio: auto; }
body.hero-plain .hero-slides::after { display: none; }
/* Cover, not contain: the box is already the picture's shape, so there is nothing for cover to
   crop and no bare strip left where contain would have stopped short. */
body.hero-plain .hero-slides__item {
  background-size: cover;
  background-repeat: no-repeat;
  transform: none;
  transition: opacity .9s ease;
}
body.hero-plain .hero-slides__item.is-active { transform: none; }

/* --- Centred section heads sit centred -------------------------------------
   The head is capped at 42rem so a heading and its intro do not run the full
   width of a wide screen. In the designs that centre their headings, that cap
   left the box pinned to the left margin with its text centred inside it — so
   the heading read as off to one side of the content beneath it. Centring the
   text means centring the box it is in. */

.variant-atrium .section-head,
.variant-campus .section-head,
.variant-prestige .section-head { margin-inline: auto; }

/* --- Room for the header's four parts ---------------------------------------
   Brand, contact, button and menu need about 1160px between them before the
   name starts being trimmed to fit. Below that the contact blocks step aside —
   the phone and email are still one line up, on the strip — rather than every
   part of the header being squeezed at once. */

@media (max-width: 1239px) {
  .header-contact { display: none; }
}
@media (min-width: 1240px) {
  .header-contact { display: flex; gap: 1.75rem; }
  .variant-atrium .header-contact { gap: 1.75rem; }
}

/* --- Ways to reach the school ----------------------------------------------
   The contact page listed an address, a phone number and an email as three
   unlabelled lines of text. Each row now says what it is and links to the
   thing it names, so a parent on a phone taps the number instead of copying
   it out. */

.contact-meta li {
  display: grid;
  /* The icon column is a fixed width. Sized to its content, it was 18px on the phone and email
     rows and nothing on the address row, which has no icon — so the address, and its label,
     started 18px left of everything under them. */
  grid-template-columns: 18px minmax(6.5rem, auto) minmax(0, 1fr);
  align-items: baseline;
  gap: 0 .75rem;
}
/* Rows with no icon still line their label up with the ones that have one. */
.contact-meta li > .contact-meta__label:first-child { grid-column: 2; }
.contact-meta .icon { width: 18px; height: 18px; color: var(--accent); align-self: center; }
.contact-meta__label {
  color: var(--muted);
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
}
@media (max-width: 560px) {
  .contact-meta li { grid-template-columns: auto minmax(0, 1fr); }
  .contact-meta li > * { grid-column: 2; }
  .contact-meta .icon { grid-column: 1; grid-row: 1 / span 2; }
}

.contact-social { display: grid; gap: .5rem; margin-bottom: 1.75rem; }

/* Icons in the strip and the footer, words nowhere — one set of shapes, so a school that adds
   an account gets it in both places. */
.social-links a { display: inline-flex; }
.social-links .icon { width: 20px; height: 20px; }
.site-footer .social-links { margin-top: .9rem; gap: 1rem; }
.site-footer .social-links .icon { width: 22px; height: 22px; }

/* --- The crest, and the name beside it --------------------------------------
   The crest sits on a disc of the header's own colour and hangs below the bar
   into whatever is under it, so the header and the banner read as one piece
   rather than two stacked bands with a line between them.

   It hangs by a transform, not a margin, so the bar's height is untouched: a
   bigger crest was the ask, a deeper header that pushes the whole page down
   was not. And because nothing in the header resizes on scroll, the crest is
   in the same place on a page halfway read as on one just opened. */

.brand img { position: relative; z-index: 2; }

/* A round crop, a square-cornered crop, or the artwork untouched — the school chooses.
   These used to draw the crest on a white disc with a shadow under it, which reads as a
   frame put around the school's own artwork rather than as a shape choice, and looked
   like a border nobody had asked for. The shape is now the shape, and nothing else. */
.logo-rounded .brand img { border-radius: 50%; }
.logo-square .brand img { border-radius: var(--r-sm, 10px); }
@media (max-width: 700px) {
  .logo-rounded .brand img,
  .logo-square .brand img { border-radius: 0; }
}
@media (max-width: 700px) {
  /* Too little header to hang from, and a crest over the banner on a phone covers the banner. */
  .brand img { transform: none; }
}

/* The name reads as the school's, in its own colour, with whatever follows it — the campus,
   the city, the board — centred under it. Set against the name's right edge it read as having
   slid off the end of the name, on every design. */
.brand strong { color: var(--brand); }
.brand small { text-align: center; }

@media (prefers-reduced-motion: reduce) {
  .brand img { transition: none; }
}

/* The menu rows that sit under the brand have to begin to the right of the crest, or the crest
   hangs straight through the first item. The header measures the crest and publishes its width;
   Atrium's menu is centred, so it is held off on both sides and stays centred. */
@media (min-width: 760px) {
  /* The pill begins to the right of the crest rather than running underneath it: padding moved
     the links but left the coloured shape passing behind the crest, which is what made the two
     look like they had collided. Atrium's menu has no shape behind it and is centred, so it is
     held off on both sides instead. */
  .variant-bulletin .site-header nav { margin-left: var(--crest-space, 0px); }
  .variant-atrium .site-header nav { padding-inline: var(--crest-space, 0px); }
}

/* --- Video ------------------------------------------------------------------
   A poster and a play button. The player itself is built only when somebody
   asks for it, so a page with three films on it is not three third-party pages
   loading before anyone has pressed anything. */

.video-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1.5rem;
  margin-top: 2rem;
}
.video-grid--single { grid-template-columns: minmax(0, min(880px, 100%)); justify-content: center; }

.video-card { margin: 0; }
.video-card video,
.video-card iframe,
.video-card__play {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  border-radius: var(--r-md, 14px);
  background: #0b1220;
  overflow: hidden;
}
.video-card__play { position: relative; padding: 0; cursor: pointer; }
.video-card__play img { width: 100%; height: 100%; object-fit: cover; }
.video-card__icon {
  position: absolute;
  inset: 50% auto auto 50%;
  translate: -50% -50%;
  display: grid;
  place-items: center;
  width: 68px;
  height: 68px;
  background: var(--accent);
  border-radius: 50%;
  box-shadow: 0 12px 30px -10px rgba(8, 17, 31, .6);
  transition: transform .2s var(--ease, ease);
}
.video-card__icon svg { width: 30px; height: 30px; fill: #fff; }
.video-card__play:hover .video-card__icon,
.video-card__play:focus-visible .video-card__icon { transform: scale(1.08); }
.video-card figcaption { margin-top: .75rem; color: var(--ink-soft); font-weight: 600; }

@media (prefers-reduced-motion: reduce) {
  .video-card__icon { transition: none; }
}

/* The contact block on the home page: details on one side, the map on the other. */
.contact-block {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: start;
}
.contact-block .map { width: 100%; min-height: 320px; border: 0; border-radius: var(--r-md, 14px); }
@media (max-width: 820px) { .contact-block { grid-template-columns: 1fr; } }

/* Bulletin puts every section's heading in its left column. The contact heading sits inside a
   wrapper with the details, so the rule never reached it: it stayed over the details in the
   right column, 280px right of every other heading on the page. On Bulletin the wrapper steps
   aside, the heading takes the left column like the rest, and the details and the map follow
   each other down the right. */
@media (min-width: 861px) {
  .variant-bulletin .contact-block > div:first-child { display: contents; }
  .variant-bulletin .contact-block > div:first-child > :not(.section-head),
  .variant-bulletin .contact-block > .map { grid-column: 2; }
}

/* Facts the board requires on the disclosure page, above the document table. */
.disclosure-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2.5rem;
  padding: 1rem 1.25rem;
  list-style: none;
  background: var(--surface-2, #f4f5f7);
  border-radius: var(--r-md, 14px);
}
.disclosure-facts span {
  display: block;
  color: var(--muted);
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
}
.disclosure-table td small { display: block; margin-top: .2rem; font-size: .82rem; }

/* --- The gallery page ------------------------------------------------------
   Its heading and each album's name sit over a grid of pictures that is
   centred on the page, so ranging them left leaves the words hanging off to
   one side of everything they belong to. */

/* The gallery used to be centred by a rule written for the gallery alone, so it disagreed with
   every other page and neither half could be changed. The choice below governs all of them. */
.gallery-block > .section-head { margin-inline: auto; }

/* A table cannot be made narrower than its content, so on a phone it has to scroll inside its
   own box rather than pushing the whole page sideways with it. */
.staff-table, .timings-table, .downloads-table, .disclosure-table table { min-width: 100%; }
.table-scroll,
.staff-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
@media (max-width: 700px) {
  .staff-table, .timings-table, .downloads-table { display: block; overflow-x: auto; max-width: 100%; }
}

/* --- How large an inner page's heading is set -------------------------------
   A school's own choice. Each stays a clamp rather than one fixed size, so a
   heading that fits a desktop still fits a phone: the choice moves the whole
   range, it does not pin the heading to a number. */

.title-small  .page-hero h1 { font-size: clamp(1.5rem,  2.4vw, 2rem); }
.title-medium .page-hero h1 { font-size: clamp(1.75rem, 3vw,   2.5rem); }
.title-large  .page-hero h1 { font-size: clamp(2rem,    3.6vw, 3rem); }
.title-xlarge .page-hero h1 { font-size: clamp(2.4rem,  4.8vw, 4rem); }

/* A page the school has written itself. It gets the width of the band and no styling of its
   own beyond what the page already provides, because anything imposed here would fight the
   markup they wrote. Wide things inside it scroll rather than pushing the page sideways. */
/* A page the school built in its own HTML: the frame is the page, edge to edge, with no band,
   border or padding of the design's around it. Its height is set to its content once loaded;
   until then it holds a screen's worth, so the footer does not flash up under the header. */
.custom-page { margin: 0; padding: 0; }
.custom-page__frame { display: block; width: 100%; height: 100vh; border: 0; }
/* The footer keeps its distance from the design's own last section. The school's page ends on
   a section of its own design, and 88px of blank page between that and the footer is a gap the
   school did not draw. */
body:has(.custom-page) .site-footer { margin-top: 0; }
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.custom-html { max-width: none; }
.custom-html img { max-width: 100%; height: auto; }
.custom-html table { display: block; overflow-x: auto; max-width: 100%; }
.custom-html iframe { max-width: 100%; }

/* --- Which way page headings are ranged ------------------------------------
   One choice for the whole site, so About, Gallery, Messages and the rest
   agree with each other. The eyebrow is a flex row and the intro line has a
   reading measure, so both need telling as well as the text itself. */

.title-align-left   .page-hero, .title-align-left   .gallery-block > .section-head { text-align: left; }
.title-align-center .page-hero, .title-align-center .gallery-block > .section-head { text-align: center; }
.title-align-right  .page-hero, .title-align-right  .gallery-block > .section-head { text-align: right; }

.title-align-center .page-hero .eyebrow { justify-content: center; }
.title-align-right  .page-hero .eyebrow { justify-content: flex-end; }

.title-align-center .page-hero .lede { margin-inline: auto; }
.title-align-right  .page-hero .lede { margin-inline: auto 0; }

/* ===========================================================================
   Small screens.

   Last in the sheet on purpose. The design identities above are written for a
   wide window — a heading column beside its section, a hero with room to
   breathe, tracking measured in whole ems — and several of them hold enough
   specificity that a media query earlier in the file would lose to them. This
   block is the one place to look for what a phone or a tablet actually gets,
   for all four designs, rather than the rules being scattered through each
   design's own section.

   Each rule here answers something that is wrong on a narrow screen, not a
   preference: copy that runs off the side, tracking that turns a one-line
   label into three, a crest sized for a desktop header, or a gap that was
   generous at 1400px and absurd at 375px.
   =========================================================================== */

/* --- Wherever the menu button is shown ------------------------------------- */

/* The menu button appears below 1000px. Bulletin and Atrium lay their header out as
   a grid with named areas, and neither names one for the button — nor for the enquiry
   button, which is itself only hidden below 700px. Both were auto-placed into implicit
   rows of their own, which is why those two headers stood 104px, 108px and 139px tall
   at various widths with a single word on the bottom row. Three cells on the brand's
   row; the enquiry column collapses to nothing at the widths where it is hidden. */
@media (max-width: 1000px) {
  /* One header for all four, rather than a grid for the two designs that named areas
     and the flex default for the other two. The default wrapped: the brand is a flex
     item that takes the width it is offered, so on a 375px screen it took all 339px of
     it and pushed the menu button onto a second row — a 148px header on Prestige and
     128px on Campus with one word on the bottom line. Each design's own header rule is
     listed so this wins over it; Prestige's, for one, centres the row and sets a 2rem
     gap, which is right at 1400px and is most of the reason the button did not fit. */
  .site-header .shell,
  .variant-prestige .site-header .shell,
  .variant-campus .site-header .shell,
  .variant-bulletin .site-header .shell,
  .variant-atrium .site-header .shell {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas: "brand cta toggle" "nav nav nav";
    align-items: center;
    /* Not justify-items: start. A grid item aligned to the start is sized to its own
       content rather than stretched to its track, so the brand grew to the full width of
       an un-wrapped school name and ran out under the enquiry button — "Riverstone
       International School" printed underneath "Request the prospectus". Stretched, it
       is the width of its track and its name wraps inside it. */
    justify-items: stretch;
    gap: 0 .6rem;
  }
  .brand { grid-area: brand; min-width: 0; }
  /* Wrapping beats ellipsis for a name: two lines of "Riverstone International School"
     is a school's name, and "Riverstone International Sch…" is not. */
  .brand strong { white-space: normal; }
  .header-cta { grid-area: cta; }
  .nav-toggle { grid-area: toggle; }
  .site-header nav { grid-area: nav; }

  /* Its nav is a horizontal scroller; let it reach the edges of the screen rather
     than sitting inside the gutter with its last item cut off. */
  .variant-bulletin .site-header nav {
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
  }

  .variant-atrium .site-header .shell { padding-block: .85rem; }
  .variant-atrium .brand { flex-direction: row; text-align: left; }
  .variant-atrium .site-header nav { justify-content: flex-start; }

  /* A crest height set in the console is an instruction for the desktop header. At
     150px — which the console allows — it hangs 52px past a 46px bar at every width,
     and on anything narrower than a laptop that is the crest sitting over the top of
     the banner. Bring it down, and let it sit in the bar rather than over it. */
  .brand img { --logo-h: min(var(--logo-size, 46px), 72px); margin-block: 0; }

  /* A hero taking four fifths of a tablet screen is a page you have to scroll
     past before the site starts. */
  .hero { min-height: min(66vh, 560px); }
  .hero-prestige, .hero-campus { min-height: min(66vh, 560px); }
}

/* --- Tablet and below ------------------------------------------------------ */

@media (max-width: 900px) {
  /* A design that puts the section heading in its own column has nowhere to put
     it once the window is narrower than the two columns together. Prestige and
     Atrium centre their headings, which reads as indecision at this width. */
  .variant-atrium .section-head,
  .variant-campus .section-head,
  .variant-prestige .section-head { margin-inline: 0; text-align: left; }
  .variant-prestige .section-head h2::after { margin-inline: 0; }
  .variant-atrium .section-head .eyebrow,
  .variant-campus .section-head .eyebrow,
  .variant-prestige .section-head .eyebrow { justify-content: flex-start; }
  .variant-campus .section-head p { margin-inline: 0; max-width: none; }
  .variant-atrium .section-head p,
  .variant-atrium .section-body { margin-inline: 0; max-width: none; }

  /* Four figures across a tablet leaves each one too narrow to read. */
  .stats-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* --- Phones ---------------------------------------------------------------- */

@media (max-width: 640px) {
  /* The console sets the crest height for the desktop header. Honoured literally
     on a phone, a 120px crest is a third of the screen and pushes the name and
     the menu button onto separate rows. Cap it, and drop the negative margin
     that only exists to stop a tall crest fattening a wide header. */
  .brand img { --logo-h: min(var(--logo-size, 46px), 52px); margin-block: 0; max-width: 44vw; }
  /* The name wraps rather than truncating: ellipsing a school's own name on its own
     website is worse than a taller bar, and clamping it to two lines — which is what was
     here — simply cut the third line off "Riverstone International School" on every page
     of that site. The header is allowed to be as tall as the name needs. */
  .brand strong { font-size: clamp(.95rem, 3.6vw, 1.2rem); white-space: normal; }
  .brand small { font-size: .72rem; white-space: normal; }
  .site-header .shell { gap: .75rem; min-height: 60px; padding-block: .65rem; }

  /* Tracking is a fraction of the font size, so the designs that set it widest
     are the ones that break first: "HERITAGE · CHARACTER · AMBITION" at .34em
     is half again the width of the screen. */
  .eyebrow { letter-spacing: .1em; gap: .35rem; }
  /* The rule before the label is 1.6rem wide plus its gap — 32px, better than a
     tenth of the screen, spent on a dash that is deciding whether the label wraps. */
  .eyebrow::before { width: .9rem; }
  .variant-prestige .eyebrow,
  .variant-atrium .eyebrow { letter-spacing: .1em; }

  /* The frame insets the hero copy by 28px on top of the page's own 18px gutter,
     which on a 375px screen is a quarter of the width gone before the headline. */
  .prestige-frame { padding: 1.25rem 1.15rem; }

  /* The hero is a banner, not a page. Two thirds of the screen is enough for
     one on a phone, and the picture still reads at that height. */
  .hero { min-height: min(64vh, 440px); padding: clamp(2.5rem, 12vw, 4rem) 0 clamp(1.75rem, 7vw, 2.5rem); }
  .hero-prestige, .hero-campus { min-height: min(64vh, 440px); }

  /* Buttons on their own line each, full width, rather than two half-width ones
     with a word wrapped inside each. */
  .cta-row { flex-direction: column; align-items: stretch; gap: .6rem; }
  .cta-row .btn { width: 100%; justify-content: center; }

  /* A card grid at this width is one column whatever its minmax says; saying so
     stops a 210px minimum from forcing two columns of four-character words.
     People and statistics keep their pairs — a face and a figure both read small. */
  .card-grid,
  .facility-list { grid-template-columns: 1fr; }

  /* Atrium is built on air. At 375px that air is most of what there is, so it
     keeps more room than the other three and much less than it takes at 1440px.
     Its four named blocks each carry their own padding with a 3rem or 4rem floor,
     so setting .section alone left the statement band at 64px top and bottom. */
  .variant-atrium .section-head { margin-bottom: 1.35rem; }
  .variant-atrium .card-grid { gap: 1.75rem; }
  .variant-atrium h1 { font-size: clamp(1.9rem, 8.5vw, 2.6rem); }
  .variant-atrium h2 { font-size: clamp(1.4rem, 6vw, 1.8rem); }
  .atrium-pillars ol { margin-top: 1.25rem; }
  /* Centred prose is ragged on both edges. Over a 40-character line that is a
     flourish; over the six lines this becomes on a phone it is work to read. The
     whole block ranges left, or the heading and its rule sit centred above copy
     that does not, which reads as two blocks rather than one. */
  .atrium-statement { text-align: left; }
  .atrium-statement .section-body { margin-inline: 0; font-size: 1rem; }

  /* Campus rounds everything generously; at this width the radius eats the
     content's own padding. */
  .variant-campus .btn { padding-inline: 1.35rem; }

  .table-scroll table { font-size: .9rem; }
  .table-scroll :is(th, td) { padding: .6rem .7rem; }
  .contact-meta__label { font-size: .78rem; }

  .section-head { margin-bottom: 1rem; }
  .variant-prestige .section-head,
  .variant-campus .section-head { margin-bottom: 1rem; }
  .page-body { padding-block: var(--section-y); }
}

/* --- Short screens ----------------------------------------------------------
   A 1366x768 laptop is a wide screen and a short one, and every rule above reads
   width. On that machine the header and the banner together took the whole of the
   first screen — worst on Atrium, whose header is 206px by design: crest, name and
   tagline centred, then a centred menu under them.

   Keyed on height, so nothing here touches a tall monitor at the same width, and
   held to laptops and up so it never reaches a phone. It buys back about forty
   pixels, which is the difference between the page appearing to end at the banner
   and showing that it does not. */

@media (min-width: 1000px) and (max-height: 820px) {
  .hero { padding-block: clamp(3.5rem, 7vw + 1rem, 4rem) clamp(2rem, 5vw, 2.75rem); }
  .hero-prestige { min-height: min(64vh, 560px); }
  .variant-atrium .site-header .shell { padding-block: .9rem; }
  .variant-atrium .site-header nav { gap: .15rem 1.15rem; }
  .variant-atrium .brand { gap: .2rem; }
}

/* --- Touch, at any width ---------------------------------------------------
   Not a width query. A tablet held in two hands is pointed at with a thumb exactly as a
   phone is, and a 36px button is as awkward on one as on the other — scoping this to a
   phone breakpoint fixed it at 375px and left it broken at 768px. A mouse-driven window
   at the same width does not need the controls grown, and does not get them. */

@media (pointer: coarse) {
  /* About 44px is what a thumb needs. The links keep their position: the padding is
     absorbed by a matching negative margin, so the target grows and the layout does not. */
  .btn, .contact-form button, .nav-toggle { min-height: 44px; }
  .text-link, .section-more a:not(.btn) {
    min-height: 44px;
    padding-block: .55rem;
    margin-block: -.55rem;
  }
  .text-link { margin-top: calc(1rem - .55rem); }

  /* The smallest text on the site, and both sit on pages a parent actually reads: the
     category chip on News at 11px, and the month under an event date at 10.7px. The
     tracking is what marks these out as labels; the size was only making them hard. */
  .tag { font-size: .76rem; padding-inline: .55rem; }
  .event-card__date span { font-size: .74rem; }
}

/* --- Narrow phones --------------------------------------------------------- */

@media (max-width: 380px) {
  .brand small { display: none; }
  .eyebrow { letter-spacing: .08em; }
}

/* --- Anything wide inside content scrolls itself --------------------------- */

/* A school pastes a fee table or a timetable into a page and it is wider than a
   phone. Without this the whole document scrolls sideways and every section is
   misaligned by the width of the widest table on the page. */
.page-body table:not(.table-scroll *):not(.custom-html *) {
  display: block;
  max-width: 100%;
  overflow-x: auto;
}
.page-body img, .section img { max-width: 100%; height: auto; }
.page-body pre, .section pre { max-width: 100%; overflow-x: auto; }

/* --- Cards that carry a picture ---------------------------------------------
   The generic card had no room for one because nothing ever drew one. The
   picture sits above the words at a settled shape, so a row of cards lines up
   whatever proportions the school uploaded. */

.card-grid > article { display: flex; flex-direction: column; overflow: hidden; }
.card-grid > article > img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  margin: calc(var(--card-pad, 1.25rem) * -1) calc(var(--card-pad, 1.25rem) * -1) 1rem;
  width: calc(100% + var(--card-pad, 1.25rem) * 2);
  max-width: none;
}
.card-grid__text { display: flex; flex-direction: column; gap: .35rem; }
.card-meta {
  margin: 0;
  color: var(--accent);
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.card-grid h3 a { color: inherit; }
.card-grid h3 a:hover { color: var(--brand); }

/* --- The quotation in a leadership message ---------------------------------
   It had no styling of its own, so the browser's default applied: margin 1em
   40px. That is what pushed the opening line 40px further in than every
   paragraph under it — the indent that reads as the text being mysteriously
   off-centre. It is the message's opening line, so it is set like one. */

.message-block blockquote {
  margin: 0 0 1rem;
  padding: 0;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 1.7vw, 1.35rem);
  font-weight: 500;
  line-height: 1.45;
}
.message-block > div > :last-child { margin-bottom: 0; }
/* A letter signs off on the right. The name and role are the end of the message, not another
   line of it, so they range right the way a signature does under a letter. */
.message-signature { margin-top: 1rem; color: var(--muted); text-align: right; }
.message-signature strong { color: var(--ink); }

/* --- Facilities ------------------------------------------------------------
   One facility per row, picture left and name right, left a long column of
   half-empty rows. They are cards: picture, then its name underneath. */

.facility-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 1.5rem;
}
@media (min-width: 1024px) { .facility-list { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (min-width: 700px) and (max-width: 1023px) { .facility-list { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.facility-list article {
  display: flex;
  flex-direction: column;
  gap: .6rem;
  padding: 0;
  border: 0;
}
.facility-list article:last-child { padding-bottom: 0; }
.facility-list img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--r-sm);
}
.facility-list h3 { margin: 0; font-size: 1.05rem; }
.facility-list p { margin: 0; color: var(--muted); font-size: .92rem; }

/* --- Mandatory disclosure --------------------------------------------------
   Three columns that read as three different kinds of thing: the document's
   name, which is prose and ranges left; its category, which is a label and
   centres in its column; and the link to the file, which sits at the end of
   the row where the eye finishes. */

.disclosure-table th:nth-child(1), .disclosure-table td:nth-child(1) { text-align: left; }
.disclosure-table th:nth-child(2), .disclosure-table td:nth-child(2) { text-align: center; }
.disclosure-table th:nth-child(3), .disclosure-table td:nth-child(3) { text-align: right; }
.disclosure-table td:nth-child(3) { white-space: nowrap; }

/* A voice from the community: who said it, and what they are to the school. */
.prestige-voice { margin: 0 0 1.5rem; display: grid; gap: .6rem; justify-items: inherit; }
.prestige-voice img { width: 72px; height: 72px; border-radius: 50%; object-fit: cover; }
.prestige-voice blockquote { margin: 0; font-family: var(--font-display); font-size: 1.1rem; }
.prestige-voice figcaption { color: var(--muted); font-size: .92rem; }
.prestige-voice figcaption strong { color: var(--ink); }

/* The crest block owns the full width of its section and its own heading.
   Written to out-rank the per-design section rules, which range every heading left and put
   every child in a second column — the empty first column is what left this block starting a
   quarter of the way across the page with nothing beside it. */
.crest-section > .crest-section__inner { grid-column: 1 / -1; }
.crest-section > .crest-section__inner > .crest-section__head { text-align: center; }
.crest-section > .crest-section__inner > .crest-section__head .eyebrow { justify-content: center; }
