/* ==========================================================================
   PRIME Academy — shared page components
   Used by every sub-page; a few blocks are shared with the homepage.
   Depends on base.css for tokens.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Announcement bar
   -------------------------------------------------------------------------- */

.announce {
  background: var(--navy);
  color: var(--white);
}

.announce__inner {
  padding: 11px var(--gut);
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  align-items: center;
  font: 500 10.5px/1.5 var(--mono);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .72);
}

.announce li { display: flex; align-items: center; gap: 18px; }
.announce li::after {
  content: "";
  width: 3px;
  height: 3px;
  background: var(--accent);
  flex: none;
}
.announce li:last-child::after { display: none; }


/* --------------------------------------------------------------------------
   Breadcrumb
   -------------------------------------------------------------------------- */

.breadcrumb {
  padding: 16px var(--gut);
  border-bottom: 1px solid var(--rule);
  font: 500 11.5px/1.6 var(--mono);
  color: var(--mauve);
  text-transform: uppercase;
  letter-spacing: .04em;
}
.breadcrumb a { color: var(--mauve); }
.breadcrumb a:hover { color: var(--accent-text); }
.breadcrumb [aria-current] { color: var(--ink); }


/* --------------------------------------------------------------------------
   Page head
   -------------------------------------------------------------------------- */

.page-head {
  padding: 52px var(--gut) 44px;
  border-bottom: 1px solid var(--rule);
}

.page-head h1 {
  font: 800 54px/1.02 var(--sans);
  letter-spacing: -.03em;
  color: var(--ink);
  max-width: 17ch;
  text-wrap: balance;
}
.page-head h1 em { font-style: normal; color: var(--accent-text); }

.page-head .lede {
  font: 400 17px/1.6 var(--sans);
  margin-top: 22px;
  max-width: 58ch;
  text-wrap: pretty;
}

.tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 24px;
}

.tag {
  border: 1px solid var(--rule-strong);
  color: var(--ink);
  font: 600 10.5px/1 var(--sans);
  letter-spacing: .16em;
  text-transform: uppercase;
  padding: 8px 11px;
}
.tag--accent {
  background: var(--lilac);
  color: var(--accent-text);
  border-color: transparent;
}
.tag--muted { color: var(--mauve); }


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

/* Sections carry top padding only; the closing band or CTA supplies the
   final gap before the footer. Every page must end with one of those. */
.section { padding: 52px var(--gut) 0; }
.section--pb { padding-bottom: 52px; }

.lede-block {
  font: 400 19px/1.6 var(--sans);
  max-width: 62ch;
  text-wrap: pretty;
  margin-top: 26px;
}

.prose p {
  font: 400 15.5px/1.65 var(--sans);
  max-width: 66ch;
  margin-top: 16px;
}


/* --------------------------------------------------------------------------
   Ruled grid — hairlines come from a 1px gap over a rule-coloured backdrop,
   so the rules stay correct at any column count without per-child borders.
   Keep item counts filling each row: an empty cell shows as a solid block.
   -------------------------------------------------------------------------- */

/* Item counts must fill every row at every breakpoint — a trailing empty cell
   shows as a solid rule-coloured block. Multiples of the column count only:
   .ruled--2 even, .ruled--3 multiples of 3, .ruled--4 multiples of 4. */
.ruled {
  display: grid;
  gap: 1px;
  background: var(--rule);
  margin-top: 28px;
}
.ruled > * {
  background: var(--surface);
  padding: 26px 28px;
}

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

.ruled h3 {
  font: 800 19px/1.2 var(--sans);
  letter-spacing: -.01em;
  color: var(--ink);
}
.ruled p {
  font: 400 14.5px/1.6 var(--sans);
  margin-top: 12px;
}
.ruled .eyebrow { display: block; margin-bottom: 14px; }

/* Figure + label cell. dt precedes dd in source for validity, so whichever
   element is the direct parent of the terms does the visual reversal —
   works whether .stat is the <dl> itself or a wrapper around one. */
.stat,
.stat > dl {
  display: flex;
  flex-direction: column-reverse;
  align-items: flex-start;
}

.stat dd {
  margin: 0;
  font: 800 34px/1 var(--sans);
  letter-spacing: -.03em;
  color: var(--ink);
}
.stat dt {
  font: 600 10.5px/1.4 var(--sans);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--mauve);
  margin-top: 10px;
}


/* --------------------------------------------------------------------------
   Programme cards
   -------------------------------------------------------------------------- */

.programme {
  display: block;
  color: inherit;
  background: var(--surface);
  padding: 30px 32px 28px;
}
a.programme:hover { background: var(--tint); color: inherit; }

.programme__top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
}

.programme h3 {
  font: 800 24px/1.1 var(--sans);
  letter-spacing: -.02em;
  color: var(--ink);
  margin-bottom: 12px;
}

.programme p {
  font: 400 15px/1.55 var(--sans);
  max-width: 42ch;
}

.programme__meta {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--rule-soft);
}
.programme__meta div {
  display: flex;
  gap: 12px;
  font: 400 13.5px/1.45 var(--sans);
}
.programme__meta dt {
  flex: none;
  width: 88px;
  font: 600 10px/1.5 var(--sans);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--mauve);
}
.programme__meta dd { margin: 0; color: var(--ink-dim); }

.programme__foot {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--rule-soft);
}
.programme__price {
  font: 800 20px/1 var(--sans);
  color: var(--ink);
}
.programme__unit {
  font: 400 13px/1 var(--sans);
  color: var(--mauve);
}
.programme__foot .spacer { flex: 1; }

.programme--soon { background: var(--tint); }
.programme--soon h3 { color: var(--mauve); }


/* --------------------------------------------------------------------------
   Pricing table
   -------------------------------------------------------------------------- */

.table-scroll {
  overflow-x: auto;
  margin-top: 28px;
  -webkit-overflow-scrolling: touch;
}

.price-table {
  width: 100%;
  min-width: 560px;
  border-collapse: collapse;
  text-align: left;
}

.price-table th,
.price-table td {
  padding: 18px 16px;
  border-bottom: 1px solid var(--rule);
  vertical-align: baseline;
}
.price-table th:first-child,
.price-table td:first-child { padding-left: 0; }

.price-table thead th {
  font: 600 10px/1.4 var(--sans);
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--mauve);
  border-bottom: 2px solid var(--rule-hard);
}

.price-table tbody th {
  font: 800 17px/1.2 var(--sans);
  color: var(--ink);
}

.price-table .price {
  font: 800 19px/1 var(--sans);
  color: var(--ink);
  white-space: nowrap;
}
.price-table .price small {
  display: block;
  font: 400 12px/1.4 var(--sans);
  color: var(--mauve);
  margin-top: 6px;
}
.price-table .price--soon {
  font: 600 11px/1 var(--sans);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--mauve);
}
.price-table td small.term {
  font: 400 13px/1.5 var(--sans);
  color: var(--mauve);
}


/* --------------------------------------------------------------------------
   FAQ
   -------------------------------------------------------------------------- */

.faq { margin-top: 12px; }

.faq details { border-bottom: 1px solid var(--rule); }

.faq summary {
  cursor: pointer;
  padding: 22px 0;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 24px;
  font: 700 17px/1.35 var(--sans);
  color: var(--ink);
  list-style: none;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+";
  flex: none;
  font: 400 22px/1 var(--sans);
  color: var(--accent-text);
}
.faq details[open] summary::after { content: "–"; }
.faq summary:hover { color: var(--accent-text); }

.faq details p {
  font: 400 15.5px/1.65 var(--sans);
  max-width: 68ch;
  padding-bottom: 24px;
}


/* --------------------------------------------------------------------------
   Learning journey
   -------------------------------------------------------------------------- */

.journey {
  counter-reset: step;
  margin-top: 24px;
}

.journey li {
  counter-increment: step;
  display: grid;
  grid-template-columns: 64px 1fr;
  gap: 22px;
  padding: 20px 4px;
  border-bottom: 1px solid var(--rule-soft);
  align-items: baseline;
}
.journey li::before {
  content: counter(step, decimal-leading-zero);
  font: 500 11.5px/1.6 var(--mono);
  color: var(--accent-text);
}
.journey h3 {
  font: 700 16px/1.3 var(--sans);
  color: var(--ink);
}
.journey p {
  font: 400 14px/1.55 var(--sans);
  color: var(--ink-dim);
  margin-top: 6px;
  max-width: 60ch;
}


/* --------------------------------------------------------------------------
   Callout note
   -------------------------------------------------------------------------- */

.note {
  display: flex;
  gap: 18px;
  align-items: flex-start;
  padding: 24px;
  background: var(--tint);
  margin-top: 32px;
}
.note__icon {
  width: 22px;
  height: 22px;
  flex: none;
  background: var(--accent);
  color: var(--white);
  font: 800 13px/22px var(--sans);
  text-align: center;
}
.note p {
  font: 400 14.5px/1.6 var(--sans);
  max-width: 70ch;
}

/* Marks content that is a stand-in for real material. */
.placeholder {
  border: 1px dashed var(--rule-strong);
  background: var(--tint);
  padding: 26px 28px;
}
.placeholder__tag {
  display: inline-block;
  font: 500 10px/1 var(--mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: #8792B8;
  background: var(--surface);
  padding: 6px 8px;
  margin-bottom: 16px;
}


/* --------------------------------------------------------------------------
   Dark closing band
   -------------------------------------------------------------------------- */

.band-navy {
  background: var(--navy);
  color: var(--white);
  padding: 52px var(--gut);
  margin-top: 56px;
}
.band-navy h2 {
  font: 800 34px/1.1 var(--sans);
  letter-spacing: -.02em;
  margin-bottom: 12px;
  max-width: 24ch;
  text-wrap: balance;
}
.band-navy p {
  font: 400 16px/1.55 var(--sans);
  color: rgba(255, 255, 255, .8);
  max-width: 52ch;
  margin-bottom: 28px;
}
.band-navy__actions { display: flex; flex-wrap: wrap; gap: 12px; }


/* --------------------------------------------------------------------------
   Motion — scroll reveal.
   Only armed once JS confirms it is running, so the page is never left
   invisible when scripts fail or are disabled.
   -------------------------------------------------------------------------- */

.js .reveal {
  opacity: 0;
  transform: translateY(14px);
}
.js .reveal.is-in {
  opacity: 1;
  transform: none;
  transition: opacity .55s cubic-bezier(.22, .61, .36, 1),
              transform .55s cubic-bezier(.22, .61, .36, 1);
}

/* Applied by the JS safety net. Transitions are driven by the rendering loop,
   so the fallback for "the rendering loop is not running" must not rely on
   one — this shows the content outright. */
.js .reveal.is-shown {
  opacity: 1;
  transform: none;
  transition: none;
}


/* --------------------------------------------------------------------------
   Animated hero background — a WebGL warp field (see warp-bg.js), grain over
   it, then a fade to the page colour at the bottom edge so the section reads
   as one piece with what follows. Same three-layer stack as the reference.

   The host sits behind its section's content and is inert until JS confirms
   WebGL2 by adding .warp-bg--on, so a browser without it shows exactly the
   flat background that was here before.
   -------------------------------------------------------------------------- */

:root {
  /* Colour ramp fed to the shader — dark base, accent glow, navy shoulder. */
  --warp-1: #07080F;
  --warp-2: #6965C8;
  --warp-3: #273473;
}

.warp-bg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  /* Covers the whole top of the page — announcement bar, header and hero —
     then fades out. Sized in vh rather than measured in JS so it stays
     correct through resize and orientation change with no relayout work.
     width:100% (not 100vw) so it never reaches under a scrollbar and
     introduces a horizontal one. */
  height: 100vh;
  min-height: 620px;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  /* Hidden until the renderer proves itself, so a failed init leaves no
     empty box sitting over the page background. */
  opacity: 0;
}
.warp-bg.warp-bg--on { opacity: 1; }

.warp-bg__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  /* The field is decorative and sits under text; hold it back so the copy
     above keeps its contrast. At this strength the brightest the field can
     reach still leaves headings at ~9:1 and body copy at ~5:1. */
  opacity: .45;
}

/* Grain, at the reference's 40px tile and 10% strength. */
.warp-bg::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--grain);
  background-size: 40px 40px;
  background-repeat: repeat;
  opacity: .1;
}

/* Fade to the page colour over the lower half. */
.warp-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: linear-gradient(rgba(7, 8, 15, 0) 45%, var(--bg) 100%);
}

/* The field is an absolutely-positioned element, so within its stacking
   context it paints above static in-flow content. Everything that must read
   over it gets lifted explicitly. */
.announce,
.site-header,
main,
.site-footer { position: relative; z-index: 1; }

/* The header sheds its own fill so the field runs unbroken behind the nav —
   this is what makes the top of the page read as one continuous background
   rather than a band under a solid bar. */
.site-header { background: transparent; }

.hero,
.page-head { position: relative; }

@media (prefers-reduced-motion: reduce) {
  /* The field still renders, as a single static frame — warp-bg.js stops
     the loop rather than the paint. */
  .warp-bg__canvas { opacity: .26; }
}


/* --------------------------------------------------------------------------
   Page entry — the reference's opening: an accent bar sweeping the full width
   across the top, then the first screen of content arriving staggered.
   Every element here is injected by JS and gated on the `js` class, so with
   scripts off there is no bar, no curtain and nothing hidden.
   -------------------------------------------------------------------------- */

.page-sweep {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 6px;
  z-index: 9600;
  background: var(--accent);
  transform: translateX(-100%);
  pointer-events: none;
}
.js .page-sweep { animation: sweep 1s cubic-bezier(.65, 0, .35, 1) forwards; }

@keyframes sweep {
  0%   { transform: translateX(-100%); }
  55%  { transform: translateX(0); }
  100% { transform: translateX(100%); }
}

/* Staggered arrival of the first screen. --enter-delay is set per element by
   JS. `js` and `is-ready` are BOTH on <html>, so the ready selector must be
   compound (.js.is-ready) — a descendant combinator here silently never
   matches and the whole entrance dies. */
.js .enter {
  opacity: 0;
  transform: translateY(12px);
}
/* The media panel arrives like the reference's hero image: settling from a
   slight zoom instead of rising. */
.js .enter.enter--media {
  transform: scale(1.05);
  transform-origin: center;
}
.js.is-ready .enter {
  opacity: 1;
  transform: none;
  transition: opacity .6s cubic-bezier(.22, .61, .36, 1) var(--enter-delay, 0ms),
              transform .6s cubic-bezier(.22, .61, .36, 1) var(--enter-delay, 0ms);
}
/* Safety net twin of .is-shown — no transition, no dependency on the
   rendering loop. */
.js .enter.is-shown {
  opacity: 1;
  transform: none;
  transition: none;
}


/* --------------------------------------------------------------------------
   Route transition — an accent curtain between pages. Split across two
   mechanisms because leaving and arriving have different timing constraints:

   LEAVING is user-triggered on a page that has already fully loaded, so a
   JS-created element (appended by the deferred site.js) is fine — there is
   nothing to race against.

   ARRIVING is different: this is a static multi-page site, so the "next
   page" is a brand-new document load. A curtain built by deferred JS is
   appended only after that new page has already parsed, and often already
   painted — which means the destination content would flash on screen for
   an instant before any JS-built curtain could cover it. The fix is a
   ::before on <html>, driven by a SYNCHRONOUS inline script placed at the
   very top of <head> (see the `<script>` before the stylesheet links in
   every page) — that script runs before the body is parsed, so the cover is
   present from the first frame with nothing to flash.

   Both default to fully absent: a curtain stuck on screen would black out
   the site, so neither may fail in a visible state.
   -------------------------------------------------------------------------- */

/* --- Leaving: JS-created div, .page-curtain[data-state="out"] --- */

.page-curtain {
  position: fixed;
  inset: 0;
  z-index: 9500;
  background: var(--accent);
  transform: scaleY(0);
  transform-origin: bottom;
  pointer-events: none;
  visibility: hidden;
}

.js .page-curtain[data-state="out"] {
  visibility: visible;
  transform: scaleY(1);
  transform-origin: bottom;
  transition: transform .45s cubic-bezier(.65, 0, .35, 1);
}

/* --- Arriving: pre-paint pseudo-element, gated by the `pa-arriving` class
   the early inline script sets when sessionStorage records a wipe in
   progress. Base rule has NO transition — it must render as an instant,
   already-covering block on the very first frame. Only adding the second
   class later (from deferred JS, once the page is interactive) introduces
   the transition and the target transform, so the wipe genuinely animates
   instead of jumping straight from scaleY(1) to scaleY(1). --- */

html.pa-arriving::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9500;
  background: var(--accent);
  transform: scaleY(1);
  transform-origin: top;
  pointer-events: none;
}
html.pa-arriving.pa-wipe-away::before {
  transform: scaleY(0);
  transition: transform .5s cubic-bezier(.22, .61, .36, 1);
}

/* --- First-load intro: a dark cover holds the page while the accent sweep
   bar plays over it, then lifts to reveal the staggered content beneath —
   the hold-and-reveal shape of the reference's own loader (a bar over a
   translucent dark panel, gating the hero until both finish).

   Set the same way as the arrival cover above: `pa-intro` is added
   synchronously in <head>, before body parses, whenever the page was NOT
   reached via an internal wipe — so the two covers are mutually exclusive
   and this one is also present from the very first frame, nothing to flash.

   The stakes here are higher than the arrival cover: that one only masks a
   destination page for half a second either way, but this one is capable of
   covering the ENTIRE page. A hold that only a script can release is exactly
   the failure this project treats as unacceptable everywhere else, so the
   whole effect is pure CSS — it plays, and clears, with no JS involvement at
   all, including when site.js never runs, is blocked, or fails. --- */

/* One animation drives the entire thing, and JS is not in the visual path at
   all. That is deliberate, and it is the fix for the bug this replaced: the
   old version had a keyframe animation pinning opacity:1 for the hold, plus a
   separate JS-toggled class that tried to fade it with a transition. CSS
   animations win over both normal declarations and transitions, so the fade
   never ran — the cover just vanished when JS removed the class. Two
   mechanisms competing for one property; now there is only one.

   `both` fill is what makes a single animation cover all three phases:
   backwards fill holds it opaque through the delay, the animation itself
   fades it, forwards fill keeps it gone afterwards. Nothing to synchronise,
   and it still self-clears if site.js never runs at all. */
/* A full-screen wash in the ACCENT colour that SPLITS AT THE MIDDLE and opens
   outward, revealing the page from the centre line. Both details come from
   screenshotting the reference rather than reading its DOM: the wash is a
   solid accent fill (not the translucent dark panel its markup suggests), and
   it parts at the horizontal midline rather than sliding away as one piece.

   Built as two fixed half-height panels — top and bottom — because a single
   element cannot open from its own centre. Each is exactly 50vh, so they meet
   with no seam and no overlap at any viewport height.

   Pure CSS throughout, `both` fill on every animation, so the whole intro
   plays and clears with no JavaScript involved at all. */

html.pa-intro::before,
html.pa-intro::after {
  content: "";
  position: fixed;
  left: 0;
  right: 0;
  height: 50vh;
  z-index: 9400;
  background: var(--accent);
  pointer-events: none;
}

html.pa-intro::before {
  top: 0;
  animation: pa-intro-top 1.65s cubic-bezier(.76, 0, .24, 1) both;
}

html.pa-intro::after {
  bottom: 0;
  animation: pa-intro-bottom 1.65s cubic-bezier(.76, 0, .24, 1) both;
}

/* Hold for the first ~58%, then part. visibility:hidden on the final frame
   so neither half can intercept anything once off screen. */
@keyframes pa-intro-top {
  0%, 58% { transform: translateY(0); }
  100%    { transform: translateY(-100%); visibility: hidden; }
}

@keyframes pa-intro-bottom {
  0%, 58% { transform: translateY(0); }
  100%    { transform: translateY(100%); visibility: hidden; }
}

/* The progress bar sits exactly on the split line, so the screen appears to
   part along the bar it just finished drawing. White because accent-on-accent
   would be invisible — the same dark-on-dark mistake that made the earlier
   version look like nothing was happening.

   On body rather than html: html's two pseudo-elements are both spoken for by
   the panels above, and this is the only remaining slot (body::after is the
   grain overlay). */
html.pa-intro body::before {
  content: "";
  position: fixed;
  z-index: 9401;
  top: 50%;
  left: 50%;
  width: 190px;
  height: 2px;
  margin: -1px 0 0 -95px;
  background: rgba(255, 255, 255, .92);
  transform: scaleX(0);
  transform-origin: left center;
  pointer-events: none;
  animation:
    pa-intro-bar .85s cubic-bezier(.4, 0, .2, 1) both,
    pa-intro-fade .2s linear .88s both;
}

@keyframes pa-intro-bar {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

@keyframes pa-intro-fade {
  from { opacity: 1; }
  to   { opacity: 0; visibility: hidden; }
}

/* One loader at a time — the top-edge sweep bar would otherwise run
   underneath the cover at the same moment, unseen but competing. */
html.pa-intro .page-sweep { display: none; }

/* Reduced motion keeps the wash but fades it instead of parting it — two
   full-screen panels travelling apart is exactly the movement this setting
   asks us not to make, whereas an opacity change carries none. Both halves
   must be handled: hiding one would leave the other covering half the screen.
   The bar goes, since its only job is to mark a split that no longer happens. */
@media (prefers-reduced-motion: reduce) {
  html.pa-intro::before,
  html.pa-intro::after {
    animation: pa-intro-fade .3s linear .35s both;
  }
  html.pa-intro body::before { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .js .page-sweep { animation: none; opacity: 0; }
  .js .enter,
  .js.is-ready .enter { opacity: 1; transform: none; transition: none; }
  .page-curtain { display: none !important; }
  html.pa-arriving::before { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .js .reveal,
  .js .reveal.is-in {
    opacity: 1;
    transform: none;
    transition: none;
  }
}


/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 1079px) {
  .page-head { padding: 40px var(--gut) 34px; }
  .page-head h1 { font-size: 40px; max-width: none; }

  .ruled--4 { grid-template-columns: repeat(2, 1fr); }
  .band-navy h2 { font-size: 28px; }
}

/* A three-up grid cannot go two-up: an odd item count would leave a trailing
   empty cell, which the gap technique paints as a solid rule-coloured block.
   Collapse straight to one column and switch to plain top borders, which is
   correct for any item count. */
@media (max-width: 899px) {
  .ruled--3 {
    grid-template-columns: 1fr;
    background: transparent;
    gap: 0;
  }
  .ruled--3 > * { padding: 24px 0; }
  .ruled--3 > * + * { border-top: 1px solid var(--rule); }
}

@media (max-width: 719px) {
  .section { padding: 34px var(--gut) 0; }
  .section--pb { padding-bottom: 34px; }
  .page-head { padding: 28px var(--gut) 26px; }
  .page-head h1 { font-size: 30px; }
  .page-head .lede { font-size: 15.5px; }

  .lede-block { font-size: 16.5px; }

  .ruled--2,
  .ruled--3,
  .ruled--4 { grid-template-columns: 1fr; }
  .ruled > * { padding: 22px 0; }
  .ruled {
    background: transparent;
    gap: 0;
  }
  .ruled > * + * { border-top: 1px solid var(--rule); }

  .journey li { grid-template-columns: 44px 1fr; gap: 14px; }

  .faq summary { font-size: 15.5px; gap: 16px; }

  .band-navy { padding: 32px var(--gut); }
  .band-navy h2 { font-size: 24px; }
  .band-navy__actions { flex-direction: column; align-items: stretch; }
  .band-navy .btn { display: block; }

  .announce__inner { font-size: 9.5px; gap: 5px 12px; }
  .announce li { gap: 12px; }
}


/* --------------------------------------------------------------------------
   Forms — placement test, login. Square, ruled, same vocabulary as the rest
   of the site: zero radius, hairline borders, accent focus.
   -------------------------------------------------------------------------- */

.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px 24px;
  margin-top: 32px;
  max-width: 640px;
}

.field { display: flex; flex-direction: column; gap: 8px; }
.field--full { grid-column: 1 / -1; }

.field label {
  font: 600 10.5px/1 var(--sans);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--mauve);
}
.field label .opt {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
}

.field input,
.field select,
.field textarea {
  font: 400 15px/1.4 var(--sans);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule-strong);
  padding: 13px 14px;
  width: 100%;
  border-radius: 0;
  -webkit-appearance: none;
  appearance: none;
}
.field textarea { resize: vertical; min-height: 96px; }

.field select {
  background-image: linear-gradient(45deg, transparent 50%, var(--mauve) 50%),
                     linear-gradient(135deg, var(--mauve) 50%, transparent 50%);
  background-position: right 16px center, right 10px center;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  padding-right: 34px;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--accent-text);
}

.field input::placeholder,
.field textarea::placeholder { color: var(--mauve); }

.field-hint {
  font: 400 12.5px/1.4 var(--sans);
  color: var(--mauve);
}

.form-actions {
  grid-column: 1 / -1;
  margin-top: 8px;
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
}

.form-actions .btn {
  border: 0;
  cursor: pointer;
  font-family: inherit;
}

.form-fine-print {
  font: 400 12.5px/1.5 var(--sans);
  color: var(--mauve);
  max-width: 46ch;
}

@media (max-width: 719px) {
  .form-grid { grid-template-columns: 1fr; }
}
