/* Hallmark · macrostructure: Marquee Hero · genre: editorial
 * theme: custom (vibe: "alpine ascent, published standard, warm paper")
 *   paper: oklch(97.4% 0.011 85)  ·  accent: oklch(56.8% 0.213 285) cool
 *   display: Fraunces  ·  body: Inter
 *   axes: light / high-contrast-serif / cool
 * hero: H6 Photographic fold · knobs: area=full-bleed, caption=lower-left, text=overlaid
 * enrichment: scroll-seeked video push-in (assets/hero/ascent/ascent.mp4)
 * nav: N1b Canonical SaaS three-section · knobs: centre=5 links, dropdowns=none, scroll=frost-on-scroll
 * footer: Ft1 Mast-headed · knobs: wordmark=display-2xl, tagline=roman body, links=inline
 * sections: S2 hanging · features: F3 tabular spec sheet (3-col, rule every row)
 * proof: T4 numbered stat strip (4-up) · cta: C1 outlined chip (slab, spacious, arrow)
 * motion: frame-scrub · reveal-rise · row-shift
 * tone: cinematic editorial · anchor hue: indigo 285°
 * pre-emit critique: P5 H5 E5 S4 R4 V5
 */

@import url("tokens.css");

/* ==========================================================================
   Base
   ========================================================================== */

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

html {
  overflow-x: clip;
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  overflow-x: clip;
  font-family: var(--font-body);
  font-size: var(--text-lg);
  line-height: 1.62;
  color: var(--color-ink);
  background: var(--color-paper);
  -webkit-font-smoothing: antialiased;
}

img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }

:focus-visible {
  outline: 2.5px solid var(--color-focus);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
  /* Never transitioned — the ring must appear the instant focus lands. */
}

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: clamp(1rem, 4vw, 1.5rem);
}

/* The nav is sticky and ~92px tall, so an un-margined anchor jump parks the
   section's heading underneath it. */
section[id] { scroll-margin-top: var(--space-3xl); }
.ascent { scroll-margin-top: 0; }

.skip-link {
  position: absolute;
  left: -9999px;
  top: var(--space-sm);
  z-index: 300;
  background: var(--color-ink);
  color: var(--color-paper);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-sm);
}
.skip-link:focus { left: var(--space-md); }

/* The page's real <h1> — the fold says it in 15rem type, but that type is
   decorative markup, so the accessible name lives here. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ==========================================================================
   Shared type
   ========================================================================== */

.h-section {
  font-family: var(--font-display);
  font-variation-settings: "opsz" 96;
  font-style: normal;
  font-weight: 600;
  font-size: var(--text-4xl);
  line-height: 1.1;
  letter-spacing: -0.022em;
  margin: 0;
  overflow-wrap: anywhere;
  min-width: 0;
}

.lede {
  font-size: var(--text-lg);
  line-height: 1.66;
  color: var(--color-ink-500);
  max-width: var(--measure);
  margin: var(--space-md) 0 0;
}

/* S2 · Hanging section head — floats in negative space, no rule, no left-margin
   label. Tag-above-heading only, never tag-left/heading-right. */
.head { max-width: 68ch; margin-bottom: var(--space-2xl); }
.head__tag {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  color: var(--color-accent);
  margin: 0 0 var(--space-md);
}
.band--ink .head__tag { color: var(--color-accent-soft); }

.band {
  padding-block: var(--space-3xl);
}
.band--ink {
  background: var(--color-ink);
  color: var(--color-paper);
}
.band--ink .lede { color: var(--color-on-ink-soft); }

/* ==========================================================================
   Nav · N1b canonical three-section, frost-on-scroll
   ========================================================================== */

.mast {
  position: sticky;
  top: 0;
  z-index: 100;
  color: var(--color-ink);
  background: transparent;
  border-bottom: var(--rule-hair) solid transparent;
  transition: background var(--dur-mid) var(--ease-out),
              color var(--dur-mid) var(--ease-out),
              border-color var(--dur-mid) var(--ease-out),
              backdrop-filter var(--dur-mid) var(--ease-out);
}
.mast.is-stuck {
  background: var(--color-paper-frost);
  backdrop-filter: blur(12px) saturate(1.4);
  border-bottom-color: var(--color-rule);
}

/* Over the fold the bar floats on a sunrise photograph. Navy type is
   unreadable there, so the whole bar inverts to cream and leans on the
   scrim's top band for contrast. The glyph and the burger follow via
   currentColor. */
/* The bar is never invisible. It used to go fully transparent here and lean on
   the scrim, which works over dark frames and vanishes over a bright sky — so
   the header appeared to come and go as the fold played. It now keeps a real
   surface at all times and only changes which surface: frosted ink over the
   photograph, frosted paper everywhere else. */
.mast.is-over-hero {
  color: var(--color-on-ink);
  background: rgba(var(--scrim-rgb), 0.5);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  border-bottom-color: rgba(250, 247, 241, 0.16);
  text-shadow: 0 1px 14px rgba(var(--scrim-rgb), 0.45);
}
.mast.is-over-hero .mainnav a:hover,
.mast.is-over-hero .mainnav a[aria-current="page"] { color: var(--color-on-ink); }
.mast.is-over-hero .mainnav a::after { background: var(--color-on-ink); }
.mast.is-over-hero .mainnav a[aria-current="page"]::after { transform: scaleX(1); }

.mast__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding-block: var(--space-md);
}

.brand {
  display: inline-flex;
  align-items: center;
  min-height: 44px;      /* WCAG AA hit target — the wordmark is a real link */
  gap: var(--space-xs);
  font-family: var(--font-display);
  font-variation-settings: "opsz" 40;
  font-weight: 600;
  font-size: var(--text-xl);
  letter-spacing: -0.01em;
  white-space: nowrap;
}

.mainnav {
  display: flex;
  align-items: center;
  gap: clamp(var(--space-md), 2.4vw, var(--space-xl));
}
.mainnav a {
  font-size: var(--text-sm);
  font-weight: 500;
  white-space: nowrap;          /* gate 49 — no two-line clickable text */
  /* Padded to a 44px target rather than sized by the text. The underline is
     then pinned relative to the text, not the padded box, so growing the hit
     area doesn't drag the rule away from the word. */
  padding-block: 12px;
  position: relative;
  transition: color var(--dur-fast) var(--ease-out);
}
.mainnav a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 10px;
  height: var(--rule-medium);
  background: var(--color-accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-mid) var(--ease-out);
}
.mainnav a:hover, .mainnav a[aria-current="page"] { color: var(--color-accent); }
.mainnav a:hover::after, .mainnav a[aria-current="page"]::after { transform: scaleX(1); }

.navtoggle {
  display: none;
  flex-direction: column;
  gap: 5px;
  width: 44px; height: 44px;
  align-items: center;
  justify-content: center;
  background: none;
  border: 0;
  cursor: pointer;
}
.navtoggle span {
  width: 22px; height: 2px;
  background: currentColor;
  border-radius: 2px;
  transition: transform var(--dur-mid) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
}
.navtoggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.navtoggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.navtoggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.drawer {
  display: none;
  flex-direction: column;
  padding: 0 clamp(1rem, 4vw, 1.5rem) var(--space-md);
  background: var(--color-paper-veil);
  backdrop-filter: blur(12px);
  border-top: var(--rule-hair) solid var(--color-rule);
}
.drawer.is-open { display: flex; }
.drawer a {
  padding-block: var(--space-sm);
  font-size: var(--text-base);
  font-weight: 500;
  border-bottom: var(--rule-hair) solid var(--color-rule);
}
.drawer a:last-child { border-bottom: 0; }

/* -- The mark in the bar -------------------------------------------------
   The logo is the mark alone. The wordmark sits on the switcher next to it,
   so the bar says "Own Your Study" once and the two elements read as a single
   lockup rather than as the brand printed twice.

   Both colourways are stacked and cross-faded: charcoal on paper, white over
   the photograph, where a charcoal mark would vanish. */
.brand { position: relative; display: inline-flex; align-items: center; }
.brand__mark {
  display: block;
  height: clamp(30px, 3.2vw, 40px);
  width: auto;
  transition: opacity var(--dur-mid) var(--ease-out);
}
.brand__mark--light { position: absolute; left: 0; top: 50%; translate: 0 -50%; opacity: 0; }
.mast.is-over-hero .brand__mark--ink { opacity: 0; }
.mast.is-over-hero .brand__mark--light { opacity: 1; }
.mast.is-over-hero .brand { text-shadow: none; }

/* ==========================================================================
   Brand switcher
   --------------------------------------------------------------------------
   One master brand, several divisions. "Own Your" is constant charcoal in
   every row and only the denomination takes colour, so the shared half reads
   as the parent identity and the coloured word as the specialism.
   ========================================================================== */

.switch { position: relative; flex: none; }

.switch__trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.28em;
  min-height: 44px;
  padding: 6px 10px 6px 0;
  border: 0;
  background: none;
  cursor: pointer;
  font-family: var(--font-display);
  font-variation-settings: "opsz" 40;
  font-weight: 600;
  font-size: var(--text-xl);
  letter-spacing: -0.01em;
  white-space: nowrap;
  color: var(--color-ink);
}

.switch__owner { color: currentColor; }
/* The space between the two halves is a real one in the markup, so it copies
   and reads aloud correctly; no margin is needed here. */
.switch__name  { color: var(--division, currentColor); }

.switch__chev {
  margin-left: 0.15em;
  color: var(--color-ink-500);
  transition: transform var(--dur-mid) var(--ease-out);
}
.switch__trigger[aria-expanded="true"] .switch__chev { transform: rotate(180deg); }

/* Over the fold the bar inverts, and the trigger has to come with it. */
.mast.is-over-hero .switch__trigger { color: var(--color-on-ink); }
.mast.is-over-hero .switch__chev    { color: rgba(250, 247, 241, 0.7); }
.mast.is-over-hero .switch__trigger .switch__name { color: var(--color-on-ink); }

/* -- The menu -------------------------------------------------------------
   A sheet of paper with a hairline and a low, wide shadow rather than a
   floating card — the page is warm and matte, and a hard drop shadow would
   read as a different product. */
.switch__menu {
  position: absolute;
  top: calc(100% + 10px);
  left: -10px;
  z-index: 120;
  min-width: 16rem;
  margin: 0;
  padding: var(--space-xs);
  list-style: none;
  background: var(--color-paper);
  border: var(--rule-hair) solid var(--color-rule);
  border-radius: var(--radius);
  box-shadow: 0 1px 2px rgba(var(--scrim-rgb), 0.04),
              0 18px 44px -18px rgba(var(--scrim-rgb), 0.28);
}
.switch__menu[hidden] { display: none; }

/* Opening: a short fade and a few pixels of travel. Nothing springs. */
.switch__menu.is-open {
  animation: switch-in 190ms var(--ease-out);
}
@keyframes switch-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

.switch__item {
  display: flex;
  align-items: baseline;
  gap: 0;
  padding: clamp(0.6rem, 1.4vw, 0.8rem) 0.85rem;
  min-height: 44px;
  border-radius: calc(var(--radius) - 6px);
  font-family: var(--font-display);
  font-variation-settings: "opsz" 40;
  font-weight: 600;
  font-size: 1.02rem;
  line-height: 1.25;
  letter-spacing: -0.012em;
  color: var(--color-ink);
  white-space: nowrap;
  transition: background-color 190ms var(--ease-out),
              transform 190ms var(--ease-out);
}

/* The tint is the division's own colour at low alpha, so the hover state is
   derived from the brand rather than a generic grey. */
.switch__item:hover,
.switch__item:focus-visible {
  background-color: rgba(var(--division-rgb, 18, 26, 47), 0.07);
  transform: translateX(3px);
}

/* Current division: a tint plus a short coloured rule on the leading edge.
   Deliberately not a checkmark — this is a brand mark, not a settings list. */
.switch__item.is-active {
  position: relative;
  background-color: rgba(var(--division-rgb, 18, 26, 47), 0.06);
  font-weight: 700;
}
.switch__item.is-active::before {
  content: "";
  position: absolute;
  left: 0.3rem;
  top: 50%;
  translate: 0 -50%;
  width: 3px;
  height: 1.15em;
  border-radius: 2px;
  background: var(--division, var(--color-ink));
}

/* -- Division cycler ------------------------------------------------------
   Fills the gap under the section heading with the argument the section is
   making: one constant brand, eight pathways. "Own Your" is fixed and only
   the denomination rolls, each in its own colour.

   The list carries a ninth row repeating the first, so the loop lands back
   where it started instead of snapping. --roll is the row height, and the
   mask is exactly one row tall. */
.cycler {
  --roll: 1.24em;
  display: flex;
  align-items: baseline;
  gap: 0.3em;
  margin: var(--space-lg) 0 0;
  font-family: var(--font-display);
  font-variation-settings: "opsz" 96;
  font-weight: 600;
  font-size: clamp(1.55rem, 3.6vw, 2.6rem);
  line-height: var(--roll);
  letter-spacing: -0.022em;
  color: var(--color-ink);
  white-space: nowrap;
}

.cycler__roll {
  display: inline-block;
  height: var(--roll);
  overflow: hidden;
}

.cycler__list {
  display: block;
  animation: cycler-roll 20s cubic-bezier(0.76, 0, 0.24, 1) infinite;
}

.cycler__list > span {
  display: block;
  height: var(--roll);
  line-height: var(--roll);
  color: var(--division, var(--color-accent));
}

@keyframes cycler-roll {
  0%, 9.6% { transform: translateY(calc(var(--roll) * -0)); }
  12.5%, 22.1% { transform: translateY(calc(var(--roll) * -1)); }
  25%, 34.6% { transform: translateY(calc(var(--roll) * -2)); }
  37.5%, 47.1% { transform: translateY(calc(var(--roll) * -3)); }
  50%, 59.6% { transform: translateY(calc(var(--roll) * -4)); }
  62.5%, 72.1% { transform: translateY(calc(var(--roll) * -5)); }
  75%, 84.6% { transform: translateY(calc(var(--roll) * -6)); }
  87.5%, 97.1% { transform: translateY(calc(var(--roll) * -7)); }
  100% { transform: translateY(calc(var(--roll) * -8)); }
}

@media (prefers-reduced-motion: reduce) {
  /* Rests on the parent brand rather than cycling. */
  .cycler__list { animation: none; }
}

@media (max-width: 40rem) {
  .cycler { font-size: clamp(1.25rem, 6.5vw, 1.75rem); }
}

/* -- Division marks -------------------------------------------------------
   Each row carries its own colourway of the one mark: charcoal book and
   climber throughout, flag and sunlit face in the division's colour. Built by
   re-laying the master's ink/accent mix against the new accent, so the edges
   stay exactly as drawn rather than being posterised by a flat swap. */
.switch__mark {
  align-self: center;
  flex: none;
  width: 1.55em;
  height: auto;
  margin-right: 0.55em;
}


.row__mark {
  align-self: start;
  width: clamp(30px, 3vw, 42px);
  height: auto;
  margin-bottom: calc(var(--space-xs) * -1);
}
@media (min-width: 60.01rem) {
  /* The rows are a three-column grid; the mark takes a lane of its own rather
     than being squeezed into the name cell. */
  .row[data-division] { grid-template-columns: auto minmax(0, 7fr) minmax(0, 9fr) minmax(0, 4fr); }
}
@media (max-width: 60rem) {
  .row__mark { width: 32px; }
}

/* An item with nowhere to go yet: dimmed, not interactive, and skipped by the
   keyboard rather than presented as a link that 404s. */
.switch__item.is-soon {
  opacity: 0.45;
  cursor: default;
  pointer-events: none;
}
.switch__item.is-soon:hover { background: none; transform: none; }

/* -- The division palette -------------------------------------------------
   One master brand, one coloured word per division. Declared as a pair per
   division — the hex for type, the channels for the low-alpha tint — so a
   division is two values and every surface that needs it (the switcher, the
   network rows) reads the same source.

   Own Your Study keeps the original brand indigo rather than taking a new
   one: it is the parent identity, not a sibling. */
[data-division="study"]          { --division: var(--color-accent); --division-rgb: 99, 91, 255; }
[data-division="ib"]             { --division: #002D72; --division-rgb: 0, 45, 114; }
[data-division="alevel"]         { --division: #B95745; --division-rgb: 185, 87, 69; }
[data-division="ap"]             { --division: #6DAE45; --division-rgb: 109, 174, 69; }
[data-division="atar"]           { --division: #EDA623; --division-rgb: 237, 166, 35; }
[data-division="uni-admissions"] { --division: #D9684A; --division-rgb: 217, 104, 74; }
[data-division="med"]            { --division: #168C8C; --division-rgb: 22, 140, 140; }
[data-division="uni-studies"]    { --division: #A04465; --division-rgb: 160, 68, 101; }

/* -- Inside the drawer ----------------------------------------------------
   Expands in place. A floating panel over a panel is never right on a phone. */
.switch--inline { width: 100%; }
.switch--inline .switch__trigger {
  width: 100%;
  justify-content: flex-start;
  padding: var(--space-sm) 0;
  font-size: var(--text-base, 1rem);
  border-bottom: var(--rule-hair) solid var(--color-rule);
}
.switch--inline .switch__chev { margin-left: auto; }
.switch--inline .switch__menu {
  position: static;
  min-width: 0;
  margin: var(--space-xs) 0 var(--space-sm);
  padding: var(--space-xs) 0;
  background: none;
  border: 0;
  border-bottom: var(--rule-hair) solid var(--color-rule);
  border-radius: 0;
  box-shadow: none;
  animation: none;
}
.switch--inline .switch__item { white-space: normal; }

/* The drawer only exists below the nav breakpoint, and the floating menu only
   above it — so each instance is hidden where the other one is doing the job. */
.drawer .switch { display: block; }
@media (min-width: 60.01rem) { .drawer { }  }
@media (max-width: 60rem) {
  .mast__inner .switch { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .switch__menu.is-open { animation: none; }
  .switch__item:hover, .switch__item:focus-visible { transform: none; }
  .switch__chev { transition: none; }
}

/* ==========================================================================
   Buttons
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  min-height: 44px;
  padding: 12px var(--space-md);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 600;
  white-space: nowrap;
  border: var(--rule-medium) solid transparent;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.btn__arrow { transition: transform var(--dur-mid) var(--ease-out); }
.btn:hover .btn__arrow { transform: translateX(3px); }

.btn--solid {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-paper-3);
  box-shadow: 0 1px 2px rgba(var(--scrim-rgb), 0.12);
}
.btn--solid:hover { background: var(--color-accent-soft); border-color: var(--color-accent-soft); }
.btn--solid:active { transform: translateY(1px); box-shadow: none; }

/* C1 · Outlined chip — slab shape, spacious, arrow adornment */
.btn--chip {
  border-radius: var(--radius-sm);
  border-color: currentColor;
  padding: 16px var(--space-lg);
  font-size: var(--text-base);
  background: transparent;
}
.btn--chip:hover { background: var(--color-ink); color: var(--color-paper); border-color: var(--color-ink); }
.btn--chip:active { transform: translateY(1px); }
.band--ink .btn--chip:hover { background: var(--color-paper); color: var(--color-ink); border-color: var(--color-paper); }

/* Portal sign-in, beside the primary CTA. Outlined rather than solid so the
   masthead still has exactly one loud action. Over the dark hero it inverts
   with the rest of the bar. */
.mast__actions {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.btn--quiet {
  background: transparent;
  border-color: var(--color-rule);
  color: var(--color-ink);
}
.btn--quiet:hover {
  background: var(--color-paper-2);
  border-color: var(--color-ink-300);
}
.btn--quiet:active { transform: translateY(1px); }

.mast.is-over-hero .btn--quiet {
  border-color: var(--color-rule-on-ink);
  color: var(--color-on-ink);
}
.mast.is-over-hero .btn--quiet:hover {
  background: rgba(250, 247, 241, 0.12);
  border-color: var(--color-on-ink-soft);
}

.btn[disabled], .btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* ==========================================================================
   The Ascent · scroll-driven photographic sequence
   --------------------------------------------------------------------------
   Twenty stills of one continuous push-in. A rAF-throttled listener writes a
   single scalar --p (0 → 1) onto .ascent; JS maps it to a frame index and
   cross-dissolves that frame into the next.

   Two properties move: opacity, cross-dissolving frame to frame, and a scale
   that grows each frame across its own interval so the push is continuous
   rather than stepped. Both are compositor-only — no layout, no paint.
   ========================================================================== */

@property --p {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

.ascent {
  --p: 0;
  position: relative;
  /* 200vh of scroll for 2.02x of travel — about 100vh per doubling, which
     holds the perceived push rate of the previous build (which spent ~123vh
     per doubling across a far longer 11.5x). Shortening this makes the move
     feel hurried; lengthening it makes a 2x push feel like drift. */
  height: 300vh;
  background: var(--color-massif-deep);
}

/* Large viewport, not small. The browser bar on a phone retracts the moment
   the scroll starts, and the stage is pinned for the whole of that scroll —
   so `100svh` left a band of massif-deep across the bottom of every frame,
   from the first flick to the last. `100lvh` is the height the fold is
   actually looked at. The only thing it can hide is the caption, which is
   read at rest under an expanded bar; that is lifted by the bar's own height
   below rather than the whole stage being cut short for it. */
.ascent__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100lvh;
  overflow: clip;
  isolation: isolate;
  background: var(--color-massif-deep);
  /* Zero wherever the two viewports agree: every desktop, and a phone whose
     bar has already gone. */
  --bar: 0px;
}
@supports (height: 100lvh) {
  .ascent__stage { --bar: calc(100lvh - 100svh); }
}

.ascent__frames { position: absolute; inset: 0; z-index: 1; }

/* The camera move is inside the file. Nothing here is transformed, nothing is
   cross-faded, and the only thing scroll changes is which frame is decoded —
   so the fold costs no layout and no paint, just the decode.

   object-position sits above centre because the summit does: the clip is 16:9
   and the stage is usually taller than that, so the crop has to keep the peak
   and the flag in frame rather than the foreground rock. */
.ascent__video,
.ascent__still {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 42%;
  background: var(--color-massif-deep);
}

/* The still is the fallback layer and sits under the video, revealed by the
   video being hidden rather than by being raised. That way a video that fails
   to decode uncovers a real image instead of a dark rectangle. */
.ascent__still { z-index: 0; opacity: 0; }
.ascent__video { z-index: 1; }

/* -- Scrim ----------------------------------------------------------------
   Two bands in one element. The top band is always on — the nav sits over a
   genuinely bright sunrise and needs it. The bottom band ramps in with
   "YOUR STUDY", so the wide opening frame stays ungraded.                    */

.ascent__scrim {
  --your:  clamp(0, calc((var(--p) - 0.42) / 0.22), 1);
  --words: clamp(0, calc((var(--p) - 0.14) / 0.16), 1);   /* tracks "OWN" */
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    /* Side grade. The lockup is left-aligned, so the left third is where the
       cream type has to survive — and at sunrise that third is bright orange,
       which cream alone only reaches ~2.5:1 against. Grading the type's own
       side leaves the climber and the flag completely untouched. */
    linear-gradient(to right,
      rgba(var(--scrim-rgb), calc(0.58 * var(--words))) 0%,
      rgba(var(--scrim-rgb), calc(0.34 * var(--words))) 26%,
      rgba(var(--scrim-rgb), calc(0.08 * var(--words))) 44%,
      rgba(var(--scrim-rgb), 0) 58%),
    /* Top band — buys the nav its contrast for the whole fold. */
    linear-gradient(to bottom,
      rgba(var(--scrim-rgb), 0.62) 0%,
      rgba(var(--scrim-rgb), 0.3)  12%,
      rgba(var(--scrim-rgb), 0)    30%),
    /* Bottom band — arrives with "YOUR STUDY". */
    linear-gradient(to top,
      rgba(var(--scrim-rgb), calc(0.86 * var(--your))) 0%,
      rgba(var(--scrim-rgb), calc(0.52 * var(--your))) 18%,
      rgba(var(--scrim-rgb), calc(0.16 * var(--your))) 34%,
      rgba(var(--scrim-rgb), 0) 48%);
}

/* -- The words ------------------------------------------------------------
   OWN and YOUR STUDY stack on one left axis as a single lockup. Left-aligned
   rather than centred: the flag flies right-of-centre through most of the
   sequence, and a centred word runs straight into it.                        */

.ascent__lockup {
  position: absolute;
  left: 0;
  top: 13%;
  z-index: 3;
  width: 100%;
  max-width: var(--shell);
  padding-inline: clamp(1rem, 4vw, 1.5rem);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(2px, 0.6vh, 10px);
  pointer-events: none;
}
@media (min-width: 1180px) {
  /* Same left rail as every section below the fold. */
  .ascent__lockup { left: 50%; transform: translateX(-50%); }
}

/* The page's display serif, as originally set. */
.ascent__word {
  position: static;
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: "opsz" 144;
  font-weight: 700;
  font-style: normal;              /* roman display — never italic */
  line-height: 0.88;
  color: var(--color-on-ink);
  text-wrap: balance;
  pointer-events: none;
  will-change: transform, opacity;
}

.ascent__word--own {
  --own: clamp(0, calc((var(--p) - 0.16) / 0.18), 1);
  font-size: var(--text-summit);
  letter-spacing: -0.045em;
  opacity: var(--own);
}

.ascent__word--your {
  --your: clamp(0, calc((var(--p) - 0.5) / 0.2), 1);
  font-size: var(--text-base-camp);
  letter-spacing: -0.02em;
  /* The two words were running together at this weight and tracking. The gap
     is opened on word-spacing rather than by loosening letter-spacing, which
     would have slackened the whole line to fix one join. */
  word-spacing: 0.22em;
  opacity: var(--your);
}

/* The rise rides `translate`, not `transform`, so it composes with the
   centring translateX above instead of overwriting it. */
.ascent__word--own  { translate: 0 calc((1 - var(--own))  * 30px); }
.ascent__word--your { translate: 0 calc((1 - var(--your)) * 24px); }

/* -- Fold caption + scroll cue -------------------------------------------- */

.ascent__caption,
.ascent__cue {
  position: absolute;
  /* Both are gone by --p ~ 0.13, i.e. before the browser bar has retracted,
     so the offset costs nothing once the fold is moving. */
  bottom: calc(clamp(1rem, 4vw, 1.75rem) + var(--bar, 0px));
  z-index: 4;
  margin: 0;
  font-size: var(--text-xs);
  line-height: 1.5;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--color-on-ink);
  text-shadow: 0 1px 12px rgba(var(--scrim-rgb), 0.6);
}
.ascent__caption {
  left: clamp(1rem, 4vw, 1.5rem);
  max-width: 30ch;
  opacity: clamp(0, calc((0.14 - var(--p)) / 0.1), 1);
}
.ascent__cue {
  right: clamp(1rem, 4vw, 1.5rem);
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  opacity: clamp(0, calc((0.1 - var(--p)) / 0.08), 1);
}
.ascent__cue svg { animation: cue-drift 2.6s var(--ease-in-out) infinite; }
@keyframes cue-drift {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(5px); }
}

/* ==========================================================================
   The climb · manifesto
   Whitespace is the layout here. Each idea gets its own band with air around
   it, so the section reads as a sequence of statements rather than a wall of
   prose — which is the argument the copy is making.
   ========================================================================== */

.climb {
  padding-block: clamp(5.5rem, 14vw, 12rem);
  background: var(--color-paper);
}

/* -- Flat layout ----------------------------------------------------------
   Laid across the width rather than stacked down it. The statement holds one
   column, the narrative the other, and the four steps run as a single row
   beneath both — which is what stops a page of short lines turning into a
   very tall page of short lines.                                            */

.climb__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  column-gap: clamp(2rem, 6vw, 5rem);
  row-gap: clamp(2.75rem, 6vw, 4.5rem);
  align-items: start;
}

@media (min-width: 60rem) {
  .climb__grid { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
  /* The statement and its signature travel together in column one now, so the
     column is claimed by their wrapper rather than by the heading itself.
     .evid and .steps span the full width and say so where they are defined. */
  .climb__intro { grid-column: 2; }
  /* Let the column set the measure. A ch-based cap fought the grid and
     squeezed the statement into two thirds of its own column. Scoped through
     the grid so it beats the base rule, which is declared further down. */
  .climb__grid .climb__open { max-width: none; }
}

/* The narrative column carries its own rhythm; the grid handles the rest. */
.climb__intro { display: flex; flex-direction: column; gap: clamp(1.5rem, 3vw, 2.25rem); }

/* -- Opening statement ----------------------------------------------------
   Two lines, one thought. The second carries the turn, so it takes the accent
   and a step up in size — the emphasis is structural, not decorative. */
.climb__open {
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: "opsz" 144;
  font-weight: 600;
  line-height: 1.02;
  letter-spacing: -0.03em;
  text-wrap: balance;
  max-width: 22ch;
}
.climb__open-a,
.climb__open-b { display: block; }
.climb__open-a {
  font-size: clamp(1.85rem, 5.4vw, 3.9rem);
  color: var(--color-ink);
}
.climb__open-b {
  font-size: clamp(2.15rem, 6.4vw, 4.7rem);
  font-weight: 700;
  color: var(--color-accent);
  margin-top: 0.12em;
}

/* -- The turn -------------------------------------------------------------
   The hinge from the opening into the story. Given its own band and a rule
   above it so it reads as a new movement rather than the next sentence. */
.climb__turn {
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: "opsz" 72;
  font-weight: 600;
  font-size: clamp(1.5rem, 3.6vw, 2.6rem);
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--color-ink);
  max-width: 24ch;
}

.climb__body {
  margin: 0;
  font-family: var(--font-body);
  font-size: clamp(1rem, 1.5vw, 1.16rem);
  line-height: 1.76;
  color: var(--color-ink-500);
  max-width: 62ch;
}

/* A single line that has to land, so it is pulled out of the prose entirely. */
.climb__aside {
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: "opsz" 60;
  font-weight: 600;
  font-size: clamp(1.15rem, 2.4vw, 1.7rem);
  line-height: 1.3;
  letter-spacing: -0.015em;
  color: var(--color-ink);
  max-width: 26ch;
}

/* ==========================================================================
   Climb v2 — evidence strip + four boxes
   --------------------------------------------------------------------------
   Replaces the scroll-scrubbed mountain ridge that used to live here
   with two quieter pieces:

     .evid    an evidence strip — three measured findings, each cited
     .steps   the four steps as plain boxes that settle in sequence

   Same contract as the ridge it replaces: every resting state below is the
   FINISHED state. climb-v2.js adds .is-live and only then does the CSS wind
   anything back to the start, so a reader with no JavaScript, a failed fetch
   or reduced motion gets the resolved layout rather than four empty boxes
   waiting on a scroll handler that will never run.
   ========================================================================== */


/* ==========================================================================
   The statement, signed
   ==========================================================================
   The opening statement had a column to itself and about two hundred pixels
   of nothing under it. The lockup fills that as a signature would: it arrives
   after the statement has been read, wiping in from the left the same way the
   route above it draws, so the two pieces of motion on this page belong to
   one idea rather than two.
   ========================================================================== */

.climb__statement {
  grid-column: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(1.75rem, 4.5vw, 3rem);
}

.sig {
  margin: 0;
  width: min(100%, 21rem);
}

/* -- The lockup, as the reference clip ------------------------------------
   The clip is the primary lockup and nothing else: cropped out of the
   presentation board on encode (x 97..986, y 338..620 of the original 1660 x
   1244 frame) and re-encoded video-only at the size it is shown. It used to
   be cropped here with a CSS window over the full board, which meant shipping
   the whole 1660 x 1244 frame and hiding four fifths of it. Now the file is
   the artwork, so the markup just shows it. */

.sig__frame {
  display: block;
  width: 100%;
  /* The clip's paper is (252,246,236) against the page's (250,247,241) — a
     warmer white that shows as a patch. The video is blended onto this
     backdrop rather than onto the page's, so the result is seamless whatever
     stacking contexts the entry transition happens to create. */
  background: var(--color-paper);
}

.sig__vid {
  display: block;
  width: 100%;
  height: auto;
  /* brightness lifts the clip's paper just above the page's in every channel
     (236 -> 242 on blue, the tightest of the three), so `darken` then takes
     the page's value for the background and the clip's for the artwork. */
  filter: brightness(1.025);
  mix-blend-mode: darken;
}

/* -- Live ------------------------------------------------------------------
   The lockup fades and settles on entry; everything after that belongs to the
   clip. Nothing here touches the flag or the arm — those are the video. */

.is-live .sig__frame {
  opacity: 0;
  transform: translateY(14px);
}
.is-live .sig.is-in .sig__frame {
  opacity: 1;
  transform: none;
  transition: opacity 520ms var(--ease-out),
              transform 760ms var(--ease-out);
  transition-delay: 260ms;
}


/* ==========================================================================
   Evidence
   ==========================================================================
   One figure, illustrated. The claim holds one column and the mountain and
   its number hold the other, which is what keeps a single statistic from
   reading as a stray pull-quote in the middle of the page.
   ========================================================================== */

.evid {
  grid-column: 1 / -1;
  margin-top: clamp(0.5rem, 2vw, 1.5rem);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(2.25rem, 5vw, 3.5rem);
  align-items: center;
}

@media (min-width: 60rem) {
  .evid { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
}

/* -- Lead ----------------------------------------------------------------- */

.evid__lead { max-width: 30ch; }

.evid__tag {
  display: block;
  margin: 0 0 clamp(0.75rem, 1.6vw, 1.1rem);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-ink-300);
}

.evid__claim {
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: "opsz" 72;
  font-weight: 600;
  font-size: clamp(1.5rem, 3.4vw, 2.5rem);
  line-height: 1.16;
  letter-spacing: -0.022em;
  color: var(--color-ink);
  text-wrap: balance;
}
.evid__claim em {
  font-style: normal;
  font-weight: 700;
  color: var(--color-accent);
}

/* -- The figure and its mountain ------------------------------------------ */

.evid__feature {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(1.25rem, 3vw, 2.25rem);
  padding-top: clamp(1rem, 2vw, 1.4rem);
  border-top: var(--rule-medium) solid var(--color-rule);
}

.evid__stat { flex: 1 1 14rem; }

.evid__fig {
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: "opsz" 144;
  font-weight: 700;
  font-size: clamp(2.8rem, 7vw, 4.4rem);
  line-height: 0.95;
  letter-spacing: -0.04em;
  color: var(--color-accent);
  font-variant-numeric: tabular-nums;
}

/* Fraunces sets the percent sign as a hairline at display size — it vanished
   against the numeral beside it. Inter's is solid, so the symbol is set in the
   body face while the number stays in the display one. Tight against the
   figure; the unit that follows carries its own space. */
.evid__fig-x {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.58em;
  letter-spacing: 0;
  margin-left: 0.04em;
}

.evid__fig-unit {
  font-size: 0.44em;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--color-ink-500);
  margin-left: 0.14em;
}

.evid__says {
  margin: 0.7rem 0 0;
  font-family: var(--font-body);
  font-size: clamp(0.98rem, 1.4vw, 1.08rem);
  line-height: 1.6;
  color: var(--color-ink-500);
  max-width: 34ch;
  text-wrap: balance;
}

/* The citation is the whole point of the block, so it is a real link and it
   looks like one — not a superscript the reader has to hunt for. */
.evid__cite {
  margin: 0.9rem 0 0;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  line-height: 1.5;
  color: var(--color-ink-300);
}
.evid__cite a {
  color: var(--color-ink-500);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  text-decoration-color: var(--color-rule);
  transition: color var(--dur-fast) var(--ease-out),
              text-decoration-color var(--dur-fast) var(--ease-out);
}
.evid__cite a:hover {
  color: var(--color-accent);
  text-decoration-color: var(--color-accent);
}


/* ==========================================================================
   The climb, at the size of a mark
   ==========================================================================
   A massif with the route up it, and an arrowhead that is CARRIED along that
   route rather than parked at the top. The head rides a motion path over the
   same two seconds the line takes to draw, so it stays at the leading edge of
   the ascent and arrives at the summit as the last of the line lands.

   Resting state is the finished climb — route drawn, head on the summit. Only
   under .is-live does any of it wind back.
   ========================================================================== */

.ridge {
  flex: 0 0 auto;
  display: block;
  width: clamp(7.5rem, 15vw, 11.5rem);
  height: auto;
  overflow: visible;
  /* Route measures ~128 units; the dash only has to exceed it. */
  --ridge-len: 140;
  --ridge-dur: 2s;
}

/* The body of the massif. The stroked line alone read as a share price;
   filling the ground beneath it is what makes it rock. */
.ridge__mass { fill: var(--color-accent-wash); }

/* What is still to climb: the whole route as a hairline, so the ascent has
   somewhere to be going before it gets there. */
.ridge__ahead {
  fill: none;
  stroke: var(--color-accent);
  stroke-width: 1.4;
  stroke-linejoin: miter;
  stroke-dasharray: 3 4;
  opacity: 0.34;
}

.ridge__line {
  fill: none;
  stroke: var(--color-accent);
  stroke-width: 4;
  stroke-linecap: round;
  /* Mitred, not rounded: a rounded join blunts every summit and the ridge
     stops reading as rock and starts reading as a share price. */
  stroke-linejoin: miter;
  stroke-miterlimit: 6;
}

/* The head is authored around its own origin pointing along +x, because that
   is the axis offset-rotate aligns to the direction of travel. */
.ridge__climber {
  fill: var(--color-accent);
  transform-box: view-box;
  /* Fallback for engines without motion paths: parked on the summit, angled
     to the last pitch. */
  transform: translate(80px, 10px) rotate(-49.9deg);
}

@supports (offset-path: path("M 0 0 L 1 1")) {
  .ridge__climber {
    transform: none;
    offset-path: path("M 4 84 L 34 34 L 48 48 L 80 10");
    offset-rotate: auto;
    offset-distance: 100%;
  }
}

/* -- Live ------------------------------------------------------------------
   The head LEADS and the line follows it. Both are keyframe animations rather
   than transitions, because the relationship between them is not a straight
   scale: the line has to stay a CONSTANT distance behind the head, and a
   transition to a shortened end value would only open that gap gradually —
   the head would sit on top of the drawn line for the first half of the climb.

   So ridge-draw bakes the easing curve into its own stops and runs linear.
   Each stop is (head position at that instant, less the length of the head),
   which keeps the line ending just under the head's base the whole way up.
   The head rides the eased curve directly. Change one and you must regenerate
   the other — they are two views of the same motion. */

@keyframes ridge-ascend { to { offset-distance: 100%; } }
@keyframes ridge-appear { to { opacity: 1; } }

@keyframes ridge-draw {
  0%   { stroke-dashoffset: 140.00; }
  4%   { stroke-dashoffset: 140.00; }
  8%   { stroke-dashoffset: 140.00; }
  12%  { stroke-dashoffset: 140.00; }
  16%  { stroke-dashoffset: 140.00; }
  20%  { stroke-dashoffset: 140.00; }
  24%  { stroke-dashoffset: 140.00; }
  28%  { stroke-dashoffset: 140.00; }
  32%  { stroke-dashoffset: 136.95; }
  36%  { stroke-dashoffset: 130.37; }
  40%  { stroke-dashoffset: 121.78; }
  44%  { stroke-dashoffset: 110.76; }
  48%  { stroke-dashoffset: 97.36; }
  52%  { stroke-dashoffset: 82.85; }
  56%  { stroke-dashoffset: 69.45; }
  60%  { stroke-dashoffset: 58.43; }
  64%  { stroke-dashoffset: 49.84; }
  68%  { stroke-dashoffset: 43.26; }
  72%  { stroke-dashoffset: 38.23; }
  76%  { stroke-dashoffset: 34.40; }
  80%  { stroke-dashoffset: 31.52; }
  84%  { stroke-dashoffset: 29.40; }
  88%  { stroke-dashoffset: 27.90; }
  92%  { stroke-dashoffset: 26.92; }
  96%  { stroke-dashoffset: 26.38; }
  100% { stroke-dashoffset: 26.21; }
}

.is-live .ridge__line {
  stroke-dasharray: var(--ridge-len);
  stroke-dashoffset: var(--ridge-len);
}
.is-live .evid__feature.is-in .ridge__line {
  animation: ridge-draw var(--ridge-dur) linear 240ms forwards;
}

@supports (offset-path: path("M 0 0 L 1 1")) {
  .is-live .ridge__climber {
    offset-distance: 0%;
    /* At zero distance the head sits at the very foot of the route with its
       body trailing outside the massif, which reads as a stray mark. The fade
       is long enough to cover the first stride, so it is still coming up as
       it clears the foot rather than sitting there fully drawn. */
    opacity: 0;
  }
  .is-live .evid__feature.is-in .ridge__climber {
    animation: ridge-ascend var(--ridge-dur) var(--ease-in-out) 240ms forwards,
               ridge-appear 640ms var(--ease-out) 240ms forwards;
  }
}

/* The ground and the dashed route arrive first, so the climb has a mountain
   to climb before it starts. */
.is-live .ridge__mass,
.is-live .ridge__ahead {
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out);
}
.is-live .evid__feature.is-in .ridge__mass  { opacity: 1; }
.is-live .evid__feature.is-in .ridge__ahead { opacity: 0.34; }

/* -- Live: the figure rises with it --------------------------------------- */
.is-live .evid__stat {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--dur-slow) var(--ease-out),
              transform var(--dur-slow) var(--ease-out);
  transition-delay: 180ms;
}
.is-live .evid__feature.is-in .evid__stat { opacity: 1; transform: none; }


/* ==========================================================================
   The four steps, as boxes
   ==========================================================================
   The ridge is gone, so the sequence has to be carried by the boxes alone.
   It is carried three ways: the ordinal, the rule that sweeps across the top
   edge, and the order they settle in. No mountain, no climber, no scrub.   */

.steps {
  grid-column: 1 / -1;
  /* Pace of the four-box sequence, in one place. The last rule starts at
     3 x stagger + sweep and runs for --step-dur, so the run finishes at
     2.16s against the 1.17s it used to take — a second longer. */
  --step-stagger: 320ms;
  --step-dur:     980ms;
  --step-sweep:   220ms;   /* how far the rule lags its own box */
  /* The grid's own row-gap already separates these two; this is the extra
     beat that marks the turn from evidence to method, nothing more. */
  margin-top: clamp(0.75rem, 2.5vw, 1.75rem);
}

.steps__lead {
  margin: 0 0 clamp(1.5rem, 3.5vw, 2.25rem);
  font-family: var(--font-display);
  font-variation-settings: "opsz" 60;
  font-weight: 600;
  font-size: clamp(1.15rem, 2.4vw, 1.7rem);
  line-height: 1.3;
  letter-spacing: -0.015em;
  color: var(--color-ink);
  max-width: 30ch;
}

.steps__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(0.75rem, 1.6vw, 1rem);
}

@media (min-width: 40rem) {
  .steps__list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Only once the boxes sit side by side. Stacked, a floor just opens a hole
     between the ordinal and a one-line statement. */
  .steps__box { min-height: clamp(9rem, 15vw, 11.5rem); }
}
@media (min-width: 64rem) { .steps__list { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* -- The box ---------------------------------------------------------------
   A hairline card on white, sitting a shade above the paper. The accent rule
   is a child rather than a border so it can be swept from zero width without
   the box itself resizing.                                                  */

.steps__box {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: clamp(1rem, 2.2vw, 1.6rem);
  padding: clamp(1.4rem, 2.9vw, 2rem);
  background: var(--color-paper-3);
  border: var(--rule-hair) solid var(--color-rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

/* The sweep. Pinned to the top edge, inside the radius. */
.steps__box::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  width: 100%;
  height: var(--rule-medium);
  background: var(--color-accent);
  transform-origin: left center;
  transition: transform var(--step-dur, var(--dur-slow)) var(--ease-out);
}

.steps__n {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-ink-300);
  font-variant-numeric: tabular-nums;
}

.steps__say {
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: "opsz" 48;
  font-weight: 600;
  font-size: clamp(1.06rem, 1.6vw, 1.38rem);
  line-height: 1.3;
  letter-spacing: -0.015em;
  color: var(--color-ink);
  text-wrap: balance;
  /* Pushed to the foot of the box so the four statements sit on one line
     across the row regardless of how many lines each one runs to. */
  margin-top: auto;
}
.steps__say em {
  font-style: normal;
  font-weight: 700;
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.16em;
  text-decoration-color: rgba(99, 91, 255, 0.4);
}

/* The outcome. It is the only one that is a result rather than a step, so it
   is the only one that carries the accent. */
.steps__box--end { border-color: var(--color-accent-wash); }
.steps__box--end .steps__n   { color: var(--color-accent-soft); }
.steps__box--end .steps__say { color: var(--color-accent); }

/* -- Live: each box settles in turn --------------------------------------- */
.is-live .steps__box {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity var(--step-dur) var(--ease-out),
              transform var(--step-dur) var(--ease-out);
  transition-delay: calc(var(--i, 0) * var(--step-stagger));
}
.is-live .steps__box::before {
  transform: scaleX(0);
  transition-duration: var(--step-dur);
  transition-delay: calc(var(--i, 0) * var(--step-stagger) + var(--step-sweep));
}
.is-live .steps__box.is-in            { opacity: 1; transform: none; }
.is-live .steps__box.is-in::before    { transform: scaleX(1); }


/* ==========================================================================
   Reduced motion
   ==========================================================================
   climb-v2.js never adds .is-live under reduced motion, so the resting states
   above are already the finished ones. Nothing to unwind — this only stops
   the transitions from firing on the .reveal wrapper around them.          */

@media (prefers-reduced-motion: reduce) {
  .evid__feature,
  .evid__stat,
  .sig__frame,
  .steps__box,
  .steps__box::before { transition: none !important; }
  .ridge__line,
  .ridge__climber { animation: none !important; }
}


/* ==========================================================================
   Practices · F3 tabular spec sheet, 3 columns, hairline rule every row
   ========================================================================== */

.roster {
  border-top: var(--rule-hair) solid var(--color-rule);
}

.row {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 9fr) minmax(0, 4fr);
  gap: var(--space-md) var(--space-lg);
  align-items: baseline;
  padding-block: var(--space-lg);
  border-bottom: var(--rule-hair) solid var(--color-rule);
  transition: background-color var(--dur-mid) var(--ease-out);
}
.row:hover { background: rgba(var(--division-rgb, 18, 26, 47), 0.05); }

/* Each practice is a division of the one brand, so the row carries the same
   coloured-denomination logic as the switcher: "Own Your" stays charcoal and
   only the specialism takes colour. */
.row__name b {
  font-weight: inherit;
  color: var(--division, var(--color-ink));
}
.row[data-division]:hover .row__go { color: var(--division, var(--color-accent)); }
.row[data-division] { position: relative; }
/* A short coloured edge on hover — the same device the switcher uses to mark
   the active division, borrowed here as the hover signal. */
.row[data-division]::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  translate: 0 -50%;
  width: 3px;
  height: 0;
  border-radius: 2px;
  background: var(--division, var(--color-accent));
  transition: height var(--dur-mid) var(--ease-out);
}
.row[data-division]:hover::before { height: 62%; }

/* Name and stage stack, always. Sharing one line meant "Own Your IB" fitted
   beside its stage label while "Own Your A Levels / GCSE" did not, so the
   block was one line tall on some rows and two on others. Stacking them makes
   every row identical at every width, which is the point of a list. */
.row__name {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  font-family: var(--font-display);
  font-variation-settings: "opsz" 72;
  font-weight: 600;
  font-size: var(--text-2xl);
  line-height: 1.15;
  letter-spacing: -0.018em;
  margin: 0;
  overflow-wrap: anywhere;
  min-width: 0;
  transition: transform var(--dur-mid) var(--ease-out);
}
.row:hover .row__name { transform: translateX(6px); }

.row__text {
  margin: 0;
  font-size: var(--text-base);
  line-height: 1.6;
  color: var(--color-ink-500);
}

.row__go {
  justify-self: end;
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  min-height: 44px;
  font-size: var(--text-sm);
  font-weight: 600;
  white-space: nowrap;
  color: var(--color-accent);
}
.row__go svg { transition: transform var(--dur-mid) var(--ease-out); }
.row:hover .row__go svg { transform: translateX(4px); }

.row__stage {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-ink-300);
  white-space: nowrap;
}

.roster__note {
  margin: var(--space-lg) 0 0;
  font-size: var(--text-sm);
  color: var(--color-ink-500);
}

/* ==========================================================================
   Standard · the five tenets
   ========================================================================== */

.tenets {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 270px), 1fr));
  gap: var(--space-xl) var(--space-2xl);
}
.tenet {
  border-top: var(--rule-hair) solid var(--color-rule-on-ink);
  padding-top: var(--space-md);
}
.tenet__name {
  font-family: var(--font-display);
  font-variation-settings: "opsz" 48;
  font-weight: 600;
  font-size: var(--text-xl);
  line-height: 1.2;
  letter-spacing: -0.015em;
  margin: 0 0 var(--space-xs);
  overflow-wrap: anywhere;
  min-width: 0;
}
.tenet__text {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.62;
  color: var(--color-on-ink-soft);
}

/* ==========================================================================
   Close
   ========================================================================== */

.close { padding-block: var(--space-3xl); text-align: center; }
.close .head { margin-inline: auto; margin-bottom: var(--space-xl); }
.close .lede { margin-inline: auto; }
.close__title {
  font-family: var(--font-display);
  font-variation-settings: "opsz" 120;
  font-weight: 600;
  font-size: var(--text-display-s);
  line-height: 1.06;
  letter-spacing: -0.028em;
  margin: 0;
  overflow-wrap: anywhere;
  min-width: 0;
}

/* ==========================================================================
   Footer · Ft1 mast-headed
   ========================================================================== */

.foot {
  background: var(--color-ink);
  color: var(--color-paper);
  padding-block: var(--space-2xl) var(--space-xl);
}
.foot__mast {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-lg);
  padding-bottom: var(--space-lg);
  border-bottom: var(--rule-hair) solid var(--color-rule-on-ink-soft);
}
.foot__wordmark {
  font-family: var(--font-display);
  font-variation-settings: "opsz" 120;
  font-weight: 600;
  font-size: var(--text-display-s);
  line-height: 0.95;
  letter-spacing: -0.03em;
  margin: 0;
  overflow-wrap: anywhere;
  min-width: 0;
}
.foot__wordmark span { color: var(--color-accent-soft); }
.foot__tagline {
  max-width: 34ch;
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--color-on-ink-soft);
}
.foot__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md) var(--space-lg);
  padding-block: var(--space-lg);
}
.foot__links a {
  font-size: var(--text-sm);
  font-weight: 500;
  white-space: nowrap;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  border-bottom: var(--rule-medium) solid transparent;
  transition: border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.foot__links a:hover { color: var(--color-accent-soft); border-bottom-color: var(--color-accent-soft); }
.foot__base {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-lg);
  justify-content: space-between;
  font-size: var(--text-xs);
  color: var(--color-on-ink-muted);
}

/* ==========================================================================
   Reveal · reveal-rise
   ========================================================================== */

.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
}
.reveal.is-in { opacity: 1; transform: none; }


/* The nav collapses on what it can actually fit, not on the layout breakpoint.
   Measured: mark 38 + switch 182 + four links 382 + two buttons 272, with three
   20px gaps, is 934px of content; .mast__inner adds 24px of padding each side,
   so the row needs 982px and the consult button is pushed off the right edge
   below that. The 60rem rule further down fires at 960px — just late enough to
   leave a sliver where the nav overflows — so this sits at 62rem (992px) and
   catches it first. Hamburger from here down.

   Was 72rem while the nav carried a fifth "Your Dashboard" link. */
@media (max-width: 62rem) {
  .mainnav,
  .mast__inner .btn,
  .mast__actions { display: none; }
  .navtoggle { display: flex; }
}

/* ==========================================================================
   Responsive · verified at 320 / 375 / 414 / 768
   ========================================================================== */

@media (max-width: 60rem) {
  .row {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-xs);
  }
  .row__go { justify-self: start; }
  .row:hover .row__name { transform: none; }


  .navtoggle { display: flex; }

  /* Mobile scroll has its own physics — shorten the track and shrink the
     dolly so the zoom lands well inside a thumb-flick. */
  .ascent { height: 240vh; }
  /* 16:9 against a portrait viewport, so `cover` crops hard to the sides.
     Bias the crop toward the summit rather than the centre. */
  .ascent__video, .ascent__still { object-position: 50% 42%; }
}

@media (max-width: 40rem) {
  body { font-size: var(--text-base); }
  .band { padding-block: var(--space-2xl); }
  .ascent__word--own { top: 15%; }
  .ascent__word--your { top: 68%; }
  .ascent__cue { display: none; }   /* the caption alone carries the fold */
  .foot__base { flex-direction: column; }
}

/* ==========================================================================
   Reduced motion — the scene resolves to its final composition, no scrubbing
   ========================================================================== */

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

  .ascent {
    --p: 0.82 !important;
    height: auto;
  }
  .ascent__stage { position: relative; }
  /* The still is revealed by JS (showStill), but pin it here too so the
     resolved composition survives even if the script never runs. */
  .ascent__video { opacity: 0; }
  .ascent__still { opacity: 1; }
  .ascent__word, .ascent__mark { will-change: auto; }
  .ascent__cue { display: none; }

  .reveal { opacity: 1; transform: none; transition: opacity 140ms linear; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 140ms !important;
  }
}
