/* ==========================================================================
   Pre-Uni New College — Epping Branch
   Design language: exam paper. Ink navy, marking-red pen, ruled notebook
   lines and graph-paper grids — the material world of a tutoring college.
   ========================================================================== */

:root {
  --ink:       #16243D;   /* pen ink / uniform navy   */
  --ink-soft:  #4A5670;   /* secondary text           */
  --paper:     #FBFAF7;   /* answer-booklet white     */
  --white:     #FFFFFF;
  --rule:      #C9D8EE;   /* notebook rule blue       */
  --grid:      #E8EDF5;   /* graph-paper grid         */
  --red:       #C13A32;   /* teacher's marking pen    */
  --red-soft:  #F7E9E7;
  --navy-deep: #0F1A2E;

  --font-display: "Bricolage Grotesque", "Segoe UI", sans-serif;
  --font-body:    "Schibsted Grotesk", "Segoe UI", sans-serif;
  --font-mono:    "Spline Sans Mono", Consolas, monospace;
  --font-hand:    "Caveat", "Segoe Script", cursive;   /* red-pen annotations */

  --container: 1320px;

  /* One curve for every arrival on the site. It spends most of itself slowing
     down, so a block is still visibly settling after the eye has found it —
     which is what reads as considered rather than snapped into place. Where it
     is used the transform outlasts the fade on purpose: the block is legible
     before it has stopped moving. */
  --ease-arrive: cubic-bezier(0.16, 1, 0.3, 1);
  --radius: 10px;
  --shadow: 0 2px 6px rgba(22, 36, 61, 0.06), 0 12px 32px rgba(22, 36, 61, 0.08);
  --header-h: 84px;
}

/* ---------- Reset ---------- */

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

/* The browser's own [hidden] rule is a bare `display: none`, which any class
   setting display beats on equal specificity — so an overlay styled
   `display: grid` sits on screen while its hidden attribute says otherwise.
   Make the attribute mean what it says. */
[hidden] { display: none !important; }

html { scroll-behavior: smooth; scroll-padding-top: calc(var(--header-h) + 16px); }

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.65;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
}

img, svg { display: block; max-width: 100%; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  line-height: 1.12;
  margin: 0 0 0.5em;
  letter-spacing: -0.015em;
}

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

a { color: var(--ink); }

:focus-visible {
  outline: 3px solid var(--red);
  outline-offset: 2px;
  border-radius: 2px;
}

.container {
  width: min(var(--container), 100% - 2.5rem);
  margin-inline: auto;
}

/* ---------- Type helpers ---------- */
.nowrap { white-space: nowrap; }

/* One word in the heading set in handwriting — Caveat by name, since the
   montage skin points --font-hand back at the body face. */
/* A line of handwriting under the hero words that writes itself on: the
   text is wiped in from the left over a second and a half, then a pen
   stroke draws under it. */
.hero-hand { margin: 1.25rem 0 0; line-height: 1; }

/* On the careers page the signature sits astride the hero's bottom edge,
   half on the photograph and half on the paper below — so the hero stops
   clipping, and the line is pulled down by the hero's own bottom padding
   and then half its own height. */
.page-hero.page-hero--signed { overflow: visible; } /* outranks .page-hero--photo's clip */
.page-hero--signed .container { position: relative; }
.page-hero--signed .hero-hand {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(-1 * clamp(3.5rem, 8vw, 6rem));
  margin: 0;
  translate: 0 50%;
  z-index: 3;
  text-align: center;
  pointer-events: none;
}
.page-hero--signed .hand-write {
  font-size: clamp(3.6rem, 7.5vw, 6rem);
  color: var(--red);
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.25);
}
/* Inside the hero, under the words, a size up. */
.page-hero--photo .hand-write { font-size: clamp(3rem, 6vw, 4.6rem); color: #8FBBFF; }

.hand-write {
  position: relative;
  display: inline-block;
  padding: 0 0.15em 0.2em;
  font-family: "Caveat", "Segoe Script", cursive;
  font-weight: 700;
  font-size: clamp(2.4rem, 5vw, 3.6rem);
  line-height: 1;
  color: #8FBBFF;
  clip-path: inset(0 100% 0 0);
  animation: hand-write 1.4s cubic-bezier(0.6, 0, 0.4, 1) 0.9s forwards;
}
.hand-write::after {
  content: "";
  position: absolute;
  left: 0.1em;
  right: 0.1em;
  bottom: 0;
  height: 0.09em;
  border-radius: 999px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  animation: hand-stroke 0.5s ease-out 2.3s forwards;
}
@keyframes hand-write { to { clip-path: inset(0 0 0 0); } }
@keyframes hand-stroke { to { transform: scaleX(1); } }
@media (prefers-reduced-motion: reduce) {
  .hand-write { clip-path: none; animation: none; }
  .hand-write::after { transform: none; animation: none; }
}

/* The one handwritten word on the courses heading takes the red of the
   logo's shield rather than the blue. */
#coursesHead .hand-word { color: #C13A32; }

/* A handwritten word can also carry the pen stroke; the svg sits a little
   lower under the taller letters. */
.hand-word.pen-word { white-space: nowrap; }
.hand-word.pen-word svg { bottom: -0.1em; height: 0.22em; }
/* Leaning, like a word dashed off at speed. */
.hand-word--slant { display: inline-block; transform: rotate(-4deg); transform-origin: left bottom; }

.hand-word {
  font-family: "Caveat", "Segoe Script", cursive;
  font-weight: 700;
  font-size: 1.3em;
  line-height: 1;
  color: var(--red);
  letter-spacing: 0;
}

/* The kicker above each heading is written like a teacher's margin note in
   red pen, not the usual tracked-out uppercase micro-label. */
.eyebrow {
  font-family: var(--font-hand);
  font-size: clamp(1.3rem, 2.5vw, 1.65rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0;
  text-transform: none;
  color: var(--red);
  display: block;
  width: fit-content;
  margin-bottom: 0.55rem;
  transform: rotate(-1.6deg);
  transform-origin: left bottom;
}

/* Two margin-note lines need air between them that line-height:1 does not give. */
.eyebrow--stacked { line-height: 1.25; text-align: center; }

/* Lighter red so the note still reads on the navy panels. */
.contact-strip .eyebrow { color: #E89B95; }

.section-title {
  font-size: clamp(1.7rem, 3.6vw, 2.5rem);
  font-weight: 700;
  /* No measure. Two of the home page headings — the first-round offers and
     the blog band — broke into a lonely second line under one, and a heading
     is a line, not a paragraph. */
  max-width: none;
}

.section-lead {
  color: var(--ink-soft);
  max-width: 58ch;
  font-size: 1.05rem;
}

/* The campus line and the blog line are one sentence each and want to stay
   one line; the measure above broke them in two under their headings. */
#gallery .section-lead,
#blog-latest .section-lead,
.section-lead--wide { max-width: none; }

/* A short bold line set above a lead — "A Clearer Path Forward." over the
   blog's opening paragraph, "Course Details & Timetables" over the timetable
   page's. It is the lead's title, so it takes the lead's measure and sits
   tight against it. */
.lead-title {
  font-weight: 700;
  color: var(--ink);
  font-size: 1.15rem;
  margin: 0 0 0.35rem;
}
.lead-title + .section-lead { margin-top: 0; }

/* Red-pen underline — the marker's stroke under a key word.
   The stroke used to hang outside the heading (104% wide, -2% left) and the
   heading clipped the overflow with `overflow-clip-margin`. Safari ignores that
   property, so on iPhone the clip ate the stroke and the line went missing.
   The stroke now sits inside the span, so nothing needs clipping. */
.pen-word { position: relative; white-space: nowrap; }

.pen-word svg {
  position: absolute;
  left: 0;
  bottom: -0.16em;
  width: 100%;
  height: 0.3em;
  overflow: visible;
}

.pen-word path {
  fill: none;
  stroke: var(--red);
  stroke-width: 7;
  stroke-linecap: round;
}

@media (prefers-reduced-motion: no-preference) {
  .pen-word path {
    stroke-dasharray: 320;
    stroke-dashoffset: 320;
    animation: pen-draw 2.2s ease-out 0.45s forwards;
  }
  @keyframes pen-draw { to { stroke-dashoffset: 0; } }
}

/* ---------- Buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.95rem;
  line-height: 1;
  padding: 0.9em 1.5em;
  border-radius: 6px;
  border: 2px solid var(--ink);
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.15s ease, background 0.15s ease, color 0.15s ease;
}

.btn:active { transform: translateY(1px); }

.btn-solid { background: var(--ink); color: var(--white); }
.btn-solid:hover { background: var(--navy-deep); border-color: var(--navy-deep); }

.btn-outline { background: transparent; color: var(--ink); }
.btn-outline:hover { background: var(--ink); color: var(--white); }

.btn-red { background: var(--red); border-color: var(--red); color: var(--white); }
.btn-red:hover { background: #A82F28; border-color: #A82F28; }

/* ---------- Header ---------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(251, 250, 247, 0.94);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--grid);
  transition: box-shadow 0.2s ease;
}

.site-header.scrolled { box-shadow: 0 4px 20px rgba(22, 36, 61, 0.08); }

/* How far down the page you are, drawn in marking red along the header's own
   bottom rule. main.js keeps --scroll-progress between 0 and 1; it stays
   hidden at the top of the page, where a bar at zero would just be a stub. */
.site-header::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  background: var(--red);
  transform: scaleX(var(--scroll-progress, 0));
  transform-origin: left center;
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;
}

.site-header.scrolled::after { opacity: 1; }

.header-inner {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  min-height: var(--header-h);
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  text-decoration: none;
  margin-right: 1.5rem;
  flex: none;
}

/* Wide lockup (~5.8:1) — set by height, so keep it short or it crowds the nav. */
.brand-logo {
  height: 56px;
  width: auto;
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  /* Takes the width the logo leaves, so the links can sit at its far end. */
  flex: 1 1 auto;
}

/* The links and the button gather on the right, against the window's edge. */
@media (min-width: 1301px) {
  .site-nav > :first-child { margin-left: auto; }
}

.site-nav a {
  font-size: 1.0625rem;
  white-space: nowrap;
  font-weight: 500;
  color: var(--ink-soft);
  text-decoration: none;
  padding: 0.5em 0.7em;
  border-radius: 6px;
  transition: color 0.15s ease, background 0.15s ease;
}

.site-nav a:hover { color: var(--ink); background: var(--grid); }

.site-nav a.active {
  color: var(--ink);
  font-weight: 700;
  box-shadow: inset 0 -2px 0 var(--red);
  border-radius: 6px 6px 0 0;
}

/* Keep the "Book a Tour" CTA looking like a button inside the nav,
   overriding the plain-link nav styling. */
.site-nav a.nav-tour {
  color: var(--white);
  background: var(--red);
  border: 2px solid var(--red);
  padding: 0.6em 1.05em;
  font-size: 0.88rem;
  font-weight: 700;
  border-radius: 6px;
  /* Set apart from the links, so it reads as the one action in the bar. */
  margin-left: 1.5rem;
}
.site-nav a.nav-tour:hover { background: #A82F28; border-color: #A82F28; color: var(--white); }
.site-nav a.nav-tour.active { box-shadow: none; }

/* ---------- Nav dropdowns ---------- */

.nav-group { position: relative; }

.nav-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  font-family: var(--font-body);
  font-size: 0.92rem;
  font-weight: 500;
  color: var(--ink-soft);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.5em 0.7em;
  border-radius: 6px;
  transition: color 0.15s ease, background 0.15s ease;
}

.nav-trigger:hover { color: var(--ink); background: var(--grid); }
.nav-caret { transition: transform 0.2s ease; }

.nav-trigger.active-parent {
  color: var(--ink);
  font-weight: 700;
  box-shadow: inset 0 -2px 0 var(--red);
  border-radius: 6px 6px 0 0;
}

.nav-menu {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  min-width: 200px;
  background: var(--white);
  border: 1px solid var(--grid);
  border-radius: 10px;
  box-shadow: var(--shadow);
  padding: 0.4rem;
  display: flex;
  flex-direction: column;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;
  z-index: 120;
}

/* Invisible bridge so the pointer can cross the gap without closing. */
.nav-menu::before {
  content: "";
  position: absolute;
  top: -10px;
  left: 0;
  right: 0;
  height: 10px;
}

.nav-group:hover .nav-menu,
.nav-group:focus-within .nav-menu,
.nav-group.open .nav-menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.nav-group:hover .nav-caret,
.nav-group:focus-within .nav-caret,
.nav-group.open .nav-caret { transform: rotate(180deg); }

.nav-menu a {
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--ink-soft);
  text-decoration: none;
  padding: 0.55em 0.7em;
  border-radius: 6px;
  white-space: nowrap;
  transition: color 0.15s ease, background 0.15s ease;
}

.nav-menu a:hover { color: var(--ink); background: var(--grid); }

.nav-menu a.active {
  color: var(--ink);
  font-weight: 700;
  box-shadow: inset 2px 0 0 var(--red);
}

.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  padding: 10px;
  background: none;
  border: 1px solid var(--grid);
  border-radius: 8px;
  cursor: pointer;
}

.nav-toggle span {
  display: block;
  height: 2px;
  width: 100%;
  background: var(--ink);
  border-radius: 2px;
  transition: transform 0.25s ease, opacity 0.2s ease;
}

.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* ---------- Hero ---------- */

.hero {
  position: relative;
  padding: clamp(3rem, 7vw, 5.5rem) 0 clamp(3rem, 6vw, 5rem);
  background-image:
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-size: 32px 32px, 32px 32px;
  /* The ruled lines drift down as the page scrolls up (main.js sets
     --hero-drift), so the grid lags behind the words sitting on it. */
  background-position: left var(--hero-drift, 0px), top left;
  background-repeat: repeat, repeat;
  background-color: var(--paper);
  border-bottom: 1px solid var(--grid);
  overflow: hidden;
}

.hero::after {
  /* fade the grid out toward the bottom so content sections start clean */
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 45%;
  background: linear-gradient(transparent, var(--paper));
  pointer-events: none;
}

/* ============================================================
   Timetable coverflow — 3D spherical carousel in the hero
   ============================================================ */
.hero--timetable {
  padding-top: clamp(2.25rem, 5vw, 3.75rem);
  padding-bottom: clamp(2.5rem, 6vw, 4.5rem);

  /* The photo stops on the container's right edge, so it keeps the same margin
     the words keep on the left. --hero-gutter repeats what .container works out
     for itself; the clamp keeps it from crowding the words on a laptop or
     shrinking to a stamp on a wide monitor, and the height comes from the
     file's own 0.878 ratio — cut deliberately tall, so the photo finishes level
     with the last button instead of leaving the right side of the hero empty
     under it. The gutter is in vw, which puts it a few px out from the true one
     whenever a scrollbar is taking up room; at this size nobody can see it. */
  --hero-gutter: max(1.25rem, calc((100vw - var(--container)) / 2));
  --hero-photo-w: clamp(18rem, 42vw, 34rem);
  --hero-photo-h: calc(var(--hero-photo-w) / 0.878);
}

/* A classroom photo beside the opening words, on its own layer ABOVE the
   hero's ruled lines. It used to live in the background stack underneath them,
   which drew the lattice straight across the children's faces. It is taken
   away at its edges with a mask rather than a wash of paper, because paper
   thick enough to hide the photo is also thick enough to hide the graph paper
   under it, and the seam that leaves is worse than the one it fixes.

   Flush to the top on purpose: the header's own rule is the line it starts
   from, so there is nothing to fade into up there. */
@media (min-width: 981px) {
  .hero--timetable::before {
    content: "";
    position: absolute;
    top: 0;
    right: var(--hero-gutter);
    width: var(--hero-photo-w);
    height: var(--hero-photo-h);
    /* Held well back, so it reads as part of the page rather than a picture
       pasted onto it. The paper is laid over the photo INSIDE this layer, on
       an opaque paper ground, rather than the whole layer being made
       see-through: turn the opacity down instead and the ruled lines behind it
       come up through the photo, which is the crossed-out faces this was moved
       above the lattice to fix in the first place. The lines stop at the photo
       and pick up again through the fade at its edges. */
    background-color: var(--paper);
    background-image:
      linear-gradient(rgba(251, 250, 247, 0.42), rgba(251, 250, 247, 0.42)),
      url("../images/hero/backhero-side.webp");
    background-size: 100% 100%, cover;
    background-position: center top, center top;
    background-repeat: no-repeat, no-repeat;
    pointer-events: none;

    /* Two fades, kept as separate mask layers and intersected: one takes the
       sides, one the bottom. A single gradient cannot turn two corners.

       The left fade is long, because that edge has to dissolve into the column
       the words are in. The right one is short: it is the edge that lines up
       with the container, and a long fade there reads as a wider margin than
       the words keep on the other side. */
    -webkit-mask-image:
      linear-gradient(to right, transparent 0, #000 20%, #000 94%,
                      transparent 100%),
      linear-gradient(to bottom, #000 0, #000 62%, transparent 100%);
    mask-image:
      linear-gradient(to right, transparent 0, #000 20%, #000 94%,
                      transparent 100%),
      linear-gradient(to bottom, #000 0, #000 62%, transparent 100%);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
  }
}

@media (max-width: 980px) {
  /* No room for the words beside the photo here, so it goes behind them —
     the same room, held back to a ghost and gone by the time the carousel
     starts. Opacity does the holding back rather than a wash, which is what
     lets the ruled lines carry on through it instead of being papered over.

     Where it stops is fitted to measurements at 390, 430 and 768. It cannot
     fit every width: a wider column costs the headline lines, but the type is
     sized off vw and grows them back, so the block of words does not simply
     get shorter as the window widens. Around 900 and up it finishes early and
     the buttons sit on plain paper — the harmless miss. Run it long instead
     and the photo is still there under the first poster. */
  .hero--timetable::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: clamp(24rem, 45rem - 41vw, 36rem);
    background: url("../images/hero/backhero-side.webp") center top / cover
                no-repeat;
    opacity: 0.3;
    pointer-events: none;
    -webkit-mask-image:
      linear-gradient(to bottom, #000 0, #000 55%, transparent 100%);
    mask-image:
      linear-gradient(to bottom, #000 0, #000 55%, transparent 100%);
  }
}

/* Override the default two-column hero layout for the carousel. */
.hero--timetable .container {
  display: block;
  position: relative;
  z-index: 1;
}

/* The page's opening statement, above the poster carousel. */
.hero-intro {
  max-width: 46rem;
  margin: 0 auto clamp(1.75rem, 4vw, 2.75rem);
  text-align: center;
}

.hero-intro h1 { margin-bottom: 0.5em; }

.hero-pitch {
  font-size: 1.08rem;
  color: var(--ink-soft);
  max-width: 54ch;
  margin-inline: auto;
}

.hero-intro .eyebrow { margin-inline: auto; }
.hero-intro .hero-actions { justify-content: center; }

/* From the width where the classroom photo appears on the right, the opening
   words swing over to the left and stop short of it. Below that width the
   photo is dropped (see the 980px block in Responsive) and the words go back
   to sitting centred over plain paper. */
@media (min-width: 981px) {
  .hero-intro {
    max-width: 32rem;
    margin-inline: 0;
    text-align: left;
  }

  .hero-intro .eyebrow,
  .hero-intro .eyebrow--stacked { margin-inline: 0; text-align: left; }

  .hero-intro .hero-pitch { margin-inline: 0; }
  .hero-intro .hero-actions { justify-content: flex-start; }
}

/* ---------- Home hero on a photograph ----------
   The classroom photograph behind the opening words only — a band the height
   of the first screen, under the same navy wash as the course pages, the
   words centred on it in white. It fades into the paper at its foot, so the
   poster carousel under it sits on plain paper as it always did. The ruled
   lines and the side photo above are switched off in this band: a lattice
   over a photograph is the crossed-out faces the side photo was moved off
   the background stack to fix. */
.hero--photo {
  --hero-photo-band: calc(100vh - var(--header-h));
  background-image: none;
  border-bottom: 1px solid var(--grid);
  isolation: isolate; /* the film, the wash and the words stack inside the band */
}

/* The film behind the opening words, filling the same band the still photo
   filled. Its poster is that photo, so nothing changes until the film plays.
   The landscape cut on a wide screen, the portrait cut on a phone held
   upright — main.js chooses, and swaps when the phone is turned. */
.hero--photo .hero-video {
  position: absolute;
  inset: 0 0 auto 0;
  width: 100%;
  height: var(--hero-photo-band);
  object-fit: cover;
  object-position: center;
  background-color: #2a2a2a;
  pointer-events: none;
}

.hero--photo::before {
  /* The wash over the film: the logo's navy (#14213D) at a depth that reads
     as a filter rather than a tint, so the words stand on it and the film is
     a mood behind them (the office's 9 September note). It thickens to
     opaque paper over the last stretch so the band has no hard edge. Sits
     between the film and the words. */
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  width: auto;
  height: var(--hero-photo-band);
  opacity: 1;
  z-index: 1;
  background-image:
    linear-gradient(180deg, rgba(20, 33, 61, 0.84) 0, rgba(20, 33, 61, 0.88) 88%,
                    var(--paper) 100%);
  background-size: 100% 100%;
  background-position: center;
  background-repeat: no-repeat;
  -webkit-mask-image: none;
  mask-image: none;
  pointer-events: none;
}

.hero--photo::after { display: none; }

.hero--photo .hero-intro {
  max-width: 46rem;
  margin-inline: auto;
  text-align: center;
  color: var(--white);
}

.hero--photo .hero-intro .eyebrow,
.hero--photo .hero-intro .eyebrow--stacked { color: #9DC0FF; margin-inline: auto; text-align: center; }
.hero--photo h1 { color: var(--white); text-shadow: 0 2px 18px rgba(0, 0, 0, 0.45); }

/* The two dates above the headline, in the same handwriting as the pen-word
   on the other heroes — a line of pale blue ink on the film, the two halves
   parted by a dot. No box around it: a pill on a moving picture reads as a
   sticker. Caveat is named outright rather than through --font-hand, which
   the Montage skin points at its own sans. */
.hero--photo .hero-intro .hero-eyebrow {
  font-family: "Caveat", "Segoe Script", cursive;
  font-size: clamp(1.45rem, 2.4vw, 1.85rem);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: 0.01em;
  text-transform: none;
  color: #9DC0FF;
  text-shadow: 0 1px 14px rgba(0, 0, 0, 0.4);
  margin: 0 auto 0.9rem;
  text-align: center;
  transform: none;
}

.hero-eyebrow-sep {
  display: inline-block;
  margin: 0 0.5em;
  color: rgba(255, 255, 255, 0.6);
}

/* On a phone the line would break mid-phrase, so the dot becomes an empty
   block — invisible, and it parts the two halves onto lines of their own. */
@media (max-width: 600px) {
  .hero-eyebrow-sep { display: block; height: 0; margin: 0; overflow: hidden; }
}

.hero--photo .pen-word path { stroke: #7FB0FF; }
/* The two key words in the headline, in the pale blue that reads on the
   photograph, with the bar under each. */
.hero--photo h1 .accent-word { color: #8FBBFF; }
.hero--photo .hero-pitch { color: rgba(255, 255, 255, 0.9); margin-inline: auto; text-shadow: 0 1px 12px rgba(0, 0, 0, 0.45); }
.hero--photo .hero-actions { justify-content: center; }

/* Under 981px the words are not held to the first screen, so the band is cut
   to the block of words instead — the same fitted height the old ghost photo
   used at these widths, which finishes under the last button. */
@media (max-width: 980px) {
  .hero--photo { --hero-photo-band: clamp(24rem, 45rem - 41vw, 36rem); }
}
.hero--photo .btn-outline {
  /* Solid, not outlined: on a phone this button lands where the wash fades
     to paper, and a bare white outline vanished there. */
  color: #14213D;
  background: #ffffff;
  border-color: #ffffff;
}
.hero--photo .btn-outline:hover { background: #E4EDFF; border-color: #E4EDFF; color: #14213D; }

.coverflow {
  --cf-w: clamp(340px, 58vw, 860px);      /* width of the centre 16:9 card */
  position: relative;
  outline: none;
}


.cf-stage {
  position: relative;
  height: calc(var(--cf-w) * 3 / 4 + 5rem);
  perspective: 1600px;
  transform-style: preserve-3d;
}

.cf-item {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--cf-w);
  margin: 0;
  aspect-ratio: 4 / 3;
  border-radius: 16px;
  overflow: hidden;
  background: var(--white);
  box-shadow: 0 24px 55px -22px rgba(20, 24, 40, 0.45);
  cursor: pointer;
  will-change: transform, opacity;
  transition: transform 0.55s cubic-bezier(.22,.61,.36,1),
              opacity 0.45s ease,
              box-shadow 0.45s ease;
  /* base transform is set inline by JS; this is the pre-JS fallback */
  transform: translate(-50%, -50%);
}

.cf-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.cf-item.is-active {
  cursor: zoom-in;   /* a tap on the centre card opens it full size */
  box-shadow: 0 34px 70px -20px rgba(20, 24, 40, 0.55);
}

.cf-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 200;
  width: 48px;
  height: 48px;
  display: grid;
  place-items: center;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: var(--white);
  color: var(--ink);
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: transform 0.2s ease, background 0.2s ease, color 0.2s ease;
}
.cf-nav:hover { background: var(--red); color: #fff; border-color: var(--red); }
.cf-nav:active { transform: translateY(-50%) scale(0.94); }
.cf-nav svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.cf-nav--prev { left: clamp(0.25rem, 2vw, 1.5rem); }
.cf-nav--next { right: clamp(0.25rem, 2vw, 1.5rem); }

.cf-caption {
  margin-top: 1.25rem;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  letter-spacing: 0.02em;
  color: var(--ink-soft);
}

@media (prefers-reduced-motion: reduce) {
  .cf-item { transition: none; }
}

/* ---------- Poster lightbox (opened from the coverflow) ---------- */

body.lb-open { overflow: hidden; }

/* Named for itself. It answered to `lightbox` alongside the teacher portraits,
   and with equal specificity the portrait block, further down the file,
   silently took this one's layout: the poster viewer came out as a
   single-column grid with the arrows stacked above and below the poster and
   the last one pushed off the bottom of the screen. The portraits no longer
   open, and their styles have gone with them — the name stays distinct so
   the next thing called a lightbox cannot do it again. */
.poster-lb {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(0.5rem, 2vw, 1.5rem);
  padding: clamp(3.5rem, 8vw, 4.5rem) clamp(0.75rem, 3vw, 2rem);
  background: rgba(9, 15, 27, 0.97);
  overscroll-behavior: contain;
}


.lb-figure {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.9rem;
  max-height: 100%;
  min-width: 0;

  /* What sits under the poster and has to be left room for: the caption line
     and the gap above it. Without this the poster claims the whole viewport
     between the padding and pushes the caption off the bottom of the screen. */
  --lb-below: 2.3rem;
}

/* And the link button, on the notices that carry one. */
.poster-lb:has(.lb-link:not([hidden])) .lb-figure { --lb-below: 6.1rem; }

.lb-figure img {
  max-width: 100%;
  max-height: calc(100vh - 9rem - var(--lb-below));
  width: auto;
  border-radius: 10px;
  background: var(--white);
  box-shadow: 0 30px 70px -25px rgba(0, 0, 0, 0.8);
}

/* One line, because the room left for it above is measured in lines. Captions
   are labels — "Term 4 · Timetable" — so there is nothing to wrap. */
.lb-caption {
  max-width: 100%;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  color: #C9D8EE;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Where a notice leads somewhere, the poster is read first and the link is
   offered under it — a tap on the card opens the picture, never the page. */
.lb-link {
  display: inline-block;
  padding: 0.6rem 1.3rem;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font-size: 0.95rem;
  font-weight: 600;
  text-decoration: none;
  transition: background 0.2s ease, border-color 0.2s ease;
}

.lb-link:hover,
.lb-link:focus-visible { background: var(--red); border-color: var(--red); }

.lb-link[hidden] { display: none; }

.lb-close,
.lb-nav {
  flex: none;
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease;
}

.lb-close:hover,
.lb-nav:hover { background: var(--red); border-color: var(--red); }

.lb-close svg,
.lb-nav svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.lb-close {
  position: absolute;
  top: clamp(0.75rem, 3vw, 1.5rem);
  right: clamp(0.75rem, 3vw, 1.5rem);
}

/* On a phone the arrows sit under the poster, clear of the image itself. */
@media (max-width: 640px) {
  .poster-lb { flex-wrap: wrap; align-content: center; }
  .lb-figure { order: -1; flex: 1 0 100%; }
  .lb-figure img { max-height: calc(100vh - 13rem - var(--lb-below)); }
  .lb-close, .lb-nav { width: 42px; height: 42px; }
}

.hero:not(.hero--timetable) .container {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1.25fr 0.9fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}

.hero h1 {
  font-size: clamp(2.35rem, 5.4vw, 3.9rem);
  font-weight: 800;
  margin-bottom: 0.4em;
}

.hero-copy p {
  font-size: 1.08rem;
  color: var(--ink-soft);
  max-width: 52ch;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.75rem;
}

.hero-note {
  margin-top: 1.5rem;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--ink-soft);
}

.hero-note a { color: var(--red); font-weight: 500; }

/* ============================================================
   First round offers — school logo ticker under the hero.
   One row of chips, cloned by JS and slid right-to-left so the
   loop never shows a seam.
   ============================================================ */
.offers {
  --offers-bg: var(--white);
  background: var(--offers-bg);
  border-bottom: 1px solid var(--grid);
  padding: clamp(2rem, 4.5vw, 3rem) 0 clamp(2.25rem, 5vw, 3.25rem);
  overflow: hidden;
}

.offers-head {
  text-align: center;
  margin-bottom: clamp(1.5rem, 3vw, 2rem);
}

.offers-head .eyebrow { margin-inline: auto; }

.offers-title {
  font-size: clamp(1.4rem, 2.8vw, 1.95rem);
  font-weight: 700;
  margin-bottom: 0.35em;
}

.offers-note {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--ink-soft);
}

/* The viewport the track slides through, with the edges faded out. */
.offers-marquee {
  position: relative;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}

.offers-track { display: flex; width: max-content; }

.offers-row {
  --offer-logo: clamp(88px, 9vw, 124px);
  display: flex;
  align-items: flex-start;
  gap: clamp(1.75rem, 3.5vw, 3.25rem);
  margin: 0;
  padding: 0 clamp(0.9rem, 1.75vw, 1.6rem);
  list-style: none;
}

.offer-chip {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
}

.offer-chip img {
  width: var(--offer-logo);
  height: var(--offer-logo);
  object-fit: contain;
}

.offer-count {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.2vw, 1.9rem);
  font-weight: 800;
  line-height: 1;
  color: var(--red);
  font-variant-numeric: tabular-nums;
}

/* Animation only runs once JS has cloned the row — otherwise a -50%
   slide would drag the single row off the screen. */
@media (prefers-reduced-motion: no-preference) {
  .offers-marquee.is-live .offers-track {
    animation: offers-scroll var(--offers-duration, 45s) linear infinite;
  }

  .offers-marquee.is-live:hover .offers-track,
  .offers-marquee.is-live:focus-within .offers-track { animation-play-state: paused; }

  @keyframes offers-scroll {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(-50%, 0, 0); }
  }
}

/* No animation: let the row be swiped instead of stranding logos off-screen. */
@media (prefers-reduced-motion: reduce) {
  .offers-marquee { overflow-x: auto; scrollbar-width: thin; }
  .offers-marquee .offers-row + .offers-row { display: none; }
}

/* Exam cover-sheet card — the page's signature artifact */
.exam-slip {
  position: relative;
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.75rem 1.75rem 1.75rem 3rem;
  transform: rotate(1.2deg);
}

.exam-slip::before {
  /* the red margin line of an answer booklet */
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 2.1rem;
  width: 1.5px;
  background: var(--red);
  opacity: 0.55;
}

.exam-slip-head {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-soft);
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 0.9rem;
  border-bottom: 1.5px solid var(--ink);
  margin-bottom: 0.35rem;
}

.exam-slip dl { margin: 0; }

.exam-slip .row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.78rem 0;
  border-bottom: 1px solid var(--rule);
}

.exam-slip .row:last-of-type { border-bottom: none; }

.exam-slip dt {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.exam-slip dd {
  margin: 0;
  font-weight: 700;
  font-size: 0.95rem;
  text-align: right;
}

.exam-stamp {
  position: absolute;
  right: -14px;
  top: -16px;
  width: 74px;
  height: 74px;
  border: 2.5px solid var(--red);
  border-radius: 50%;
  display: grid;
  place-items: center;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--red);
  background: var(--white);
  transform: rotate(9deg);
  line-height: 1.3;
}

/* ---------- Sections ---------- */

.section { padding-block: clamp(3.25rem, 7vw, 5.5rem); }
.section-alt { background: var(--white); border-block: 1px solid var(--grid); }

.section-head { margin-bottom: clamp(1.75rem, 4vw, 3rem); }

/* A closing button under a section's content. */
.section-cta { margin-top: clamp(1.75rem, 4vw, 2.5rem); }

/* YouTube facade — loads the iframe only when clicked */
.yt-facade {
  position: relative;
  margin: 0;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--navy-deep);
  box-shadow: var(--shadow);
  border: 1px solid var(--grid);
  cursor: pointer;
}

.yt-facade .yt-poster {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s ease;
}

.yt-facade:hover .yt-poster { transform: scale(1.03); }

.yt-play {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 84px;
  height: 60px;
  display: grid;
  place-items: center;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
}

.yt-play svg { width: 84px; height: 60px; filter: drop-shadow(0 4px 12px rgba(0,0,0,0.35)); }
.yt-play .yt-play-bg { fill: #1f1f1f; fill-opacity: 0.85; transition: fill 0.2s ease; }
.yt-facade:hover .yt-play-bg,
.yt-play:focus-visible .yt-play-bg { fill: var(--red); fill-opacity: 1; }

.yt-facade figcaption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 2.5rem 1.2rem 1rem;
  font-size: 0.85rem;
  font-weight: 500;
  color: #fff;
  background: linear-gradient(transparent, rgba(15, 26, 46, 0.85));
}

.yt-facade iframe { width: 100%; height: 100%; border: 0; display: block; }

/* ---------- Rail (side-scrolling shelf, used for clips and campus shots) ----
   --rail-card sets how much of the track one card takes, so a rail shows two
   big items or several small ones just by changing that one number. */

.rail { position: relative; --rail-card: 46%; }

.rail-track {
  display: flex;
  gap: 1.25rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  overscroll-behavior-x: contain;
  /* Room for the card shadow to breathe, plus space under the scrollbar. */
  padding: 0.4rem 0.4rem 1.1rem;
  margin-inline: -0.4rem;
  /* Snapping measures from the padding box, so without this the shelf settles
     6px short of its own start and the first card sits a hair left of where
     the layout puts it — visible as a lopsided gap either side of a rail. */
  scroll-padding-inline: 0.4rem;
  scrollbar-width: thin;
  scrollbar-color: var(--rule) transparent;
}

.rail-track.is-dragging {
  scroll-snap-type: none;
  scroll-behavior: auto;
  cursor: grabbing;
  user-select: none;
}

.rail-track.is-dragging .rail-card { cursor: grabbing; }

.rail-track::-webkit-scrollbar { height: 8px; }
.rail-track::-webkit-scrollbar-track { background: var(--grid); border-radius: 99px; }
.rail-track::-webkit-scrollbar-thumb { background: var(--rule); border-radius: 99px; }
.rail-track::-webkit-scrollbar-thumb:hover { background: var(--ink-soft); }

.rail-card {
  flex: 0 0 var(--rail-card);
  scroll-snap-align: start;
}

/* The play badge is a shared <use> sprite, so colour it through `fill`,
   which inherits into the symbol (the arrow keeps its own white fill). */
.rail-card .yt-play { width: 76px; height: 54px; }
.rail-card .yt-play svg {
  width: 76px;
  height: 54px;
  fill: rgba(31, 31, 31, 0.85);
  transition: fill 0.2s ease;
}
.rail-card:hover .yt-play svg,
.rail-card .yt-play:focus-visible svg { fill: var(--red); }

.rail-card figcaption {
  padding: 2.6rem 1.3rem 1rem;
  font-family: var(--font-hand);
  font-size: 1.35rem;
  font-weight: 600;
  line-height: 1;
}

/* Campus shots sit smaller and wider — several on screen at once. */
.rail--campus { --rail-card: 30%; }

/* The campus shelf runs edge to edge — out of the container to the sides of
   the window, the first photo flush with the left edge. The section clips
   sideways so the shelf's own width never adds a page scrollbar. */
#gallery { overflow-x: clip; }

.rail--campus {
  width: 100vw;
  margin-left: calc(50% - 50vw);
}

.rail--campus .rail-track {
  padding-inline: 0;
  margin-inline: 0;
  scroll-padding-inline: 0;
}

.rail--campus .rail-nav--prev { left: clamp(0.75rem, 2vw, 1.5rem); }
.rail--campus .rail-nav--next { right: clamp(0.75rem, 2vw, 1.5rem); }

.rail--campus .rail-card {
  position: relative;
  margin: 0;
  aspect-ratio: 3 / 2;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow);
  border: 1px solid var(--grid);
}

.rail--campus .rail-card img {
  /* Held taller than the frame it sits in, so the drift main.js writes never
     drags an edge into view. The hover zoom is on `scale` rather than inside
     `transform`: the two compose, where a second `transform` would simply
     replace the first and one of the effects would lose. */
  position: absolute;
  left: 0;
  top: -20px;
  width: 100%;
  height: calc(100% + 40px);
  object-fit: cover;
  --fx-shift: 28px;
  scale: 1;
  transition: scale 0.4s ease;
}

.rail--campus .rail-card:hover img { scale: 1.04; }

.rail--campus figcaption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 2rem 1.1rem 0.85rem;
  font-size: 1.15rem;
  color: #fff;
  background: linear-gradient(transparent, rgba(15, 26, 46, 0.88));
}

/* The rails used to fade out at both edges to hint at more cards off-screen.
   Removed — the arrows and the cut-off card already say the same thing, and
   the gradient read as a smudge over the artwork. `at-start`/`at-end` are
   still set in main.js; they disable the arrows. */

.rail-nav {
  position: absolute;
  top: calc(50% - 0.55rem);
  transform: translateY(-50%);
  z-index: 3;
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--grid);
  background: var(--white);
  color: var(--ink);
  box-shadow: var(--shadow);
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease, opacity 0.25s ease;
}

.rail-nav:hover { background: var(--red); color: #fff; border-color: var(--red); }
.rail-nav:active { transform: translateY(-50%) scale(0.94); }
.rail-nav svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.rail-nav--prev { left: -0.9rem; }
.rail-nav--next { right: -0.9rem; }

.rail-nav[disabled] { opacity: 0; pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
  .rail-track { scroll-behavior: auto; }
}

/* ---------- Cyberschool band ---------- */

.cyber-band {
  background: var(--navy-deep);
  color: var(--white);
}

.cyber-layout {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}

.cyber-band .eyebrow { color: #E89B95; }
.cyber-band .section-title { color: var(--white); }
.cyber-band .section-lead { color: var(--rule); }

.cyber-points {
  list-style: none;
  margin: 1.5rem 0 2rem;
  padding: 0;
  display: grid;
  gap: 0.7rem;
}

.cyber-points li {
  position: relative;
  padding-left: 1.9rem;
  color: var(--rule);
  font-size: 1.02rem;
}

.cyber-points li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.15em;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  background: rgba(232, 155, 149, 0.18) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23E89B95' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 13.5 9.5 19 20 5.5'/%3E%3C/svg%3E") center / 0.8rem no-repeat;
}

.cyber-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; }

/* Outline button that works on the dark band. */
.btn-ghost {
  background: transparent;
  color: var(--white);
  border-color: rgba(201, 216, 238, 0.5);
}
.btn-ghost:hover { background: rgba(255, 255, 255, 0.08); border-color: var(--white); }

.cyber-window {
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid rgba(201, 216, 238, 0.25);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
  background: #182742;
}

.cyber-chrome {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.7rem 0.9rem;
  background: #101d33;
  border-bottom: 1px solid rgba(201, 216, 238, 0.16);
}

.cyber-chrome > span {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: rgba(201, 216, 238, 0.3);
}

.cyber-url {
  margin: 0 0 0 0.7rem;
  flex: 1;
  padding: 0.3rem 0.75rem;
  border-radius: 99px;
  background: rgba(201, 216, 238, 0.12);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--rule);
}

.cyber-screen {
  position: relative;
  aspect-ratio: 4 / 3;
  background:
    linear-gradient(rgba(201, 216, 238, 0.07) 1px, transparent 1px),
    linear-gradient(90deg, rgba(201, 216, 238, 0.07) 1px, transparent 1px);
  background-size: 34px 34px;
}

.cyber-screen img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ---------- Course cards ---------- */

/* The section asks the reader to choose, so the question sits over the middle
   of the row of answers rather than off to one side. */
.section-head--centred { text-align: center; }
.section-head--centred .eyebrow { margin-inline: auto; }
.section-head--centred .section-title { max-width: none; margin-inline: auto; }
.section-head--centred .section-lead { max-width: 58ch; margin-inline: auto; }

/* main.js wraps the closing word of the heading, because the heading itself is
   typed in /admin/ and arrives as plain text. */
/* Coloured and underscored, but not a link: the stroke is heavier than a
   text underline and sits clear of the baseline, so it reads as a marker line
   under the word rather than something to click. */
.accent-word {
  color: var(--red);
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  /* The bar is bottom-aligned inside the padding, so the padding is what sets
     how far under the word it sits — shifting the position past 100% instead
     would push it out of the paint box and clip it away entirely. */
  background-size: 100% 0.1em;
  background-position: 0 100%;
  border-radius: 2px;
  padding-bottom: 0.26em;
}
/* The same word in handwriting and a size up, so it stands out from the
   heading around it — the STTC page's "Why". Caveat by name, since the
   montage skin points --font-hand back at the body face. */
.accent-word--hand {
  font-family: "Caveat", "Segoe Script", cursive;
  font-size: 1.45em;
  font-weight: 700;
  line-height: 0.8;
  letter-spacing: 0;
  padding-inline: 0.04em;
  padding-bottom: 0.12em;
}

/* Flex rather than grid, so that every card is the same width and a row that
   does not fill — five courses leave two on the second row — is centred under
   the ones above it instead of hanging off the left with a hole on the end.
   A grid can hand the stragglers extra width to close the gap, but then one
   card is wider than its neighbours for no reason the reader can see. */
.card-grid {
  --cols: 3;
  --card-gap: 1.25rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--card-gap);
  margin-top: 2.75rem;
}

/* A course page cross-links the other four. Three across would leave the
   fourth on a row of its own, so that grid asks for four. */
.card-grid--quad { --cols: 4; }

.course-card,
.card-grid > .review-card {
  flex: 0 1 calc((100% - (var(--cols) - 1) * var(--card-gap)) / var(--cols));
}

.course-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1.75rem 1.6rem 1.6rem;
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}

.course-card:hover { box-shadow: var(--shadow); }

/* Every card grows a little under the pointer — the same 2% on all of them,
   so the page answers the cursor the same way wherever it lands. It is on
   `scale`, not inside `transform`: the reveal animates transform, the last
   rule would win, and one of the two effects would lose. The two properties
   compose. The reveal's own transition list is extended to carry it (see the
   motion block), because that list replaces this one the moment a card is
   tagged .reveal. */
.course-card, .post-card, .review-card, .teacher-card, .subject,
.contact-card, .feature, .step, .download-group, .hours-card,
.fact-sheet, .faq-item, .yt-facade, .glance-tile, .fee-card,
.timetable-figure, .rail--campus .rail-card {
  scale: 1;
  transition: scale 0.25s ease, box-shadow 0.25s ease, border-color 0.2s ease,
              background 0.15s ease;
}

.course-card:hover, .post-card:hover, .review-card:hover, .teacher-card:hover,
.subject:hover, .contact-card:hover, .feature:hover, .step:hover,
.download-group:hover, .hours-card:hover, .fact-sheet:hover, .faq-item:hover,
.yt-facade:hover, .glance-tile:hover, .fee-card:hover, .timetable-figure:hover,
.rail--campus .rail-card:hover {
  scale: 1.02;
}

/* A grown card overlaps its neighbours by a few pixels; it has to be the one
   on top while it does. */
.course-card:hover, .post-card:hover, .review-card:hover, .teacher-card:hover,
.rail--campus .rail-card:hover { z-index: 2; }

.course-tag {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--red);
  align-self: flex-start;
  margin-bottom: 0.9rem;
}

.course-card h3 { font-size: 1.28rem; font-weight: 700; margin-bottom: 0.6rem; }

/* The pill straddles the top edge, so the card it belongs to is picked out
   before any of the words on it are read. */
.course-badge {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--white);
  background: var(--red);
  border-radius: 999px;
  padding: 0.45em 0.9em;
}

.course-badge::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
}

/* What the badged course gets is the ink, not a tint of the accent — one card
   in the row is a different material from the rest, which is the whole point
   of pointing at it. */
.course-card.is-featured {
  background: var(--navy-deep);
  border-color: var(--navy-deep);
  color: var(--white);
}

.course-card.is-featured h3 { color: var(--white); }
.course-card.is-featured .course-tag { color: var(--red-soft); }
.course-card.is-featured p { color: rgba(255, 255, 255, 0.78); }
.course-card.is-featured .course-points { border-color: rgba(255, 255, 255, 0.18); }
.course-card.is-featured .course-points li { color: rgba(255, 255, 255, 0.9); }

/* A tick list of what a term of the course actually includes, taken from the
   same wording as the course page rather than written fresh for the card. */
.course-points {
  list-style: none;
  margin: 1.25rem 0 0;
  padding: 1.25rem 0 0;
  border-top: 1px solid var(--rule);
  display: grid;
  gap: 0.6rem;
  flex-grow: 1;
  align-content: start;
}

.course-points li {
  display: grid;
  grid-template-columns: 1.15rem 1fr;
  gap: 0.6rem;
  align-items: start;
  font-size: 0.9rem;
  line-height: 1.45;
  color: var(--ink-soft);
}

.course-points li::before {
  content: "";
  margin-top: 0.28em;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  background: var(--red-soft);
  /* A tick, drawn rather than typed, so it keeps its weight at any size. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23C13A32' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 0.72rem;
}

.course-cta {
  margin-top: 1.5rem;
  width: 100%;
  justify-content: center;
  text-align: center;
}

/* Marking red on navy is the one pairing on this site that does not read, so
   the ink card's action inverts instead. */
.course-card.is-featured .btn-red {
  background: var(--white);
  border-color: var(--white);
  color: var(--navy-deep);
}

.course-card.is-featured .btn-red:hover {
  background: rgba(255, 255, 255, 0.88);
  border-color: rgba(255, 255, 255, 0.88);
}

.course-card.is-featured .course-points li::before {
  background-color: rgba(255, 255, 255, 0.14);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7'/%3E%3C/svg%3E");
}

/* Pointer to the trial test courses, under the Years 1–6 card grid. */
.course-aside {
  margin-top: 1.5rem;
  color: var(--ink-soft);
  font-size: 0.95rem;
}

.course-aside a { color: var(--red); font-weight: 500; }

/* The home page shows three courses and sends the reader to /courses/ for
   the rest; this is the button that does the sending. */
.course-more {
  margin: clamp(1.75rem, 4vw, 2.5rem) 0 0;
  text-align: center;
}

.course-card p {
  color: var(--ink-soft);
  font-size: 0.95rem;
  flex-grow: 1;
}

/* The three cards on the home page are a shelf, not the catalogue: tighter
   padding, a two-line blurb, three points, and the button follows the
   points rather than being pushed to a shared bottom. The catalogue on
   /courses/ keeps the full card. */
#courseCards .course-card { padding: 1.4rem 1.4rem 1.35rem; }
#courseCards .course-tag { margin-bottom: 0.6rem; }
#courseCards .course-card h3 { margin-bottom: 0.4rem; }
#courseCards .course-card p {
  flex-grow: 0;
  /* The blurb comes from /admin/ at whatever length the office wrote it;
     on the shelf it is cut to three lines. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}
#courseCards .course-points {
  margin-top: 1rem;
  padding-top: 1rem;
  gap: 0.45rem;
  flex-grow: 0;
  margin-bottom: 1.25rem;
}
#courseCards .course-cta { margin-top: auto; }

/* ---------- Downloads (ruled answer-sheet list) ---------- */

.download-groups {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.25rem;
}

.download-group {
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}

.download-group h3 {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  margin: 0;
  padding: 1rem 1.5rem;
  background: var(--ink);
  color: var(--white);
  font-size: 1.2rem;
  font-weight: 700;
}

.download-group h3 small {
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: 0.8rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--rule);
}

.download-list { position: relative; padding-left: 1.1rem; }

.download-list::before {
  /* answer-booklet red margin */
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 1.1rem;
  width: 1px;
  background: var(--red);
  opacity: 0.4;
}

.download-list a {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 1rem 1.25rem;
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  font-size: 1.08rem;
  font-weight: 500;
  transition: background 0.15s ease;
}

.download-list a:last-child { border-bottom: none; }
.download-list a:hover { background: var(--paper); }
.download-list a:hover .dl-name { color: var(--red); }

.dl-name { flex-grow: 1; }

.dl-type {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--red);
  border: 1.5px solid currentColor;
  border-radius: 6px;
  padding: 0.45em 1.6em;
  min-width: 6.5rem;
  text-align: center;
  flex: none;
}

/* The term timetable card: a size up, and a bigger button. */
.featured-download h3 { font-size: 1.35rem; }
.featured-download p { font-size: 1.05rem; }
.featured-download .btn { font-size: 1.1rem; padding: 0.9em 1.6em; }

.featured-download {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.25rem;
  background: var(--ink);
  color: var(--white);
  border-radius: var(--radius);
  padding: 1.5rem 1.75rem;
  margin-bottom: 1.5rem;
}

.featured-download .fd-text { flex: 1 1 280px; }

.featured-download h3 { color: var(--white); font-size: 1.2rem; margin-bottom: 0.2em; }

.featured-download p {
  color: var(--rule);
  font-size: 0.92rem;
  margin: 0;
}

/* ---------- Feature list (why us) ---------- */

.feature-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.25rem;
}

.feature {
  display: flex;
  gap: 1rem;
  align-items: flex-start;
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1.4rem 1.5rem;
}

.feature-tick {
  flex: none;
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  background: var(--red-soft);
  color: var(--red);
  border-radius: 50%;
  margin-top: 0.15rem;
}

.feature-tick svg { width: 15px; height: 15px; }

.feature h3 { font-size: 1.05rem; margin-bottom: 0.25em; }
.feature p { color: var(--ink-soft); font-size: 0.93rem; }

/* ---------- Contact strip / info cards ---------- */

.contact-strip {
  background: var(--navy-deep);
  color: var(--white);
}

.contact-strip { text-align: center; }
.contact-strip .section-title { color: var(--white); max-width: none; margin-inline: auto; }
.contact-strip .section-lead { color: var(--rule); margin-inline: auto; max-width: none; }
.contact-strip .hero-actions { justify-content: center; }

/* The outline button in this band was carrying an inline
   `border-color:#C9D8EE;color:#fff` on nine pages, because .btn-outline's ink
   is invisible on the navy. Written here instead, so a theme can override it —
   an inline style cannot be. */
.contact-strip .btn-outline { border-color: var(--rule); color: var(--white); }
.contact-strip .btn-outline:hover { background: var(--white); color: var(--ink); }

.contact-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.25rem;
  margin-top: 2rem;
}

.contact-card {
  border: 1px solid rgba(201, 216, 238, 0.25);
  border-radius: var(--radius);
  padding: 1.5rem;
}

.contact-card h3 {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--rule);
  margin-bottom: 0.75rem;
}

.contact-card a, .contact-card p {
  color: var(--white);
  font-size: 1.15rem;
  font-weight: 700;
  text-decoration: none;
  font-family: var(--font-display);
}

.contact-card a:hover { color: #E89B95; }

.contact-card small {
  display: block;
  margin-top: 0.4rem;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 0.85rem;
  color: var(--rule);
}

/* ---------- Page hero (subpages) ---------- */

.page-hero {
  padding: clamp(2.5rem, 6vw, 4rem) 0;
  background-image:
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-size: 32px 32px;
  /* Same drifting ruled lines as the home page hero. */
  background-position: left var(--hero-drift, 0px), top left;
  border-bottom: 1px solid var(--grid);
}

.page-hero h1 {
  font-size: clamp(2rem, 4.6vw, 3.2rem);
  font-weight: 800;
  /* No measure: "Timetables & term newsletters" broke under a 20ch cap
     with the whole container free beside it. */
  max-width: none;
}

.page-hero .section-lead { font-size: 1.08rem; max-width: 74ch; }
/* A lead written as two sentences, one to a line. */
.page-hero .section-lead--wide { max-width: none; }

/* Every page hero sits on the centre line, photographed or not. */
.page-hero { text-align: center; }
.page-hero h1, .page-hero .section-lead, .page-hero .eyebrow, .page-hero .post-crumbs { margin-inline: auto; }
.page-hero .eyebrow { transform: none; }
.page-hero .hero-actions { justify-content: center; }

/* ---------- Prose blocks ---------- */

.prose { max-width: 68ch; }
.prose h2 { font-size: 1.6rem; margin-top: 2em; }
.prose h2:first-child,
.prose .eyebrow + h2 { margin-top: 0; }
.prose p { color: var(--ink-soft); }
.prose strong { color: var(--ink); }

.two-col {
  display: grid;
  grid-template-columns: 1.2fr 0.8fr;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
}

/* Fact sheet (about page sidebar) */
/* Two boxes ride beside the prose, and the pair sticks rather than each one
   separately — two sticky siblings would slide over each other. */
.fact-stack {
  position: sticky;
  top: calc(var(--header-h) + 1.5rem);
  display: grid;
  gap: 1rem;
  align-content: start;
}

.fact-sheet {
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1.5rem;
}

/* The branch box is the one this page is actually about. */
.fact-sheet--branch { border-color: var(--red); }

.fact-sheet-cta {
  margin: 1rem 0 0;
  font-size: 0.9rem;
  font-weight: 600;
}

.fact-sheet-cta a { color: var(--red); text-decoration: none; }
.fact-sheet-cta a::after { content: " →"; }
.fact-sheet-cta a:hover { text-decoration: underline; }

.fact-sheet h3 {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--red);
  margin-bottom: 0.5rem;
}

.fact-sheet .row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--rule);
  font-size: 0.92rem;
}

.fact-sheet .row:last-child { border-bottom: none; }
.fact-sheet .row span { color: var(--ink-soft); }
.fact-sheet .row strong { text-align: right; }
.fact-sheet .row a { text-decoration: none; }

/* Press clipping (about page). A scan of newsprint, so it sits on white with a
   thin rule like the paper it was cut from. It stops well short of its own
   907px scan width — at full size the clipping dominates the page, and it is
   an aside to Mrs Kim's story rather than the story itself. */
.press-clip {
  margin: 0 auto;
  max-width: 600px;
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: clamp(0.6rem, 2vw, 1rem);
  box-shadow: var(--shadow);
}

.press-clip img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: calc(var(--radius) - 4px);
}

.press-clip figcaption {
  margin-top: 0.9rem;
  font-size: 0.85rem;
  color: var(--ink-soft);
  text-align: center;
}

/* The clipping beside the words that say what it is, and the branch beside
   a photograph of it — the two media rows on /about-us/. */
.press-layout,
.branch-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}

.press-layout .press-clip { margin: 0; max-width: 560px; }

.press-copy p,
.branch-copy p { color: var(--ink-soft); max-width: 52ch; }

.press-copy .section-title,
.branch-copy .section-title { margin-bottom: 0.5em; }

.press-copy .hero-actions,
.branch-copy .hero-actions { margin-top: 1.5rem; }

.branch-photo {
  margin: 0;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--rule);
  box-shadow: var(--shadow);
}

.branch-photo img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
}

@media (max-width: 900px) {
  .press-layout,
  .branch-layout { grid-template-columns: 1fr; }
  .press-layout .press-clip { margin: 0 auto; }
  .branch-layout .branch-photo { order: -1; }
}

/* ---------- Page heroes with a photograph ---------- */

/* The course pages and the courses index open on a photograph under the same
   navy wash as the home page, the words centred on it. Each page names its
   own picture in an inline background-image on the section — inline, and
   not a custom property, because a url() inside a variable is resolved
   against this stylesheet rather than the page, and ../assets from here is
   the wrong place. */
.page-hero--photo {
  position: relative;
  overflow: hidden;
  padding: clamp(3.5rem, 8vw, 6rem) 0;
  background-color: #14213D;
  background-image: url("../images/hero/backhero-wide.jpg");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border-bottom: 0;
  color: var(--white);
  text-align: center;
}

.page-hero--photo::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(20, 33, 61, 0.7), rgba(20, 33, 61, 0.92));
  pointer-events: none;
}

.page-hero--photo > .container { position: relative; z-index: 1; }

.page-hero--photo h1 { color: var(--white); max-width: none; margin-inline: auto; }

/* The bracketed abbreviation after a trial course's name — "(OCTTC)" — set
   lighter and smaller, so the name is what is read and the code is what is
   remembered. */
.page-hero h1 .h1-abbr {
  font-weight: 500;
  font-size: 0.55em;
  opacity: 0.8;
  white-space: nowrap;
}
.page-hero--photo .eyebrow { color: #9DC0FF; margin-inline: auto; transform: none; }
.page-hero--photo .section-lead { color: rgba(255, 255, 255, 0.86); margin-inline: auto; }
.page-hero--photo .pen-word path { stroke: #7FB0FF; }
.page-hero--photo .post-crumbs { color: rgba(255, 255, 255, 0.7); }
.page-hero--photo .post-crumbs a { color: #9DC0FF; }
.page-hero--photo .hero-actions { justify-content: center; }
.page-hero--photo .btn-outline { color: var(--white); border-color: rgba(255, 255, 255, 0.55); }
.page-hero--photo .btn-outline:hover { background: var(--white); color: #14213D; border-color: var(--white); }

/* ---------- Course pages ---------- */

/* Long-form course copy — paragraphs read from the left inside a measured
   column that itself sits on the page's centre line. */
.prose-block {
  max-width: none;
  margin: 0;
  text-align: left;
}
.prose-block p { color: var(--ink-soft); line-height: 1.7; }
.prose-block p + p { margin-top: 1rem; }
.prose-block .course-lead { max-width: none; margin-inline: 0; margin-bottom: 1.1rem; }
/* ---------- Marking sheet ----------
   Short figures in a row, each circled by a red pen as the list reveals —
   the way a marker rings the answers that are right. The circle is an svg
   path drawn with stroke-dashoffset, one after another. */
.mark-list {
  list-style: none;
  margin: 0 auto;
  padding: 0;
  max-width: 62rem;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.5rem 2rem;
}
.mark-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  text-align: center;
}
.mark-chip {
  position: relative;
  display: inline-block;
  padding: 0.35em 0.9em;
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 2.4vw, 1.9rem);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.mark-chip > span { position: relative; z-index: 1; }
.mark-pen {
  position: absolute;
  inset: -0.35em -0.55em;
  width: calc(100% + 1.1em);
  height: calc(100% + 0.7em);
  overflow: visible;
  pointer-events: none;
}
.mark-pen path {
  fill: none;
  stroke: #C13A32;
  stroke-width: 3.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 900;
  stroke-dashoffset: 900;
}
.mark-note { font-size: 0.95rem; color: var(--ink-soft); max-width: 18rem; }
/* Phrases rather than figures ("Teaching Experience") — a size down so each
   stays on one line inside its column, and the ring drawn a little wider. */
.mark-list--words .mark-chip { font-size: clamp(1.2rem, 2vw, 1.55rem); white-space: nowrap; }
.mark-list--words .mark-pen { inset: -0.4em -0.7em; width: calc(100% + 1.4em); height: calc(100% + 0.8em); }

@media (prefers-reduced-motion: no-preference) {
  .mark-list.in .mark-pen path { animation: mark-ring 0.9s cubic-bezier(0.5, 0, 0.3, 1) forwards; }
  .mark-list.in .mark-item:nth-child(1) .mark-pen path { animation-delay: 0.3s; }
  .mark-list.in .mark-item:nth-child(2) .mark-pen path { animation-delay: 0.9s; }
  .mark-list.in .mark-item:nth-child(3) .mark-pen path { animation-delay: 1.5s; }
  .mark-list.in .mark-item:nth-child(4) .mark-pen path { animation-delay: 2.1s; }
  .mark-list.in .mark-item:nth-child(5) .mark-pen path { animation-delay: 2.7s; }
  .mark-list.in .mark-item:nth-child(6) .mark-pen path { animation-delay: 3.3s; }
  @keyframes mark-ring { to { stroke-dashoffset: 0; } }
}
@media (prefers-reduced-motion: reduce) { .mark-pen path { stroke-dashoffset: 0; } }
@media (max-width: 900px) { .mark-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .mark-list { grid-template-columns: 1fr; } }

/* A specification table — the sections of an exam. Scrolls sideways inside
   its own box on a phone rather than pushing the page out. */
.table-scroll { overflow-x: auto; margin-top: clamp(1.5rem, 3vw, 2.25rem); }
.spec-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.98rem;
  text-align: center;
}
.spec-table th, .spec-table td { padding: 0.9rem 1rem; border-bottom: 1px solid var(--rule); }
/* The outer cells sit flush with the column's edges, so the table's own
   margins match the paragraphs above it. */
.spec-table th:first-child, .spec-table td:first-child { padding-left: 0; text-align: left; }
.spec-table th:last-child, .spec-table td:last-child { padding-right: 0; text-align: right; }
.spec-table thead th { color: var(--red); font-weight: 600; border-bottom: 1px solid var(--red); white-space: nowrap; }
.spec-table tbody th { text-align: left; font-weight: 500; color: var(--ink); white-space: nowrap; }
.spec-table td { color: var(--ink-soft); }
/* A week-by-week overview: the number in a narrow first column, the
   description read from the left like prose. */
.spec-table--overview th:first-child, .spec-table--overview td:first-child { width: 4.5rem; white-space: nowrap; }
.spec-table--overview th:last-child, .spec-table--overview td:last-child { text-align: left; padding-right: 0; line-height: 1.55; }
.spec-table--overview td strong { color: var(--ink); font-weight: 600; }
/* Numbered steps read down the page, the number set in the accent. */
.step-list {
  list-style: none;
  counter-reset: step;
  margin: 0 auto;
  padding: 0;
  max-width: 44rem;
  text-align: left;
}
.step-list li {
  counter-increment: step;
  position: relative;
  padding: 0.9rem 0 0.9rem 3.2rem;
  border-bottom: 1px solid var(--rule);
  color: var(--ink-soft);
  line-height: 1.6;
}
.step-list li:last-child { border-bottom: 0; }
.step-list li::before {
  content: counter(step, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 0.95rem;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--red);
}
.step-list li strong { color: var(--ink); font-weight: 600; }

/* The extra services: four cards across, two on a tablet, one on a phone.
   Cards read from the left; the block of paragraphs inside them does too. */
.feature-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1rem;
  margin-top: 0;
  text-align: left;
  /* Every card in a row the same height: the words are cut to six lines and
     a Read more (main.js shows it only where the cut bites) lets a card
     open to its full length. The button sits on one line across the row. */
  align-items: stretch;
}
.feature-grid .subject { display: flex; flex-direction: column; }
.feature-grid .subject h3 { font-size: 1.08rem; margin-bottom: 0.6rem; }
.feature-grid .subject p { font-size: 0.93rem; line-height: 1.6; color: var(--ink-soft); }
.feature-grid .subject p + p { margin-top: 0.8rem; }

.clamp-body {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 6;
  overflow: hidden;
}
.is-open .clamp-body { -webkit-line-clamp: unset; overflow: visible; }

.clamp-toggle {
  align-self: flex-start;
  margin-top: auto;
  padding: 0.6rem 0 0;
  border: 0;
  background: none;
  color: var(--red);
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.clamp-toggle[hidden] { display: none; }
@media (max-width: 1100px) { .feature-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.feature-grid--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 900px) { .feature-grid--three { grid-template-columns: 1fr; } }
.feature-grid .check-list { margin-top: 0; }
.subject-time {
  display: inline-block;
  margin-left: 0.4em;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
  vertical-align: middle;
}
@media (max-width: 640px) { .feature-grid { grid-template-columns: 1fr; } }

/* At a glance, as a strip of four tiles at the top of the body — the first
   thing under the hero, before the words. They sat in the hero for a day,
   which made it a screen and a half tall. */
.glance-strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.75rem;
  max-width: 62rem;
  margin: 0 auto clamp(2rem, 4vw, 3rem);
  padding: 0;
}

.glance-tile {
  margin: 0;
  padding: 1rem 1.15rem;
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  text-align: left;
}

.glance-tile dt {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--red);
  margin-bottom: 0.35rem;
}

.glance-tile dd {
  margin: 0;
  font-weight: 700;
  font-size: 1.02rem;
  line-height: 1.4;
  color: var(--ink);
}

/* The paragraph that opens the body is the one sentence most readers will
   take, so it is set a size up from the rest, and it sits on the page's
   centre line with everything else. */
.course-copy { text-align: center; }

.course-lead {
  font-size: 1.12rem;
  line-height: 1.7;
  color: var(--ink-soft);
  max-width: 70ch;
  margin: 0 auto;
}

.course-lead + .course-lead { margin-top: 1.1rem; }

/* Subject cards read from the left even on a centred page — a description
   is a paragraph, and centred paragraphs are hard to read. */
.course-copy .subject { text-align: left; }

/* A tinted band for the part of a course a parent is actually weighing up —
   what a term involves, what their child gets out of it. */
.band-soft {
  background: var(--red-soft);
  border-block: 1px solid var(--grid);
}

/* The heading on that band, sized as a section heading rather than a
   sub-label, with a short rule under it. */
.course-sub--big {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3vw, 2.15rem);
  font-weight: 700;
  letter-spacing: -0.015em;
  text-transform: none;
  color: var(--ink);
  text-align: center;
  margin: 0 0 1.25rem;
}

/* No rule under it. A short coloured bar under every heading is the kind of
   flourish that makes a page look generated rather than written. */
.course-sub--big::after { display: none; }

.course-sub--big + .course-sub--big,
p + .course-sub--big,
.check-list + .course-sub--big { margin-top: 2.5rem; }

/* Compound selectors on purpose: .check-list's own `margin: 1.25rem 0 0`
   comes later in the file and, as a shorthand, would reset the auto side
   margins that centre these — which is how the STTC list ended up hanging
   off the left of a centred heading. */
.check-list.check-list--centred {
  max-width: 46rem;
  margin-inline: auto;
}

/* Two columns of ticks on a wide screen, so a list of six does not run a
   column down the middle of an empty band. */
.check-list.check-list--two {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem 2rem;
  max-width: 62rem;
  margin-inline: auto;
}

/* The paragraph under the two columns keeps to their width, and reads from
   the left like them. */
.band-soft .course-copy p { max-width: 62rem; margin: 1.5rem auto 0; color: var(--ink-soft); text-align: left; }
.band-soft .course-copy .band-col p { margin: 0 0 0.5rem; max-width: none; }
/* Except the lead under a centred heading, which the rule above was pulling
   to the left edge, and a closing note the office asked to see centred. */
.band-soft .course-copy .section-head--centred .section-lead,
.band-soft .course-copy p.band-note--centred { text-align: center; }

/* The line that introduces a table, read as a caption to it: a size up
   and bold. */
.course-lead.course-lead--strong {
  margin-top: 1.75rem;
  font-size: 1.3rem;
  font-weight: 700;
  color: var(--ink);
}

/* Two lists side by side as a pair of cards, each with its heading set
   smaller and to the left, so the band is half the height it was as one
   column. */
.band-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.25rem;
  max-width: 62rem;
  margin-inline: auto;
  text-align: left;
}

.band-col {
  background: rgba(255, 255, 255, 0.72);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1.5rem 1.6rem;
}
/* Lifts under the pointer like the course cards. */
.band-col { transition: box-shadow 0.2s ease, border-color 0.2s ease, transform 0.2s ease; }
.band-col:hover { box-shadow: var(--shadow); border-color: var(--red); }

.band-col .course-sub--big {
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  color: var(--red);
  text-align: left;
  margin-bottom: 1rem;
}

.band-col .check-list { margin-top: 0; }

@media (max-width: 640px) {
  .band-cols { grid-template-columns: 1fr; }
}

/* ---------- Fees and timetable ---------- */

/* This term's fees beside this term's timetable, so a parent reads the price
   next to the thing it buys. The figures and the picture are set in /admin/
   → Course pages; what is written in the page is the fallback. */
.fees-grid {
  display: grid;
  /* The fee card a third narrower than it was, the timetable taking the
     room — the picture is the thing a parent squints at (September 2026
     notes). Roughly 30 : 70. */
  grid-template-columns: minmax(16rem, 0.6fr) minmax(0, 1.4fr);
  gap: clamp(1.5rem, 3vw, 2.5rem);
  /* The two boxes are one height: the timetable's box stretches to the fee
     card and the picture sits centred in it, the caption on the floor. */
  align-items: stretch;
}

.fee-card {
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: clamp(1.5rem, 3vw, 2.25rem);
}

.fee-card .section-title { font-size: clamp(1.4rem, 2.6vw, 1.9rem); margin-bottom: 0.3em; }
.fee-card .eyebrow { margin-bottom: 0.4rem; }

.fee-rows { margin: 1.25rem 0 0; }

.fee-rows > div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.85rem 0;
  margin: 0;
  border-bottom: 1px solid var(--rule);
}

.fee-rows > div:last-child { border-bottom: 0; }
.fee-rows > div[hidden] { display: none; }

.fee-rows dt { color: var(--ink-soft); font-size: 0.95rem; }
.fee-rows dd { margin: 0; text-align: right; font-size: 0.95rem; color: var(--ink-soft); }

.fee-rows dd strong {
  font-family: var(--font-display);
  font-size: 1.55rem;
  font-weight: 800;
  color: var(--red);
  margin-right: 0.3rem;
  font-variant-numeric: tabular-nums;
}

.fee-note {
  margin: 1.1rem 0 0;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--ink-soft);
}

.fee-note a { color: var(--red); font-weight: 500; }

/* The one action on the fee card: full width of the card, centred, a size
   up from the other buttons, pushed to the card's foot. */
.fee-cta { margin-top: auto; padding-top: 1.75rem; text-align: center; }
.fee-cta .btn {
  width: 100%;
  justify-content: center;
  font-size: 1.1rem;
  padding: 0.95em 1.5em;
}
.fee-card { display: flex; flex-direction: column; }

.timetable-figure {
  margin: 0;
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.75rem;
}

.timetable-figure { display: flex; flex-direction: column; }
.timetable-figure a { display: flex; flex: 1 1 auto; align-items: center; }

.timetable-figure img {
  width: 100%;
  height: auto;
  border-radius: calc(var(--radius) - 4px);
}

.timetable-figure figcaption {
  margin-top: 0.7rem;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  color: var(--ink-soft);
}

/* ---------- Fee schedule (9 September 2026) ----------
   The office's own fee table, as they publish it: a title, a bordered grid
   with a grey heading row, the fine print, and the Enrol button. The
   timetable picture sits under it at the full column width — the picture is
   the thing a parent squints at, so it gets the room. .fees-grid above is no
   longer used by the course pages; it stays for anything else that has it. */
.fee-sheet {
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: clamp(1.25rem, 3vw, 2rem);
  margin: 0 auto clamp(1.5rem, 3vw, 2.25rem);
}
.fee-sheet-title {
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 2vw, 1.4rem);
  font-weight: 700;
  text-align: center;
  margin: 0 0 1rem;
  color: var(--ink);
}
.fee-sheet .table-scroll { margin-top: 0; }
.fee-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.95rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.fee-table th, .fee-table td {
  border: 1px solid #b9c2d0;
  padding: 0.6rem 0.65rem;
  vertical-align: middle;
  line-height: 1.35;
}
.fee-table thead th {
  background: #eef0f3;
  color: var(--ink);
  font-weight: 700;
  font-size: 0.88rem;
}
.fee-table tbody th {
  font-weight: 600;
  color: var(--ink);
  background: var(--paper);
}
.fee-table td { color: var(--ink-soft); }
/* The last two columns are the prices a parent actually pays; the discount
   ones read a little heavier, as they do on the office's sheet. */
.fee-table td:nth-last-child(-n+2) { font-weight: 600; color: var(--ink); }
.fee-sheet .fee-note { max-width: 70ch; margin-inline: auto; text-align: center; }
.fee-sheet .fee-cta { margin-top: 1.25rem; padding-top: 0; }
.fee-sheet .fee-cta .btn { width: auto; min-width: 16rem; }
.timetable-figure--wide { max-width: 64rem; margin-inline: auto; }
.timetable-figure--wide + .timetable-empty { max-width: 64rem; margin-inline: auto; }
@media (max-width: 720px) {
  .fee-table { font-size: 0.85rem; }
  .fee-table th, .fee-table td { padding: 0.45rem 0.4rem; }
  .fee-table thead th { font-size: 0.72rem; }
}

/* Shown in the timetable's place while no picture has been uploaded for
   the page. */
.timetable-empty {
  margin: 0;
  padding: 2rem 1.5rem;
  border: 1.5px dashed var(--rule);
  border-radius: var(--radius);
  text-align: center;
  color: var(--ink-soft);
  font-size: 0.95rem;
}

.timetable-empty a { color: var(--red); font-weight: 500; }

/* One or two cards on the "other course" row, held to half the width each
   so a lone card is not stretched across the page. */
.card-grid--pair { --cols: 2; max-width: 52rem; margin-inline: auto; }

@media (max-width: 980px) {
  .glance-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .fees-grid { grid-template-columns: 1fr; }
}

/* ---------- Course detail blocks ---------- */

/* ==========================================================================
   Courses page

   Courses here are year ranges that overlap — a Year 4 student sits in WEMT
   and OCTTC in the same week. The page opens on a scale that shows that, then
   splits into two acts: taught programs on paper, trial tests in the exam
   hall's own navy.
   ========================================================================== */

/* ---------- The year ruler ---------- */

.ruler-section { padding-bottom: 0; }

.ruler {
  --ruler-name: clamp(58px, 13vw, 96px);
  --ruler-gap: 4px;
  padding: clamp(1.25rem, 3.5vw, 2rem);
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

.ruler-scale,
.ruler-row {
  display: grid;
  grid-template-columns: var(--ruler-name) repeat(6, minmax(0, 1fr));
  gap: var(--ruler-gap);
  align-items: center;
}

.ruler-scale {
  padding-bottom: 0.5rem;
  border-bottom: 2px solid var(--ink);
}

.ruler-label,
.ruler-tick {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.ruler-tick {
  position: relative;
  text-align: center;
  color: var(--ink);
  font-weight: 500;
}

/* Tick marks dropping off the scale line, like the edge of a ruler. */
.ruler-tick::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -0.5rem;
  width: 1px;
  height: 6px;
  background: var(--rule);
}

.ruler-rows {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
}

.ruler-row {
  padding: 0.5rem 0;
  border-radius: 6px;
  text-decoration: none;
  transition: background 0.15s;
}

.ruler-row:hover { background: var(--paper); }

/* The course's full name, on a card that rises over the row while the
   pointer (or keyboard focus) is on it — the abbreviation in the first
   column is what fits, the name is what a parent needs. */
.ruler-row { position: relative; }

.ruler-row[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  left: 0;
  bottom: calc(100% + 8px);
  z-index: 5;
  width: max-content;
  max-width: min(32rem, 90vw);
  padding: 0.55rem 0.85rem;
  border-radius: 8px;
  background: var(--ink);
  color: var(--white);
  font-family: var(--font-body);
  font-size: 0.88rem;
  font-weight: 500;
  line-height: 1.4;
  white-space: normal;
  box-shadow: 0 8px 24px rgba(22, 36, 61, 0.18);
  opacity: 0;
  translate: 0 4px;
  pointer-events: none;
  transition: opacity 0.15s ease, translate 0.15s ease;
}

/* The little point under the card, aimed at the name. */
.ruler-row[data-tip]::before {
  content: "";
  position: absolute;
  left: 1.1rem;
  bottom: calc(100% + 2px);
  z-index: 6;
  border: 6px solid transparent;
  border-top-color: var(--ink);
  border-bottom: 0;
  opacity: 0;
  translate: 0 4px;
  pointer-events: none;
  transition: opacity 0.15s ease, translate 0.15s ease;
}

.ruler-row[data-tip]:hover::after,
.ruler-row[data-tip]:hover::before,
.ruler-row[data-tip]:focus-visible::after,
.ruler-row[data-tip]:focus-visible::before {
  opacity: 1;
  translate: 0 0;
}

.ruler-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--ink);
}

/* The band sits on the year columns it covers: column 1 is the name, so a
   course starting in Year N begins at grid column N + 1. */
.ruler-band {
  grid-column: calc(var(--from) + 1) / span var(--span);
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 34px;
  padding: 0 0.5rem;
  border-radius: 5px;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  white-space: nowrap;
  transition: transform 0.15s;
}

.ruler-row:hover .ruler-band { translate: 0 -1px; }

/* Solid = we teach it. Hatched = the student sits a paper. The fill carries
   the distinction the old flat list buried. */
.ruler-band.is-taught {
  background: var(--ink);
  color: var(--white);
}

.ruler-band.is-trial {
  background:
    repeating-linear-gradient(45deg,
      var(--red-soft) 0 6px,
      #F1D9D6 6px 12px);
  border: 1px solid var(--red);
  color: #8C2A24;
}

/* A course that starts and ends mid-year. It spans two whole year columns, then
   pulls in a quarter of that width each side — a quarter of two years is half a
   year, so the band lands on the second half of the first year and the first
   half of the second. */
.ruler-band--mid { margin-inline: 25%; }
/* A course that starts halfway through its only year, and runs to the end. */
.ruler-band--late { margin-left: 50%; }

/* On narrow columns the label would overflow its band — the row name and the
   position on the scale already say which years it is. */
.ruler-band-text { overflow: hidden; text-overflow: ellipsis; }

.ruler-key {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 1.2rem;
  margin: 1.25rem 0 0;
  padding-top: 1rem;
  border-top: 1px dashed var(--grid);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.key-item {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}

.key-swatch {
  width: 22px;
  height: 12px;
  flex: none;
  border-radius: 3px;
}

.key-swatch.is-taught { background: var(--ink); }

.key-swatch.is-trial {
  background: repeating-linear-gradient(45deg, var(--red-soft) 0 4px, #F1D9D6 4px 8px);
  border: 1px solid var(--red);
}

.ruler-note { text-align: center; }
.ruler-note {
  margin: 1rem 0 0;
  font-size: 0.88rem;
  color: var(--ink-soft);
}

/* The notice is a footnote to both acts, so it sits closer than a full
   section's worth of air would put it. */
.notice-section { padding-top: clamp(2rem, 5vw, 3rem); }

.ruler-note a { color: var(--red); font-weight: 500; }

/* ---------- Acts ---------- */

/* Centred, the index over the words, so the two acts read as chapter
   openings down the middle of the page rather than as a margin note beside a
   left-hung block. */
.act-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.75rem;
  margin-bottom: clamp(1.75rem, 4vw, 2.75rem);
}

.act-index {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 7vw, 4rem);
  font-weight: 800;
  line-height: 0.8;
  color: var(--red);
}

.act-head .section-head,
.act-head > div > * { margin-inline: auto; text-align: center; }
.act-head .section-title { max-width: none; }
.act-head .section-lead { max-width: 60ch; }
.act-head .section-lead--wide { max-width: none; }

.act-trial {
  background: var(--navy-deep);
  color: rgba(255, 255, 255, 0.82);
}

.act-trial .section-title { color: var(--white); }
.act-trial .section-lead { color: rgba(255, 255, 255, 0.72); }
.act-trial .act-index { color: rgba(255, 255, 255, 0.45); }
.act-trial .eyebrow { color: #E9A9A3; }

/* ---------- Google reviews ---------- */

/* Three at a time on a wide screen, and the shelf carries the rest. */
/* ---------- Review wall ---------- */
/* Three columns of cards drifting upward, fading in at the top and out at
   the bottom. Each column's track is doubled by main.js and slides to -50%,
   which is exactly one copy, so the loop is seamless. The columns start at
   different points and run at slightly different speeds so they never move
   in step. */
.review-wall {
  --wall-h: 40rem;
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.25rem;
  height: var(--wall-h);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 12%, #000 88%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 12%, #000 88%, transparent 100%);
}
.review-wall .review-source { display: none; }
.review-col { position: relative; overflow: hidden; }
.review-col-track {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  padding-bottom: 1.25rem;
  will-change: transform;
  animation: review-drift var(--wall-duration, 40s) linear infinite;
}
.review-col:nth-child(2) .review-col-track { animation-duration: calc(var(--wall-duration, 40s) * 1.18); animation-delay: -12s; }
.review-col:nth-child(3) .review-col-track { animation-duration: calc(var(--wall-duration, 40s) * 0.92); animation-delay: -6s; }
.review-wall:hover .review-col-track { animation-play-state: paused; }
@keyframes review-drift { to { transform: translateY(-50%); } }

/* In the wall a card shows its whole review — nothing to open. */
.review-wall .review-card { flex: none; }
.review-wall .review-body { display: block; -webkit-line-clamp: unset; overflow: visible; }
.review-wall .review-toggle { display: none; }

@media (max-width: 759px) {
  .review-wall { grid-template-columns: 1fr; --wall-h: 32rem; }
}
@media (prefers-reduced-motion: reduce) {
  .review-col-track { animation: none; }
  .review-wall { height: auto; -webkit-mask-image: none; mask-image: none; }
}

.rail--reviews { --rail-card: 32%; }

/* Over a photo an arrow sitting half on the first card reads as an overlay.
   Over a paragraph it covers words, so this shelf is inset and the arrows
   take the gutter either side — nothing scrolls underneath them. The gutter
   is the 46px button plus an even 1.1rem of air on each side. */
.rail--reviews { padding-inline: 4rem; }
.rail--reviews .rail-nav--prev { left: 0; }
.rail--reviews .rail-nav--next { right: 0; }

.review-card {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 1.4rem 1.5rem;
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  quotes: none;
}

/* Reviews run from four words to three paragraphs. Clamping the body keeps
   every card on the shelf the same height; main.js unhides the toggle only on
   the ones the clamp actually cuts, so a short review has no dead button. */
.review-body {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 7;
  overflow: hidden;
}

.review-card.is-open .review-body {
  -webkit-line-clamp: unset;
  overflow: visible;
}

.review-body p {
  margin-bottom: 0.8rem;
  color: var(--ink-soft);
  font-size: 0.97rem;
}

.review-body p:last-child { margin-bottom: 0; }

.review-toggle {
  align-self: flex-start;
  margin-top: 0.6rem;
  padding: 0;
  border: 0;
  background: none;
  color: var(--red);
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* The byline is pinned to the bottom, so it lines up across the shelf however
   much text sits above it. */
.review-card footer {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin-top: auto;
  padding-top: 0.9rem;
  border-top: 1px solid var(--rule);
}

/* No avatars are stored; the initial stood in for one, and was read as a
   badge with nothing behind it. The name is enough. */
.review-avatar {
  display: none;
  flex: none;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--red-soft);
  color: var(--red);
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1;
}

.review-who { display: flex; flex-direction: column; min-width: 0; }

.review-name { font-weight: 600; }

.review-meta {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  color: var(--ink-soft);
}

.review-more { margin: clamp(1.5rem, 3vw, 2.5rem) 0 0; text-align: center; }

/* Google's own mark beside the heading, and a small one on every card, so
   the reviews are seen to come from somewhere rather than to have been
   typed in. The colours are Google's. */
.google-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0 0 1rem;
  padding: 0.45rem 0.95rem 0.45rem 0.6rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--white);
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--ink);
}

.google-badge svg { width: 22px; height: 22px; flex: none; }

.section-head--centred .google-badge { margin-inline: auto; }

.review-card footer .review-g {
  margin-left: auto;
  width: 22px;
  height: 22px;
  flex: none;
  opacity: 0.9;
}

/* ---------- Exam stat row ---------- */

.exam-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px;
  margin: 0 0 clamp(2rem, 4vw, 3rem);
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius);
  overflow: hidden;
}

.exam-stats > div {
  padding: 1.15rem 1.25rem;
  background: var(--navy-deep);
}

.exam-stats dt {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
}

/* An i in a ring after a label; the explanation comes up over it while the
   pointer or keyboard focus rests on it. Sized in em so it follows whatever
   label it sits after. */
.info-tip {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: 1.5em;
  height: 1.5em;
  margin-left: 0.2em;
  border: 1px solid currentColor;
  border-radius: 50%;
  font-family: var(--font-body);
  font-size: 0.85em;
  font-weight: 700;
  font-style: italic;
  line-height: 1;
  text-transform: none;
  letter-spacing: 0;
  vertical-align: middle;
  cursor: help;
  opacity: 0.75;
  transition: opacity 0.15s ease;
}
.info-tip:hover, .info-tip:focus-visible { opacity: 1; outline: none; }

.info-tip::after {
  content: attr(data-tip);
  position: absolute;
  left: 50%;
  bottom: calc(100% + 10px);
  z-index: 5;
  width: max-content;
  max-width: min(22rem, 80vw);
  padding: 0.6rem 0.85rem;
  border-radius: 8px;
  background: var(--ink);
  color: var(--white);
  font-family: var(--font-body);
  font-size: 0.85rem;
  font-weight: 500;
  font-style: normal;
  line-height: 1.45;
  letter-spacing: 0;
  text-transform: none;
  text-align: left;
  white-space: normal;
  box-shadow: 0 8px 24px rgba(22, 36, 61, 0.18);
  opacity: 0;
  translate: -50% 4px;
  pointer-events: none;
  transition: opacity 0.15s ease, translate 0.15s ease;
}
.info-tip::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: calc(100% + 4px);
  z-index: 6;
  border: 6px solid transparent;
  border-top-color: var(--ink);
  border-bottom: 0;
  opacity: 0;
  translate: -50% 4px;
  pointer-events: none;
  transition: opacity 0.15s ease, translate 0.15s ease;
}
.info-tip:hover::after, .info-tip:hover::before,
.info-tip:focus-visible::after, .info-tip:focus-visible::before { opacity: 1; translate: -50% 0; }

/* The stat tiles clip nothing, so the card can rise out of the tile. */
.exam-stats, .exam-stats > div { overflow: visible; }
/* Above the heading before it, or the card is drawn behind that block. */
.exam-stats { position: relative; z-index: 3; }
.exam-stats dd { color: var(--red); }
.exam-stats dd span { color: rgba(255, 255, 255, 0.6); }

.exam-stats dd {
  margin: 0.35rem 0 0;
  font-family: var(--font-display);
  font-size: 1.9rem;
  font-weight: 800;
  line-height: 1;
  color: var(--white);
}

.exam-stats dd span {
  display: inline-block;
  margin-left: 0.35rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: 0.04em;
  color: #E9A9A3;
}

/* ---------- Course blocks ---------- */

/* Each course collapses to its masthead. Five courses of full prose made a
   page nobody scrolled to the end of; closed, the whole catalogue fits on a
   screen and a parent opens only the one they came for. Built on <details>
   so it works with a keyboard and with find-in-page. */
.course-detail {
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0;
  margin-bottom: 0.85rem;
  scroll-margin-top: calc(var(--header-h) + 20px);
  transition: border-color 0.15s, box-shadow 0.15s;
}

.course-detail[open] {
  border-color: var(--ink);
  box-shadow: var(--shadow);
}

.act-trial .course-detail {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.16);
}

.act-trial .course-detail[open] {
  border-color: rgba(255, 255, 255, 0.45);
  background: rgba(255, 255, 255, 0.07);
}

/* A ruled masthead, the way a paper names itself above the first question. */
.course-detail-head {
  position: relative;
  display: block;
  padding: clamp(1.15rem, 3vw, 1.6rem) clamp(3.2rem, 6vw, 4rem) clamp(1.15rem, 3vw, 1.6rem) clamp(1.25rem, 4vw, 2.5rem);
  cursor: pointer;
  list-style: none;
}

.course-detail-head::-webkit-details-marker { display: none; }

.course-detail-head:hover .course-detail-head-title,
.course-detail-head:hover h2 { color: var(--red); }

.act-trial .course-detail-head:hover h2 { color: #F3BDB8; }

.course-detail[open] .course-detail-head {
  border-bottom: 2px solid var(--ink);
}

.act-trial .course-detail[open] .course-detail-head { border-bottom-color: rgba(255, 255, 255, 0.35); }

.course-detail-head .course-tag { display: inline-block; margin-bottom: 0.6rem; }

.course-toggle {
  position: absolute;
  top: 50%;
  right: clamp(1rem, 3vw, 1.5rem);
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--paper);
  border: 1px solid var(--rule);
  color: var(--ink);
  transition: transform 0.2s, background 0.15s;
}

.act-trial .course-toggle {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.25);
  color: var(--white);
}

.course-detail[open] .course-toggle { transform: translateY(-50%) rotate(180deg); }

.course-detail-head:hover .course-toggle { background: var(--red-soft); }
.act-trial .course-detail-head:hover .course-toggle { background: rgba(255, 255, 255, 0.18); }

.course-body {
  padding: clamp(1.25rem, 3.5vw, 2rem) clamp(1.25rem, 4vw, 2.5rem) clamp(1.5rem, 4vw, 2.5rem);
}

.course-body > p:first-child { margin-top: 0; }

/* A tick list carries no bottom margin, so prose after one would sit right up
   against the last item. */
.course-body .check-list + p { margin-top: 1.4rem; }

.course-detail-head h2 {
  margin: 0;
  font-size: clamp(1.25rem, 2.8vw, 1.7rem);
  letter-spacing: -0.02em;
  transition: color 0.15s;
}

.act-trial .course-detail-head h2 { color: var(--white); }

.course-deck {
  margin: 0.4rem 0 0;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  letter-spacing: 0.02em;
  color: var(--ink-soft);
}

.act-trial .course-deck { color: rgba(255, 255, 255, 0.6); }

.course-body > p { color: var(--ink-soft); max-width: 66ch; }
.act-trial .course-body > p { color: rgba(255, 255, 255, 0.78); }
.act-trial .course-detail strong { color: var(--white); }

.course-sub {
  font-size: 0.78rem;
  font-family: var(--font-mono);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--red);
  margin-top: 1.9rem;
  margin-bottom: 0.4rem;
}

.act-trial .course-sub { color: #E9A9A3; }

/* Two, three or four subjects depending on the course, across the full width
   of a course page rather than a narrow column — so the count decides the
   columns instead of a fixed pair leaving the odd one out on its own row. */
.subject-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: 1rem;
  margin-top: 1.75rem;
}

.subject {
  position: relative;
  border: 1px solid var(--rule);
  border-radius: 6px;
  padding: 1.1rem 1.25rem 1.1rem 1.4rem;
  background: var(--paper);
  overflow: hidden;
}

/* The margin rule down an answer booklet. */
.subject::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--red);
}

.subject h3 { font-size: 1rem; margin-bottom: 0.3em; }
.subject p { color: var(--ink-soft); font-size: 0.9rem; margin: 0; }

.act-trial .check-list li { color: rgba(255, 255, 255, 0.78); }
.act-trial .check-list li::before { color: #E9A9A3; }
.act-trial .course-detail a { color: #F3BDB8; }

.check-list {
  list-style: none;
  padding: 0;
  margin: 1.25rem 0 0;
  display: grid;
  gap: 0.6rem;
}

/* The tick is positioned, not a flex item. A flex row turns each inline
   child into an item of its own, so a line with a link in it became three
   boxes shrinking side by side and the link broke in the middle of a word. */
.check-list li {
  position: relative;
  padding-left: 1.5rem;
  color: var(--ink-soft);
  font-size: 0.97rem;
  text-align: left;
  min-width: 0;
  overflow-wrap: break-word;
}

.check-list li::before {
  content: "✓";
  position: absolute;
  left: 0;
  top: 0;
  color: var(--red);
  font-weight: 700;
  line-height: 1.5;
}

/* ==========================================================================
   Blog

   These pages are written as real HTML by scripts/build-blog.js, so an article
   is in the source of its own URL. The measure is deliberately narrow — this
   is the only part of the site meant to be read straight through.
   ========================================================================== */

.container--narrow { max-width: 720px; }

.post-head { padding: clamp(2rem, 6vw, 3.5rem) 0 1.5rem; }

.post-crumbs {
  margin-bottom: 1rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.post-crumbs a { color: var(--red); text-decoration: none; }
.post-crumbs a:hover { text-decoration: underline; }
.post-crumbs [aria-current="page"] { color: var(--ink); }
.page-hero--photo .post-crumbs [aria-current="page"] { color: rgba(255, 255, 255, 0.92); }

/* ---------- Back to top ----------
   Added to every page by main.js. A small pill in the corner that fades in
   once the reader is a screen down. */
.to-top {
  position: fixed;
  right: clamp(1rem, 3vw, 1.75rem);
  bottom: clamp(1rem, 3vw, 1.75rem);
  z-index: 60;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.6rem 0.95rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--white);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1;
  box-shadow: 0 6px 18px rgba(20, 33, 61, 0.14);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px);
  transition: opacity 0.2s, transform 0.2s, visibility 0s linear 0.2s;
}
.to-top.is-shown { opacity: 1; visibility: visible; transform: none; transition: opacity 0.2s, transform 0.2s; }
.to-top:hover, .to-top:focus-visible { color: var(--red); border-color: var(--red); }
.to-top svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
@media (prefers-reduced-motion: reduce) { .to-top, .to-top.is-shown { transition: none; } }

.post-head h1 {
  font-size: clamp(1.9rem, 5vw, 3rem);
  letter-spacing: -0.025em;
  margin-bottom: 0.5rem;
}

.post-standfirst {
  font-size: clamp(1.05rem, 2.2vw, 1.25rem);
  line-height: 1.55;
  color: var(--ink-soft);
  margin-bottom: 1.2rem;
}

.post-byline {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1rem;
  margin: 0;
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
}

.post-cover {
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  margin-bottom: clamp(1.5rem, 4vw, 2.5rem);
}

/* ---------- Article body ---------- */

.post-body {
  font-size: 1.06rem;
  line-height: 1.75;
  color: var(--ink);
}

.post-body > * + * { margin-top: 1.15em; }

.post-body h2 {
  font-size: clamp(1.35rem, 3vw, 1.7rem);
  margin-top: 2.2em;
  margin-bottom: 0.1em;
  letter-spacing: -0.015em;
}

.post-body h3 {
  font-size: 1.18rem;
  margin-top: 1.9em;
  margin-bottom: 0.1em;
}

.post-body h4 {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--red);
  margin-top: 1.8em;
  margin-bottom: 0.1em;
}

.post-body a { color: var(--red); text-underline-offset: 3px; }

.post-body ul, .post-body ol { padding-left: 1.3em; }
.post-body li + li { margin-top: 0.4em; }

.post-body img {
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  border: 1px solid var(--rule);
}

.post-body blockquote {
  margin: 1.6em 0;
  padding: 0.2em 0 0.2em 1.3em;
  border-left: 3px solid var(--red);
  color: var(--ink-soft);
  font-size: 1.1em;
}

.post-body blockquote p { margin: 0; }

.post-body code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  padding: 0.1em 0.35em;
  border-radius: 4px;
  background: var(--grid);
}

.post-body pre {
  overflow-x: auto;
  padding: 1rem 1.2rem;
  border-radius: var(--radius);
  background: var(--navy-deep);
  color: #E8EDF5;
}

.post-body pre code { background: none; padding: 0; font-size: 0.85rem; }

.post-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 2.5rem 0 0;
  padding: 0;
  list-style: none;
}

.post-tags li {
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--ink-soft);
}

.post-back {
  margin: 2.5rem 0 clamp(2rem, 6vw, 4rem);
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule);
}

.post-back a { color: var(--red); font-weight: 500; }

/* ---------- Listing ---------- */

/* Cards in columns, not one wide row each: a year of articles stacked full
   width is a page you scroll rather than a page you scan. auto-fill lets the
   listing use whatever room it has — three across on a desktop, two on a
   tablet, one on a phone — and `min()` keeps the track from outgrowing a narrow
   screen. */
.post-grid {
  display: grid;
  gap: 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
}

.post-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: 1rem;
  padding: 1.25rem;
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  transition: border-color 0.15s, box-shadow 0.15s;
}

.post-card:hover { border-color: var(--ink); box-shadow: var(--shadow); }

.post-card-media { display: block; }

.post-card-media img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 8px;
}

.post-card-date {
  margin: 0 0 0.35rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  color: var(--ink-soft);
}

/* h2 on the blog list, where the card is the page's main content; h3 on the
   home page, where the section already owns the h2. Same card either way. */
.post-card h2,
.post-card h3 { font-size: clamp(1.15rem, 2.5vw, 1.4rem); margin-bottom: 0.4em; }
.post-card h2 a,
.post-card h3 a { color: var(--ink); text-decoration: none; }
.post-card:hover h2 a,
.post-card:hover h3 a { color: var(--red); }
.post-card p { margin: 0; color: var(--ink-soft); font-size: 0.95rem; }

/* A card shows the gist and the page has the rest: two lines of title, two
   of summary, and the row of cards stays one height. The full text is still
   in the markup for search engines and screen readers. */
.post-card h2,
.post-card h3,
.post-card p {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.post-card h2,
.post-card h3 { -webkit-line-clamp: 2; }
.post-card p { -webkit-line-clamp: 2; }

/* The listing page has room for a third line of summary. */
#blogGrid .post-card p { -webkit-line-clamp: 3; }

/* Three fixed tracks on the home page rather than the listing's auto-fill,
   which would stretch a lone card across the whole container on a quiet week. */
.post-grid--home { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.home-posts-more,
.home-faq-more { margin: 1.75rem 0 0; }

.home-faq-more { text-align: center; }

/* A section on a photograph under the navy wash — the same treatment as the
   page heroes, for a band in the middle of a page. The picture is named
   inline on the section; the panel on it stays white and readable. */
.section--photo {
  position: relative;
  overflow: hidden;
  background-color: #14213D;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  background-attachment: scroll;
  color: var(--white);
}

.section--photo::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(20, 33, 61, 0.82), rgba(20, 33, 61, 0.9));
  pointer-events: none;
}

.section--photo > .container { position: relative; z-index: 1; }

.section--photo .section-title { color: var(--white); }
.section--photo .section-lead { color: rgba(255, 255, 255, 0.85); }
.section--photo .btn-outline { color: var(--white); border-color: rgba(255, 255, 255, 0.6); }
.section--photo .btn-outline:hover { background: var(--white); color: #14213D; border-color: var(--white); }

/* The coloured word in the heading, and the badged card, on the navy. */
.section--photo .accent-word { color: #8FBBFF; }
.section--photo .course-card.is-featured { border-color: rgba(255, 255, 255, 0.4); }

/* Buttons inside the white cards keep their own colours; the white outline
   above is for buttons that sit directly on the photograph. */
.section--photo .course-card .btn-outline { color: var(--red); border-color: var(--red); }
.section--photo .course-card .btn-outline:hover { background: var(--red); color: var(--white); border-color: var(--red); }

/* On a phone the question rows tighten up and the answer runs full width. */
@media (max-width: 640px) {
  .faq-item summary { padding: 1rem 1.1rem; gap: 0.9rem; }
  .faq-item summary h2,
  .faq-item summary h3 { font-size: 1.02rem; }
  .faq-answer { padding: 0 1.1rem 1.2rem; }
}

/* ---------- Steps (new students) ---------- */

.steps { counter-reset: step; display: grid; gap: 1.25rem; }

.step {
  counter-increment: step;
  display: flex;
  gap: 1.5rem;
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1.6rem 1.75rem;
}

.step::before {
  content: counter(step, decimal-leading-zero);
  font-family: var(--font-mono);
  font-size: 1.5rem;
  font-weight: 500;
  color: var(--red);
  flex: none;
  line-height: 1.2;
}

.step h3 { font-size: 1.15rem; margin-bottom: 0.3em; }
.step p { color: var(--ink-soft); font-size: 0.95rem; }
.step .btn { margin-top: 0.9rem; }

/* The bank page's first step: label and value, one pair to a row. */
.account-details { display: grid; gap: 0.35rem; margin: 0.6rem 0 0; }
.account-details div { display: flex; flex-wrap: wrap; gap: 0 1rem; }
.account-details dt { color: var(--ink-soft); font-size: 0.95rem; min-width: 9rem; }
.account-details dd { margin: 0; color: var(--ink); font-family: var(--font-mono); font-weight: 500; }

/* ---------- Hours tables ---------- */

.hours-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.25rem;
}

.hours-card {
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}

/* Set at the newsletter list's sizes — the card title at its group title's
   1.2rem, the rows at its links' 1.08rem — so the two pages read alike. */
.hours-card h3 {
  margin: 0;
  padding: 1rem 1.5rem;
  background: var(--ink);
  color: var(--white);
  font-size: 1.2rem;
}

.hours-card.holiday h3 { background: var(--red); }

.hours-card table {
  width: 100%;
  border-collapse: collapse;
  font-size: 1.08rem;
}

.hours-card td {
  padding: 0.8rem 1.5rem;
  border-bottom: 1px solid var(--rule);
}

.hours-card tr:last-child td { border-bottom: none; }

.hours-card td:last-child {
  text-align: right;
  font-family: var(--font-mono);
}

.hours-card .closed td:last-child { color: var(--red); }

.map-embed {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow);
  margin-top: 2rem;
}

.map-embed iframe {
  display: block;
  width: 100%;
  height: 380px;
  border: 0;
}

/* ---------- Enquiry form + map ---------- */

.enquiry-layout {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(1.75rem, 4vw, 3rem);
  align-items: start;
}

.enquiry-form {
  background: var(--paper);
  border: 1px solid var(--grid);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: clamp(1.35rem, 3vw, 2rem);
}

.enquiry-solo { max-width: 640px; }

.enquiry-submit { width: 100%; margin-top: 0.35rem; }

.enquiry-fineprint {
  margin-top: 0.9rem;
  font-size: 0.85rem;
  color: var(--ink-soft);
  line-height: 1.6;
}

.enquiry-fineprint a { color: var(--red); font-weight: 500; }

.enquiry-status {
  margin-top: 1rem;
  padding: 0.85rem 1rem;
  border-radius: var(--radius);
  border-left: 3px solid #1F7A54;
  background: #E3F1EA;
  font-size: 0.92rem;
}

.enquiry-status.is-error {
  border-left-color: var(--red);
  background: var(--red-soft);
}

.enquiry-submit[disabled] { opacity: 0.65; cursor: progress; }

.enquiry-aside .map-embed { margin-top: 0; }
.enquiry-aside .map-embed iframe { height: 280px; }

/* Four tiles under the map rather than a list down its side: address and
   phone side by side, email and hours under them, each in a box of its own
   so a parent's eye lands on the one they came for. */
.visit-facts {
  margin: 1.25rem 0 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem;
}

.visit-facts > div {
  margin: 0;
  padding: 0.95rem 1.1rem;
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  min-width: 0;
  overflow-wrap: anywhere;
}

.visit-facts dt {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 0.2rem;
}

.visit-facts dd {
  margin: 0;
  font-size: 0.96rem;
  line-height: 1.6;
}

.visit-facts dd a { color: var(--red); font-weight: 500; }

/* The opening hours in their tile, a day per row with the time set against
   it, rather than three lines of dots. Filled by main.js from the same
   setting as the footer and the contact page. */
.hours-mini {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.92rem;
  line-height: 1.4;
}

.hours-mini td { padding: 0.28rem 0; border-bottom: 1px solid var(--rule); vertical-align: top; }
.hours-mini tr:last-child td { border-bottom: 0; }
.hours-mini td:first-child { color: var(--ink-soft); padding-right: 0.75rem; white-space: nowrap; }
.hours-mini td:last-child { text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }
.hours-mini tr.closed td:last-child { color: var(--meta, var(--ink-soft)); font-weight: 500; }

/* The hours tile spans the row on its own, so the table has room. */
.visit-facts > div:has(.hours-mini) { grid-column: 1 / -1; }

/* ---------- Notice box ---------- */

.notice {
  display: flex;
  gap: 0.9rem;
  background: var(--red-soft);
  border: 1px solid #E5C4C0;
  border-radius: var(--radius);
  padding: 1.25rem 1.5rem;
  color: var(--ink);
  font-size: 0.95rem;
  justify-content: center;
  text-align: center;
}

.notice strong { color: var(--red); }

/* ---------- Footer ---------- */

.site-footer {
  background: var(--navy-deep);
  color: var(--rule);
  padding: clamp(2.5rem, 6vw, 4rem) 0 1.5rem;
  margin-top: auto;
}

body { display: flex; flex-direction: column; min-height: 100vh; }

.footer-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1fr;
  gap: 2rem;
  padding-bottom: 2.5rem;
  border-bottom: 1px solid rgba(201, 216, 238, 0.2);
}

.footer-logo {
  display: block;
  margin-bottom: 0.9rem;
}

/* Same lockup as the header, wordmark reversed out for the navy ground.
   Sized by width so the 5.8:1 strip can never outgrow its grid column. */
.footer-logo img {
  width: min(300px, 100%);
  height: auto;
}

.footer-brand p { font-size: 0.9rem; max-width: 34ch; }

.footer-col h3 {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #8FA3C4;
  margin-bottom: 1rem;
}

.footer-col a {
  display: block;
  color: var(--rule);
  text-decoration: none;
  font-size: 0.92rem;
  padding: 0.22rem 0;
}

.footer-col a:hover { color: var(--white); text-decoration: underline; }

/* Instagram, looking like Instagram: the handle sits behind the app's own
   camera glyph on its gradient tile, so the link reads as a social account
   rather than one more line in the contact list. */
.footer-col a.footer-insta {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.footer-insta-mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.55rem;
  height: 1.55rem;
  border-radius: 8px;
  color: var(--white);
  background:
    radial-gradient(circle at 28% 108%, #FDF497 0%, #FDF497 5%, #FD5949 45%, #D6249F 60%, #285AEB 90%);
}

.footer-insta:hover { text-decoration: none; }
.footer-insta:hover span:last-child { text-decoration: underline; }
.footer-col p { font-size: 0.92rem; margin-top: 0.5rem; }
.footer-hours { line-height: 1.9; }

.footer-bottom {
  padding-top: 1.5rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: #8FA3C4;
}

/* ---------- Reveal on scroll ---------- */

/* main.js decides which blocks carry .reveal and adds .in once they cross into
   view; siblings in the same row get a --reveal-delay so they land one after
   the other rather than all at once. */
@media (prefers-reduced-motion: no-preference) {
  .reveal {
    opacity: 0;
    transform: translate3d(var(--reveal-x, 0), var(--reveal-y, 38px), 0)
               scale(var(--reveal-scale, 0.98));
    transition:
      opacity 0.85s var(--ease-arrive) var(--reveal-delay, 0s),
      transform 1.15s var(--ease-arrive) var(--reveal-delay, 0s);
  }

  /* The blog cards fade in where they stand. They rose 38px like the other
     blocks, and three cards rising one after another read as the row
     bobbing — the office asked for them to hold still. */
  .post-card.reveal { --reveal-y: 0px; --reveal-scale: 1; }

  /* Arrived. The list is restated with the hover properties on the end,
     because the shorthand above replaced the card's own transition list
     and a hover with no transition snaps. The first two entries are the
     same as above, so the arrival itself is unchanged. */
  .reveal.in {
    opacity: 1;
    transform: none;
    transition:
      opacity 0.85s var(--ease-arrive) var(--reveal-delay, 0s),
      transform 1.15s var(--ease-arrive) var(--reveal-delay, 0s),
      scale 0.25s ease, box-shadow 0.25s ease, border-color 0.2s ease;
  }

  /* A rail is watched as one block, because a card parked off to the right of
     it never meets the viewport on its own — watch the cards instead and they
     sit invisible until someone happens to scroll that rail across. So the
     rail is only the trigger here, and the motion belongs to its cards, each
     waiting a little longer than the one before (main.js writes --card-delay).
     Without this the six campus photos all landed on one frame. */
  .rail.reveal { opacity: 1; transform: none; }

  /* Cards rise into place, the same way every other block on the page does.
     They used to slide in from the right along the rail's own axis, and on a
     shelf that itself scrolls sideways that read as the shelf lurching and
     snapping back — reported on all three rails. */
  .rail.reveal .rail-card {
    opacity: 0;
    transform: translate3d(0, 14px, 0) scale(0.98);
  }

  .rail.reveal.in .rail-card {
    opacity: 1;
    transform: none;
    transition:
      opacity 0.8s var(--ease-arrive) var(--card-delay, 0s),
      transform 1.05s var(--ease-arrive) var(--card-delay, 0s),
      scale 0.25s ease, box-shadow 0.25s ease, border-color 0.2s ease;
  }

  /* The year ruler on /courses/ draws itself: each band grows out from the
     year it starts in, one row after another, and its caption appears once
     there is room for it. The row's hover lift is on `translate` so it
     composes with this rather than replacing it. */
  .ruler.reveal .ruler-band {
    transform: scaleX(0);
    transform-origin: left center;
  }

  .ruler.reveal .ruler-band-text { opacity: 0; }

  .ruler.reveal.in .ruler-band {
    transform: scaleX(1);
    transition: transform 1.1s var(--ease-arrive) var(--band-delay, 0s);
  }

  .ruler.reveal.in .ruler-band-text {
    opacity: 1;
    transition: opacity 0.5s ease calc(var(--band-delay, 0s) + 0.6s);
  }

  .ruler-rows li:nth-child(2) { --band-delay: 0.14s; }
  .ruler-rows li:nth-child(3) { --band-delay: 0.28s; }
  .ruler-rows li:nth-child(4) { --band-delay: 0.42s; }
  .ruler-rows li:nth-child(5) { --band-delay: 0.56s; }
  .ruler-rows li:nth-child(6) { --band-delay: 0.7s; }

  /* A head arrives a line at a time — kicker, then title, then the lead under
     it — so the eye is walked down the block instead of meeting all three on
     one frame. Same shape as the rails above: the block is what gets watched,
     its lines carry the motion. */
  .section-head.reveal,
  .offers-head.reveal { opacity: 1; transform: none; }

  .section-head.reveal > *,
  .offers-head.reveal > * {
    opacity: 0;
    transform: translate3d(0, 26px, 0);
  }

  .section-head.reveal.in > *,
  .offers-head.reveal.in > * {
    opacity: 1;
    transform: none;
    transition:
      opacity 0.8s var(--ease-arrive) var(--line-delay, 0s),
      transform 1s var(--ease-arrive) var(--line-delay, 0s);
  }

  /* Counted over the lines that are actually there: the office can switch off
     a kicker or a lead from /admin/, and a hidden one must not hold a place in
     the queue or the only visible line sits waiting its turn. Browsers without
     `of` drop these two rules and the head arrives as one piece, which is
     where it started. */
  .section-head.reveal > :nth-child(2 of :not([hidden])),
  .offers-head.reveal > :nth-child(2 of :not([hidden])) { --line-delay: 0.13s; }

  .section-head.reveal > :nth-child(n + 3 of :not([hidden])),
  .offers-head.reveal > :nth-child(n + 3 of :not([hidden])) { --line-delay: 0.26s; }

  /* main.js strips the queue off a block it decides was flicked past rather
     than read up to. The inline card delays it removes itself; these are the
     ones set just above, and beating them takes matching their weight and
     coming after. `of :not([hidden])` adds its own weight to :nth-child(), so
     the plain `> *` this started as lost and the queue survived. */
  .section-head.reveal.at-once > :not([hidden]),
  .offers-head.reveal.at-once > :not([hidden]) { --line-delay: 0s; }

  /* Two-column blocks come in from their own side of the layout — but only
     while there are two columns. Below the breakpoint they stack, and a
     sideways slide on a full-width block would push the page wider. */
  @media (min-width: 900px) {
    .reveal--left  { --reveal-x: -34px; --reveal-y: 0; }
    .reveal--right { --reveal-x: 34px;  --reveal-y: 0; }
  }

  /* Big visuals drift against the scroll. --fx runs 0 → 1 as the element
     crosses the viewport, so the shift is centred on the middle of that pass
     and the element sits where the layout put it as it goes by. */
  .fx-parallax {
    transform: translate3d(0, calc((0.5 - var(--fx, 0.5)) * var(--fx-shift, 34px)), 0);
    will-change: transform;
  }

  /* The opening block fades back as it leaves, so the section under it arrives
     on a clean page rather than sliding over a full-strength headline. */
  .hero-intro,
  .hero-copy,
  .page-hero > .container {
    opacity: var(--hero-fade, 1);
    transform: translate3d(0, var(--hero-lift, 0px), 0);
  }
}

/* ==========================================================================
   Teacher profiles
   ========================================================================== */

.teacher-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.25rem;
}

.teacher-card {
  display: flex;
  flex-direction: column;
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1.6rem;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.teacher-card:hover { box-shadow: var(--shadow); }

.teacher-head {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1.1rem;
}

.teacher-avatar {
  flex-shrink: 0;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--grid);
  border: 2px solid var(--rule);
}

/* Alternate the ring accent so the grid reads as a set, not a block. */
.teacher-card:nth-child(3n+2) .teacher-avatar { border-color: var(--red); }
.teacher-card:nth-child(3n+3) .teacher-avatar { border-color: var(--ink); }

.teacher-head h3 { font-size: 1.2rem; font-weight: 700; margin: 0; }

.teacher-role {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--red);
  margin: 0.2rem 0 0;
}

.teacher-bio {
  color: var(--ink-soft);
  font-size: 0.93rem;
  flex-grow: 1;
  margin-bottom: 1.1rem;
}

.teacher-subjects {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0 0 1.1rem;
  padding: 0;
}

.teacher-subjects li {
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--ink);
  background: var(--grid);
  border-radius: 4px;
  padding: 0.3em 0.65em;
}

.teacher-meta {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
  margin: 0;
  padding-top: 1rem;
  border-top: 1px solid var(--grid);
}

.teacher-meta div { margin: 0; }

.teacher-meta dt {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.teacher-meta dd {
  margin: 0.15rem 0 0;
  font-weight: 700;
  font-size: 0.92rem;
}

/* ==========================================================================
   Book a tour
   ========================================================================== */

/* One step is on screen at a time, so the wizard is a single column held to a
   comfortable measure rather than a two-column grid. Wide enough for the
   calendar and the times to sit side by side, and no wider — past this the
   details form's rows start to sprawl. */
.booking-steps { max-width: 780px; margin: 0 auto; }

/* Calendar on the left at its capped width, times in what is left over. */
.booking-when-grid {
  display: grid;
  grid-template-columns: 21rem minmax(0, 1fr);
  gap: clamp(1.25rem, 3vw, 2rem);
  align-items: start;
}

/* The times replace this once a day is chosen. It keeps the right-hand column
   occupied in the meantime, so nothing shifts when they appear. */
.booking-prompt {
  margin: 0;
  color: var(--ink-soft);
  font-size: 0.95rem;
}

@media (max-width: 720px) {
  .booking-when-grid { grid-template-columns: minmax(0, 1fr); }
  .booking-prompt { text-align: center; }
}


.booking-col {
  min-width: 0; /* let the card shrink with its column instead of setting it */
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: clamp(1.35rem, 3vw, 2rem);
}

.booking-step {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.15rem;
  margin-bottom: 1.25rem;
}

.step-no {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.7em;
  height: 1.7em;
  font-family: var(--font-mono);
  font-size: 0.9rem;
  color: var(--white);
  background: var(--ink);
  border-radius: 50%;
}

/* ---------- Calendar ---------- */

/* Capped rather than filling the step card. The cells are square, so every
   pixel of extra width becomes six rows of extra height — across the full
   620px wizard the month came out taller than the screen. At this width a cell
   is about 42px, which is still a comfortable tap target. */
.calendar {
  max-width: 21rem;
  margin: 0 auto;
  border: 1px solid var(--grid);
  border-radius: var(--radius);
  padding: 0.9rem;
  background: var(--paper);
}

.cal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.75rem;
}

.cal-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.05rem;
}

.cal-nav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: 8px;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, opacity 0.15s ease;
}

.cal-nav:hover:not(:disabled) { background: var(--ink); color: var(--white); }
.cal-nav:disabled { opacity: 0.3; cursor: not-allowed; }

/* minmax(0, 1fr) rather than 1fr: a plain 1fr track keeps its automatic
   minimum, so seven day columns refuse to shrink past their content and the
   whole card grows wider than a narrow phone. */
.cal-weekdays {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 4px;
  margin-bottom: 4px;
}

.cal-weekdays span {
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  padding: 0.25rem 0;
}

.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 4px;
}

.cal-cell {
  aspect-ratio: 1 / 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-body);
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, transform 0.1s ease, border-color 0.15s ease;
}

.cal-cell:hover:not(:disabled):not(.cal-blank) {
  border-color: var(--ink);
  background: var(--grid);
}

.cal-cell.is-selected {
  background: var(--red);
  border-color: var(--red);
  color: var(--white);
  font-weight: 700;
}

.cal-cell.is-off {
  color: var(--rule);
  background: transparent;
  border-color: transparent;
  cursor: not-allowed;
}

.cal-blank { border: none; background: none; cursor: default; }

.cal-note {
  margin: 0.9rem 0 0;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--ink-soft);
}

/* ---------- Time slots ---------- */

.timeslots { margin-top: 1.5rem; }

/* Beside the calendar rather than under it — the grid gap does the spacing. */
.booking-when-grid .timeslots { margin-top: 0; }

.timeslots-label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--red);
  margin-bottom: 0.75rem;
}

.slot-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
  gap: 0.6rem;
}

.slot {
  padding: 0.7em 0.5em;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.92rem;
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.slot:hover { border-color: var(--ink); background: var(--grid); }

.slot.is-selected {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--white);
  font-weight: 700;
}

/* ---------- Form fields ---------- */

.field { margin-bottom: 1.15rem; }

.field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

.field label {
  display: block;
  font-weight: 500;
  font-size: 0.9rem;
  margin-bottom: 0.4rem;
}

.field .optional { color: var(--ink-soft); font-weight: 400; }

.field input,
.field select,
.field textarea {
  width: 100%;
  font-family: var(--font-body);
  font-size: 0.98rem;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 8px;
  padding: 0.7em 0.85em;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.field textarea { resize: vertical; min-height: 80px; }

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--ink);
  box-shadow: 0 0 0 3px rgba(22, 36, 61, 0.08);
}

.field input::placeholder,
.field textarea::placeholder { color: #9AA6BC; }

/* ---------- Summary + submit ---------- */

/* What was chosen on the step before, carried forward so a parent can see it
   without stepping back to check. */
.booking-chosen {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  padding: 0.8rem 1rem;
  margin: 0 0 1.4rem;
  background: var(--red-soft);
  border: 1px dashed var(--red);
  border-radius: 8px;
  font-size: 0.95rem;
  color: var(--ink);
}

/* The way back out of a step, sitting at the end of its heading. */
.booking-back {
  margin-left: auto;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: transparent;
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--ink-soft);
  cursor: pointer;
  white-space: nowrap;
}

.booking-back::before { content: "← "; }
.booking-back:hover { border-color: var(--red); color: var(--red); }

.summary-icon { color: var(--red); flex-shrink: 0; display: inline-flex; }

.booking-submit { width: 100%; }

.booking-fineprint {
  margin: 0.9rem 0 0;
  text-align: center;
  font-size: 0.8rem;
  color: var(--ink-soft);
}

.booking-error {
  margin: 0.9rem 0 0;
  padding: 0.85rem 1rem;
  border-radius: var(--radius);
  border-left: 3px solid var(--red);
  background: var(--red-soft);
  font-size: 0.9rem;
  line-height: 1.6;
}

.booking-error a { color: var(--red); font-weight: 500; }

.booking-submit[disabled] { opacity: 0.6; cursor: progress; }

/* Honeypot — off-screen rather than display:none, which some bots skip. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ---------- Confirmation ---------- */

.booking-done {
  position: relative;
  max-width: 560px;
  margin-inline: auto;
  text-align: center;
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: clamp(2rem, 5vw, 3rem);
  box-shadow: var(--shadow);
}

.booking-done h2 { font-size: clamp(1.5rem, 3vw, 2rem); }
.booking-done p { color: var(--ink-soft); margin-bottom: 1.5rem; }

.done-stamp {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--red);
  border: 2px solid var(--red);
  border-radius: 6px;
  padding: 0.3em 0.7em;
  margin-bottom: 1.25rem;
  transform: rotate(-4deg);
}

/* ==========================================================================
   Announcement strip — the scrolling notice under the header

   Same trick as the offers ticker: main.js clones the row so the loop has no
   seam, and the duration is set from the measured width so a long notice and a
   short one travel at the same speed rather than in the same time.
   ========================================================================== */

.announce {
  position: relative;
  z-index: 90;
  background: var(--ink);
  color: var(--white);
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
  overflow: hidden;
}

.announce[hidden] { display: none; }

.announce-viewport { overflow: hidden; }

.announce-track {
  display: flex;
  width: max-content;
  will-change: transform;
}

.announce-row {
  display: flex;
  align-items: center;
  gap: 2.5rem;
  padding: 0.9rem 1.25rem;
  margin: 0;
  list-style: none;
}

.announce-row li {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  white-space: nowrap;
  font-size: 1rem;
  letter-spacing: 0.01em;
}

/* A red pen dot between notices, in place of a bullet. */
.announce-row li::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--red);
  flex: none;
}

.announce a {
  color: var(--white);
  text-underline-offset: 3px;
  font-weight: 500;
}

@media (prefers-reduced-motion: no-preference) {
  .announce-marquee.is-live .announce-track {
    animation: announce-scroll var(--announce-duration, 40s) linear infinite;
  }

  .announce-marquee.is-live:hover .announce-track,
  .announce-marquee.is-live:focus-within .announce-track { animation-play-state: paused; }

  @keyframes announce-scroll {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
  }
}

/* Without motion the strip is a scrollable row, and the clone would just be
   the same notices twice. */
@media (prefers-reduced-motion: reduce) {
  .announce-viewport { overflow-x: auto; scrollbar-width: thin; }
  .announce-track .announce-row + .announce-row { display: none; }
}

/* ==========================================================================
   Popup — the modal from /admin/ → Site text

   Two shapes out of one box: an uploaded image fills it edge to edge, and a
   typed title/body gets padding instead. Which one renders is decided by what
   the office filled in, so the panel below styles both and main.js writes only
   the one it has content for.
   ========================================================================== */

.popup {
  position: relative;
  width: min(30rem, calc(100vw - 2rem));
  max-height: calc(100dvh - 2rem);
  padding: 0;
  border: 1px solid var(--rule);
  /* The red is the one mark on the box, drawn across the top the way the
     marking pen rules off the top of a page. */
  border-top: 4px solid var(--red);
  border-radius: var(--radius);
  background: var(--white);
  color: var(--ink);
  box-shadow: 0 32px 70px -20px rgba(15, 26, 46, 0.45);
  overflow: hidden;
}

.popup::backdrop {
  background: rgba(15, 26, 46, 0.58);
  backdrop-filter: blur(3px);
}

/* It arrives on the same curve as everything else on the site, so it settles
   into place rather than being switched on. */
@media (prefers-reduced-motion: no-preference) {
  .popup[open] { animation: popup-arrive 0.42s var(--ease-arrive) both; }
  .popup[open]::backdrop { animation: popup-veil 0.42s ease both; }
}

@keyframes popup-arrive {
  from { opacity: 0; transform: translateY(14px) scale(0.985); }
}

@keyframes popup-veil { from { opacity: 0; } }

/* An image popup is usually a poster; give it room to be one. */
.popup:has(.popup-image) { width: min(38rem, calc(100vw - 2rem)); }

/* A tally is read down, one row at a time, so it stays a single column; the
   box takes only the extra width the longest school name needs. */
.popup:has(.popup-tally) { width: min(34rem, calc(100vw - 2rem)); }

.popup-body {
  max-height: calc(100dvh - 8rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  border-radius: var(--radius) var(--radius) 0 0;
}

/* A typed notice that runs long scrolls inside the box rather than stretching
   it to the height of the screen: a modal the size of the page reads as a
   page, and there is nothing left of the site behind it to go back to. A
   poster is exempt — an image cut off at 27rem is a cropped image. */
.popup-body:not(:has(.popup-image)) {
  max-height: min(calc(100dvh - 8rem), 27rem);
}

/* The image is the whole popup — no padding to letterbox it. */
.popup-image { display: block; }

.popup-image img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius) var(--radius) 0 0;
}

.popup-text { padding: 1.75rem 1.6rem 1.5rem; }

/* The heading stays put while a long notice scrolls under it: what the box is
   about should not be the first thing to leave the screen. The negative
   margins are the panel's own padding, undone so the sticky bar and its rule
   run the full width. */
.popup-text h2 {
  position: sticky;
  top: 0;
  z-index: 1;
  margin: -1.75rem -1.6rem 1.15rem;
  padding: 1.5rem 1.6rem 0.85rem;
  font-size: clamp(1.15rem, 2.6vw, 1.4rem);
  line-height: 1.25;
  background: var(--white);
  border-bottom: 1px solid var(--grid);
}

.popup-text p { color: var(--ink-soft); margin: 0 0 0.9em; }
.popup-text p:last-child { margin-bottom: 0; }

/* A ruled tally: the name reads down the left, the count sits out on the
   right in the marking red, and each row keeps the notebook rule under it. */
.popup-tally {
  margin: 0 0 1em;
  padding: 0;
  list-style: none;
}

.popup-tally li {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.28rem 0;
  border-bottom: 1px solid var(--grid);
  font-size: 0.9rem;
  line-height: 1.3;
}

.popup-tally span { flex: 1; }

.popup-tally b {
  flex: none;
  min-width: 1.9rem;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  background: var(--red-soft);
  color: var(--red);
  font-family: var(--font-mono);
  font-size: 0.8rem;
  font-weight: 600;
  text-align: center;
}
/* The one call to action sits centred under the message rather than hanging
   off the left edge — the foot's Close is the only other button on the box,
   and it is deliberately the quieter of the two. */
.popup-text .btn {
  display: block;
  width: fit-content;
  margin: 1.1rem auto 0;
}

/* The foot holds the two things that are not the notice itself: the tick,
   which is a choice, and Close, which is the way out. Esc and the backdrop
   still work; this is the one that is visible. */
.popup-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.7rem 1.1rem 0.85rem;
  border-top: 1px solid var(--grid);
  background: var(--paper);
  border-radius: 0 0 var(--radius) var(--radius);
}

.popup-hide {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-size: 0.85rem;
  color: var(--ink-soft);
  cursor: pointer;
}

.popup-hide input {
  width: 1rem;
  height: 1rem;
  margin: 0;
  accent-color: var(--red);
  cursor: pointer;
}

/* Bare text, no box: if the notice carries its own call to action, the way out
   must not be the louder of the two. The padding is still there — it is what
   makes a one-word button big enough to hit on a phone. */
.popup-close {
  flex: none;
  padding: 0.4rem 0.4rem 0.4rem 1rem;
  border: 0;
  background: none;
  color: var(--ink-soft);
  font: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  line-height: 1.4;
  cursor: pointer;
  transition: color 0.18s ease;
}

.popup-close:hover {
  color: var(--red);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ==========================================================================
   FAQ — one <details> per question
   ========================================================================== */

/* Long-form pages read better narrow than the 1200px grid the site is built
   on: a line of body text wants roughly 70 characters, not 140. */
.container-narrow { max-width: 58rem; }

/* The questions are one panel with a rule between each, not a stack of
   separate boxes — a list to run a finger down, the way the printed FAQ
   sheet at the front desk reads. One column, heading over it and the button
   under it, so it is the same page on a phone as on a desk. */
.faq-list {
  display: block;
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}

.faq-item {
  background: transparent;
  border: 0;
  border-top: 1px solid var(--rule);
  border-radius: 0;
  transition: background 0.2s ease;
}

.faq-item:first-child { border-top: 0; }
.faq-item[open] { background: var(--paper); }

.faq-item summary {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  padding: 1.3rem 1.5rem;
  cursor: pointer;
  list-style: none;
}

.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary:focus-visible { outline: 3px solid var(--red); outline-offset: 2px; }

/* h2 on the FAQ page, where the question is the page's own content; h3 in the
   home page teaser, where the section already owns the h2. */
.faq-item summary h2,
.faq-item summary h3 {
  flex: 1;
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.4;
  margin: 0;
}

.faq-item:hover summary h2,
.faq-item:hover summary h3,
.faq-item[open] summary h2,
.faq-item[open] summary h3 { color: var(--red); }

/* A chevron that turns over when the answer is open. (Tried without: a row
   of questions with nothing on the right did not say it could be opened.) */
.faq-mark {
  flex: none;
  width: 11px;
  height: 11px;
  margin-right: 0.35rem;
  border-right: 2px solid var(--ink-soft);
  border-bottom: 2px solid var(--ink-soft);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.25s ease, border-color 0.2s ease;
}

.faq-item:hover .faq-mark,
.faq-item[open] .faq-mark { border-color: var(--red); }
.faq-item[open] .faq-mark { transform: rotate(225deg) translate(-2px, -2px); }

.faq-answer {
  padding: 0 1.5rem 1.5rem;
  max-width: 62ch;
  color: var(--ink-soft);
}

.faq-answer p { font-size: 0.97rem; line-height: 1.75; }
.faq-answer p + p { margin-top: 0.8em; }

/* ==========================================================================
   Policy documents — enrolment conditions, privacy policy
   ========================================================================== */

.policy-toc {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 1.5rem;
  padding-bottom: 1.5rem;
  margin-bottom: 2.5rem;
  border-bottom: 1px solid var(--rule);
}

.policy-toc .eyebrow { margin: 0; width: 100%; }
.policy-toc a { color: var(--red); font-weight: 500; }

.policy-doc {
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: clamp(1.5rem, 4vw, 2.75rem);
  /* The red margin rule of an answer booklet, down the left edge. */
  box-shadow: inset 3px 0 0 var(--red);
}

.policy-doc + .policy-doc { margin-top: 2.5rem; }

.policy-doc h2 {
  font-size: clamp(1.5rem, 3vw, 1.9rem);
  scroll-margin-top: calc(var(--header-h) + 1.5rem);
}

.policy-doc h3 {
  font-size: 1.05rem;
  font-weight: 700;
  margin-top: 2rem;
  margin-bottom: 0.5em;
}

.policy-doc p,
.policy-doc li {
  color: var(--ink-soft);
  font-size: 0.97rem;
  line-height: 1.75;
}

.policy-doc p + p { margin-top: 0.9em; }
.policy-doc a { color: var(--red); font-weight: 500; }

.policy-doc ul {
  margin: 0.8em 0 0;
  padding-left: 1.2rem;
}

.policy-doc li + li { margin-top: 0.4em; }

.policy-meta {
  font-family: var(--font-mono);
  font-size: 0.78rem !important;
  color: var(--ink-soft);
  margin-top: 0.5rem;
}

.policy-contact {
  margin-top: 2rem !important;
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
}

/* ==========================================================================
   Careers — the résumé field, and cards that are not links
   ========================================================================== */

/* The role cards describe rather than navigate, so they must not lift on
   hover the way the course cards do — nothing happens if you click one. */
.course-card.is-static:hover { transform: none; box-shadow: none; scale: 1; }

.file-field {
  border: 1.5px dashed var(--rule);
  border-radius: 8px;
  padding: 1rem 1.1rem;
  background: var(--paper);
  transition: border-color 0.2s ease;
}

.file-field:focus-within { border-color: var(--red); }

.file-field input[type="file"] {
  width: 100%;
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--ink-soft);
}

.file-field input[type="file"]::file-selector-button {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.85rem;
  color: var(--white);
  background: var(--ink);
  border: 0;
  border-radius: 5px;
  padding: 0.55em 1em;
  margin-right: 0.9rem;
  cursor: pointer;
}

.file-field input[type="file"]::file-selector-button:hover { background: var(--red); }

.file-hint {
  margin: 0.7rem 0 0;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--ink-soft);
}

.file-hint.is-error { color: var(--red); }

/* ==========================================================================
   Responsive
   ========================================================================== */

@media (max-width: 980px) {
  .hero .container { grid-template-columns: 1fr; }
  .hero {
    background-image:
      linear-gradient(var(--grid) 1px, transparent 1px),
      linear-gradient(90deg, var(--grid) 1px, transparent 1px);
    background-size: 32px 32px, 32px 32px;
    background-position: top left, top left;
    background-repeat: repeat, repeat;
  }
  .exam-slip { max-width: 460px; transform: rotate(0.8deg); }
  .contact-cards, .teacher-grid,
  .post-grid--home { grid-template-columns: 1fr 1fr; }

  /* Two across, and an odd card still centres itself under them. */
  .card-grid { --cols: 2; }
  .two-col { grid-template-columns: 1fr; }
  .fact-stack { position: static; }
  .cyber-layout,
  .enquiry-layout { grid-template-columns: 1fr; }
  .rail--reviews { --rail-card: 46%; }
}

@media (max-width: 1300px) {
  .nav-toggle { display: flex; }

  .site-nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--white);
    border-bottom: 1px solid var(--grid);
    box-shadow: 0 16px 32px rgba(22, 36, 61, 0.12);
    padding: 0.5rem 1.25rem 1.25rem;
    display: none;
  }

  .site-nav.open { display: flex; }

  /* The header is sticky, so the menu hanging off it never scrolls with the
     page — a menu taller than the screen was simply cut off. The menu
     scrolls inside itself instead, and the page behind it holds still. */
  .site-nav {
    max-height: calc(100dvh - var(--header-h) - 1rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }

  body.nav-open { overflow: hidden; }

  .site-nav a {
    padding: 0.9em 0.5em;
    font-size: 1.05rem;
    border-bottom: 1px solid var(--grid);
    border-radius: 0;
  }

  .site-nav a.active { box-shadow: inset 3px 0 0 var(--red); padding-left: 1em; }

  .site-nav a.nav-tour {
    margin: 1rem 0 0;
    padding: 0.85em 1.05em;
    font-size: 1rem;
    text-align: center;
    justify-content: center;
  }

  /* Dropdowns become inline accordions inside the mobile menu. */
  .nav-group { position: static; }

  .nav-trigger {
    width: 100%;
    justify-content: space-between;
    padding: 0.9em 0.5em;
    font-size: 1.05rem;
    border-bottom: 1px solid var(--grid);
    border-radius: 0;
  }

  .nav-menu {
    position: static;
    display: none;
    opacity: 1;
    visibility: visible;
    transform: none;
    box-shadow: none;
    border: none;
    border-radius: 0;
    padding: 0;
    min-width: 0;
  }

  .nav-menu::before { display: none; }

  /* On touch, only an explicit toggle opens the submenu — not hover/focus. */
  .nav-group:hover .nav-menu,
  .nav-group:focus-within .nav-menu { display: none; }
  .nav-group.open .nav-menu { display: flex; }

  .nav-menu a {
    padding: 0.85em 0.5em 0.85em 1.5em;
    font-size: 1rem;
    border-bottom: 1px solid var(--grid);
    border-radius: 0;
    white-space: normal;
  }

  /* Swiping is the natural gesture here — drop the arrows, and with them the
     gutter the review shelf was holding open for them. */
  .rail-nav { display: none; }
  .rail--reviews { padding-inline: 0; }
  .rail { --rail-card: 84%; }
  .rail--campus { --rail-card: 62%; }
  .rail--reviews { --rail-card: 84%; }
  .rail-card .yt-play,
  .rail-card .yt-play svg { width: 64px; height: 46px; }
}

@media (max-width: 640px) {
  :root { --header-h: 66px; }

  .brand-logo { height: 36px; }

  .contact-cards,
  .download-groups,
  .feature-grid,
  .hours-grid,
  .subject-grid,
  .teacher-grid { grid-template-columns: 1fr; }

  /* One card per row — .card-grid is flex, so it is the count that changes. */
  .card-grid { --cols: 1; }

  /* The blog band is a teaser, and stacking its three cards turns a glance
     into most of a screen's scrolling. On a phone they lie down in a strip
     that is swiped instead — the same gesture the notices and the reviews
     above it already take. */
  .post-grid--home {
    display: flex;
    grid-template-columns: none;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    overscroll-behavior-x: contain;
    /* Room for the card's own border and shadow, taken back off the container
       so the strip still starts on the container's line. */
    padding: 0.4rem 0.4rem 1rem;
    margin-inline: -0.4rem;
    scroll-padding-inline: 0.4rem;
    scrollbar-width: thin;
    scrollbar-color: var(--rule) transparent;
  }

  /* Short of the full width on purpose: the sliver of the next card showing
     past the edge is what says there is more to the right. */
  .post-grid--home .post-card {
    flex: 0 0 84%;
    scroll-snap-align: start;
  }

  .post-grid--home::-webkit-scrollbar { height: 8px; }
  .post-grid--home::-webkit-scrollbar-track { background: var(--grid); border-radius: 99px; }
  .post-grid--home::-webkit-scrollbar-thumb { background: var(--rule); border-radius: 99px; }

  @media (prefers-reduced-motion: reduce) {
    .post-grid--home { scroll-behavior: auto; }
  }

  .field-row { grid-template-columns: 1fr; }

  .check-list--two { grid-template-columns: 1fr; }
  .glance-strip { grid-template-columns: 1fr; }
  .visit-facts { grid-template-columns: 1fr; }

  /* Six year columns get too narrow for the band captions here — the row name
     and where the band sits on the scale still carry the meaning. */
  .ruler { --ruler-gap: 2px; }
  .ruler-band-text { display: none; }
  .ruler-band { min-height: 30px; padding: 0; }

  .act-head { gap: 0.9rem; }
  .act-index { font-size: 2.25rem; }

  .exam-stats dd { font-size: 1.6rem; }

  .footer-grid { grid-template-columns: 1fr; gap: 1.75rem; }

  .hero-actions .btn { flex: 1 1 100%; }

  /* "OC and Selective" carries the pen stroke, so it must not break across
     lines. At the desktop minimum (2.35rem) that phrase is wider than a 320px
     phone, so scale the heading down with the viewport instead. */
  .hero h1 { font-size: clamp(1.9rem, 8.2vw, 2.35rem); }

  /* Narrower centre card so the neighbouring timetables peek in more. */
  .coverflow { --cf-w: clamp(210px, 64vw, 360px); }

  .offers-row { --offer-logo: 76px; gap: 1.4rem; }

  .step { flex-direction: column; gap: 0.6rem; padding: 1.35rem; }

  .exam-slip { padding: 1.4rem 1.25rem 1.4rem 2.4rem; }
  .exam-slip::before { left: 1.7rem; }
  .exam-stamp { right: -8px; top: -14px; width: 64px; height: 64px; }

  .map-embed iframe { height: 300px; }
}
