/* Hallmark · macrostructure: Photographic · H6 knobs: area=full-bleed, caption=centred, text=overlaid
 * nav: N9 edge-aligned minimal · footer: Ft5 Statement
 * theme: custom tuned · vibe: "white snow, India blue, quiet record"
 * paper oklch(99% 0.003 250) · accent oklch(50% 0.17 252) · Archivo + Newsreader + IBM Plex Mono
 * axes: light / grotesk-sans / cool
 * enrichment: none — the photographs are the enrichment
 * tone: austere · audience: sponsors, event bookers, press · use: one enquiry
 *
 * BENCHMARK: rogerfederer.com, the site the client named best.
 *
 * Not "inspired by". The skeleton is copied deliberately, because the first
 * attempt at V2 improvised a shape and the client was right that it drifted.
 * What Federer actually does, measured by scrolling the whole page:
 *
 *   1. a thin chrome bar, a wordmark, no link row
 *   2. a full-bleed hero, type CENTRED over it, one outlined pill button
 *   3. a sponsor band, one solid colour, logos only
 *   4. repeated full-bleed panels: centred sans title, serif subtitle
 *      underneath, one outlined button. Under forty words per panel.
 *   5. one 50/50 split: photograph on one side, a pull quote with
 *      attribution and two short paragraphs on the other
 *   6. a dark footer, two rows of links, legal line
 *
 * The one thing changed from the benchmark is the palette, because the client
 * asked for white and blue and Federer is near-black. Everything structural is
 * held: centred panel type, the serif subtitle, outlined pills, the quote in
 * the split, the dark footer.
 *
 * V2 is a parallel build. It shares content/*.json and assets/img with V1 and
 * shares no CSS, no JavaScript and no markup with it.
 */

/* Hallmark · tokens · Bhavani Thekkada V2
 * theme: custom (tuned) · vibe: "white snow, India blue, quiet record"
 * paper oklch(99% 0.003 250) light  ·  accent oklch(50% 0.17 252) cool
 * display Archivo · body Newsreader · caption IBM Plex Mono
 * axes: paper-band=light / display-style=grotesk-sans / accent-hue=cool
 *
 * V1 lives at the repository root and is untouched. This file is the whole
 * colour and spacing vocabulary for V2; every rule in v2.css references a
 * token by name and never an inline value.
 */

:root {
  /* ---- paper ---------------------------------------------------------- */
  /* Three grounds, not one. White carries the photographs, the ice band
     separates a section without needing a rule, and the deep band is used
     once, in the footer, so it reads as an ending. */
  --color-paper: oklch(99% 0.003 250);
  --color-paper-2: oklch(96.5% 0.008 245);
  --color-paper-3: oklch(93% 0.012 245);
  --color-paper-deep: oklch(22% 0.035 255);

  /* ---- ink ------------------------------------------------------------ */
  --color-ink: oklch(24% 0.035 255);
  --color-ink-2: oklch(43% 0.028 255);
  /* Was 58%. Measured against paper that gave 3.87:1, under the 4.5:1
     WCAG AA floor for text this size, and every caption on the site sits
     in it. At 49% the same pairing measures about 5.4:1. Every use of
     this token is on a light ground, so nothing loses by the change. */
  --color-ink-3: oklch(49% 0.022 255);
  --color-ink-on-deep: oklch(97% 0.006 250);
  --color-ink-on-deep-2: oklch(76% 0.018 250);

  /* ---- accent --------------------------------------------------------- */
  /* One blue. It is the blue of her race suit, and it is the only chromatic
     colour in the interface. Every other colour on the page arrives inside a
     photograph. */
  --color-accent: oklch(50% 0.17 252);
  --color-accent-bright: oklch(59% 0.18 250);
  --color-accent-wash: oklch(95% 0.03 250);

  /* ---- medal ---------------------------------------------------------- */
  --color-gold: oklch(72% 0.13 82);
  --color-silver: oklch(72% 0.012 250);
  --color-bronze: oklch(62% 0.10 55);

  /* ---- lines and focus ------------------------------------------------ */
  --color-line: oklch(89% 0.012 245);
  --color-line-soft: oklch(93.5% 0.008 245);
  --color-focus: oklch(50% 0.17 252);

  /* ---- type ----------------------------------------------------------- */
  --font-display: "Archivo", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-body: "Newsreader", Georgia, "Times New Roman", serif;
  --font-caption: "IBM Plex Mono", ui-monospace, "SFMono-Regular", monospace;

  /* Was 0.75rem. 12px carried the section kickers, the fact labels,
     the footer fine print, the buttons and several links at once, which
     is one size too many jobs. 13px lifts all of them; the tracked caps
     it is mostly used for read a further point larger again. */
  --text-xs: 0.8125rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: clamp(1.25rem, 0.6vw + 1.1rem, 1.5rem);
  --text-2xl: clamp(1.5rem, 1.1vw + 1.25rem, 2rem);
  --text-3xl: clamp(1.9rem, 1.9vw + 1.4rem, 2.75rem);
  --text-4xl: clamp(2.3rem, 3.2vw + 1.5rem, 4rem);
  --text-display-s: clamp(2.6rem, 5vw + 1rem, 5rem);
  --text-display: clamp(3rem, 8vw + 0.5rem, 7.5rem);

  /* ---- space (4pt) ----------------------------------------------------- */
  --space-3xs: 0.25rem;
  --space-2xs: 0.5rem;
  --space-xs: 0.75rem;
  --space-sm: 1rem;
  --space-md: 1.5rem;
  --space-lg: 2.5rem;
  --space-xl: 4rem;
  --space-2xl: 6rem;
  --space-3xl: clamp(5rem, 10vw, 9rem);

  /* ---- rules and radii -------------------------------------------------- */
  --rule-hair: 1px;
  --radius-none: 0;
  --radius-sm: 2px;
  --radius-pill: 999px;

  /* ---- motion ---------------------------------------------------------- */
  /* Three easings, no browser default. Durations are short on purpose: the
     Photographic macrostructure asks for no spatial motion, so the only
     movement here is a short fade and a slow image scale on hover. */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in: cubic-bezier(0.7, 0, 0.84, 0);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --dur-fast: 140ms;
  --dur-mid: 320ms;
  --dur-slow: 700ms;

  /* ---- measure --------------------------------------------------------- */
  --measure: 58ch;
  --measure-tight: 42ch;
  --col: 76rem;
}


/* ------------------------------------------------------------------ reset */

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

html,
body {
  /* `clip`, never `hidden`. `hidden` on the root turns the viewport into a
     scroll container and breaks position: sticky further down the page. */
  overflow-x: clip;
  margin: 0;
  padding: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  /*
   * This design is light on purpose: the brief is white and blue.
   *
   * Without this declaration a browser set to prefer dark, or one running an
   * auto-darkening feature like Brave's, decides the page has no opinion and
   * inverts it. Measured before this line existed: `--color-paper` resolved
   * correctly to oklch(99% 0.003 250) and the computed `background-color` on
   * body came back as rgb(25, 28, 29). The tokens were never the problem.
   */
  color-scheme: light;
}

body {
  background: var(--color-paper);
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--text-lg);
  line-height: 1.6;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
}

img {
  display: block;
  max-width: 100%;
  /*
   * `height: auto` is load-bearing, not tidiness.
   *
   * Every <img> this build emits carries width and height attributes, which
   * exist to reserve space and stop the page shifting as photographs arrive.
   * Those attributes set a used height. With only `max-width: 100%` here, a
   * tile that declared `width: 100%` and `aspect-ratio: 3 / 4` still rendered
   * at the attribute's height: measured at 260x3060 for a 2448x3060 source,
   * against the 260x347 the ratio asks for. `aspect-ratio` only governs when
   * the other axis is auto.
   *
   * Everything that fills its box sets an explicit height of its own
   * (`.panel-shot img`, `.split-shot img`, `.sponsors img`), so this default
   * never fights the full-bleed photographs.
   */
  height: auto;
}

h1,
h2,
h3 {
  /* Roman only. An italicised display face, or an italic emphasis word inside
     an upright heading, is the most reliable generated-design tell there is. */
  font-style: normal;
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.03;
  margin: 0;
  /* Long place names break inside themselves rather than pushing the page
     sideways at 320px. */
  overflow-wrap: anywhere;
  min-width: 0;
}

p,
figure,
blockquote {
  /* `figure` carries a default 40px inline margin in every browser. Inside a
     grid track it does not shrink the track, it shrinks the card: the media
     row measured 186px wide against the journey row's 266px, on identical
     columns, purely because those tiles were figures and these were anchors. */
  margin: 0;
}

a {
  color: inherit;
}

:focus-visible {
  /* Never animated. A focus ring that fades in is one the keyboard user has
     already moved past. */
  outline: 2px solid var(--color-focus);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.wrap {
  width: min(100% - 2 * var(--space-md), var(--col));
  margin-inline: auto;
}

@media (min-width: 900px) {
  .wrap {
    width: min(100% - 2 * var(--space-xl), var(--col));
  }
}

/* ------------------------------------------------------------------ shared */

/* The benchmark's caption voice: a place and a year, tracked out, never a
   sentence of marketing. */
.caption {
  /* Tracked capitals in the display face. These were IBM Plex Mono, and a
     mono label on every photograph, form field and section head made the
     whole site read as a technical document. The mono stays for numbers
     only, where alignment is the point. */
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-ink-3);
  margin: 0;
}

/* The serif line that sits under every panel title on the benchmark. It is
   what stops a page of sans headlines reading like a deck. */
.sub {
  font-family: var(--font-body);
  font-size: var(--text-xl);
  font-weight: 300;
  line-height: 1.45;
  color: var(--color-ink-2);
  margin: 0;
}

/* ------------------------------------------------------------------ button */

/* Outlined pill. The benchmark has no filled buttons anywhere on its home
   page; every call to action is an outline that fills on hover. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  padding: 0.95rem 1.9rem;
  border: var(--rule-hair) solid var(--color-ink);
  border-radius: var(--radius-none);
  background: transparent;
  color: var(--color-ink);
  cursor: pointer;
  transition: background var(--dur-mid) var(--ease-out),
    color var(--dur-mid) var(--ease-out),
    border-color var(--dur-mid) var(--ease-out);
}

.btn:hover {
  background: var(--color-ink);
  color: var(--color-paper);
}

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

/* On a photograph, the outline is white and fills white. */
.btn[data-on="shot"] {
  border-color: oklch(100% 0 0 / 0.72);
  color: var(--color-ink-on-deep);
}

.btn[data-on="shot"]:hover {
  background: var(--color-ink-on-deep);
  color: var(--color-ink);
  border-color: var(--color-ink-on-deep);
}

.btn[data-on="accent"] {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.btn[data-on="accent"]:hover {
  background: var(--color-accent);
  color: var(--color-paper);
}

/* --------------------------------------------------------------------- nav */

/* N9 edge-aligned minimal, which is what the benchmark runs: a wordmark and
   nothing else competing with it. The five destinations live in the footer
   and in the panels' own buttons, so the bar stays quiet. */
.nav {
  position: sticky;
  top: 0;
  z-index: 60;
  background: var(--color-paper-deep);
  color: var(--color-ink-on-deep);
}

.nav-inner {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  min-height: 4rem;
}

.wordmark {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-sm);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--color-ink-on-deep);
  white-space: nowrap;
}

.nav-links {
  display: none;
  gap: var(--space-lg);
  align-items: center;
  margin-inline: auto;
}

.nav-links a {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--color-ink-on-deep-2);
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease-out);
}

.nav-links a:hover {
  color: var(--color-ink-on-deep);
}

@media (min-width: 900px) {
  .nav-links {
    display: flex;
  }
}

/* ------------------------------------------------------------------ panels */

/* One shape, used five times, exactly as the benchmark uses it. A photograph
   fills the fold; the type sits centred over it; one button underneath. */
.panel {
  position: relative;
  isolation: isolate;
  display: grid;
  /* The type block hangs on the site column's left edge. `.wrap` centres
     itself with auto margins, which beat grid alignment and quietly centred
     the whole body; the panel carries the column padding itself instead and
     the body's margins are zeroed below. */
  padding-inline: max(var(--space-md), calc((100% - var(--col)) / 2));
  /* Bottom-left, not centre. Centred stacks read as slides in sequence, and
     the client said the page felt like a document. A cover anchors its type
     to a corner; five covers in a row read as a magazine. */
  place-items: end start;
  min-height: min(80svh, 46rem);
  text-align: left;
  padding-block: var(--space-2xl);
}

.panel[data-size="hero"] {
  min-height: min(94svh, 56rem);
}

.panel-shot {
  position: absolute;
  inset: 0;
  z-index: -2;
  overflow: hidden;
}

.panel-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.panel-shot::after {
  content: "";
  position: absolute;
  inset: 0;
  /* The middle stop is the one that matters: the sub line and the button sit
     at the centre of the frame, and 0.34 let a busy midtone eat them. The
     photograph still reads; the words now always do. */
  background: linear-gradient(
      to right,
      oklch(16% 0.03 255 / 0.42) 0%,
      oklch(16% 0.03 255 / 0.08) 52%,
      oklch(16% 0.03 255 / 0) 78%
    ),
    linear-gradient(
      to bottom,
      oklch(16% 0.03 255 / 0.12) 0%,
      oklch(16% 0.03 255 / 0.1) 45%,
      oklch(16% 0.03 255 / 0.62) 100%
    );
}

.panel-body {
  display: grid;
  justify-items: start;
  margin-inline: 0;
  gap: var(--space-md);
  max-width: 40rem;
  color: var(--color-ink-on-deep);
}

.panel-body h1,
.panel-body h2 {
  font-size: var(--text-4xl);
  font-weight: 700;
}

.panel[data-size="hero"] .panel-body h1 {
  font-size: var(--text-display);
  font-weight: 800;
  letter-spacing: -0.04em;
  line-height: 0.94;
}

.panel-body .sub {
  color: var(--color-ink-on-deep);
  max-width: 32ch;
}

.panel-body .caption {
  color: var(--color-ink-on-deep-2);
}

/* A light panel: same centred anatomy, no photograph behind it. The benchmark
   alternates one of these in so the run of images has somewhere to breathe. */
.panel[data-ground="paper"],
.panel[data-ground="ice"] {
  min-height: 0;
  padding-block: var(--space-2xl);
  color: var(--color-ink);
}

.panel[data-ground="paper"] {
  background: var(--color-paper);
}

.panel[data-ground="ice"] {
  background: var(--color-paper-2);
}

.panel[data-ground="paper"] .panel-body,
.panel[data-ground="ice"] .panel-body {
  color: var(--color-ink);
}

.panel[data-ground="paper"] .panel-body .sub,
.panel[data-ground="ice"] .panel-body .sub {
  color: var(--color-ink-2);
}

/* ------------------------------------------------------------ sponsor band */

/* The benchmark's Rolex strip. One solid colour, logos, nothing else. */
.sponsors {
  /* A light band, and the marks in their own colours. Forcing them to white
     on navy flattened the Reliance lockup and turned the Karnataka crest
     into a silhouette nobody could read. A logo is a thing you do not get to
     recolour. */
  background: var(--color-paper-2);
  padding-block: var(--space-lg);
  border-bottom: var(--rule-hair) solid var(--color-line);
}

.sponsors-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-md) var(--space-2xl);
}

.sponsors img {
  height: 2.4rem;
  width: auto;
}

/* Shape, not importance: the crest is square where the lockup is wide. */
.sponsors img[src*="karnataka"] {
  height: 3.4rem;
}

/* ------------------------------------------------------------------- split */

/* The benchmark's profile block: photograph one side, quote and two short
   paragraphs the other. The only place on the home page with more than forty
   words in it. */
.split {
  display: grid;
  align-items: stretch;
}

@media (min-width: 900px) {
  .split {
    /* minmax(0, …) on both tracks. A bare `1fr` refuses to shrink below the
       image's intrinsic width and pushes the page sideways. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}

.split-shot {
  background: var(--color-paper-3);
  overflow: hidden;
}

.split-shot img {
  width: 100%;
  height: 100%;
  min-height: 22rem;
  object-fit: cover;
}

.split-body {
  background: var(--color-paper-deep);
  color: var(--color-ink-on-deep);
  display: grid;
  align-content: center;
  gap: var(--space-md);
  padding: var(--space-2xl) var(--space-lg);
}

@media (min-width: 900px) {
  .split-body {
    padding: var(--space-3xl) var(--space-xl);
  }
}

.split-body h2 {
  font-size: var(--text-3xl);
}

.split-quote {
  font-family: var(--font-body);
  font-size: var(--text-2xl);
  font-weight: 300;
  line-height: 1.4;
  color: var(--color-ink-on-deep);
  margin: 0;
  max-width: 26ch;
}

.split-cite {
  font-family: var(--font-caption);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-ink-on-deep-2);
  font-style: normal;
}

.split-body p:not(.split-quote) {
  color: var(--color-ink-on-deep-2);
  font-size: var(--text-base);
  max-width: 44ch;
}

.split-body .btn {
  justify-self: start;
  margin-top: var(--space-2xs);
  border-color: oklch(100% 0 0 / 0.72);
  color: var(--color-ink-on-deep);
}

.split-body .btn:hover {
  background: var(--color-ink-on-deep);
  color: var(--color-ink);
  border-color: var(--color-ink-on-deep);
}

/* ----------------------------------------------------------------- records */

/* Two divisions, medals only, which is the whole brief for this block. The
   numbers are counted from the results rows at build time. */
.tally {
  display: grid;
  gap: var(--space-lg);
  width: 100%;
  margin-top: var(--space-lg);
}

@media (min-width: 720px) {
  .tally {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-xl);
  }
}

.tally-card {
  display: grid;
  gap: var(--space-sm);
  justify-items: center;
  padding: var(--space-lg) var(--space-md);
  border: var(--rule-hair) solid var(--color-line);
  background: var(--color-paper);
}

.tally-card h3 {
  font-family: var(--font-caption);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-ink-3);
}

.tally-total {
  font-family: var(--font-display);
  font-size: var(--text-display-s);
  font-weight: 800;
  line-height: 0.88;
  letter-spacing: -0.045em;
  color: var(--color-accent);
  font-variant-numeric: tabular-nums;
}

.tally-label {
  font-family: var(--font-body);
  font-size: var(--text-base);
  color: var(--color-ink-2);
}

.tally-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-md);
  padding-top: var(--space-sm);
  margin-top: var(--space-2xs);
  border-top: var(--rule-hair) solid var(--color-line-soft);
  width: 100%;
}

.medal {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--font-caption);
  font-size: var(--text-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-ink-2);
}

.medal b {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--color-ink);
  font-variant-numeric: tabular-nums;
}

.medal i {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: var(--radius-pill);
}

.medal[data-medal="gold"] i {
  background: var(--color-gold);
}
.medal[data-medal="silver"] i {
  background: var(--color-silver);
}
.medal[data-medal="bronze"] i {
  background: var(--color-bronze);
}

/* ------------------------------------------------------------------ footer */

/* Ft5 Statement, on the benchmark's dark ground. One line, two rows of links,
   a legal line. No four columns of navigation nobody reads. */
.foot {
  background: var(--color-paper-deep);
  color: var(--color-ink-on-deep);
  padding-block: var(--space-2xl) var(--space-lg);
}

.foot-statement {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-3xl);
  letter-spacing: -0.03em;
  line-height: 1.05;
  max-width: 15ch;
  margin: 0;
}

.foot-rows {
  display: grid;
  gap: var(--space-lg);
  margin-top: var(--space-xl);
  padding-top: var(--space-md);
  border-top: var(--rule-hair) solid oklch(100% 0 0 / 0.14);
}

@media (min-width: 760px) {
  .foot-rows {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
  }
}

.foot-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-lg);
}

.foot-links a {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--color-ink-on-deep-2);
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease-out);
}

.foot-links a:hover {
  color: var(--color-ink-on-deep);
}

.foot-fine {
  font-family: var(--font-caption);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  color: var(--color-ink-on-deep-2);
}

/* ------------------------------------------------------------------ reveal */

/* The only motion on the page: a fade with 10px of lift, once, on entry.
   Elements are visible by default and hidden only after JavaScript confirms
   it is running, so a blocked script can never leave a blank page. */
.js [data-rise] {
  opacity: 0;
  transform: translate3d(0, 10px, 0);
}

.js [data-rise][data-shown="true"] {
  opacity: 1;
  transform: none;
  transition: opacity var(--dur-slow) var(--ease-out),
    transform var(--dur-slow) var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }

  .js [data-rise] {
    opacity: 1;
    transform: none;
  }
}

/* ------------------------------------------------------------------- tiles */

/*
 * The card row, taken from badosapaula.com, the client's second reference.
 *
 * Its whole section is four things: a title, one line, a "See more" link, and
 * a row of four image cards. Nothing else. That is what makes a section land
 * as a glance rather than as reading, and it is the shape the client asked
 * for when they said the sections should arrive one after another, simplified.
 *
 * It alternates with the full-bleed panels above: photograph, tiles,
 * photograph, tiles. The page never asks for two dense folds in a row.
 */
.tiles-section {
  /*
   * Asymmetric on purpose. Two neighbouring sections each padding
   * `--space-3xl` top and bottom stacked to about 288px of empty page, which
   * reads as something failing to load rather than as room to breathe. The
   * head carries the space above it, so the section only needs a little
   * underneath.
   */
  padding-block: var(--space-2xl) var(--space-xl);
}

.tiles-section[data-ground="ice"] {
  background: var(--color-paper-2);
}

.tiles-head {
  display: grid;
  gap: var(--space-sm);
  justify-items: start;
  margin-bottom: var(--space-md);
}

/* Title and line on the left, the button closing the same row on the right.
   The head used to cap at the measure and leave the right half of the band
   empty, which the client read as a hole, correctly. */
@media (min-width: 860px) {
  .tiles-head {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
  }

  .tiles-head .btn {
    grid-column: 2;
    grid-row: 1 / span 2;
  }
}

.tiles-head h2 {
  font-size: var(--text-3xl);
}

.tiles-head .sub {
  font-size: var(--text-lg);
}

.tiles {
  display: grid;
  gap: var(--space-md);
}

@media (min-width: 560px) {
  .tiles {
    /* minmax(0, …), never a bare 1fr. A bare 1fr refuses to shrink below the
       image's intrinsic width and pushes the whole page sideways. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1000px) {
  .tiles {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.tile {
  display: grid;
  gap: var(--space-xs);
  align-content: start;
  text-decoration: none;
  color: inherit;
}

.tile-shot {
  overflow: hidden;
  background: var(--color-paper-3);
}

.tile-shot img {
  width: 100%;
  /* 4:5, up from 3:4. The taller window keeps shoulders under every face in
     the portrait frames; the client saw the top row cropping and was right. */
  aspect-ratio: 4 / 5;
  height: auto;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out);
}

.tile:hover .tile-shot img,
.tile:focus-visible .tile-shot img {
  transform: scale(1.04);
}

.tile h3 {
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1.25;
}

.tile .caption {
  color: var(--color-accent);
}


/* --------------------------------------------------------- interior pages */

/* A short photographic header. Same anatomy as a panel, half the height, so
   an interior page states its name and gets on with it. */
.panel[data-size="head"] {
  min-height: min(46svh, 26rem);
}

.panel[data-size="head"] .panel-body h1 {
  font-size: var(--text-4xl);
}

.prose-fold {
  padding-block: var(--space-2xl);
}

.prose-fold[data-ground="ice"] {
  background: var(--color-paper-2);
}

.prose {
  max-width: var(--measure);
  display: grid;
  gap: var(--space-md);
}

.prose h2 {
  font-size: var(--text-2xl);
}

.prose p {
  color: var(--color-ink-2);
}

/* Key–value facts: Born, Mentored by, Trains across. The benchmark carries
   these as quiet rows, never as a table. */
.facts {
  margin: var(--space-sm) 0 0;
  display: grid;
  gap: var(--space-md);
}

.fact {
  display: grid;
  gap: var(--space-3xs);
  padding-top: var(--space-sm);
  border-top: var(--rule-hair) solid var(--color-line);
}

.fact dd {
  margin: 0;
  color: var(--color-ink-2);
}

/* The medal tables. Three columns, hairline rows, the dot carrying colour. */
.medals {
  width: 100%;
  border-collapse: collapse;
  margin-top: var(--space-2xs);
}

.medals td {
  padding: var(--space-sm) var(--space-sm) var(--space-sm) 0;
  border-top: var(--rule-hair) solid var(--color-line);
  vertical-align: baseline;
}

.medals td b {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-base);
  letter-spacing: -0.01em;
  display: block;
}

.medals td span {
  display: block;
  font-size: var(--text-sm);
  color: var(--color-ink-2);
}

.c-medal {
  white-space: nowrap;
  font-family: var(--font-caption);
  font-size: var(--text-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-ink-2);
  text-align: right;
}

.c-medal i {
  display: inline-block;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: var(--radius-pill);
  margin-right: 0.4rem;
}

.c-medal i[data-medal="gold"] { background: var(--color-gold); }
.c-medal i[data-medal="silver"] { background: var(--color-silver); }
.c-medal i[data-medal="bronze"] { background: var(--color-bronze); }

/* The press list. Headline and outlet, one hairline per row, the whole row
   the link. */
.press {
  list-style: none;
  margin: var(--space-2xs) 0 0;
  padding: 0;
}

.press li {
  border-top: var(--rule-hair) solid var(--color-line);
}

.press a,
.press li > p {
  display: grid;
  gap: var(--space-3xs);
  padding-block: var(--space-sm);
  text-decoration: none;
  margin: 0;
}

.press b {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-base);
  letter-spacing: -0.01em;
  transition: color var(--dur-fast) var(--ease-out);
}

.press a:hover b {
  color: var(--color-accent);
}


/* --------------------------------------------------------------- the route */

.route-map {
  position: relative;
  margin: var(--space-md) 0 0;
  display: grid;
  gap: var(--space-sm);
}

/* Every pin carries its venue, its years and her best result there. The
   circle takes a tabindex so the keyboard reaches it and a title so a
   screen reader gets the same line the panel shows. */
.route-map .pin {
  cursor: pointer;
  transition: r var(--dur-fast) var(--ease-out);
}

.route-map .pin:hover,
.route-map .pin:focus-visible,
.route-map .pin[data-on] {
  r: 2.6;
}

.route-map .pin:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 3px;
}

/* HTML, not SVG text, so it sets at real sizes rather than in viewBox
   units that scale with the frame. Script places it beside its pin. */
.route-tip {
  position: absolute;
  z-index: 3;
  width: max-content;
  max-width: 15rem;
  padding: var(--space-sm) var(--space-md);
  background: var(--color-paper);
  border: var(--rule-hair) solid var(--color-line);
  border-left: 3px solid var(--color-accent);
  box-shadow: 0 12px 30px oklch(20% 0.03 255 / 0.18);
  pointer-events: none;
}

.route-tip[hidden] {
  display: none;
}

.route-tip p {
  margin: 0;
}

.route-tip-where {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-base);
  letter-spacing: -0.01em;
  color: var(--color-ink);
}

.route-tip-said {
  color: var(--color-ink-3);
  margin-top: var(--space-3xs) !important;
}

.route-tip-best {
  font-family: var(--font-body);
  font-size: 0.95rem;
  line-height: 1.45;
  color: var(--color-ink-2);
  margin-top: var(--space-2xs) !important;
}

.route-tip-best:empty,
.route-tip-said:empty {
  display: none;
}

.route-map svg {
  width: 100%;
  height: auto;
  display: block;
}

/* Measured at 1.19:1 against paper, which is a shape you have to be
   told is there, and once the scenery ran behind this section it stopped
   being one at all. The continents are the frame the pins are read
   against, so they get enough weight to be a silhouette. */
.route-map .land {
  fill: oklch(84% 0.024 245);
  stroke: none;
}

/* Three venues three units apart overlap at this scale, and without a
   stroke they merge into one shape. paint-order puts the fill over the
   inner half of the stroke, so each dot keeps its radius and reads as a
   circle against the one behind it. */
.route-map .pin {
  fill: var(--color-accent);
  stroke: var(--color-paper);
  stroke-width: 0.6;
  paint-order: stroke fill;
}

/* One leader per group of venues too close to label individually. */
.route-map .map-lead {
  fill: none;
  stroke: var(--color-ink-3);
  stroke-width: 0.28;
}

.route-map text {
  font-family: var(--font-caption);
  /* 3.2 svg units. These are viewBox units, so they scale with the crop:
     when the frame widened east to take in New Zealand on 31 Aug the
     labels went from 13.6px rendered to 12.1px, and below a 1280 window
     they fell under the 12px floor the legibility pass set. 3.2 puts
     them back at 13.8px at 1280. The first pass used 6.5 and they came
     out at 22px, larger than body copy. */
  font-size: 3.2px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  fill: var(--color-ink-2);
}

.route-map .ring {
  fill: none;
  stroke: var(--color-accent);
  stroke-width: 0.5;
  stroke-dasharray: 1.6 1.1;
}

.route-map figcaption {
  max-width: none;
  line-height: 1.8;
}

/* ------------------------------------------------------------- the ladder */

/* 2027 to 2030, one rung a year. An ordered list because it is one. */
.road {
  list-style: none;
  margin: var(--space-sm) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-md);
}

.road li {
  display: grid;
  gap: var(--space-3xs);
  padding-top: var(--space-sm);
  border-top: var(--rule-hair) solid var(--color-line);
}

.road b {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-lg);
  letter-spacing: -0.01em;
}

.road p {
  font-size: var(--text-base);
}

/* The count beside a records heading. A number, not a sentence. */
.prose h2 .count {
  font-family: var(--font-display);
  color: var(--color-accent);
  margin-left: var(--space-2xs);
  font-variant-numeric: tabular-nums;
}


/* ------------------------------------------------------------ press strips

   The first version's press list, rebuilt. Each story is a strip: her
   photograph on the left, the brief on the right, a hairline between
   strips. V1's thumbnail was 72px; here it is a real frame, because on
   this page the picture is half of what makes a row worth stopping on.

   They arrive from alternating sides as the reader reaches them. V1 drove
   that offset from scroll position every frame, and two rows caught at
   different points of their travel opened uneven gaps in the list. Here
   each strip slides once, on entry, and then behaves like text. */

.press-strips {
  list-style: none;
  margin: var(--space-md) 0 0;
  padding: 0;
  border-top: var(--rule-hair) solid var(--color-line);
}

.strip {
  border-bottom: var(--rule-hair) solid var(--color-line);
}

.strip a,
.strip > p {
  display: grid;
  gap: var(--space-sm);
  margin: 0;
  padding-block: var(--space-lg);
  text-decoration: none;
  color: inherit;
  align-items: start;
}

@media (min-width: 760px) {
  .strip a,
  .strip > p {
    grid-template-columns: minmax(0, 0.42fr) minmax(0, 1fr);
    gap: var(--space-xl);
  }
}

.press-shot {
  display: block;
  overflow: hidden;
  background: var(--color-paper-3);
  aspect-ratio: 3 / 2;
}

/* A story with no picture keeps its slot, so the brief beside it lines
   up with every other strip, but it must not read as a broken image. */
.press-shot.is-empty {
  background: none;
  border: var(--rule-hair) solid var(--color-line);
}

.press-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--dur-slow) var(--ease-out);
}

.strip a:hover .press-shot img {
  transform: scale(1.03);
}

.press-body {
  display: grid;
  align-content: start;
  gap: var(--space-2xs);
}

.press-body .caption {
  color: var(--color-accent);
}

.press-body b {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-xl);
  letter-spacing: -0.015em;
  line-height: 1.25;
  text-wrap: balance;
  transition: color var(--dur-fast) var(--ease-out);
}

.press-brief {
  font-family: var(--font-body);
  color: var(--color-ink-body);
  line-height: 1.55;
  max-width: 58ch;
}

.press-more {
  margin-top: var(--space-2xs);
  color: var(--color-ink);
}

.press-more::after {
  content: " \2192";
}

.strip a:hover .press-body b,
.strip a:hover .press-more {
  color: var(--color-accent);
}

/* The arrival. The site's observer already sets data-shown the moment a
   strip enters; this only changes where it comes from. Behind .js like
   every reveal, so a blocked script leaves the strips simply there. */
.js .strip[data-rise][data-side="l"] {
  transform: translate3d(-48px, 0, 0);
}

.js .strip[data-rise][data-side="r"] {
  transform: translate3d(48px, 0, 0);
}

.js .strip[data-rise][data-shown="true"] {
  transform: none;
  transition: opacity 640ms var(--ease-out), transform 640ms var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .js .strip[data-rise] {
    transform: none;
  }
}


/* -------------------------------------------------------- page transitions */

/*
 * Cross-document view transitions, and nothing else.
 *
 * The question was whether navigation should animate. On a multi-page site
 * the honest answer is: barely. A reader clicking VIEW JOURNEY wants the
 * journey, and every hundred milliseconds of choreography between the click
 * and the content is a cost they did not ask to pay. What a cut needs is not
 * a performance, it is continuity: the browser cross-fades the two documents
 * over a fifth of a second, the shared navy bar holds still because it looks
 * identical on both sides, and the page never white-flashes.
 *
 * This is two rules of CSS, runs in the compositor, and degrades to an
 * instant cut in browsers that do not know the at-rule. No JavaScript, no
 * router, nothing to break.
 */
@view-transition {
  navigation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 200ms;
  animation-timing-function: var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  @view-transition {
    navigation: none;
  }
}


/* ---------------------------------------------------------- the hero, met */

/*
 * The hero introduces her, so she faces the reader, and the type keeps out
 * of her face by height, not by side.
 *
 * A first pass tried to steer her to the right and set the name on the left.
 * Geometry refused: the source is portrait and the hero is wide, so the crop
 * is vertical and she is horizontally centred whatever object-position asks.
 * The benchmark solves this the honest way on its own hero: face in the
 * upper third, the name across the chest. So the crop holds her face high,
 * the body anchors to the foot of the panel where the scrim is strongest,
 * and the name crosses her jacket, never her face, at every width.
 */
.panel[data-size="hero"] {
  min-height: min(94svh, 56rem);
}

.panel[data-size="hero"] .panel-body h1 {
  /* Big enough to own the frame, small enough that the surname never breaks.
     At the full display clamp "Thekkada" measured wider than the body column
     and split mid-word. */
  font-size: clamp(3rem, 7vw, 6.5rem);
  font-weight: 800;
  letter-spacing: -0.04em;
  line-height: 0.92;
  overflow-wrap: normal;
}


/* ------------------------------------------------------------ the cinema */

/*
 * The motion pass, and the discipline behind it.
 *
 * The one video in the archive is a vertical phone montage with a caption
 * burned into every frame, so the hero stays photographic and the cinema is
 * done with motion instead: a twenty-second drift on the hero crop, the kind
 * a documentary holds on a still. Slow enough that nobody watches it happen;
 * the frame simply feels alive rather than printed.
 *
 * Everything here animates transform and opacity only, runs once, and
 * collapses under prefers-reduced-motion. Nothing loops except the hero
 * drift, and nothing moves against the reader's scroll.
 */
@keyframes hero-drift {
  from { transform: scale(1); }
  to { transform: scale(1.07); }
}

.panel[data-size="hero"] .panel-shot img {
  animation: hero-drift 22s var(--ease-in-out) forwards;
  transform-origin: 50% 30%;
}

/* The name arrives once, from below, ahead of the line and the button. A
   landing, not a slideshow. */
@keyframes hero-enter {
  from { opacity: 0; transform: translate3d(0, 18px, 0); }
  to { opacity: 1; transform: none; }
}

.panel[data-size="hero"] .panel-body > * {
  animation: hero-enter 700ms var(--ease-out) backwards;
}

.panel[data-size="hero"] .panel-body > :nth-child(2) { animation-delay: 120ms; }
.panel[data-size="hero"] .panel-body > :nth-child(3) { animation-delay: 240ms; }
.panel[data-size="hero"] .panel-body > :nth-child(4) { animation-delay: 340ms; }

/* Tiles stagger by column, 70ms apart. One wave across the row, then still. */
.js .tiles [data-rise]:nth-child(2)[data-shown="true"] { transition-delay: 70ms; }
.js .tiles [data-rise]:nth-child(3)[data-shown="true"] { transition-delay: 140ms; }
.js .tiles [data-rise]:nth-child(4)[data-shown="true"] { transition-delay: 210ms; }

/* Sponsors rest quiet and answer the cursor. Grey is the resting state, so
   the band never competes with the hero above it; colour is the reward. */
.sponsors img {
  filter: grayscale(1) opacity(0.72);
  transition: filter var(--dur-mid) var(--ease-out);
}

.sponsors img:hover {
  filter: none;
}

/* The record counts up when it enters. The number is set by JS text, so the
   markup already holds the true value for anything that does not run it. */
.tally-total {
  font-kerning: none;
}

@media (prefers-reduced-motion: reduce) {
  .panel[data-size="hero"] .panel-shot img,
  .panel[data-size="hero"] .panel-body > * {
    animation: none;
  }

  .sponsors img {
    filter: none;
  }
}


}


/* The way back. Subpages open with a small crumb to the front page, so the
   reader never depends on knowing the wordmark is a link. */
.crumb {
  text-decoration: none;
  color: var(--color-ink-on-deep-2);
  transition: color var(--dur-fast) var(--ease-out);
}

.crumb:hover {
  color: var(--color-ink-on-deep);
}

/* The nav names where you are. */
.nav-links a[aria-current="page"] {
  color: var(--color-ink-on-deep);
  border-bottom: 2px solid var(--color-accent-bright);
  padding-bottom: 0.3rem;
}


/* ---------------------------------------------------------------- enquiry */

.enquiry {
  display: grid;
  gap: var(--space-md);
  margin-top: var(--space-sm);
}

@media (min-width: 700px) {
  .enquiry {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .field-wide {
    grid-column: 1 / -1;
  }
}

.field {
  display: grid;
  gap: var(--space-3xs);
}

.enquiry input,
.enquiry select,
.enquiry textarea {
  font-family: var(--font-body);
  font-size: var(--text-base);
  color: var(--color-ink);
  background: var(--color-paper);
  border: var(--rule-hair) solid var(--color-line);
  border-radius: var(--radius-none);
  padding: 0.7rem 0.85rem;
  transition: border-color var(--dur-fast) var(--ease-out);
}

.enquiry input:hover,
.enquiry select:hover,
.enquiry textarea:hover {
  border-color: var(--color-ink-3);
}

.enquiry input:focus-visible,
.enquiry select:focus-visible,
.enquiry textarea:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 1px;
  border-color: var(--color-accent);
}

.enquiry [data-invalid="true"] {
  border-color: oklch(55% 0.19 25);
}

.enquiry button[disabled] {
  opacity: 0.6;
  cursor: progress;
}

#enquiry-note {
  margin-top: var(--space-2xs);
  text-transform: none;
  letter-spacing: 0.02em;
}


/* ---------------------------------------------------------------- journal */

.entry {
  display: grid;
  gap: var(--space-2xs);
  padding-block: var(--space-md);
  border-top: var(--rule-hair) solid var(--color-line);
  text-decoration: none;
}

.entry h2 {
  font-size: var(--text-2xl);
  transition: color var(--dur-fast) var(--ease-out);
}

.entry:hover h2 {
  color: var(--color-accent);
}

.post h2 {
  font-size: var(--text-xl);
  margin-top: var(--space-md);
}

.post p {
  color: var(--color-ink-2);
}

/* --------------------------------------------------------------- lightbox */

/* The full frame, on the page's own terms: the grid shows an ordered crop,
   the lightbox shows the photograph as shot. Click anywhere or press Escape
   to come back. Without JavaScript the same link opens the image file. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  padding: var(--space-lg);
  background: oklch(16% 0.03 255 / 0.92);
  cursor: zoom-out;
}

.lightbox[hidden] {
  display: none;
}

.lightbox img {
  max-width: min(92vw, 70rem);
  max-height: 82vh;
  width: auto;
  height: auto;
  object-fit: contain;
  box-shadow: 0 30px 80px oklch(0% 0 0 / 0.5);
}

.lightbox .caption {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: var(--space-md);
  color: var(--color-ink-on-deep);
}

.tile[data-full] {
  cursor: zoom-in;
}


/* ------------------------------------------------------------ record band */

/* Numbers left, podium right. The tallies used to sit in a 40rem column
   with nothing beside them, and the empty half read as a mistake. */
.record-band {
  display: grid;
  background: var(--color-paper-2);
}

@media (min-width: 900px) {
  .record-band {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    align-items: stretch;
  }
}

.record-copy {
  display: grid;
  gap: var(--space-md);
  justify-items: start;
  align-content: center;
  padding: var(--space-2xl) var(--space-lg);
}

@media (min-width: 900px) {
  .record-copy {
    padding-left: max(var(--space-xl), calc((100vw - var(--col)) / 2));
  }
}

.record-shot {
  margin: 0;
  overflow: hidden;
  min-height: 20rem;
}

.record-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}


/* ------------------------------------------------- about split and facts */

.about-split {
  display: grid;
  gap: var(--space-lg);
  align-items: center;
}

@media (min-width: 860px) {
  .about-split {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: var(--space-xl);
  }
}

.about-shot {
  margin: 0;
  overflow: hidden;
  background: var(--color-paper-3);
}

.about-shot img {
  width: 100%;
  aspect-ratio: 4 / 5;
  height: auto;
  object-fit: cover;
}

/* The fact strip runs as a row of cards under the story, not a column
   inside it. */
.facts-row {
  display: grid;
  gap: var(--space-md);
}

@media (min-width: 720px) {
  .facts-row {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* Grouped medal lines: the campaign left, its medals as counted dots. */
.c-meds {
  text-align: right;
  white-space: nowrap;
}

/* The tally card's .medal is a flex row sized for a card; inside a table
   cell it stacked its own number over its own label. Inline here, and the
   count sits beside its metal on one line. */
.c-meds .medal {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
  margin-left: var(--space-sm);
  white-space: nowrap;
}

.c-meds .medal i {
  align-self: center;
}

/* Numbers keep the mono; that is where fixed width earns its place. */
.medal b,
.tally-total,
.count {
  font-variant-numeric: tabular-nums;
}


/* -------------------------------------------------- the signature layer */

/* Skip link: invisible until a keyboard asks for it. */
.skip {
  position: fixed;
  top: 0.5rem;
  left: 0.5rem;
  z-index: 200;
  padding: 0.6rem 1rem;
  background: var(--color-paper);
  color: var(--color-ink);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  text-decoration: none;
  transform: translateY(-300%);
}

.skip:focus-visible {
  transform: none;
}

/* The stat line: her career in four measured figures, one quiet row. */
.statline {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs) var(--space-lg);
  padding-block: var(--space-md);
  border-bottom: var(--rule-hair) solid var(--color-line);
  color: var(--color-ink-2);
}

/*
 * The latitude rail. Her life runs 12 degrees north to 66 degrees north,
 * and this thin scale on the right edge ticks with scroll: the reader
 * travels the page and the marker travels her map. It is the one ornament
 * the site allows itself, because it is her fact and nobody else's.
 * Desktop only; on a phone it would fight the thumb.
 */
/* In off the window edge. It was fixed 1.1rem from the right of the
   viewport, which since the map left this section put it half a screen
   from anything it refers to; it now hangs off the same column the page
   is set in. */
.lat-rail {
  position: fixed;
  /* Hard against the frame, not 3.5rem inside the site column. It was
     placed relative to that column when the column had air to spare, but
     the split panels set their own measure and run well past it: at 1900
     the closing line of the profile reaches within 188px of the edge,
     while the rail sat at 269px and the readout hung 100px further left
     again, straight through the text.

     Nothing on the page can be relied on to stop at the column, so the
     rail stops relying on it. Pinned to the frame the readout still hangs
     about 75px inward, which clears the widest running text at every
     width the rail appears at. */
  right: clamp(0.6rem, 1vw, 1.25rem);
  top: 50%;
  transform: translateY(-50%);
  height: min(46vh, 22rem);
  width: 1px;
  background: var(--color-line);
  z-index: 50;
  display: none;
}

@media (min-width: 1100px) {
  .lat-rail {
    display: block;
  }
}

.lat-rail i {
  position: absolute;
  left: 50%;
  top: 0;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  transform: translate(-50%, -50%);
}

/* The readout takes the accent, because it is the thing that moves and it
   pairs with the dot. It was 9.92px, which is smaller than anything else
   on the site and the only element on the page changing as you scroll. */
.lat-rail b {
  position: absolute;
  right: 0.8rem;
  top: 0;
  transform: translateY(-50%);
  font-family: var(--font-caption);
  font-weight: 500;
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  /* Accent measured 4.33:1 against the rail track, just under the floor,
     and this is the one number on the rail a reader might actually use.
     The dot keeps the accent; the figure takes full ink. */
  color: var(--color-ink);
  white-space: nowrap;
}

/* The ends are the scale the readout runs between. At 0.58rem they were
   9.28px on the rail track at 3.09:1, which is not a label, it is a
   texture that happens to be made of numbers. */
.lat-rail .lat-end {
  position: absolute;
  right: 0.8rem;
  font-family: var(--font-caption);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  color: var(--color-ink-2);
  white-space: nowrap;
}

.lat-rail .lat-end[data-end="n"] { top: -1.35rem; }
.lat-rail .lat-end[data-end="s"] { bottom: -1.35rem; }

/* On the dark grounds the rail would vanish; it dims instead of lying. */

/* The places arrive one at a time, in the order she reached them. Each pin
   pops to size with a single halo pulse, then rests; no connecting line,
   because her route doubles back on itself and drawn honestly it read as
   string. */
.route-map .pin[data-stop] {
  transform-box: fill-box;
  transform-origin: center;
}

.route-map .pin[data-lit="false"] {
  opacity: 0;
  transform: scale(0);
}

.route-map .pin[data-lit="true"] {
  animation: pin-pop 500ms var(--ease-out) both;
}

@keyframes pin-pop {
  0% { opacity: 0; transform: scale(0); }
  60% { opacity: 1; transform: scale(1.9); }
  100% { opacity: 1; transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .route-map .pin[data-lit] {
    opacity: 1;
    transform: none;
    animation: none;
  }
}

/* The pull quote on About: the reported line at reading-aloud size. */
.pull {
  font-family: var(--font-body);
  font-size: var(--text-2xl);
  font-weight: 300;
  line-height: 1.35;
  color: var(--color-ink);
  border-left: 2px solid var(--color-accent);
  padding-left: var(--space-md);
}

.pull-cite {
  padding-left: calc(var(--space-md) + 2px);
}

/* Journal reading progress: one hairline, no chrome. */
.read-progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  z-index: 90;
  background: transparent;
}

.read-progress i {
  display: block;
  height: 100%;
  width: 100%;
  background: var(--color-accent-bright);
  transform-origin: 0 50%;
  transform: scaleX(var(--read, 0));
}

/* The 2030 ladder climbs left to right toward the Alps. */
@media (min-width: 720px) {
  .road li {
    margin-left: calc(var(--rung, 0) * 3.5rem);
    border-left: 2px solid var(--color-accent-wash);
    border-top: none;
    padding: 0 0 var(--space-2xs) var(--space-md);
  }

  .road li:last-child {
    border-left-color: var(--color-accent);
  }
}

/* The photograph wall: natural ratios in columns, nothing cropped at all. */
.masonry {
  columns: 2;
  column-gap: var(--space-md);
}

@media (min-width: 1000px) {
  .masonry {
    columns: 3;
  }
}

.masonry .tile {
  display: block;
  break-inside: avoid;
  margin-bottom: var(--space-md);
}

.masonry .tile-shot img {
  aspect-ratio: auto;
}

/* Lightbox grows arrows and a counter. */
.lightbox nav {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: var(--space-sm);
  pointer-events: none;
}

.lightbox nav button {
  pointer-events: auto;
  width: 2.75rem;
  height: 2.75rem;
  display: grid;
  place-items: center;
  border: var(--rule-hair) solid oklch(100% 0 0 / 0.5);
  border-radius: var(--radius-pill);
  background: oklch(16% 0.03 255 / 0.4);
  color: var(--color-ink-on-deep);
  font-size: 1.1rem;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}

.lightbox nav button:hover {
  background: oklch(16% 0.03 255 / 0.75);
}

.lightbox .count {
  position: absolute;
  top: var(--space-md);
  right: var(--space-md);
  font-family: var(--font-caption);
  font-size: var(--text-xs);
  letter-spacing: 0.12em;
  color: var(--color-ink-on-deep-2);
}


/* ------------------------------------------------------------ the masthead */

/*
 * The typographic hero. Every full-bleed attempt fought its photograph;
 * this one leads with the name at display size, staggered like Badosa's,
 * and lets the photograph be a photograph in its own frame beside it.
 * White ground, so the site opens in daylight.
 */
.masthead {
  background: var(--color-paper);
  padding-block: var(--space-xl) 0;
}

.masthead-grid {
  display: grid;
  gap: var(--space-lg);
  align-items: center;
  padding-bottom: var(--space-xl);
}

@media (min-width: 900px) {
  .masthead-grid {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    gap: var(--space-2xl);
  }
}

.masthead-copy {
  display: grid;
  gap: var(--space-md);
  justify-items: start;
}

.masthead h1 {
  /* Sized so the surname always holds one line inside its seven columns:
     at 9vw the first render broke Thekkada mid-word again. The global
     overflow-wrap:anywhere is overridden here because a name never breaks. */
  font-size: clamp(3rem, 7vw, 6.75rem);
  font-weight: 800;
  letter-spacing: -0.045em;
  line-height: 0.92;
  color: var(--color-ink);
  display: grid;
  overflow-wrap: normal;
}

.masthead h1 .shift {
  margin-left: clamp(1.25rem, 4.5vw, 4.5rem);
  color: var(--color-accent);
}

.masthead-line {
  font-family: var(--font-body);
  font-size: var(--text-2xl);
  font-weight: 300;
  color: var(--color-ink-2);
}

.masthead-cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.masthead-shot {
  margin: 0;
  position: relative;
  overflow: hidden;
  background: var(--color-paper-3);
}

.masthead-shot img {
  width: 100%;
  aspect-ratio: 4 / 5;
  height: auto;
  object-fit: cover;
  animation: hero-drift 22s var(--ease-in-out) forwards;
  transform-origin: 50% 30%;
}

.masthead-shot .caption {
  position: absolute;
  left: var(--space-sm);
  bottom: var(--space-sm);
  color: var(--color-ink-on-deep);
  text-shadow: 0 0 10px oklch(16% 0.03 255 / 0.7);
}

@media (prefers-reduced-motion: reduce) {
  .masthead-shot img {
    animation: none;
  }
}

/* --------------------------------------------------------------- the wire */

/* Press as a wire service: numbered rows, the headline carrying the weight,
   and on a cursor screen the photograph floats beside the pointer instead
   of sitting in a grey box. */
.press-wire {
  list-style: none;
  margin: var(--space-md) 0 0;
  padding: 0;
}

.press-wire li {
  border-top: var(--rule-hair) solid var(--color-line);
}

.press-wire li:last-child {
  border-bottom: var(--rule-hair) solid var(--color-line);
}

.press-wire a,
.press-wire li > p {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--space-sm);
  padding-block: var(--space-md);
  margin: 0;
  text-decoration: none;
}

.wire-idx {
  color: var(--color-ink-3);
}

.wire-main {
  display: grid;
  gap: var(--space-3xs);
}

.wire-main b {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-xl);
  letter-spacing: -0.015em;
  line-height: 1.25;
  transition: color var(--dur-fast) var(--ease-out);
}

.press-wire a:hover .wire-main b {
  color: var(--color-accent);
}

.wire-arrow {
  font-size: var(--text-lg);
  color: var(--color-ink-3);
  transition: transform var(--dur-mid) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.press-wire a:hover .wire-arrow {
  transform: translate(3px, -3px);
  color: var(--color-accent);
}

.press-peek {
  position: fixed;
  z-index: 80;
  width: 15rem;
  pointer-events: none;
  opacity: 0;
  transform: scale(0.96);
  transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out);
}

.press-peek img {
  width: 100%;
  aspect-ratio: 3 / 2;
  height: auto;
  object-fit: cover;
  box-shadow: 0 18px 50px oklch(0% 0 0 / 0.3);
}

.press-peek[data-on="true"] {
  opacity: 1;
  transform: none;
}

@media (hover: none), (max-width: 899px) {
  .press-peek {
    display: none;
  }
}

/* --------------------------------------------------------------- the talks */

.talks {
  display: grid;
  gap: var(--space-md);
  margin-top: var(--space-lg);
}

@media (min-width: 760px) {
  .talks {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-lg);
  }
}

.talk {
  position: relative;
  border: var(--rule-hair) solid var(--color-line);
  background: var(--color-paper);
  padding: var(--space-lg);
  display: grid;
  gap: var(--space-xs);
}

.talk-n {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--text-2xl);
  color: var(--color-accent-wash);
  -webkit-text-stroke: 1px var(--color-accent);
  line-height: 1;
}

.talk h3 {
  font-size: var(--text-xl);
  font-weight: 600;
  letter-spacing: -0.015em;
}

.talk p {
  color: var(--color-ink-2);
  font-size: var(--text-base);
}

/* -------------------------------------------------------------- the target */

.target {
  position: relative;
  padding-block: var(--space-lg);
}

.target-year {
  position: absolute;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(6rem, 18vw, 16rem);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--color-paper-2);
  z-index: -1;
  user-select: none;
}

.funds .fact {
  background: var(--color-paper);
  border: var(--rule-hair) solid var(--color-line);
  border-top: none;
  padding: var(--space-lg);
}

.funds {
  margin-top: var(--space-lg);
}


/* ---------------------------------------------------- the blended opening */

/*
 * What makes the reference heroes read premium is not the photograph, it is
 * that the photograph owns the entire frame: the navigation floats over it
 * as glass, the image runs to every edge, and the frame hands over to the
 * page through a graded fade rather than a hard seam. Three pieces below do
 * exactly that, index only.
 */

/* The nav over the hero: transparent glass at the top of the page, its own
   scrim guaranteeing the links, turning solid navy the moment the reader
   scrolls. Interior pages never set data-glass and keep the solid bar. */
.nav[data-glass="true"] {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  background: linear-gradient(
    to bottom,
    oklch(16% 0.03 255 / 0.55),
    oklch(16% 0.03 255 / 0)
  );
  transition: background var(--dur-mid) var(--ease-out);
}

.nav[data-glass="true"][data-scrolled="true"] {
  background: var(--color-paper-deep);
}

/* The hero fills the viewport from the very top, under the glass nav. */
.nav[data-glass="true"] + main .panel[data-size="hero"] {
  min-height: 100svh;
}

/* The grade. One navy wash multiplied over the photograph pulls every frame
   into the site's palette, which is what "blended" looks like in practice:
   the image, the nav and the page reading as one material. */
.panel[data-size="hero"] .panel-shot img {
  filter: saturate(0.92) contrast(1.04);
}

.panel[data-size="hero"] .panel-shot::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--color-paper-deep);
  opacity: 0.18;
  mix-blend-mode: multiply;
}

/* The handover: the photograph fades into the page ground at its foot, so
   the section ends as a gradient rather than a cut. */
.panel[data-size="hero"] .panel-shot::after {
  background: linear-gradient(
      to right,
      oklch(16% 0.03 255 / 0.48) 0%,
      oklch(16% 0.03 255 / 0.1) 52%,
      oklch(16% 0.03 255 / 0) 78%
    ),
    linear-gradient(
      to bottom,
      oklch(16% 0.03 255 / 0.35) 0%,
      oklch(16% 0.03 255 / 0.08) 30%,
      oklch(16% 0.03 255 / 0.55) 82%,
      var(--color-paper) 100%
    );
}

/* The name over a full-viewport frame earns more size than the panel default. */
.panel[data-size="hero"] .panel-body h1 {
  font-size: clamp(3.2rem, 8vw, 7.75rem);
}


/* ----------------------------------------------------- the mobile menu */

/*
 * Below 900px the link row used to vanish entirely, which left phones with
 * the wordmark and nothing else. The row now folds into a panel under the
 * bar, opened by one plain word. Same links, same order, Work with me last.
 */
.nav-toggle {
  display: none;
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-ink-on-deep);
  background: none;
  border: none;
  padding: 0.75rem 0;
  min-height: 44px;
  cursor: pointer;
}

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

  .nav-inner {
    position: relative;
  }

  .nav-links {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin-inline: calc(-1 * var(--space-md));
    padding: var(--space-2xs) var(--space-md) var(--space-md);
    background: var(--color-paper-deep);
  }

  .nav[data-open="true"] .nav-links {
    display: flex;
  }

  .nav-links a {
    padding-block: 0.8rem;
    border-top: var(--rule-hair) solid oklch(100% 0 0 / 0.12);
  }
}

/* Work with me: the one action the brief asks the navigation to carry. */
.nav-links .nav-work {
  color: var(--color-ink-on-deep);
}

@media (min-width: 900px) {
  .nav-links .nav-work {
    margin-left: var(--space-sm);
    padding: 0.5rem 1rem;
    border: 1px solid oklch(100% 0 0 / 0.45);
    transition: border-color var(--dur-fast) var(--ease-out),
      background var(--dur-fast) var(--ease-out);
  }

  .nav-links .nav-work:hover {
    border-color: var(--color-ink-on-deep);
    background: oklch(100% 0 0 / 0.08);
  }
}

/* The talk card's provenance line sits quiet under the body. */
.talk .caption {
  color: var(--color-ink-3);
}

/* -------------------------------------------------- field-level form errors */

.field-err {
  color: oklch(45% 0.17 25);
  margin-top: var(--space-3xs);
}

.enquiry input[aria-invalid="true"],
.enquiry textarea[aria-invalid="true"] {
  border-color: oklch(45% 0.17 25);
}

/* "Organisation optional" reads as a two-word field name. The brackets
   make it the aside it is. They are drawn rather than typed so the
   label's own text stays clean, and because a bracket is punctuation
   about the layout rather than part of what the field is called.

   The margin is negative because the uppercase label is letter-spaced
   and that tracking is still applied to its final space, which left a
   visible gap in front of the bracket. */
.field .opt {
  text-transform: none;
  letter-spacing: 0;
  color: var(--color-ink-3);
  margin-left: -0.12em;
}

.field .opt::before {
  content: "(";
}

.field .opt::after {
  content: ")";
}

/* --------------------------------------------------- the lightbox, closed */

.lightbox .shut {
  position: absolute;
  top: var(--space-sm);
  right: var(--space-sm);
  font-size: var(--text-xl);
  line-height: 1;
  color: var(--color-ink-on-deep);
  background: none;
  border: none;
  padding: 0.6rem;
  min-width: 44px;
  min-height: 44px;
  cursor: pointer;
}

/* -------------------------------------- decorative numerals, reined in */

/* The oversized year is desktop furniture. On a phone it pressed the copy
   toward the edge; smaller and it still reads as altitude. */
@media (max-width: 640px) {
  .target-year {
    font-size: clamp(3.6rem, 24vw, 6rem);
  }
}


/* On a phone the three-column medal rows ran 130px past the frame, the
   nowrap medal chips doing the pushing. Below 640px each result stacks:
   year, event, medals, one under the other, same hairline per row. */
@media (max-width: 640px) {
  .medals,
  .medals tbody,
  .medals tr,
  .medals td {
    display: block;
  }

  .medals td {
    border-top: none;
    padding: 0;
  }

  .medals tr {
    border-top: var(--rule-hair) solid var(--color-line);
    padding-block: var(--space-sm);
  }

  .c-meds {
    text-align: left;
    white-space: normal;
    margin-top: var(--space-3xs);
  }

  .c-meds .medal {
    margin-left: 0;
    margin-right: var(--space-sm);
  }
}


/* ----------------------------------------------- daylight, 26 Aug review */

/*
 * The client: blue and white rather than dark backgrounds. The navy keeps
 * two jobs, the glass bar over the hero photograph and the type itself;
 * the chrome that used to sit on navy, the bar and the footer, now sits
 * on paper. Appended last so these win the cascade at equal specificity.
 */
.nav {
  background: var(--color-paper);
  color: var(--color-ink);
  border-bottom: var(--rule-hair) solid var(--color-line);
}

.nav .wordmark {
  color: var(--color-ink);
}

.nav .nav-links a {
  color: var(--color-ink-2);
}

.nav .nav-links a:hover,
.nav .nav-links a[aria-current="page"] {
  color: var(--color-ink);
}

.nav .nav-toggle {
  color: var(--color-ink);
}

.nav .nav-links .nav-work {
  color: var(--color-accent);
}

@media (min-width: 900px) {
  .nav .nav-links .nav-work {
    border-color: var(--color-accent);
  }

  .nav .nav-links .nav-work:hover {
    background: var(--color-accent);
    color: var(--color-paper);
  }
}

@media (max-width: 899px) {
  .nav .nav-links {
    background: var(--color-paper);
    border-bottom: var(--rule-hair) solid var(--color-line);
  }

  .nav .nav-links a {
    border-top-color: var(--color-line);
  }
}

/* The glass bar over the hero keeps its light-on-photo type until the
   reader scrolls, then joins the daylight chrome. */
.nav[data-glass="true"]:not([data-scrolled="true"]) {
  background: linear-gradient(
    to bottom,
    oklch(16% 0.03 255 / 0.55),
    oklch(16% 0.03 255 / 0)
  );
  border-bottom: none;
}

.nav[data-glass="true"]:not([data-scrolled="true"]) .wordmark,
.nav[data-glass="true"]:not([data-scrolled="true"]) .nav-toggle {
  color: var(--color-ink-on-deep);
}

.nav[data-glass="true"]:not([data-scrolled="true"]) .nav-links a {
  color: var(--color-ink-on-deep-2);
}

.nav[data-glass="true"]:not([data-scrolled="true"]) .nav-links a:hover,
.nav[data-glass="true"]:not([data-scrolled="true"]) .nav-links a[aria-current="page"] {
  color: var(--color-ink-on-deep);
}

.nav[data-glass="true"]:not([data-scrolled="true"]) .nav-links .nav-work {
  color: var(--color-ink-on-deep);
}

@media (max-width: 899px) {
  .nav[data-glass="true"]:not([data-scrolled="true"]) .nav-links {
    background: var(--color-paper);
  }

  .nav[data-glass="true"]:not([data-scrolled="true"]) .nav-links a {
    color: var(--color-ink-2);
  }
}

.nav[data-glass="true"][data-scrolled="true"] {
  background: var(--color-paper);
}

/* The footer joins the daylight too: ice ground, ink type. */
.foot {
  background: var(--color-paper-2);
  color: var(--color-ink);
}

.foot-rows {
  border-top-color: var(--color-line);
}

.foot-links a {
  color: var(--color-ink-2);
}

.foot-links a:hover {
  color: var(--color-ink);
}

.foot-fine {
  color: var(--color-ink-3);
}

/* Four format cards for the sport: two up on a phone, four across wide. */
.facts-quad {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (min-width: 900px) {
  .facts-quad {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* The who-she-is facts row sits directly under its split, quiet. */
#who-facts {
  padding-top: 0;
}


/* Who-she-is joins the daylight: the story panel sits on ice, not navy. */
.split-body {
  background: var(--color-paper-2);
  color: var(--color-ink);
}

.split-body .caption {
  color: var(--color-ink-3);
}

.split-body p {
  color: var(--color-ink-2);
}

.split-body h2 {
  color: var(--color-ink);
}


/* ------------------------------------------------------ the hero rotation */

/* Four frames crossfading the way stadium screens change: slow, no
   controls, the photograph simply becomes another photograph. */
.panel-shot[data-slides="true"] img.slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1100ms var(--ease-out);
}

.panel-shot[data-slides="true"] img.slide[data-on="true"] {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .panel-shot[data-slides="true"] img.slide {
    transition: none;
  }
}

/* The merged who-she-is: her statement leads, the story follows under a
   quieter subhead, and the facts close the section on the same ground. */
.split-body h3 {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 600;
  letter-spacing: -0.015em;
  margin-top: var(--space-md);
}

#who-facts .statline {
  margin-top: var(--space-md);
}


/* ------------------------------------------------------- the quote card */

/* Her reference: Badosa's quote treatment. Accent bar, oversized mark,
   the words in the reading face, her name beneath in caps. */
.quote-card {
  position: relative;
  margin: 0;
  background: var(--color-paper-2);
  border-left: 3px solid var(--color-accent);
  padding: var(--space-xl) var(--space-lg) var(--space-lg);
  max-width: 62rem;
  margin-inline: auto;
}

.qmark {
  position: absolute;
  top: 0.2rem;
  left: var(--space-md);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 5rem;
  line-height: 1;
  color: var(--color-accent);
  opacity: 0.35;
  user-select: none;
}

.quote-card blockquote {
  margin: 0;
}

.quote-card blockquote p {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--text-xl);
  font-weight: 300;
  line-height: 1.55;
  color: var(--color-ink);
  text-align: center;
}

.quote-card figcaption {
  margin-top: var(--space-md);
  text-align: center;
  color: var(--color-ink-3);
}

/* The hero's second line sits quieter under the tagline. */
.panel[data-size="hero"] .sub-2 {
  font-size: var(--text-base);
  opacity: 0.85;
}

/* Sport group labels and prose h3 in general. */
.prose h3 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin-top: var(--space-lg);
}


/* ------------------------------------------------ the homepage, composed */

/* Generous vertical rhythm: sections breathe the way the benchmark's do. */
.prose-fold {
  padding-block: clamp(3.5rem, 8vw, 6.5rem);
}

/* The hero tagline: her sentence, set as an aside rather than a shout. */
.panel[data-size="hero"] .hero-line {
  font-style: italic;
  font-size: var(--text-lg);
  max-width: 40ch;
  opacity: 0.92;
}

/* A quiet scroll cue where the button used to be: one thin line, falling. */
.scroll-cue {
  position: absolute;
  left: 50%;
  bottom: var(--space-md);
  width: 1px;
  height: 56px;
  overflow: hidden;
}

.scroll-cue i {
  position: absolute;
  left: 0;
  top: -100%;
  width: 1px;
  height: 100%;
  background: linear-gradient(to bottom, transparent, var(--color-ink-2));
  animation: cue-drop 2.2s var(--ease-in-out) infinite;
}

@keyframes cue-drop {
  0% { top: -100%; }
  60% { top: 100%; }
  100% { top: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-cue { display: none; }
}

/* The who-she-is facts sit in a ruled strip, not loose on the page. */
#who-facts .facts-row {
  border-block: var(--rule-hair) solid var(--color-line);
  padding-block: var(--space-md);
}

/* Sport: editorial column left, the four format cards right. */
.sport-grid {
  display: grid;
  gap: var(--space-lg);
}

@media (min-width: 960px) {
  .sport-grid {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    gap: var(--space-2xl);
    align-items: start;
  }
}

.sport-cards h3 {
  font-family: var(--font-caption);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-ink-3);
  margin-top: var(--space-lg);
}

.sport-cards h3:first-child {
  margin-top: 0;
}

.facts-pair {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* Tile-band kickers share the caption voice of every other section. */
.tiles-head .caption {
  margin-bottom: var(--space-2xs);
  color: var(--color-ink-3);
}


/* ---------------------------------------------------- the opening stack */

/*
 * The benchmark's signature move: the first folds pin at the top and the
 * next one rides up over them, so the opening reads as cards being laid
 * on a table. Pure sticky positioning, no scroll hijacking; a reader
 * scrolling fast simply sees sections. Desktop only, because the story
 * card cannot fit a phone viewport and a clipped sticky card is worse
 * than an honest scroll.
 */
@media (min-width: 900px) {
  .stack > * {
    position: sticky;
    top: 0;
  }

  /* Every card after the first casts back onto the one beneath it. */
  .stack > * + * {
    box-shadow: 0 -28px 70px oklch(20% 0.03 255 / 0.28);
  }

  /* The statement card: full viewport, her words centred, opaque ground
     so the hero disappears completely beneath it. */
  .stack .stack-card {
    min-height: 100svh;
    display: grid;
    align-content: center;
    background: var(--color-paper);
  }

  /* The story card: the split stretches to the full viewport. The
     photograph pins inside it, holding the viewport while the story
     scrolls through beside her, so she watches the reader read. */
  .stack .split {
    min-height: 100svh;
    background: var(--color-paper-2);
    align-items: start;
  }

  .stack .split-shot {
    position: sticky;
    top: 0;
    height: 100svh;
  }

  .stack .split-shot img {
    height: 100%;
    object-fit: cover;
  }
}


/* The facts close the story column, spaced like a signature. */
.split-body .facts {
  margin-top: var(--space-lg);
}

.split-body .facts .fact {
  border-top: var(--rule-hair) solid var(--color-line);
  padding-top: var(--space-2xs);
}


/* ------------------------------------------------- the hero, recomposed */

/*
 * The client: the hero felt odd, not premium. Two causes, both here.
 * The grade was a flat grey veil that muted every frame equally; it
 * becomes one cinematic treatment, richer image, a corner vignette
 * where the type lives, and the same paper handover at the foot. And
 * the type stack was three unrelated voices; it becomes one: a ruled
 * caps kicker, a tighter 700-weight name, and the tagline upright at
 * reading size, each a measured step from the last.
 */
.panel[data-size="hero"] .panel-shot img {
  filter: saturate(1.02) contrast(1.07) brightness(0.99);
}

.panel[data-size="hero"] .panel-shot::before {
  opacity: 0.10;
}

.panel[data-size="hero"] .panel-shot::after {
  background: radial-gradient(
      120% 90% at 18% 78%,
      oklch(16% 0.03 255 / 0.52) 0%,
      oklch(16% 0.03 255 / 0.18) 45%,
      oklch(16% 0.03 255 / 0) 70%
    ),
    linear-gradient(
      to bottom,
      oklch(16% 0.03 255 / 0.30) 0%,
      oklch(16% 0.03 255 / 0.05) 30%,
      oklch(16% 0.03 255 / 0.38) 80%,
      var(--color-paper) 100%
    );
}

.panel[data-size="hero"] .panel-body {
  gap: var(--space-sm);
  max-width: 46rem;
}

.panel[data-size="hero"] .panel-body .caption {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  letter-spacing: 0.22em;
  color: oklch(100% 0 0 / 0.85);
}

.panel[data-size="hero"] .panel-body .caption::before {
  content: "";
  width: 2.2rem;
  height: 1px;
  background: var(--color-accent);
}

.panel[data-size="hero"] .panel-body h1 {
  font-size: clamp(3rem, 6.5vw, 6.25rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 0.95;
  text-shadow: 0 2px 40px oklch(16% 0.03 255 / 0.35);
}

.panel[data-size="hero"] .hero-line {
  font-style: normal;
  font-size: var(--text-xl);
  line-height: 1.5;
  max-width: 34ch;
  color: oklch(100% 0 0 / 0.93);
  opacity: 1;
  margin-top: var(--space-2xs);
}


/* -------------------------------------- hero brief, 30 Aug: final pass */

/*
 * Implementation brief applied verbatim where it speaks, reconciled with
 * the design system where it defers: Archivo is the strong sans the brief
 * says to reuse; the paper handover at the foot stays because the page
 * below is white and the brief leaves hero height and sections alone.
 */
.panel[data-size="hero"] .panel-shot::before {
  opacity: 0.08;
}

.panel[data-size="hero"] .panel-shot::after {
  background:
    linear-gradient(
      90deg,
      rgba(5, 12, 22, 0.72) 0%,
      rgba(5, 12, 22, 0.42) 42%,
      rgba(5, 12, 22, 0.08) 72%
    ),
    linear-gradient(
      to bottom,
      rgba(5, 12, 22, 0.2) 0%,
      rgba(5, 12, 22, 0) 38%,
      rgba(5, 12, 22, 0.24) 78%,
      var(--color-paper) 100%
    );
}

/* Lower-left third, with breathing room off the bottom edge. */
.panel[data-size="hero"] {
  padding-bottom: clamp(4rem, 11vh, 7rem);
}

/* The name: the strongest element, no effects, the overlay does the work. */
.panel[data-size="hero"] .panel-body h1 {
  font-size: clamp(3.4rem, 7vw, 7.5rem);
  font-weight: 800;
  line-height: 0.92;
  letter-spacing: -0.045em;
  text-shadow: none;
  color: oklch(99% 0.002 250);
}

/* The eyebrow: secondary, tracked, firm. */
.panel[data-size="hero"] .panel-body .caption {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.18em;
}

/* The sub-punchline: refined sans, short measure, quiet confidence. */
.panel[data-size="hero"] .hero-line {
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 1.5vw, 1.5rem);
  font-weight: 400;
  line-height: 1.4;
  max-width: 32rem;
  color: oklch(100% 0 0 / 0.92);
}


/* Hero, client round: the photograph leads. Lighter scrim, the name a
   step smaller, the eyebrow gone since the title line now carries the
   role. Name plus one line, nothing else. */
.panel[data-size="hero"] .panel-shot::after {
  /* The corner radial guarantees the copy zone on every frame: white
     type never sits on bare snow, whatever the rotation shows. */
  background:
    radial-gradient(
      85% 65% at 14% 86%,
      rgba(5, 12, 22, 0.58) 0%,
      rgba(5, 12, 22, 0.22) 45%,
      rgba(5, 12, 22, 0) 68%
    ),
    linear-gradient(
      90deg,
      rgba(5, 12, 22, 0.5) 0%,
      rgba(5, 12, 22, 0.22) 44%,
      rgba(5, 12, 22, 0) 70%
    ),
    linear-gradient(
      to bottom,
      rgba(5, 12, 22, 0.14) 0%,
      rgba(5, 12, 22, 0) 38%,
      rgba(5, 12, 22, 0.18) 78%,
      var(--color-paper) 100%
    );
}

.panel[data-size="hero"] .panel-body h1 {
  font-size: clamp(3.2rem, 6vw, 6.5rem);
}

.panel[data-size="hero"] .hero-line {
  font-size: clamp(1.15rem, 1.6vw, 1.6rem);
  color: oklch(100% 0 0 / 0.95);
}


/* The name is a two-line lockup at every width; a wide window was
   setting it as one line that ran out of the protected corner and
   across the subject. The reinforcement also grows taller for short
   windows where the copy rides higher. */
.panel[data-size="hero"] .panel-body h1 span {
  display: block;
}

.panel[data-size="hero"] .panel-shot::after {
  background:
    radial-gradient(
      90% 85% at 16% 80%,
      rgba(5, 12, 22, 0.6) 0%,
      rgba(5, 12, 22, 0.24) 48%,
      rgba(5, 12, 22, 0) 70%
    ),
    linear-gradient(
      90deg,
      rgba(5, 12, 22, 0.5) 0%,
      rgba(5, 12, 22, 0.22) 44%,
      rgba(5, 12, 22, 0) 70%
    ),
    linear-gradient(
      to bottom,
      rgba(5, 12, 22, 0.14) 0%,
      rgba(5, 12, 22, 0) 38%,
      rgba(5, 12, 22, 0.18) 78%,
      var(--color-paper) 100%
    );
}


/* --------------------------------------- the sport, redesigned (UI brief) */

/* Visual first: heading and two short serif lines, then the two technique
   blocks doing the explaining, then the formats pair, then her line as a
   ruled close. No repeated hairlines, no data-sheet columns. */
.sport-head {
  max-width: 60ch;
  display: grid;
  gap: var(--space-2xs);
}

.sport-head h2 {
  margin-bottom: var(--space-2xs);
}

.sport-intro {
  font-family: var(--font-body);
  font-size: var(--text-lg);
  color: var(--color-ink-2);
}

.tech-blocks {
  display: grid;
  gap: var(--space-md);
  margin-top: var(--space-xl);
}

@media (min-width: 760px) {
  .tech-blocks {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-lg);
  }
}

.tech {
  position: relative;
  background: var(--color-paper-2);
  padding: var(--space-lg) var(--space-lg) var(--space-md);
  display: grid;
  gap: var(--space-sm);
  justify-items: start;
}

.tech-n {
  position: absolute;
  top: var(--space-sm);
  right: var(--space-md);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(2.6rem, 4.5vw, 4rem);
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--color-accent);
  opacity: 0.16;
  user-select: none;
}

.tech h3 {
  font-family: var(--font-caption);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-ink);
}

.tech svg {
  width: min(15rem, 100%);
  height: auto;
  color: var(--color-ink-2);
  margin-block: var(--space-2xs);
}

.tech p {
  font-family: var(--font-body);
  color: var(--color-ink-2);
}

.formats {
  margin-top: var(--space-xl);
}

.formats-row {
  display: grid;
  gap: var(--space-md) var(--space-2xl);
  margin-top: var(--space-sm);
}

@media (min-width: 640px) {
  .formats-row {
    grid-template-columns: repeat(2, minmax(0, 22rem));
  }
}

.format h4 {
  font-family: var(--font-caption);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.format p {
  font-family: var(--font-display);
  font-weight: 500;
  color: var(--color-ink-2);
  margin-top: var(--space-3xs);
}

.sport-bridge {
  margin-top: var(--space-lg);
  padding-top: var(--space-md);
  border-top: var(--rule-hair) solid var(--color-line);
  color: var(--color-ink-2);
  letter-spacing: 0.14em;
}

.sport-bridge::before {
  content: "";
  display: block;
  width: 2.4rem;
  height: 2px;
  background: var(--color-accent);
  margin-bottom: var(--space-2xs);
  margin-top: calc(-1 * var(--space-md) - 1px);
}


/* ----------------------------------------- hero UI/UX brief, final pass */

/* The image leads, the name anchors, everything else supports. */

/* Copy block lifts off the bottom edge into the lower-left third. */
.panel[data-size="hero"] {
  padding-bottom: clamp(6.5rem, 15vh, 10rem);
}

.panel[data-size="hero"] .panel-body {
  gap: var(--space-xs);
}

/* Eyebrow above the name, secondary. */
.panel[data-size="hero"] .hero-eyebrow {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: oklch(100% 0 0 / 0.85);
}

/* Name at the brief's scale, tight, never cropped. */
.panel[data-size="hero"] .panel-body h1 {
  font-size: clamp(3.4rem, 6.5vw, 7rem);
  font-weight: 800;
  line-height: 0.9;
  letter-spacing: -0.045em;
}

/* Supporting statement, not a caption. */
.panel[data-size="hero"] .hero-line {
  font-size: clamp(1.25rem, 1.5vw, 1.6rem);
  font-weight: 400;
  line-height: 1.35;
  max-width: 26rem;
  margin-top: var(--space-2xs);
}

/* Text-zone gradient only; the image keeps its light. The foot fades to
   paper because the page below is white. */
.panel[data-size="hero"] .panel-shot::after {
  background:
    linear-gradient(
      90deg,
      rgba(3, 11, 24, 0.62) 0%,
      rgba(3, 11, 24, 0.36) 32%,
      rgba(3, 11, 24, 0.08) 58%,
      rgba(3, 11, 24, 0) 72%
    ),
    linear-gradient(
      to bottom,
      rgba(3, 11, 24, 0.18) 0%,
      rgba(3, 11, 24, 0) 30%,
      rgba(3, 11, 24, 0) 68%,
      var(--color-paper) 100%
    );
}

/* Photographic calm: a touch less saturation and contrast, per brief. */
.panel[data-size="hero"] .panel-shot img {
  filter: saturate(0.94) contrast(1.02);
}

/* Crossfade inside the brief's 700-1000ms window. */
.panel-shot[data-slides="true"] img.slide {
  transition-duration: 1000ms;
}

/* Nav: quieter tracking, glass-blur only once scrolled on the hero page. */
.nav-links a {
  font-size: 0.84rem;
  font-weight: 600;
  letter-spacing: 0.1em;
}

.nav[data-glass="true"][data-scrolled="true"] {
  background: rgba(4, 12, 25, 0.66);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.nav[data-glass="true"][data-scrolled="true"] .wordmark,
.nav[data-glass="true"][data-scrolled="true"] .nav-toggle,
.nav[data-glass="true"][data-scrolled="true"] .nav-links a:hover,
.nav[data-glass="true"][data-scrolled="true"] .nav-links a[aria-current="page"] {
  color: var(--color-ink-on-deep);
}

.nav[data-glass="true"][data-scrolled="true"] .nav-links a {
  color: var(--color-ink-on-deep-2);
}

.nav[data-glass="true"][data-scrolled="true"] .nav-links .nav-work {
  color: var(--color-ink-on-deep);
  border-color: rgba(255, 255, 255, 0.32);
}

.nav[data-glass="true"][data-scrolled="true"] .nav-links .nav-work:hover {
  background: rgba(255, 255, 255, 0.08);
}

/* Contact pill, less boxy. */
@media (min-width: 900px) {
  .nav-links .nav-work {
    padding: 0.7rem 1.3rem;
    border-width: 1px;
  }
}

/* The coordinate rail stays as sitewide language, quieter. */
.lat-rail {
  opacity: 0.55;
}

/* Mobile: its own composition. Focal points per frame, name scale held,
   copy off the bottom edge with safe-area padding. */
@media (max-width: 768px) {
  .panel[data-size="hero"] {
    padding-bottom: max(4rem, env(safe-area-inset-bottom, 0px) + 3.5rem);
  }

  .panel[data-size="hero"] .panel-body h1 {
    font-size: clamp(3.25rem, 15vw, 4rem);
  }

  .panel-shot[data-slides="true"] img.slide[data-i="1"] {
    object-position: 58% 45% !important;
  }

  .panel-shot[data-slides="true"] img.slide[data-i="2"] {
    object-position: 52% 34% !important;
  }
}


/* --------------------------------- hero, the client's mock of 30 Aug */

/* Black Rusher, the client-supplied face V1 carried on its wordmark,
   now trialled on the hero name at her request. Script faces need no
   negative tracking and a little more line room. */
@font-face {
  font-family: "Black Rusher";
  src: url("../fonts/black-rusher.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

.panel[data-size="hero"] .panel-body h1 {
  font-family: "Black Rusher", var(--font-display);
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.02;
  font-size: clamp(3.2rem, 6vw, 6.4rem);
}

/* The accent rule between name and statement, as drawn. */
.panel[data-size="hero"] .panel-body h1::after {
  content: "";
  display: block;
  width: 7rem;
  height: 2px;
  background: var(--color-accent);
  margin-top: var(--space-sm);
}

/* The mock's eyebrow is plain tracked caps, no dash. */
.panel[data-size="hero"] .panel-body .caption::before {
  display: none;
}

/* Her figure rides the right side; the left is a navy ground the copy
   owns. The image block starts at 26% and the gradient bridges the
   seam, so the frame reads as one composition. Desktop only. */
@media (min-width: 900px) {
  .panel[data-size="hero"] .panel-shot {
    background: rgb(5, 14, 30);
  }

  .panel-shot[data-slides="true"] img.slide {
    left: 26%;
    width: 74%;
  }

  .panel[data-size="hero"] .panel-shot::after {
    background:
      linear-gradient(
        90deg,
        rgba(5, 14, 30, 0.98) 0%,
        rgba(5, 14, 30, 0.9) 27%,
        rgba(5, 14, 30, 0.52) 44%,
        rgba(5, 14, 30, 0.12) 60%,
        rgba(5, 14, 30, 0) 72%
      );
    /* No bottom fade: the photograph runs to the section's edge as in
       the mock, and the statement card covers the seam as it rides up. */
  }
}


/* ------------------------------- hero, minimal one-frame (Badosa ref) */

/* One photograph end to end, one name across it. The script face moves
   to the BT monogram, where a signature belongs; the name returns to
   the grotesk at display scale, staggered like the reference. */
.wordmark-mono {
  font-family: "Black Rusher", var(--font-display);
  font-size: 1.7rem;
  font-weight: 400;
  letter-spacing: 0.02em;
  text-transform: none;
}

.panel[data-size="hero"] {
  place-items: center stretch;
  padding-bottom: 0;
}

.panel[data-size="hero"] .hero-min {
  max-width: none;
  justify-items: stretch;
}

.panel[data-size="hero"] .hero-min h1 {
  font-family: "Black Rusher", var(--font-display);
  font-size: clamp(3.4rem, 8.5vw, 8rem);
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: 0.01em;
  color: oklch(99% 0.002 250);
  /* The name crosses bright bib and snow; the strokes carry their own
     edge: one tight shadow for definition, two soft ones for ground. */
  text-shadow:
    0 1px 2px rgba(3, 9, 20, 0.6),
    0 4px 16px rgba(3, 9, 20, 0.55),
    0 12px 44px rgba(3, 9, 20, 0.5);
}

.panel[data-size="hero"] .hero-min h1::after {
  display: none;
}

.panel[data-size="hero"] .hero-min h1 .stagger {
  margin-left: clamp(5rem, 24vw, 26rem);
}

/* One even veil so the name reads on any exposure; no zones, no fades. */
.panel[data-size="hero"] .panel-shot::after {
  background: linear-gradient(
    rgba(3, 9, 20, 0.42),
    rgba(3, 9, 20, 0.42)
  );
}

@media (min-width: 900px) {
  .panel[data-size="hero"] .panel-shot::after {
    background: linear-gradient(
      rgba(3, 9, 20, 0.42),
      rgba(3, 9, 20, 0.42)
    );
  }
}

/* The image is full-bleed again; the right-block composition retires. */
@media (min-width: 900px) {
  .panel[data-size="hero"] .panel-shot img {
    left: 0;
    width: 100%;
  }
}


/* -------------------------------------- hero tagline and credential line */

.panel[data-size="hero"] .hero-tag {
  margin-top: var(--space-sm);
  color: oklch(100% 0 0 / 0.88);
  letter-spacing: 0.18em;
  font-weight: 700;
}

/* One fixed line; credentials fade through it. No layout shift, ever. */
.hero-cred {
  position: relative;
  height: 1.6em;
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 500;
  color: oklch(100% 0 0 / 0.95);
}

.hero-cred span {
  position: absolute;
  left: 0;
  top: 0;
  opacity: 0;
  transition: opacity 700ms var(--ease-out);
  text-shadow: 0 1px 2px rgba(3, 9, 20, 0.6), 0 4px 16px rgba(3, 9, 20, 0.5);
}

.hero-cred span[data-on="true"] {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .hero-cred span {
    transition: none;
  }
}


/* The lower-third chip: the quote card's language translated for sitting
   on a photograph. Frosted dark plate, accent bar, tagline over the
   rotating credential. */
.hero-chip {
  display: inline-grid;
  gap: var(--space-3xs);
  justify-self: start;
  margin-top: var(--space-md);
  padding: var(--space-sm) var(--space-md) var(--space-xs);
  background: rgba(10, 22, 40, 0.26);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-left: 3px solid var(--color-accent);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}

.panel[data-size="hero"] .hero-chip .hero-tag {
  margin-top: 0;
}

.hero-chip .hero-cred span {
  text-shadow: none;
}


/* Inside the chip: the credential cycles as a stat, oversized numeral in
   the light accent with a caps label beside it, the same voice as the
   Level of competition band further down the page. */
.hero-chip .hero-tag {
  color: oklch(78% 0.1 245);
  letter-spacing: 0.22em;
  font-size: 0.72rem;
}

.hero-chip .hero-cred {
  height: 2.6rem;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: oklch(100% 0 0 / 0.92);
}

.hero-chip .hero-cred span {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  white-space: nowrap;
}

.hero-chip .hero-cred b {
  font-family: var(--font-display);
  font-size: 2.1rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1;
  color: oklch(78% 0.1 245);
  font-variant-numeric: tabular-nums;
}


/* The chip must be as wide as its widest credential. Absolute spans do
   not size their parent, so the lines stack as grid layers instead: same
   crossfade, honest width. */
.hero-chip .hero-cred {
  display: grid;
  height: auto;
  min-height: 2.6rem;
}

.hero-chip .hero-cred span {
  position: static;
  grid-area: 1 / 1;
}


/* ------------------------------ the name moves to the wordmark (Runciman
   reference). Her name is the first thing read on every page, set in the
   V1 display face; the photograph then carries a punchline, not a
   repeat of the name. */

.wordmark-name {
  font-family: "Black Rusher", var(--font-display);
  font-size: clamp(1.15rem, 2.1vw, 1.65rem);
  font-weight: 400;
  letter-spacing: 0.015em;
  text-transform: none;
  line-height: 1;
  white-space: nowrap;
}

/* Four words, three lines, lower-left. Uppercase grotesk so it never
   competes with the script wordmark above it. */
.panel[data-size="hero"] .hero-min .hero-punch {
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 6.2vw, 5.4rem);
  font-weight: 800;
  line-height: 0.94;
  letter-spacing: -0.025em;
  text-transform: uppercase;
  color: oklch(99% 0.002 250);
  text-shadow:
    0 1px 2px rgba(3, 9, 20, 0.6),
    0 4px 16px rgba(3, 9, 20, 0.55),
    0 12px 44px rgba(3, 9, 20, 0.5);
}

.panel[data-size="hero"] .hero-min .hero-punch > span {
  display: block;
}

/* The second line steps in, the way the reference steps its middle line. */
.panel[data-size="hero"] .hero-min .hero-punch > span:nth-child(2) {
  margin-left: clamp(1.5rem, 5vw, 4.5rem);
}

.panel[data-size="hero"] .hero-min .hero-punch > span:nth-child(3) {
  margin-left: clamp(3rem, 10vw, 9rem);
}

.hero-punch .hl {
  color: oklch(80% 0.105 245);
}

.panel[data-size="hero"] .hero-min .hero-punch::after {
  display: none;
}

/* The chip sits closer now that the display block is shorter. */
.panel[data-size="hero"] .hero-chip {
  margin-top: var(--space-lg);
}

@media (max-width: 620px) {
  .panel[data-size="hero"] .hero-min .hero-punch > span:nth-child(2) {
    margin-left: 1rem;
  }

  .panel[data-size="hero"] .hero-min .hero-punch > span:nth-child(3) {
    margin-left: 2rem;
  }
}

/* Below the menu breakpoint the wordmark shares the bar with the toggle;
   it stays legible rather than large. */
@media (max-width: 520px) {
  .wordmark-name {
    font-size: 1.05rem;
  }
}

/* The sitewide h1 span rule turns every span into a block; the accent
   word inside a line is an exception and stays inline. */
.panel[data-size="hero"] .panel-body .hero-punch span .hl,
.panel[data-size="hero"] .panel-body .hero-punch .hl {
  display: inline;
}


/* ------------------------------------- hero composition, punchline pass

   The reference keeps its copy in the lower-left quadrant, clear of the
   rider, over a gradient that darkens only that corner. Ours has to do
   the same or the words run across her. Three moves: drop the block to
   the lower third, set it flush left inside a narrow measure, and grade
   the left edge down while she keeps her light. */

.panel[data-size="hero"] {
  align-content: end;
  padding-bottom: clamp(3.5rem, 9vh, 6.5rem);
}

.panel[data-size="hero"] .hero-min {
  justify-items: start;
}

.panel[data-size="hero"] .hero-min .hero-punch {
  font-size: clamp(2rem, 4.6vw, 3.9rem);
  line-height: 0.98;
  max-width: 13ch;
}

/* Flush left. The staggered indent pushed the middle line into her body;
   a clean left edge reads more assured anyway. */
.panel[data-size="hero"] .hero-min .hero-punch > span:nth-child(2),
.panel[data-size="hero"] .hero-min .hero-punch > span:nth-child(3) {
  margin-left: 0;
}

@media (max-width: 620px) {
  .panel[data-size="hero"] .hero-min .hero-punch > span:nth-child(2),
  .panel[data-size="hero"] .hero-min .hero-punch > span:nth-child(3) {
    margin-left: 0;
  }
}

/* A corner grade, not a flat veil: dark where the words are, clear where
   she is, a touch of weight along the foot so the chip has ground. */
.panel[data-size="hero"] .panel-shot::after {
  background:
    linear-gradient(
      95deg,
      rgba(3, 9, 20, 0.84) 0%,
      rgba(3, 9, 20, 0.78) 14%,
      rgba(3, 9, 20, 0.62) 30%,
      rgba(3, 9, 20, 0.34) 46%,
      rgba(3, 9, 20, 0.14) 64%,
      rgba(3, 9, 20, 0.22) 100%
    ),
    linear-gradient(
      to top,
      rgba(3, 9, 20, 0.55) 0%,
      rgba(3, 9, 20, 0.12) 34%,
      rgba(3, 9, 20, 0) 60%
    );
}

@media (min-width: 900px) {
  .panel[data-size="hero"] .panel-shot::after {
    background:
      linear-gradient(
        95deg,
        rgba(3, 9, 20, 0.84) 0%,
        rgba(3, 9, 20, 0.78) 14%,
        rgba(3, 9, 20, 0.62) 30%,
        rgba(3, 9, 20, 0.34) 46%,
        rgba(3, 9, 20, 0.14) 64%,
        rgba(3, 9, 20, 0.22) 100%
      ),
      linear-gradient(
        to top,
        rgba(3, 9, 20, 0.55) 0%,
        rgba(3, 9, 20, 0.12) 34%,
        rgba(3, 9, 20, 0) 60%
      );
  }
}

/* On the darker ground the chip can lose its frost and simply sit. */
.hero-chip {
  background: rgba(6, 16, 32, 0.4);
}

.panel[data-size="hero"] .hero-chip {
  margin-top: var(--space-md);
}

/* ---------------------------- the bar, now that the name sits in it

   Her full name is 167px of wordmark where the monogram was 26. Between
   the menu breakpoint and 1200 the row no longer fits, so the links
   tighten rather than the name shrinking away. */

@media (min-width: 900px) and (max-width: 1200px) {
  .wordmark-name {
    font-size: 1.05rem;
  }

  .nav-links {
    gap: 1.35rem;
  }

  .nav-links a {
    font-size: 0.76rem;
    letter-spacing: 0.08em;
  }

  .nav-links .nav-work {
    padding: 0.6rem 1rem;
  }
}

/* On a tall phone frame the corner grade does almost nothing, because the
   copy sits over the middle of the picture rather than its left edge. The
   foot carries the weight there instead. */
@media (max-width: 700px) {
  .panel[data-size="hero"] .panel-shot::after {
    background:
      linear-gradient(
        to top,
        rgba(3, 9, 20, 0.86) 0%,
        rgba(3, 9, 20, 0.7) 22%,
        rgba(3, 9, 20, 0.34) 42%,
        rgba(3, 9, 20, 0.08) 62%,
        rgba(3, 9, 20, 0.24) 100%
      );
  }

  .panel[data-size="hero"] .hero-min .hero-punch {
    font-size: clamp(2rem, 9.2vw, 2.9rem);
  }
}

/* ------------------------------- the level of competition, typed

   The client's note: she is rewriting the record, so the hero should
   look like it is being written. One plate, one caret, five figures
   cycling through it, and the way to the full record at the far right
   of the same line. */

.hero-foot {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-md);
  width: 100%;
  justify-self: stretch;
  margin-top: var(--space-md);
}

.hero-chip {
  margin-top: 0;
}

.cred-kicker {
  color: oklch(80% 0.105 245);
  letter-spacing: 0.22em;
  font-size: 0.7rem;
  margin: 0;
}

/* The live line and the ghosts share one grid cell. The ghosts are there
   only to hold the plate at its widest, so the caret never shifts it. */
.hero-chip .hero-cred > .cred-live,
.hero-chip .hero-cred > .cred-ghost {
  position: static;
  grid-area: 1 / 1;
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  white-space: nowrap;
  opacity: 1;
  transition: none;
}

.hero-chip .hero-cred > .cred-ghost {
  visibility: hidden;
}

.hero-chip .hero-cred .cred-t {
  position: static;
  display: inline;
  opacity: 1;
  transition: none;
  font-variant-numeric: normal;
}

/* Tabular figures so 03 and 20 occupy the same width as they swap. */
.hero-chip .hero-cred .cred-n {
  min-width: 2ch;
}

.cred-caret {
  display: inline-block;
  width: 2px;
  height: 1.05em;
  margin-left: 0.1em;
  background: oklch(80% 0.105 245);
  animation: credCaret 1.05s step-end infinite;
}

@keyframes credCaret {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
}

/* The way to the record, at the far right of the hero foot. */
.hero-record {
  font-family: var(--font-caption);
  /* Was 0.72rem. It is a link, sitting over a photograph, and it was the
     smallest interactive text on the site. */
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: oklch(100% 0 0 / 0.9);
  text-decoration: none;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid oklch(100% 0 0 / 0.3);
  white-space: nowrap;
  transition: color 200ms var(--ease-out), border-color 200ms var(--ease-out);
}

.hero-record span {
  display: inline-block;
  margin-left: 0.4em;
  transition: transform 200ms var(--ease-out);
}

.hero-record:hover,
.hero-record:focus-visible {
  color: oklch(84% 0.105 245);
  border-color: oklch(84% 0.105 245);
}

.hero-record:hover span {
  transform: translateX(3px);
}


@media (prefers-reduced-motion: reduce) {
  .cred-caret {
    animation: none;
  }

  .hero-record span {
    transition: none;
  }
}

/* On a phone the two stack; the record link keeps its own line so it
   stays a comfortable tap target and never wraps mid-phrase. */
@media (max-width: 700px) {
  .hero-foot {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-sm);
  }

  /* A 25px link is not a tap target; the underline drops with the padding. */
  .hero-record {
    padding-block: 0.7rem;
  }
}

/* The record link goes to the true right edge of the panel, not the right
   edge of the text column: the column stops 200px short to leave the
   coordinate rail its air, and the client asked for the far extreme. It
   sits on the panel's own bottom inset, so it lines up with the plate. */
@media (min-width: 900px) {
  .hero-foot {
    width: calc(100% + 2 * var(--space-xl));
  }
}


/* =========================== the sport, as one horizontal band

   The client's reference is a ski poster: the athlete set inside a
   geometric shape on a near-white ground, a wide-tracked display word
   under her, small caps everywhere else. Borrowed here as two devices
   only, the cut plate and the type lockup. Everything else stays site
   language, because pastiche reads as pastiche. */

.sport-band {
  padding-block: var(--space-3xl) var(--space-2xl);
  overflow: hidden;
}

.sport-top {
  display: grid;
  gap: var(--space-xl);
  align-items: center;
}

@media (min-width: 900px) {
  .sport-top {
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
    gap: var(--space-3xl);
  }
}

/* The plate: her photograph cut on a slant, with a pale wedge behind it
   offset up and left, the way the poster sets its subject in a shape. */
.sport-cut {
  position: relative;
  margin: 0;
  isolation: isolate;
}

/* One pale wedge sits behind, its slant parallel to the plate's, so it
   reads as part of the composition rather than a stray shard. */
.sport-cut::before {
  content: "";
  position: absolute;
  inset: 4% 58% 4% -9%;
  background: oklch(90% 0.038 245);
  clip-path: polygon(0 14%, 100% 0, 100% 86%, 0 100%);
  z-index: 0;
}

.cut-plate {
  position: relative;
  z-index: 1;
  aspect-ratio: 3 / 4;
}

.cut-plate img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Poster grade: near-monochrome, cool, snow held bright. */
  filter: saturate(0.34) contrast(1.09) brightness(1.05);
}

/* Inside the wedge she is fully exposed; outside it she falls away to a
   ghost on the ice ground. The two layers share a focal point, so the
   edge cuts cleanly through one continuous frame. */
.cut-main {
  z-index: 2;
  clip-path: polygon(19% 0, 100% 8%, 100% 100%, 19% 92%);
}

.cut-ghost {
  z-index: 1;
  opacity: 0.16;
  filter: saturate(0.12) contrast(0.82) brightness(1.22);
}

/* The lockup, the poster's own: one wide-tracked word, a rule, a line of
   small caps under it. */
.sport-kicker {
  color: var(--color-ink-2);
}

.sport-word {
  font-family: var(--font-display);
  font-size: clamp(3.2rem, 8vw, 6.4rem);
  font-weight: 400;
  line-height: 0.98;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-ink);
  margin: var(--space-2xs) 0 0;
  /* The tracking adds a trailing space the eye reads as a left indent. */
  text-indent: 0.16em;
}

.sport-word-sub {
  font-family: var(--font-caption);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--color-ink-2);
  margin-top: var(--space-2xs);
  padding-top: var(--space-2xs);
  border-top: var(--rule-hair) solid var(--color-line);
  display: inline-block;
}

.sport-lede {
  font-family: var(--font-body);
  font-size: var(--text-lg);
  color: var(--color-ink-2);
  max-width: 42ch;
  margin-top: var(--space-md);
}

.sport-lede-2 {
  margin-top: var(--space-2xs);
}

.sport-band .sport-bridge {
  font-family: var(--font-body);
  font-size: var(--text-base);
  text-transform: none;
  letter-spacing: 0;
  margin-top: var(--space-lg);
  padding-top: var(--space-sm);
  border-top: 2px solid var(--color-accent);
  border-image: none;
  color: var(--color-ink);
  max-width: 34ch;
  display: inline-block;
}

.fmt {
  border-top: 2px solid var(--color-ink);
  padding-top: var(--space-sm);
}

.fmt h3 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: -0.01em;
  text-transform: none;
  margin-top: 0;
}

.fmt p {
  font-family: var(--font-body);
  color: var(--color-ink-2);
  margin-top: var(--space-3xs);
}

@media (max-width: 899px) {
  .sport-cut {
    max-width: 26rem;
  }
}


/* ==================== hero typography, second pass

   Three lines at one size read flat, however large they are. The impact
   comes from scale contrast: a tracked-out overline, then two lines at
   full display weight with the point of difference in accent. Her name
   grows in the bar to match, since it is now the wordmark. */

/* Her name is the first thing on the page and the punchline sits under
   it on the same left edge, so the two read as one move down the screen.
   That only works if the name is big enough to start it. */
.wordmark-name {
  font-size: clamp(1.6rem, 3.4vw, 3.2rem);
}

/* The link row tightens so her name can hold its size down to a laptop.
   Below 1080 the row genuinely runs out of width, so the name gives way
   there and only there. */
@media (min-width: 900px) and (max-width: 1080px) {
  .wordmark-name {
    font-size: 1.75rem;
  }
}

@media (min-width: 900px) and (max-width: 1280px) {
  .nav-links {
    gap: 1.35rem;
  }

  .nav-links a {
    font-size: 0.76rem;
    letter-spacing: 0.08em;
  }

  .nav-links .nav-work {
    padding: 0.6rem 1rem;
  }
}

/* The overline: small, wide, with a drawn rule leading into it. */
.panel[data-size="hero"] .hero-min .hero-punch .punch-eyebrow {
  font-size: clamp(0.72rem, 1.1vw, 0.95rem);
  font-weight: 600;
  letter-spacing: 0.36em;
  line-height: 1;
  margin-bottom: var(--space-sm);
  color: oklch(100% 0 0 / 0.92);
  display: flex;
  align-items: center;
  gap: 0.9em;
}

.panel[data-size="hero"] .hero-min .hero-punch .punch-eyebrow::before {
  content: "";
  width: clamp(1.6rem, 4vw, 3.2rem);
  height: 2px;
  background: oklch(80% 0.105 245);
  flex: none;
}

/* The two lines that carry it. */
.panel[data-size="hero"] .hero-min .hero-punch .punch-big {
  font-size: clamp(2.4rem, 5.6vw, 5rem);
  line-height: 0.9;
  letter-spacing: -0.035em;
}

.panel[data-size="hero"] .hero-min .hero-punch {
  max-width: none;
}

@media (max-width: 700px) {
  .panel[data-size="hero"] .hero-min .hero-punch .punch-big {
    font-size: clamp(2.2rem, 10.5vw, 3.2rem);
  }

  .panel[data-size="hero"] .hero-min .hero-punch .punch-eyebrow {
    letter-spacing: 0.28em;
  }
}

/* ---------------------- a panel whose copy sits on the right

   The journey panel: she stands left of frame, so the words go right and
   the grade follows them. One column at reading width, pushed to the end
   of the grid, with the text itself still ranged left inside it. */

.panel[data-align="right"] .panel-body {
  display: grid;
  grid-template-columns: min(34rem, 100%);
  justify-content: end;
  /* The body is capped for centred panels; here the cap becomes the inner
     column instead, so the block can actually reach the right edge. */
  max-width: none;
}

.panel[data-align="right"] .panel-shot::after {
  background:
    linear-gradient(
      265deg,
      rgba(3, 9, 20, 0.8) 0%,
      rgba(3, 9, 20, 0.66) 20%,
      rgba(3, 9, 20, 0.38) 42%,
      rgba(3, 9, 20, 0.16) 62%,
      rgba(3, 9, 20, 0.28) 100%
    ),
    linear-gradient(
      to top,
      rgba(3, 9, 20, 0.4) 0%,
      rgba(3, 9, 20, 0) 46%
    );
}

@media (max-width: 899px) {
  .panel[data-align="right"] .panel-body {
    grid-template-columns: 100%;
    justify-content: start;
    max-width: 40rem;
  }

  .panel[data-align="right"] .panel-shot::after {
    background: linear-gradient(
      to top,
      rgba(3, 9, 20, 0.82) 0%,
      rgba(3, 9, 20, 0.58) 30%,
      rgba(3, 9, 20, 0.2) 58%,
      rgba(3, 9, 20, 0.3) 100%
    );
  }
}

/* A photographic panel inside the stack has to fill the card, or the one
   beneath shows through as it slides up. */
@media (min-width: 900px) {
  .stack > .panel {
    min-height: 100svh;
  }
}

/* ---------------------- a panel whose copy sits on the right

   The journey panel: she stands left of frame, so the words go right and
   the grade follows them. One column at reading width, pushed to the end
   of the grid, with the text itself still ranged left inside it. */

.panel[data-align="right"] .panel-body {
  display: grid;
  grid-template-columns: min(34rem, 100%);
  justify-content: end;
}

.panel[data-align="right"] .panel-shot::after {
  background:
    linear-gradient(
      265deg,
      rgba(3, 9, 20, 0.8) 0%,
      rgba(3, 9, 20, 0.66) 20%,
      rgba(3, 9, 20, 0.38) 42%,
      rgba(3, 9, 20, 0.16) 62%,
      rgba(3, 9, 20, 0.28) 100%
    ),
    linear-gradient(
      to top,
      rgba(3, 9, 20, 0.4) 0%,
      rgba(3, 9, 20, 0) 46%
    );
}

@media (max-width: 899px) {
  .panel[data-align="right"] .panel-body {
    grid-template-columns: 100%;
    justify-content: start;
  }

  .panel[data-align="right"] .panel-shot::after {
    background: linear-gradient(
      to top,
      rgba(3, 9, 20, 0.82) 0%,
      rgba(3, 9, 20, 0.58) 30%,
      rgba(3, 9, 20, 0.2) 58%,
      rgba(3, 9, 20, 0.3) 100%
    );
  }
}

/* A photographic panel inside the stack has to fill the card, or the one
   beneath shows through as it slides up. */
@media (min-width: 900px) {
  .stack > .panel {
    min-height: 100svh;
  }
}

/* ==================== body copy, sized to be read

   The client's note: the paragraphs are too small and the right half of
   the story column sits empty. Both are the same fault. Running text
   scales with the viewport now instead of sitting at 16px, the measure
   widens to use the column it was given, and body paragraphs take a
   darker ink than the quiet grey used for labels. */

:root {
  --color-ink-body: oklch(34% 0.032 255);
}

body {
  font-size: clamp(1.125rem, 0.3vw + 1.05rem, 1.28rem);
}

.prose p,
.sport-lede,
.split-body p:not(.split-quote) {
  color: var(--color-ink-body);
}

.prose {
  max-width: 62ch;
}

.split-body p:not(.split-quote) {
  font-size: inherit;
  max-width: 58ch;
  line-height: 1.55;
}

.sport-lede {
  font-size: inherit;
  max-width: 48ch;
}

.sport-band .sport-bridge {
  font-size: inherit;
  max-width: 40ch;
}

/* The panel subtitle sits on a photograph; 300 is too fine to hold there. */
.sub {
  font-weight: 400;
}

/* The story column had room to spare on the right. Give the text the
   width and keep the gutter honest. */
@media (min-width: 900px) {
  .split-body {
    padding: var(--space-3xl) var(--space-lg) var(--space-3xl) var(--space-xl);
  }
}

/* The four notes sit in narrow columns; running-text size crowds them, so
   they hold just under it. */
.fmt p {
  font-size: clamp(1rem, 0.2vw + 0.95rem, 1.1rem);
  line-height: 1.5;
}

/* ==================== the sport as the third card

   The client's section order: hero, Who she is, then the sport, all three
   sliding up as cards. The band needs to fill the card and paint its own
   ground, or the one beneath shows through as it rises. */

@media (min-width: 900px) {
  .stack > .sport-band {
    min-height: 100svh;
    display: grid;
    align-content: center;
    background: var(--color-paper);
    padding-block: var(--space-2xl);
  }
}


/* ==================== the wrap-up: who backs the season

   This was a logo strip wedged between two sections. As the last thing on
   the page it says what each supporter provides, which is the part a
   visiting sponsor is reading for. The marks keep a light ground. */

.backing {
  padding-block: var(--space-2xl) var(--space-lg);
}

.backing-head {
  font-size: var(--text-3xl);
  text-align: center;
  margin: 0;
}

.backers {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(2rem, 6vw, 5.5rem);
  margin-top: var(--space-xl);
}

/* One box, one optical size, whatever shape the artwork arrives in. The
   marks keep a light ground; forcing them white flattened the Reliance
   lockup and lost the Karnataka crest. */
.backer-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 5rem;
  flex: 0 1 auto;
}

.backer-mark img {
  max-height: 100%;
  max-width: 14rem;
  width: auto;
  height: auto;
  object-fit: contain;
  /* Two of the three marks are transparent and this does nothing to
     them. The third is a white wordmark on a solid field, which its
     owner supplies that way, and on a pale ground a hard-cornered block
     of colour reads as an image that failed to cut out. A small radius
     makes it a tile instead. */
  border-radius: var(--radius-sm, 0.35rem);
}

/* A partner whose artwork has not landed yet. Their name in the display
   face at a mark's weight, holding the same box the image would, so the
   row does not move when the file arrives. */

.backer-type {
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.15;
  color: var(--color-ink-2);
  text-align: center;
  max-width: 14rem;
  text-wrap: balance;
}

.backer-row .backer-type {
  text-align: left;
  font-size: var(--text-base);
}

/* The band is one solid colour, so a name set in it takes the band's ink
   rather than the page's. */
.sponsor-mark {
  display: flex;
  align-items: center;
  justify-content: center;
}

.sponsors .backer-type {
  color: inherit;
  opacity: 0.86;
}

/* The only commercial ask on the homepage, so it is a button rather
   than a caption link. A tiny underlined line in the last inch of a page
   is a footnote, and this is the whole reason a sponsor is reading. */
.backing-more {
  margin-top: var(--space-xl);
  padding-top: var(--space-lg);
  border-top: var(--rule-hair) solid var(--color-line);
  text-align: center;
}

.backing-more .btn {
  display: inline-flex;
}

/* ------------------- the sport lockup, flipped

   The reference could shout NORDIC over a small SKIING because both were
   halves of one name. Here the borrowed word was covering the real one,
   so the sport's actual name takes the display size and the category
   drops to the small line. Nordic is the umbrella that also holds ski
   jumping and nordic combined; she races cross-country. */

.sport-word {
  /* Thirteen tracked-out characters have to hold one line in a column
     that is 57.5% of the wrap. Measured: the word needs 561px against a
     589px column at 1280, and the cap keeps that true once the wrap
     stops growing. */
  font-size: clamp(1.7rem, 4.1vw, 3.3rem);
  letter-spacing: 0.1em;
  text-indent: 0.1em;
}

.sport-word span {
  display: block;
}

/* The lead sits directly under the name; it reads as a subtitle, not a
   paragraph, so it carries a little more weight than the copy below. */
.sport-say .sport-lede:first-of-type {
  color: var(--color-ink);
  margin-top: var(--space-sm);
}

/* ==================== the stack, for cards taller than the screen

   A card pinned at top:0 that is taller than the viewport can never show
   its own foot: it stops moving the moment its top hits zero, and every
   line past the first screenful sits below the fold with nothing able to
   scroll it up. That is what swallowed the Born / Mentored by / Trains
   across rows at the end of Who she is.

   The hero is exactly one viewport tall, so it still pins. The two cards
   that follow are taller, so they ride over the hero and scroll through
   in full. They keep their own ground and their cast shadow, so they
   still read as cards stacking; only the pinning goes. Relative, not
   static, or the pinned hero would paint over them. */

@media (min-width: 900px) {
  /* Superseded: tall cards now pin their bottom edge instead of opting
     out of pinning altogether. See 'every section is a card' below. */

  /* The portrait still pins inside the card while the story scrolls past
     it, which is the behaviour the client asked for and the reason the
     text column carries the facts at all. */
  .stack .split-shot {
    position: sticky;
    top: 0;
    height: 100svh;
  }
}

/* The plate loses its label; the typed line says what it is. */
.hero-chip {
  padding-block: var(--space-xs);
}

/* ---------------------- the closing panel, to the client's reference

   The reference sets one long first-person line at display size with the
   discipline alone beneath it. A sixty-six character sentence needs a
   wider measure than a four-word title did, so this panel gets one. */

/* The reference runs its line across the page and centres it; ours was
   boxed into a 46rem column on the left, which is why it read as another
   section rather than the finish. Every other panel on the site anchors
   its type to the bottom-left corner. This one deliberately does not. */
/* Centred was right when the frame had nobody in it. This photograph
   puts the climber at 62 to 74 percent of the width, so the words move
   to the lower left and stop before he starts: the copy column caps at
   46rem, which is 60 percent of the site column, and the block sits on
   the panel's bottom inset like every other cover on the site. */
.panel[data-size="closer"] {
  place-items: end start;
  text-align: left;
  padding-bottom: clamp(3rem, 8vh, 6rem);
}

.panel[data-size="closer"] .panel-body {
  max-width: min(100%, 46rem);
  justify-items: start;
  text-align: left;
}

/* Sized so sixty-six characters land on two lines across the full column,
   the shape the reference has. Measured: the face advances about 0.53em a
   character, so two lines need roughly column-width over 17.5. */
/* The destination sits on its own line, so the first line has to hold
   forty-five characters unbroken. That needs more room than the wrap's
   128px inset leaves, so this panel takes the full content box and a
   narrower gutter, and the type is sized against what is left:
   45 chars x 0.53em, so the cap has to stay under content-width / 23.9.
   At 1280 that is 48px and at 1900 it is 71px; 4.2rem and 3.5vw sit
   comfortably under both. */
.panel[data-size="closer"] {
  padding-inline: clamp(1.5rem, 5vw, 6rem);
}

.panel[data-size="closer"] .panel-body {
  width: 100%;
  max-width: none;
}

.panel[data-size="closer"] h2 {
  font-size: clamp(1.55rem, 3.1vw, 3.3rem);
  line-height: 1.08;
  letter-spacing: -0.025em;
  text-wrap: nowrap;
}

/* display: table shrink-wraps to the words and still breaks the line, so
   the stroke below spans the phrase and not the column. */
.panel[data-size="closer"] h2 .mark-draw {
  display: table;
  position: relative;
  margin: 0.1em auto 0 0;
  color: oklch(82% 0.115 245);
  text-wrap: nowrap;
}

.draw-line {
  position: absolute;
  left: -0.06em;
  right: -0.06em;
  bottom: -0.30em;
  width: calc(100% + 0.12em);
  height: 0.34em;
  overflow: visible;
}

/* Drawn by default and undrawn only once the script has confirmed it is
   running, the same rule the reveal animations follow: a blocked or
   broken script can never leave the stroke invisible. pathLength=1 makes
   the dash maths independent of how long the path actually is. */
.js .draw-line path {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.mark-draw[data-drawn="true"] .draw-line path {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1100ms cubic-bezier(0.22, 0.61, 0.36, 1) 260ms;
}

@media (prefers-reduced-motion: reduce) {
  .draw-line path {
    stroke-dashoffset: 0;
  }

  .mark-draw[data-drawn="true"] .draw-line path {
    transition: none;
  }
}

/* On a phone the first line cannot hold forty-five characters at any
   readable size, so it wraps and the phrase still takes its own line. */
@media (max-width: 700px) {
  .panel[data-size="closer"] h2 {
    text-wrap: balance;
  }
}

.panel[data-size="closer"] .sub {
  font-family: var(--font-caption);
  margin-inline: 0;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  margin-top: var(--space-xs);
  color: oklch(100% 0 0 / 0.86);
}

/* ==================== every section is a card, and tall cards still work

   The client wants the whole homepage stacking, not just the first two.
   The blocker was that a card pinned at top:0 which is taller than the
   viewport stops moving the moment its top hits zero, so its own foot can
   never be scrolled to. That is what swallowed the Born / Mentored by
   rows.

   Pinning the bottom edge instead solves it. A card scrolls normally
   until its last screenful is in view, then holds there while the next
   card rises over it. Everything above was read on the way past, and a
   card exactly one viewport tall behaves the same as it did before. */

@media (min-width: 900px) {
  /* Relative is the floor, not the effect: without JavaScript every card
     scrolls normally and nothing is unreachable. The script promotes each
     one to sticky and gives it the offset it needs, because a card taller
     than the viewport has to pin at a NEGATIVE top to hold its last
     screenful, and that value is the element's own height, which CSS
     cannot read. `bottom: 0` is not the answer: it pins on the way up,
     not the way down. Measured before this line: at 1500px of scroll a
     bottom-pinned card had already travelled to -780. */
  .stack > * {
    position: relative;
    min-height: 100svh;
  }

  /* Later cards paint over earlier ones. Sibling order already does this,
     but the stack now mixes image panels and painted grounds, so the
     order is written down rather than inferred. */
  .stack > :nth-child(1) { z-index: 1; }
  .stack > :nth-child(2) { z-index: 2; }
  .stack > :nth-child(3) { z-index: 3; }
  .stack > :nth-child(4) { z-index: 4; }
  .stack > :nth-child(5) { z-index: 5; }
  .stack > :nth-child(6) { z-index: 6; }
  .stack > :nth-child(7) { z-index: 7; }

  /* Each card needs an opaque ground or the one beneath shows through. */
  /* The last card is a footer plate, not a fold. Holding it to a full
     viewport left a screenful of nothing between the marks and the site
     footer, so it is only as tall as what it carries. */
  .stack > .backing {
    background: var(--color-paper-2);
    display: block;
    min-height: 0;
  }

  /* The footer is not a card; without a stacking order of its own the
     pinned cards would paint over it. */
  .site-footer,
  footer {
    position: relative;
    z-index: 10;
  }
}

/* ==================== the way through to Media

   The client's reference leans a pair of skis across the page at a steep
   angle. Four photographs do the same job here: one lean, stepped down
   the page, so the group reads as skis stood against a wall rather than a
   row of thumbnails. The frames are decorative duplicates of the button
   beside them, so they are out of the tab order and hidden from readers
   who are being read to; the button is the real way through. */

.media-strip {
  background: var(--color-paper);
  padding-block: var(--space-3xl);
  overflow: hidden;
}

.media-strip-inner {
  display: grid;
  gap: var(--space-2xl);
  align-items: center;
}

@media (min-width: 900px) {
  .media-strip-inner {
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
    gap: var(--space-3xl);
  }
}

.media-say {
  display: grid;
  justify-items: start;
  gap: var(--space-md);
}

.media-say h2 {
  font-size: var(--text-3xl);
  margin-top: var(--space-2xs);
}

.media-say p {
  color: var(--color-ink-body);
  max-width: 34ch;
}

/* --------------------------------------------------------------- collage

   The client's reference is a wall of results rather than a row of
   pictures, so the section carries a mosaic: ten frames on four columns
   at mixed sizes, hand-placed in the builder because the sizes are the
   composition. The spans total sixteen cells, so four square columns give
   four square rows and the block closes without a hole.

   Landscape photographs take the two-column slots and portrait ones the
   singles, so nothing is asked to fill a shape it was not shot for; the
   rest is object-fit. */

.collage {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-auto-rows: 1fr;
  gap: clamp(0.35rem, 0.7vw, 0.6rem);
  list-style: none;
  margin: 0;
  padding: 0;
  /* Sixteen cells over four columns is four rows, and square cells make a
     square block. Stated rather than left to the content so the tiles
     cannot be pulled out of shape by one tall photograph. */
  aspect-ratio: 1 / 1;
  align-self: center;
}

.collage-tile {
  grid-column: span var(--c);
  grid-row: span var(--r);
  overflow: hidden;
  border-radius: var(--radius-sm, 0.35rem);
  background: var(--color-paper-2);
  box-shadow: 0 1px 2px oklch(20% 0.03 255 / 0.1);
}

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

/* The block arrives a frame at a time.

   The site's own observer already sets the flag on the container when it
   comes into view, so this needs no script of its own. What it does need
   is for the container to stop fading as one object, which is what the
   sitewide rise does and what would have hidden the stagger completely:
   the whole collage would have appeared at once, tiles and all. Only the
   tiles move now, each waiting its turn off its own index, so the wall
   builds over about a second and lands complete.

   All of it is behind .js, like the sitewide rise: with no script the
   tiles are simply there, which is the correct fallback for a decorative
   block that would otherwise never appear. */

.js .collage[data-rise] {
  opacity: 1;
  transform: none;
}

.js .collage[data-rise] .collage-tile {
  opacity: 0;
  transform: translate3d(0, 12px, 0) scale(0.965);
}

.js .collage[data-rise][data-shown="true"] .collage-tile {
  opacity: 1;
  transform: none;
  transition: opacity 520ms var(--ease-out), transform 520ms var(--ease-out);
  transition-delay: calc(var(--i) * 110ms);
}

@media (prefers-reduced-motion: reduce) {
  .js .collage[data-rise] .collage-tile {
    opacity: 1;
    transform: none;
    transition: none;
    transition-delay: 0s;
  }
}

/* Below the split the collage halves rather than shrinks. Four columns in
   a phone's width puts a podium at 90px across, which is a texture and
   not a photograph; two columns keeps the two-column frames full width
   and the singles at half, so the rhythm of the wall survives. */
@media (max-width: 899px) {
  .collage {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    aspect-ratio: auto;
    gap: var(--space-2xs);
  }

  .collage-tile {
    aspect-ratio: 1 / 1;
  }

  .collage-tile[style*="--c:2"] {
    aspect-ratio: 2 / 1;
  }
}

@media (min-width: 900px) {
  .stack > .media-strip {
    display: grid;
    align-content: center;
  }
}

/* ---------------------- the note that closes the story column

   Not a quote: these are the site's words about what she is trying to do,
   not hers, so it takes a marked plate rather than quotation marks. An
   accent rule down the left, a tint the ice ground can carry, and body
   type a step up from the paragraphs above it. */

.split-note {
  margin: var(--space-sm) 0 0;
  padding: var(--space-md) var(--space-md) var(--space-md) var(--space-sm);
  border-left: 3px solid var(--color-accent);
  background: oklch(94% 0.018 245 / 0.55);
  font-family: var(--font-body);
  font-size: clamp(1.1rem, 0.35vw + 1rem, 1.32rem);
  line-height: 1.5;
  color: var(--color-ink);
  max-width: 46ch;
}

/* The rule above the facts was doing the separating; the plate does it
   now, so the facts sit closer to it. */
.split-body .split-note + .facts {
  margin-top: var(--space-lg);
}

/* ---------------------- the map, and the places arriving

   Fifteen filled dots and their labels, and nothing else. It carried a
   ringed destination in the Alps, a leader naming La Clusaz and three
   hollow rings for stops she has not made, plus a five-stage reveal
   built to bring them on in order. All of it went on 31 Aug: the map
   shows where she races.

   What is left is the one thing that was always doing the work. The
   pins light one at a time in racing order, Kodagu first and New
   Zealand last, so the map assembles itself the way the career did. */

/* The map sits inside the road section, so it needs its own air, and
   since 31 Aug it needs its own ground as well: the scenery runs behind
   this section now, and a diagram over photography is noise rather than
   atmosphere. The wash still shows around the card and behind the
   heading above it. */
#road .route-map {
  margin-block: var(--space-lg) 0;
  padding: var(--space-lg) var(--space-md);
  background: var(--color-paper);
  border: var(--rule-hair) solid var(--color-line);
}

/* The labels are sized in viewBox units, so they scale with the frame.
   At 292px of map they came out at 3.55 CSS pixels, which is not small
   type, it is a smudge. Below the breakpoint the map is a dot map and
   the caption under it carries all fifteen names as real text at 13px.
   The leaders go with the labels; a leader pointing at nothing is worse
   than no leader. */
@media (max-width: 899px) {
  #road .route-map {
    padding: var(--space-md) var(--space-sm);
  }

  .route-map .past text,
  .route-map .map-lead {
    display: none;
  }

  /* Bigger, because they are now the only thing on the map. 1.6 units
     came out at 1.8px of radius here against 6.9px on a desktop; 3
     lands at about 3.5px, which reads at arm's length. */
  .route-map .pin {
    r: 3;
  }
}

/* ---------------------- the record, with photographs either side

   The client's reference runs its story down a centre column with photos
   held in the margins, arriving as you scroll. The same here, with two
   rules: the rails only exist where there is genuine margin to put them
   in, and they carry nothing the tables do not, so they are hidden from
   screen readers and out of the tab order. */

.rail {
  display: none;
}

@media (min-width: 1240px) {
  .rail-layout {
    display: grid;
    /* 62rem down the middle left the rails at 207px, which is a thumbnail
       rather than a photograph. The tables do not need that width. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 54rem) minmax(0, 1fr);
    /* Stretch, so the rails can spread their frames down the whole run of
       the tables instead of bunching at the top. */
    align-items: stretch;
    column-gap: var(--space-lg);
  }

  /* Spread across the whole run of the tables left gaps the length of a
     screen between frames. They follow each other now: one arrives, the
     next is already on its way in. */
  .rail {
    display: grid;
    align-content: start;
    gap: var(--space-lg);
    padding-block: var(--space-xl);
  }

  .rail-left {
    justify-items: end;
  }

  .rail-right {
    justify-items: start;
  }

  /* Staggered, so the two sides do not read as a pair of columns. */
  .rail-right {
    padding-top: calc(var(--space-xl) + 9rem);
  }

  .rail-shot {
    margin: 0;
    width: min(100%, 19rem);
  }


  .rail-shot img {
    width: 100%;
    height: auto;
    display: block;
  }

  /* The sections inside keep their own wrap; it just fills the middle
     column now instead of centring against the viewport. */
  .rail-main .wrap {
    width: 100%;
  }
}

/* ---------------------- section labels take the lockup face

   The client liked the cross-country skiing lockup and asked for it on
   the headings. Applied by what a heading is, not where it sits: a short
   noun phrase that NAMES a block of the page gets it, and a heading you
   read as a sentence does not. Wide tracking at light weight is a label
   treatment; it works on two words the eye takes in as one shape and it
   pulls a sentence apart.

   So this covers the section labels, which all live in .prose or carry
   their own class. It deliberately does not cover the medal table
   headers, which carry a count. */

.prose > h2,
.media-say h2,
.backing-head {
  font-family: var(--font-display);
  font-size: clamp(1.3rem, 2.1vw, 2.2rem);
  font-weight: 400;
  line-height: 1.14;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  text-transform: uppercase;
  color: var(--color-ink);
}

/* The map heading is a counted sentence rather than a two-word label, so
   it takes a step down in size. 22ch was set for the old six-word title
   and broke this one over three lines; 26ch holds it in two, balanced. */
#road .prose > h2 {
  font-size: clamp(1.15rem, 1.8vw, 1.85rem);
  max-width: 26ch;
  text-wrap: balance;
}

/* ---------------------- what she speaks to

   The client's reference runs six one-word cards down its speaking page,
   each with a paragraph. Six here too, but the paragraph is a sentence or
   two: they asked for simple words, not a wall. The numeral is the only
   ornament, set large and faint the way the sport formats row does it, so
   the cards have structure without needing six photographs the archive
   would have to stretch to supply. */

.skills {
  display: grid;
  gap: var(--space-lg);
  margin-top: var(--space-xl);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}

@media (min-width: 900px) {
  .skills {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-xl) var(--space-lg);
  }
}

.skill {
  position: relative;
  border-top: 2px solid var(--color-ink);
  padding-top: var(--space-sm);
  display: grid;
  align-content: start;
  gap: var(--space-2xs);
}

.skill-n {
  position: absolute;
  top: var(--space-xs);
  right: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(2.4rem, 4vw, 3.4rem);
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--color-accent);
  opacity: 0.14;
  font-variant-numeric: tabular-nums;
  user-select: none;
}

.skill h3 {
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 1.5vw, 1.35rem);
  font-weight: 400;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  text-transform: uppercase;
  color: var(--color-ink);
}

.skill p {
  font-family: var(--font-body);
  font-size: clamp(1rem, 0.2vw + 0.95rem, 1.1rem);
  line-height: 1.55;
  color: var(--color-ink-body);
  max-width: 32ch;
}

/* ---------------------- partnership: the three, and what she is open to */

/* On the homepage the marks stand alone in a row. Here each one carries
   the line saying what that support actually is, so they stack. */
.backers-stack {
  display: grid;
  gap: var(--space-lg);
  margin-top: var(--space-lg);
}

@media (min-width: 760px) {
  .backers-stack {
    gap: var(--space-xl);
  }
}

.backer-row {
  display: grid;
  gap: var(--space-sm);
  justify-items: start;
  text-align: left;
  padding: 0;
  background: none;
  border-top: var(--rule-hair) solid var(--color-line);
  padding-top: var(--space-md);
}

@media (min-width: 640px) {
  .backer-row {
    grid-template-columns: 12rem minmax(0, 1fr);
    align-items: start;
    gap: var(--space-lg);
  }
}

.backer-row .backer-mark {
  height: 3.4rem;
  justify-content: flex-start;
}

.backer-row .backer-mark img {
  max-width: 11rem;
}

.backer-row h3 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: -0.01em;
}

.backer-row p {
  font-family: var(--font-body);
  color: var(--color-ink-body);
  margin-top: var(--space-3xs);
  max-width: 52ch;
}

/* ---------------------- speaking cards carry a frame

   The reference puts a photograph on every skill card. Six here, one a
   card, each chosen against what the card actually says: her in a tuck
   for resilience, a floodlit trail for endurance, the flag at Harbin for
   representation, a roped line of climbers for the apprenticeship, a
   group on their skis for persuasion, and a set of cut tracks running
   away for pathways. All owned-rights, filtered at build time. */

.skill {
  overflow: hidden;
}

.skill-shot {
  display: block;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--color-paper-2);
  margin-bottom: var(--space-sm);
}

.skill-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 700ms var(--ease-out);
}

.skill:hover .skill-shot img {
  transform: scale(1.04);
}

/* The numeral moves off the photograph and onto the rule above it. */
.skill-n {
  top: calc(-1 * var(--space-sm));
}

@media (prefers-reduced-motion: reduce) {
  .skill-shot img {
    transition: none;
  }
}


/* ---------------------- what support funds, as a mosaic

   The client's reference lays its proof out as a bento of mixed tiles:
   pale plates carrying type, photographic ones carrying none, and one
   tall frame with its text over the picture. Same here.

   Columns rather than a grid, because the tiles are genuinely different
   heights and a grid would either stretch them or leave holes. The
   photo-only tiles are texture: they say nothing the type tiles do not,
   so they are hidden from screen readers. */

/* -------------------------------------------- where your support goes

   The bento, back, and placed this time.

   Her reference lays its proof out as a mosaic of unequal tiles: pale
   plates carrying type, photographic ones carrying none, and one frame
   with its text over the picture. That was built on 29 Aug and it was
   the right idea. What went wrong was underneath it: CSS columns, which
   balance by total content height and can only break between tiles, so
   six tiles of very different heights came out two per column with the
   three columns ending 250px apart. A mosaic that lands differently at
   every window width does not read as composed, it reads as accidental,
   and that is what "haphazard" was describing.

   So the shapes are declared instead of discovered. Four columns, rows
   that grow with their content, and every tile placed:

     01  cols 1-2, rows 1-2   the hero, type on the photograph
     02  cols 3-4, row 1      wide and short
     A   col  3,   row 2      picture
     03  col  4,   rows 2-3   tall and narrow
     04  cols 1-2, row 3      wide and short
     B   col  3,   row 3      picture

   Twelve cells, six tiles, no holes, and the same arrangement at every
   width above the breakpoint.

   The hero takes the floodlit trail because it is the darkest frame in
   the set and its type sits on the picture. The grade under that type
   has to carry further than a caption's would: this is thirty-odd words
   in a column, not a two-word label. */

.funds {
  margin-top: var(--space-lg);
  display: grid;
  gap: var(--space-md);
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 640px) {
  .funds {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1040px) {
  .funds {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    /* Stated, not discovered. On auto rows the tall narrow tile drove
       row 2 to 672px and dragged the hero to 713 with it, which is the
       same fault the columns had in a different costume: one tile's
       content deciding the shape of the whole mosaic. minmax keeps a
       floor under each row and still lets one grow if its copy needs
       it. */
    grid-template-rows:
      minmax(17rem, auto) minmax(12rem, auto) minmax(12rem, auto);
  }

  .fund[data-cell="a1"] {
    grid-column: 1 / span 2;
    grid-row: 1 / span 2;
  }

  .fund[data-cell="a2"] {
    grid-column: 3 / span 2;
    grid-row: 1;
  }

  .fund[data-cell="p1"] {
    grid-column: 3;
    grid-row: 2;
  }

  .fund[data-cell="a3"] {
    grid-column: 4;
    grid-row: 2 / span 2;
  }

  .fund[data-cell="a4"] {
    grid-column: 1 / span 2;
    grid-row: 3;
  }

  .fund[data-cell="p2"] {
    grid-column: 3;
    grid-row: 3;
  }
}

.fund {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  align-content: start;
  gap: var(--space-2xs);
  background: var(--color-paper-2);
  padding: var(--space-md);
}

.fund-n {
  position: absolute;
  top: var(--space-sm);
  right: var(--space-md);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(2rem, 3.2vw, 2.8rem);
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--color-accent);
  opacity: 0.16;
  font-variant-numeric: tabular-nums;
  user-select: none;
}

.fund h3 {
  font-family: var(--font-display);
  font-size: clamp(1rem, 1.4vw, 1.2rem);
  font-weight: 400;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  text-transform: uppercase;
  color: var(--color-ink);
  max-width: 18ch;
}

.fund p {
  font-family: var(--font-body);
  color: var(--color-ink-body);
  margin: 0;
  max-width: 52ch;
}

/* The hero: its type sits on the photograph, so the tile has to give it
   a ground and the type goes to the foot where the ground is deepest. */
.fund-shot {
  align-content: end;
  padding: var(--space-lg);
  color: var(--color-ink-on-deep);
  min-height: clamp(18rem, 26vw, 24rem);
}

.fund-shot h3,
.fund-shot p {
  color: inherit;
  max-width: 40ch;
}

.fund-shot .fund-n {
  color: oklch(88% 0.09 245);
  opacity: 0.85;
}

.fund-img {
  position: absolute;
  inset: 0;
  z-index: -2;
  display: block;
}

.fund-img img,
.fund-plate img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Thirty-odd words reach well up the tile, so this runs much further
   than a caption's grade would. */
.fund-shot::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    to top,
    rgba(3, 9, 20, 0.9) 0%,
    rgba(3, 9, 20, 0.82) 34%,
    rgba(3, 9, 20, 0.55) 62%,
    rgba(3, 9, 20, 0.3) 100%
  );
}

/* A picture tile takes the shape the grid gives it, so its photograph
   must not have an opinion about height. Left in flow, the portrait
   frame asked for 381px at 286 wide and drove the whole middle row,
   which is one tile's content deciding the mosaic again. Out of flow it
   asks for nothing and object-fit does the rest. */
.fund-plate {
  position: relative;
  padding: 0;
  overflow: hidden;
  display: block;
  min-height: 10rem;
}

.fund-plate img {
  position: absolute;
  inset: 0;
}

/* ---------------------- partnership: the three, and what she is open to */

/* On the homepage the marks stand alone in a row. Here each one carries
   the line saying what that support actually is, so they stack. */
.backers-stack {
  display: grid;
  gap: var(--space-lg);
  margin-top: var(--space-lg);
}

@media (min-width: 760px) {
  .backers-stack {
    gap: var(--space-xl);
  }
}

.backer-row {
  display: grid;
  gap: var(--space-sm);
  justify-items: start;
  text-align: left;
  padding: 0;
  background: none;
  border-top: var(--rule-hair) solid var(--color-line);
  padding-top: var(--space-md);
}

@media (min-width: 640px) {
  .backer-row {
    grid-template-columns: 12rem minmax(0, 1fr);
    align-items: start;
    gap: var(--space-lg);
  }
}

.backer-row .backer-mark {
  height: 3.4rem;
  justify-content: flex-start;
}

.backer-row .backer-mark img {
  max-width: 11rem;
}

.backer-row h3 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: -0.01em;
}

.backer-row p {
  font-family: var(--font-body);
  color: var(--color-ink-body);
  margin-top: var(--space-3xs);
  max-width: 52ch;
}

/* ---------------------- speaking cards carry a frame

   The reference puts a photograph on every skill card. Six here, one a
   card, each chosen against what the card actually says: her in a tuck
   for resilience, a floodlit trail for endurance, the flag at Harbin for
   representation, a roped line of climbers for the apprenticeship, a
   group on their skis for persuasion, and a set of cut tracks running
   away for pathways. All owned-rights, filtered at build time. */

.skill {
  overflow: hidden;
}

.skill-shot {
  display: block;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--color-paper-2);
  margin-bottom: var(--space-sm);
}

.skill-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 700ms var(--ease-out);
}

.skill:hover .skill-shot img {
  transform: scale(1.04);
}

/* The numeral moves off the photograph and onto the rule above it. */
.skill-n {
  top: calc(-1 * var(--space-sm));
}

@media (prefers-reduced-motion: reduce) {
  .skill-shot img {
    transition: none;
  }
}


/* The grade follows the words: dark at the left where they sit, clear on
   the right where he stands. */
.panel[data-size="closer"] .panel-shot::after {
  background:
    linear-gradient(
      95deg,
      rgba(3, 9, 20, 0.72) 0%,
      rgba(3, 9, 20, 0.56) 26%,
      rgba(3, 9, 20, 0.3) 50%,
      rgba(3, 9, 20, 0.1) 70%,
      rgba(3, 9, 20, 0.18) 100%
    ),
    linear-gradient(
      to top,
      rgba(3, 9, 20, 0.5) 0%,
      rgba(3, 9, 20, 0.12) 34%,
      rgba(3, 9, 20, 0) 62%
    );
}

@media (max-width: 700px) {
  .panel[data-size="closer"] .panel-shot::after {
    background: linear-gradient(
      to top,
      rgba(3, 9, 20, 0.84) 0%,
      rgba(3, 9, 20, 0.6) 30%,
      rgba(3, 9, 20, 0.22) 58%,
      rgba(3, 9, 20, 0.3) 100%
    );
  }
}

/* ==================== speaking, to the client's reference

   That page opens with an outlined phrase over a solid one, then runs
   photographic cards in two staggered columns with the word set over the
   frame in accent. Both borrowed here. What is not borrowed is the
   density: their cards carry sixty to ninety words, hers carry twenty. */

/* The statement band, in the site's own registers.

   The first pass drew the phrase in outlined type because the client's
   reference does. Nothing else here is outlined, so it read as borrowed.
   This is the shape the homepage hero already uses for a two-part
   statement: a tracked-out overline with a drawn accent rule, then the
   line itself in the heavy display weight. Same two registers the rest
   of the site runs on, no sixth one invented for one band. */
.statement-band {
  padding-block: var(--space-2xl) var(--space-xl);
}

.head-stack {
  display: grid;
  justify-items: start;
  gap: var(--space-sm);
  margin: 0;
}

.head-out {
  display: flex;
  align-items: center;
  gap: 0.9em;
  font-family: var(--font-caption);
  font-size: clamp(0.72rem, 1.1vw, 0.95rem);
  font-weight: 600;
  letter-spacing: 0.36em;
  line-height: 1;
  text-transform: uppercase;
  color: var(--color-ink-2);
}

.head-out::before {
  content: "";
  width: clamp(1.6rem, 4vw, 3.2rem);
  height: 2px;
  background: var(--color-accent);
  flex: none;
}

.head-in {
  font-family: var(--font-display);
  font-size: clamp(1.8rem, 4.2vw, 3.7rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.035em;
  color: var(--color-ink);
  max-width: 19ch;
}

.head-in i {
  font-style: normal;
  color: var(--color-accent);
}

/* Two columns, the right one dropped, so the set reads as a mosaic
   rather than a table. */
.says {
  display: grid;
  gap: var(--space-lg);
}

@media (min-width: 900px) {
  .says {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-xl);
  }

  .says > :nth-child(even) {
    margin-top: var(--space-3xl);
  }

  /* The stagger would leave a hole at the foot of the left column, so
     the columns pull back level at the end. */
  .says > :nth-child(odd):last-child {
    margin-bottom: var(--space-3xl);
  }
}

.say {
  position: relative;
  isolation: isolate;
  display: grid;
  align-content: end;
  min-height: clamp(22rem, 44vw, 33rem);
  padding: var(--space-lg);
  overflow: hidden;
  color: var(--color-ink-on-deep);
}

.say-shot {
  position: absolute;
  inset: 0;
  z-index: -2;
  display: block;
}

.say-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 900ms var(--ease-out);
}

/* The word and the sentence sit on the photograph, so the photograph has
   to give them a ground to sit on. */
.say::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    to top,
    rgba(3, 9, 20, 0.86) 0%,
    rgba(3, 9, 20, 0.66) 34%,
    rgba(3, 9, 20, 0.28) 62%,
    rgba(3, 9, 20, 0.14) 100%
  );
}

.say:hover .say-shot img {
  transform: scale(1.04);
}

.say-body {
  display: grid;
  gap: var(--space-2xs);
  max-width: 40ch;
}

.say h2 {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.6vw, 2.3rem);
  font-weight: 400;
  line-height: 1;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  text-transform: uppercase;
  color: oklch(82% 0.115 245);
}

.say p {
  font-family: var(--font-body);
  font-size: clamp(1rem, 0.2vw + 0.95rem, 1.1rem);
  line-height: 1.55;
  color: oklch(97% 0.006 250);
}

.say-for {
  color: oklch(100% 0 0 / 0.72);
  letter-spacing: 0.16em;
  margin-top: var(--space-2xs);
  padding-top: var(--space-2xs);
  border-top: 1px solid oklch(100% 0 0 / 0.24);
}

@media (prefers-reduced-motion: reduce) {
  .say-shot img {
    transition: none;
  }
}

.book-body {
  margin-top: var(--space-lg);
  margin-bottom: var(--space-lg);
  max-width: 52ch;
}

/* ---------------------- the bar goes to the page edges

   The client's reference sets the name hard left and the link row hard
   right, with the whole width between them. Ours were both inset by the
   content column, about 105px a side at 1440, which read as a centred
   block rather than a bar.

   The bar gets its own measure now: a narrow gutter instead of the
   column's. That also buys back roughly 140px of row, which is what the
   900-to-1280 tightening was there to find, so the links can hold their
   size further down. */

.nav .nav-inner {
  width: min(100% - 2 * clamp(1.25rem, 2.4vw, 3rem), 120rem);
}

@media (min-width: 900px) {
  .nav .nav-inner {
    width: min(100% - 2 * clamp(1.25rem, 2.4vw, 3rem), 120rem);
  }
}

/* With the extra width the row no longer needs squeezing until 1080. */
@media (min-width: 1081px) and (max-width: 1280px) {
  .nav-links {
    gap: var(--space-md);
  }

  .nav-links a {
    font-size: 0.84rem;
    letter-spacing: 0.1em;
  }

  .nav-links .nav-work {
    padding: 0.7rem 1.3rem;
  }
}

.fund {
  margin-bottom: var(--space-lg);
}

/* The bar: the link row sits hard right, the way the reference does it.
   Without this the row was ending 117px short of the gutter the wordmark
   was using. */
.nav .nav-inner {
  justify-content: space-between;
}

.nav .nav-links {
  margin-right: 0;
}

/* ---------------------- open to, five extended cards

   The client gave each of the five a sentence, so the tile stops being a
   label on a picture and becomes a card: the frame it always had, and
   the sentence underneath it on the card's own ground.

   Under it, not on it, for a reason worth writing down. At the old
   thirteen-rem track the tiles came out 224px wide, which is 176px of
   measure, about 25 characters a line. Her longest write-up runs 121
   characters, so it would have been five lines of small type over a
   photograph, under a label already taking two. Twenty rem gives three
   across in the same 1216px wrap, 389px tiles and 341px of measure,
   about 48 characters, and the same sentence lands in three lines of ink
   on paper.

   The five level themselves. They share one grid row so every card
   stretches to the tallest, and inside each card the copy is the
   flexible row, so a short sentence leaves white space rather than a
   short card. */

.open-tiles {
  list-style: none;
  counter-reset: opt;
  margin: var(--space-md) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-md);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
}

.open-tiles li {
  counter-increment: opt;
  display: grid;
  background: var(--color-paper-2);
  overflow: hidden;
}

/* The frame, which is what the whole tile used to be. Each carries a
   photograph chosen against its own category with the label over it,
   which is the language the sport cards and the funded tiles use. */
.opt-frame {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: var(--space-md);
  min-height: clamp(9rem, 14vw, 12rem);
  display: grid;
  align-content: end;
  font-family: var(--font-display);
  font-size: clamp(0.95rem, 1.3vw, 1.15rem);
  font-weight: 400;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  text-transform: uppercase;
  color: var(--color-ink);
}

.opt-shot {
  position: absolute;
  inset: 0;
  z-index: -2;
  display: block;
}

.opt-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 700ms var(--ease-out);
}

.opt-shot-tile .opt-frame::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    to top,
    rgba(3, 9, 20, 0.9) 0%,
    rgba(3, 9, 20, 0.68) 40%,
    rgba(3, 9, 20, 0.36) 72%,
    rgba(3, 9, 20, 0.24) 100%
  );
}

.opt-shot-tile .opt-frame {
  color: var(--color-ink-on-deep);
}

.opt-shot-tile:hover .opt-shot img {
  transform: scale(1.05);
}

@media (prefers-reduced-motion: reduce) {
  .opt-shot img {
    transition: none;
  }
}

.opt-frame::before {
  content: counter(opt, decimal-leading-zero);
  position: absolute;
  top: var(--space-sm);
  right: var(--space-md);
  font-weight: 800;
  font-size: clamp(1.8rem, 2.8vw, 2.4rem);
  line-height: 1;
  letter-spacing: -0.03em;
  text-indent: 0;
  color: var(--color-accent);
  opacity: 0.16;
  font-variant-numeric: tabular-nums;
}

.opt-shot-tile .opt-frame::before {
  color: oklch(88% 0.09 245);
  opacity: 0.85;
}

/* Her second heading, demoted to a caption so the section can open on
   the first one instead of on a two-word label. */
.open-label {
  margin: var(--space-xl) 0 0;
  color: var(--color-ink-3);
}

/* ---------------------- touch targets

   Audited across nine pages at 390, 768 and 1024. No horizontal scroll
   anywhere and no clickable text wrapping to two lines, but a set of
   text links sit at their own line height: the wordmark at 26px, the
   Home crumb and the footer links at 19px, Partnership options at 14px,
   Full record at 25px above the 700px breakpoint that was padding it.

   A finger is not a cursor. On a coarse pointer these take enough
   padding to clear 44px, which changes nothing on a mouse. */

@media (pointer: coarse) {
  .wordmark {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  .crumb,
  .foot-links a,
  .backing-more a,
  .hero-record,
  .split-body .btn,
  .prose > p > a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  /* The record link is padded below 700 already; this carries it up
     through tablet widths, where a finger is still the pointer. */
  .hero-record {
    padding-block: 0.7rem;
  }
}


/* ====================================================================
 * 30 August: the name in the bar, Career at a glance, the sport's four
 * ways without their numbering, and the record as leaning slabs.
 *
 * Appended, so these win at equal specificity against the passes above.
 * ==================================================================== */

/* ---------------------------------------------------- the name in the bar

   The script face was carrying her name and reading as a texture rather
   than a word. It goes back to the display grotesk at bold, which is the
   face the sport lockup uses, and stays mixed case so it never competes
   with the uppercase punchline directly beneath it. Smaller than the
   script was: a bold grotesk sits far heavier on the bar at the same
   size. */
.wordmark-name {
  font-family: var(--font-display);
  font-weight: 700;
  /* Capitals, at her request of 30 Aug. Uppercase runs about a seventh
     wider than mixed case at the same size and needs tracking opened
     rather than closed, so the size comes down to hold the bar. */
  font-size: clamp(1.25rem, 2.4vw, 2.15rem);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
}

/* The two tightening bands from the earlier pass, restated at the new
   weight. Narrower first, so the tighter one still wins where they
   overlap. */
@media (min-width: 900px) and (max-width: 1200px) {
  .wordmark-name {
    font-size: 1.35rem;
  }
}

@media (min-width: 900px) and (max-width: 1000px) {
  /* Measured: at 905 the link row genuinely runs out of bar, and Contact
     clips off the right edge above this. By 1000 there is room again, so
     the band stops there rather than holding the name small to 1080. */
  .wordmark-name {
    font-size: 1rem;
  }
}

@media (max-width: 520px) {
  .wordmark-name {
    font-size: 1.05rem;
  }
}


/* ------------------------------------------------- Career at a glance

   The second card, and it has to be neither of its neighbours: the hero
   is a photograph carrying four words, the story card after it is a
   portrait beside running text. So this one is type on a dark ground
   with the photograph pushed back to a texture. */
.glance {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--color-paper-deep);
  color: var(--color-ink-on-deep);
  display: grid;
  align-content: center;
  padding-block: var(--space-3xl);
}

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

/* Her note of 30 Aug: the ground was so far back it had stopped being a
   photograph. It comes forward, and the wash comes off with it. What is
   left is enough to hold white type at display weight over the brightest
   part of the frame, and no more. */
.glance-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.72;
}

/* The wash carries the ground when the photograph has not arrived, and
   keeps the quotation off the lit half of the frame. Heaviest at the
   left, where the type sits, clearing by the right edge. */
.glance-shot::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      104deg,
      oklch(16% 0.035 255 / 0.78) 0%,
      oklch(16% 0.035 255 / 0.6) 46%,
      oklch(16% 0.035 255 / 0.22) 100%
    ),
    linear-gradient(
      to top,
      oklch(16% 0.035 255 / 0.42) 0%,
      oklch(16% 0.035 255 / 0) 44%
    );
}

.glance-body {
  position: relative;
}

/* The line is hers, so the section label gave way to a signature. The
   attribution belongs to the figure rather than inside the quotation,
   which is what the spec asks for and what a screen reader wants. */
.glance-quote,
.glance-quote blockquote {
  margin: 0;
}

.glance-head {
  font-family: var(--font-display);
  font-size: clamp(2.1rem, 4.6vw, 4.2rem);
  font-weight: 800;
  line-height: 1.03;
  letter-spacing: -0.03em;
  color: var(--color-ink-on-deep);
  max-width: 34ch;
  margin: 0;
}

/* Two deliberate lines, broken at her own comma. The marked phrase was
   an inline-block landing straight after it, and a bold b at display
   size against a comma reads as one word whatever the measured space
   between them says. */
.q-a,
.q-b {
  display: block;
}

/* The opening quote mark hangs into the margin, so the W still lines up
   with the second line and with the paragraphs underneath rather than
   sitting a mark's width to the right of them. */
.q-a {
  text-indent: -0.42em;
}

.glance-by {
  display: block;
  /* Clear of the drawn stroke, which hangs below the line box it sits
     under and was crowding the signature. */
  margin-top: var(--space-lg);
  color: var(--color-accent-bright);
  font-style: normal;
}

/* A short rule ahead of the name, the way a signature sits under a
   quotation rather than butting straight against it. */
.glance-by::before {
  content: "";
  display: inline-block;
  width: 2.2rem;
  height: var(--rule-hair);
  background: currentColor;
  vertical-align: 0.28em;
  margin-right: 0.7rem;
}

/* The stroke under her imperative takes the accent rather than the ink,
   which is the one place on this card the blue appears. The path carries
   stroke="currentColor"; a CSS property outranks a presentation
   attribute, so this is all it takes. */
/* The stroke was only ever positioned inside the closer panel, so
   anywhere else the absolutely-placed line resolved against the nearest
   positioned ancestor and drew itself across the foot of the card. The
   phrase carries its own containing block now. The closer's own rule is
   more specific and still wins there. */
.mark-draw {
  position: relative;
  display: inline-block;
}

/* The path carries stroke="currentColor" as a presentation attribute, so
   the colour has to be set on the path itself: an inherited value from
   the svg loses to an attribute on the element. */
.glance-head {
  text-shadow:
    0 1px 2px oklch(14% 0.03 255 / 0.45),
    0 6px 26px oklch(14% 0.03 255 / 0.5);
}

.glance-head .draw-line path {
  stroke: var(--color-accent-bright);
}

/* Her note of 30 Aug: one below the other, not side by side. Two columns
   spanning the card put a block of text across the middle of the frame,
   which is where the climbers are. Stacked in one measure on the left,
   the photograph keeps the rest of it. */
.glance-cols {
  display: grid;
  gap: var(--space-md);
  /* Around 64 characters. Wider and the serif lines get hard to track
     back, and the block starts reaching the climbers in the frame. */
  max-width: 36rem;
  margin-top: var(--space-xl);
}

.glance-cols p {
  font-family: var(--font-body);
  font-size: clamp(1.05rem, 0.4vw + 0.98rem, 1.28rem);
  line-height: 1.6;
  color: var(--color-ink-on-deep);
  margin: 0;
  text-shadow: 0 1px 12px oklch(14% 0.03 255 / 0.55);
}


/* --------------------------------------------- the sport's four ways

   Her note of 30 Aug: the numbering goes. With the numerals gone the row
   was left repeating TECHNIQUE, TECHNIQUE, RACE FORMAT, RACE FORMAT down
   its four cards, which the numerals had been masking. So they group into
   two pairs under one label each, which is this section's own headline
   said as a layout. The cards keep .fmt, so the rule, the name and the
   note are unchanged. */
.sport-ways {
  margin-top: var(--space-xl);
  display: grid;
  gap: var(--space-xl);
}

@media (min-width: 760px) {
  .sport-ways {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2xl);
  }
}

.way {
  display: grid;
  gap: var(--space-md);
  align-content: start;
}

.way-label {
  color: var(--color-accent);
  letter-spacing: 0.2em;
  margin: 0;
}


/* ------------------------------------------------ the record, in figures

   Five numbers, their labels under them, hairlines between. Nothing
   else.

   It has been three things. Leaning plates in alternating navy and blue,
   from her reference of 30 Aug, then the same plates with a photograph
   inside each, then the plates again. All three were the same mistake in
   different clothes: a shape drawn around a number, stepped and skewed so
   the row would not look flat, which is decoration compensating for the
   fact that nothing was wrong with the row in the first place.

   The figures are the content and they are large enough to carry the
   section on their own. What the plates were doing, separating one from
   the next, a rule does in one pixel.

   The rule is drawn as a left border on every item but the first rather
   than as a gap fill, so the row can wrap on a phone without a rule
   ending up against the edge of the screen. */

.slabs {
  background: var(--color-paper);
  padding-block: var(--space-3xl);
}

.slabs[data-bare] {
  padding-block: 0 var(--space-2xl);
}

.slabs-head h2 {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: 700;
  letter-spacing: -0.015em;
}

.slab-row {
  list-style: none;
  margin: var(--space-xl) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-lg) 0;
}

@media (min-width: 900px) {
  .slab-row {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}

.slab {
  display: grid;
  align-content: start;
  gap: var(--space-3xs);
  padding-inline: var(--space-md);
}

/* Every one but the first in its row, which is not the same selector at
   both widths. On two columns "all but the first" puts a rule down the
   left edge of the third and fifth, which sit in the left column: a
   stray vertical line against the margin. So the pair rule is nth-child
   even, and only the five-across layout can use the sibling one. */
.slab:nth-child(even) {
  border-left: var(--rule-hair) solid var(--color-line);
}

@media (min-width: 900px) {
  .slab:nth-child(even) {
    border-left: none;
  }

  .slab + .slab {
    border-left: var(--rule-hair) solid var(--color-line);
  }

  .slab:first-child {
    padding-left: 0;
  }
}

.slab-n {
  display: block;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(2.8rem, 6vw, 4.6rem);
  line-height: 0.95;
  letter-spacing: -0.045em;
  color: var(--color-ink);
  font-variant-numeric: tabular-nums;
}

.slab-k {
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--color-ink);
  margin-top: var(--space-2xs);
}

.slab-s {
  color: var(--color-ink-3);
  line-height: 1.4;
}

.slabs-more {
  margin-top: var(--space-xl);
}

/* --------------------------------------- the new card in the stack

   Each card has to fill its card and paint its own ground, or the one
   beneath shows through as it rises. The slabs were the eighth of these
   until 31 Aug, when they moved to Career; the z-index rules are keyed
   by position rather than by name, so they need nothing. */
@media (min-width: 900px) {
  .stack > :nth-child(8) {
    z-index: 8;
  }

  .stack > :nth-child(9) {
    z-index: 9;
  }

  .stack > .glance {
    min-height: 100svh;
    display: grid;
    align-content: center;
  }
}


/* ==================== 30 August, second pass

   Her father's line closes the story column instead of sitting on a
   plate beside it, and the sport card takes her copy and closes on
   where the season goes.
   ==================================================================== */

/* ------------------------------------------- the line that ends the story

   Not a plate. The marked block read as a pull quote pinned to the side
   of the column, which made her father's advice look like commentary on
   the story rather than the end of it. It sits in the column now, at the
   end, in bold and a step up from the paragraphs above it. Newsreader is
   asked for at 300..700 so this is a real weight, not a synthesised one. */
.split-close {
  margin: var(--space-md) 0 0;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: clamp(1.15rem, 0.4vw + 1.05rem, 1.4rem);
  line-height: 1.45;
  color: var(--color-ink);
  max-width: 42ch;
}

.split-body .split-close + .facts {
  margin-top: var(--space-xl);
}


/* --------------------------------------------- where the season goes

   The closing band on the sport card. Two columns of grey text under a
   hairline was the quietest thing on the page, and it carries the single
   fact that explains her whole year: no permanent snow at home, so the
   season travels. It goes full width on a dark ground with an open ridge
   behind it, and closes the card rather than trailing off the end of it.

   The places along the foot are the ones her own paragraph names, in her
   order. Nothing there is a claim the sentence above does not make. */
.sport-season {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  /* Her note: the run of empty paper between the four ways and this band
     was reading as the end of the section rather than a break inside it.
     Close enough that the band still belongs to the card. */
  margin-top: var(--space-xl);
  padding-block: var(--space-2xl);
  background: var(--color-paper-deep);
  color: var(--color-ink-on-deep);
}

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

.season-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.55;
}

/* Heaviest under the heading, clearing to the right where the ridge is
   worth seeing. It also carries the ground if the frame never arrives. */
.season-shot::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      100deg,
      oklch(17% 0.035 255 / 0.9) 0%,
      oklch(17% 0.035 255 / 0.74) 40%,
      oklch(17% 0.035 255 / 0.42) 100%
    ),
    linear-gradient(
      to top,
      oklch(17% 0.035 255 / 0.55) 0%,
      oklch(17% 0.035 255 / 0) 40%
    );
}

.season-inner {
  position: relative;
  display: grid;
  gap: var(--space-md);
}

@media (min-width: 860px) {
  .season-inner {
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.3fr);
    column-gap: var(--space-2xl);
    row-gap: var(--space-xl);
  }

  .season-places {
    grid-column: 1 / -1;
  }
}

.season-head {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.6vw, 2.4rem);
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 1.05;
  text-transform: none;
  color: var(--color-ink-on-deep);
  max-width: 14ch;
  margin: 0;
}

.season-say {
  display: grid;
  gap: var(--space-sm);
  max-width: 62ch;
}

.season-say p {
  font-family: var(--font-body);
  font-size: clamp(1.02rem, 0.3vw + 0.96rem, 1.18rem);
  line-height: 1.6;
  color: var(--color-ink-on-deep-2);
  margin: 0;
  text-shadow: 0 1px 10px oklch(14% 0.03 255 / 0.5);
}

/* Where the winter takes her, along the foot. A ruled strip rather than a
   list, so it reads as a route across the band. */
.season-places {
  list-style: none;
  margin: 0;
  /* The list keeps a UA indent unless the inline padding is named, and it
     was pushing the row 40px clear of the heading above it. */
  padding-inline: 0;
  padding-bottom: 0;
  padding-top: var(--space-md);
  border-top: var(--rule-hair) solid oklch(80% 0.02 250 / 0.28);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs) var(--space-md);
}

/* Not accent. Accent on this site means either a link or the one word in
   a headline that carries the point, and five words in accent blue on a
   dark ground read as a nav that does not respond to a cursor. They are
   a data strip, so they take the same white the heading takes, a step
   quieter. */
.season-places li {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-ink-on-deep);
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

/* A hairline between the places, never after the last one. */
.season-places li:not(:last-child)::after {
  content: "";
  width: 1.6rem;
  height: var(--rule-hair);
  background: oklch(80% 0.02 250 / 0.4);
}

/* Inside the card stack the sport section pads its own block, which left
   the band floating clear of the foot. It reaches the edge instead, so
   the card ends dark and the white record card after it lands clean. */
@media (min-width: 900px) {
  .stack > .sport-band .sport-season {
    margin-bottom: calc(var(--space-2xl) * -1);
  }
}


/* ==================== 31 August: the journey, drawn

   The six alternating photo cards became one route winding down the
   page with a stop on it every year, which is the shape of her
   reference. The photograph sits on the outside of each bend; the year
   and its one line sit under it; the write-up waits for a hover, a tap
   or a tab. Past 2026 the same route carries on dashed and fades out,
   because that stretch is a plan and not a record.

   The route is drawn by script, in pixels, from where the markers
   actually landed. The path in the markup is the no-script fallback and
   is the only thing on the page that uses the distorted viewBox.
   ==================================================================== */

/* Her instruction of 31 Aug: one line above the route, in the face and
   the treatment "One line a year" used. It is the heading now, so it
   takes .prose > h2 whole rather than imitating it.

   The one departure is size. That rule was built for two and three word
   section labels and this is a sentence, so it drops a step to hold two
   lines instead of three. The Journey hero heading a few rules up
   carries the same accommodation for the same reason. */
/* .prose > h2 is (0,1,1) and this was (0,1,0), so the size below never
   applied and the line ran at the full section-heading size. */
.prose > h2.jr-head {
  font-size: clamp(1.15rem, 1.55vw, 1.65rem);
  /* Measured: at 26ch it broke into three lines of tracked caps, which is
     a lot of label for one sentence. 24rem holds it in two. */
  max-width: 24rem;
  text-wrap: balance;
}

.jr {
  position: relative;
  margin-top: var(--space-xl);
}

/* ------------------------------------------------------------ the scenery

   Five thousand pixels of white was a lot of page to scroll, and her
   reference answers that with photography bled behind the content
   rather than with more content. Her own frames run down all three
   columns the whole way, so there is no scroll position where any part
   of it is bare.

   The mask carries the legibility, not the opacity. Each frame is solid
   at the very edge of the screen and three quarters gone by the column
   the cards sit in, which leaves an effective alpha of roughly 0.06
   behind any text on the page.

   Three columns, each from its own list, the three sharing no file, so
   the same photograph can never turn up in two of them at once. The
   middle is pines, behind the route, and it is the only column with
   nothing over it but the line itself: the panels that open into that
   band paint their own paper ground.

   Off on the rail layout: the years run down the left edge at full
   width there, so there is no blank space to fill and nothing behind
   them but text. */

.jr-scenery {
  display: none;
}

.jr-nodes {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* --------------------------------------------------------- the year card */

.jr-hit {
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.jr-card {
  display: block;
}

.jr-year {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 1.4vw + 0.9rem, 1.8rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--color-ink);
}

.jr-line {
  display: block;
  margin-top: var(--space-3xs);
  font-family: var(--font-body);
  font-size: clamp(0.95rem, 0.2vw + 0.9rem, 1.05rem);
  line-height: 1.45;
  color: var(--color-ink-2);
}

/* The years that have not happened read a shade back from the ones that
   have, so the change is carried by more than the dashes on the line. */
.jr-node[data-kind="ahead"] .jr-year {
  color: var(--color-ink-2);
  font-weight: 600;
}

.jr-node[data-kind="ahead"] .jr-line {
  color: var(--color-ink-3);
}

.jr-ahead-note {
  display: block;
  margin-bottom: var(--space-3xs);
  font-family: var(--font-caption);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-accent);
}

.jr-dot {
  display: block;
  width: 0.9rem;
  height: 0.9rem;
  border-radius: 50%;
  background: var(--color-accent);
  box-shadow: 0 0 0 0.3rem var(--color-paper);
  transition: transform var(--dur-fast) var(--ease-out);
}

/* Hollow, the same convention the map uses for a start line she has not
   stood on yet. */
.jr-node[data-kind="ahead"] .jr-dot {
  background: var(--color-paper);
  box-shadow: inset 0 0 0 2px var(--color-accent),
    0 0 0 0.3rem var(--color-paper);
}

.jr-hit:hover ~ .jr-dot,
.jr-hit:focus-visible ~ .jr-dot,
.jr-node[data-open] .jr-dot {
  transform: scale(1.35);
}

.jr-hit:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 6px;
}

/* ------------------------------------------------------------- the panel

   The frame and the write-up, both held back for a click. The page is a
   year and a line; this is everything else about that year.

   Capped, because it is anchored near its own row and a panel taller
   than the viewport has nowhere to be. The longest write-up scrolls
   inside it; the rest never reach the ceiling. */

.jr-pop {
  display: flex;
  flex-direction: column;
  max-height: min(36rem, 76vh);
  background: var(--color-paper);
  border: var(--rule-hair) solid var(--color-line);
  border-left: 3px solid var(--color-accent);
  box-shadow: 0 18px 46px oklch(20% 0.03 255 / 0.16);
  overflow: hidden;
}

.jr-pop-shot {
  flex: none;
  margin: 0;
  background: var(--color-paper-3);
}

.jr-pop-shot img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  height: auto;
  object-fit: cover;
}

.jr-pop-copy {
  display: grid;
  align-content: start;
  gap: var(--space-3xs);
  padding: var(--space-md);
  min-height: 0;
  overflow-y: auto;
}

.jr-pop-where {
  color: var(--color-ink-3);
  margin: 0;
}

.jr-pop h3 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.15;
  margin: var(--space-3xs) 0 var(--space-2xs);
}

.jr-pop p:not(.caption) {
  font-family: var(--font-body);
  font-size: 0.98rem;
  line-height: 1.55;
  color: var(--color-ink-2);
  margin: 0;
}

/* On the rail layout the panel is in flow under its own year, so it does
   not need the cap or the scroll. */
.jr-pop {
  max-height: none;
}

/* ------------------------------------- under 900px: a straight left rail

   A route that crosses the page needs a page to cross. At 380px it is a
   scribble and a panel pinned beside it has nowhere to go, so the path
   is hidden, the years fall into flow, and the panel opens under its own
   year on tap. */

.jr-path {
  display: none;
}

.jr-node {
  position: relative;
  padding-left: 2.5rem;
  padding-bottom: var(--space-xl);
}

/* The rail from one year to the next, dashed once the years stop being
   things that happened. */
.jr-node::before {
  content: "";
  position: absolute;
  left: 0.44rem;
  top: 0.5rem;
  bottom: 0;
  border-left: 2px solid var(--color-accent);
  opacity: 0.4;
}

.jr-node[data-kind="ahead"]::before {
  border-left-style: dashed;
}

/* It does not stop at 2030. The last stretch runs on and fades out,
   because the target is the point of the plan, not the end of it. */
.jr-node:last-child {
  padding-bottom: var(--space-2xl);
}

.jr-node:last-child::before {
  bottom: 0.5rem;
  -webkit-mask-image: linear-gradient(to bottom, #000 30%, transparent);
  mask-image: linear-gradient(to bottom, #000 30%, transparent);
}

.jr-dot {
  position: absolute;
  left: 0;
  top: 0.3rem;
}

.jr-shot {
  max-width: 26rem;
}

.jr-pop {
  display: none;
  margin-top: var(--space-sm);
  max-width: 30rem;
}

.jr-node[data-open] .jr-pop {
  display: block;
}

/* Where the track runs out. Her list ends on it and so does her
   reference: the dashes fade and this is the last thing said. */
.jr-tail-note {
  margin: 0;
  padding-left: 2.5rem;
  /* ink-3 measured 4.35:1 against the darkest pine frame, under the 4.5
     floor at 13px. ink-2 is what the taglines beside it already use. */
  color: var(--color-ink-2);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* Nothing arrives before it is scrolled to, here as well. There is no
   route to draw on the rail layout, so the segment between one year and
   the next grows out of its marker and the card comes with it. A reader
   without script never matches these rules and gets the lot at once,
   which is the right way for it to fail. */
.js .jr-node .jr-hit,
.js .jr-node .jr-dot {
  transition: opacity 600ms var(--ease-out), transform 600ms var(--ease-out);
}

.js .jr-node[data-lit="false"] .jr-hit,
.js .jr-node[data-lit="false"] .jr-dot {
  opacity: 0;
  transform: translateY(14px);
}

.js .jr-node::before {
  transform-origin: top;
  transform: scaleY(1);
  transition: transform 600ms var(--ease-out);
}

.js .jr-node[data-lit="false"]::before {
  transform: scaleY(0);
}

/* --------------------------------------- 900px and up: the drawn route */

@media (min-width: 900px) {
  /* Half what it was. The rows carried a photograph until 31 Aug and
     needed the height for it; a year and a line do not. */
  .jr {
    height: calc(var(--jr-n) * 8.5rem);
  }

  .jr-path {
    display: block;
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
  }

  .jr-run,
  .jr-tail,
  .jr-lane,
  .jr-trail,
  .jr-trailband {
    fill: none;
    stroke-linecap: round;
  }

  /* Before the mountains: a single faint line, no corridor and no
     grooves, because a child in Kodagu is not on a ski track. */
  .jr-lane {
    stroke: var(--color-accent);
    stroke-width: 1.4;
    opacity: 0.4;
  }

  /* The trekking years. A trodden path reads as a broken line and the
     ground either side of it is disturbed, not groomed, so the band
     under it is far narrower and fainter than the piste that follows. */
  .jr-trailband {
    stroke: oklch(72% 0.055 250 / 0.14);
    stroke-width: 18;
  }

  .jr-trail {
    stroke: var(--color-accent);
    stroke-width: 2.2;
    stroke-dasharray: 1 6;
    opacity: 0.75;
  }

  /* The groomed corridor the grooves are cut into. Translucent, so it
     tints whatever scenery is behind it rather than blanking it, which
     is what a corridor of packed snow does to the ground it crosses. */
  .jr-piste {
    fill: none;
    stroke: oklch(72% 0.055 250 / 0.2);
    stroke-width: 34;
    stroke-linecap: round;
    stroke-linejoin: round;
  }

  /* The two grooves. Measured against the darkest pixel of the darkest
     middle-column frame, a single line came out at 2.77:1 at 0.72,
     under the 3:1 floor for a graphic that carries meaning; the route is
     the whole point of this section, so it is not decoration. */
  .jr-run {
    stroke: var(--color-accent);
    stroke-width: 1.6;
    opacity: 0.85;
  }

  .jr-ahead {
    stroke-dasharray: 3 10;
    opacity: 0.88;
  }

  .jr-tail {
    stroke: url(#jr-fade);
    stroke-width: 1.6;
    stroke-dasharray: 3 10;
  }

  /* Seen from above, like the rest of the drawing: two skis in the
     grooves, a body, two poles. Script puts it on the drawing front and
     turns it to the tangent, so it leans into the bends. Drawn rather
     than photographed because every photograph here is hers and rights
     settled, and a stock skier would be neither. */
  /* The halo is what makes it one object rather than five marks. Two
     passes of a paper drop-shadow cut the whole group out of the track
     it is standing on, so the parts share a silhouette instead of each
     one sitting on the grooves separately. */
  /* No transition: the script fades each figure out before a marker and
     the next one in after it, frame by frame. A CSS transition on top of
     that lags the dip and puts two of them on the track at once, which
     is what the first cross-fade did. */
  .jr-fig {
    opacity: 0;
    filter: drop-shadow(0 0 2px var(--color-paper))
      drop-shadow(0 0 4px var(--color-paper));
  }

  /* Two tones, the way her reference gets its legibility: the suit in
     the India blue the site is built on, the kit under her in ink. */
  .jr-limb {
    stroke: var(--color-accent);
    stroke-width: 4;
    stroke-linecap: round;
    stroke-linejoin: round;
    fill: none;
  }

  .jr-torso { stroke-width: 5.4; }
  .jr-arm { stroke-width: 3.2; }

  .jr-ski,
  .jr-pole {
    stroke: var(--color-ink-2);
    stroke-linecap: round;
    fill: none;
  }

  .jr-ski { stroke-width: 2.8; }
  .jr-pole { stroke-width: 1.8; opacity: 0.85; }

  /* The helmet. A paper ring keeps it off the shoulder it sits on. */
  .jr-head {
    fill: var(--color-accent);
    stroke: var(--color-paper);
    stroke-width: 1.6;
  }

  /* The cadet cap. The crown follows the skull and the peak projects
     forward and nowhere else; a bar overhanging both sides is a
     mortarboard, which is what the first one read as. */
  .jr-cap {
    fill: none;
    stroke: var(--color-ink-2);
    stroke-width: 2.6;
    stroke-linecap: round;
  }

  .jr-peak {
    fill: none;
    stroke: var(--color-ink-2);
    stroke-width: 1.9;
    stroke-linecap: round;
  }

  /* The pack she carries from the first mountaineering year, and the
     helmet from the first year she is on a slope. Both stay on once
     they arrive, so neither is switched by the script. */
  .jr-pack {
    fill: var(--color-ink-2);
    stroke: var(--color-paper);
    stroke-width: 1.2;
  }

  .jr-shell {
    fill: none;
    stroke: var(--color-ink-2);
    stroke-width: 2.4;
    stroke-linecap: round;
  }

  /* Parts that belong to one year rather than to a figure: the camp
     bedroll, the rope and axe of the qualified year, the race bib. The
     script raises each on the year that earns it. */
  .jr-kit {
    fill: var(--color-ink-2);
    stroke: var(--color-paper);
    stroke-width: 1.1;
    opacity: 0;
  }

  .jr-coil {
    fill: none;
    stroke: var(--color-ink-2);
    stroke-width: 1.5;
  }

  .jr-qual { opacity: 0; }

  .jr-bib {
    fill: var(--color-paper);
    stroke: var(--color-ink-2);
    stroke-width: 1.1;
    opacity: 0;
  }

  /* The two either side of her. The script raises them on the approach
     to the parade and drops them after it, and never above 0.42, so she
     stays the subject rather than one of three. */
  .jr-rank { opacity: 0; }

  /* Lifted snow, so a cool pale grey rather than white: white on this
     paper is invisible, and accent blue would read as part of the
     route rather than as something thrown off it. */
  /* Heavier and darker. At oklch 76% and one to two pixels it measured
     1.84:1 against the piste, which on paper this pale is a decision to
     draw nothing. Snow reads as lifted because it casts, not because it
     is white. */
  .jr-spray circle {
    fill: oklch(62% 0.045 250);
    pointer-events: none;
  }

  /* The stretch she has just cut, brighter than the settled track. The
     gradient moves with her; above the band it is transparent and the
     ordinary groove underneath shows through instead. */
  .jr-fresh {
    fill: none;
    stroke: url(#jr-cut);
    stroke-width: 2.6;
    stroke-linecap: round;
  }

  .jr-cut-a {
    stop-color: var(--color-accent);
    stop-opacity: 0;
  }

  .jr-cut-b {
    stop-color: var(--color-accent-bright, var(--color-accent));
    stop-opacity: 0.9;
  }

  /* Nothing here animates on a clock. Script sets an SVG rotate on
     each side and each leg from how far down the track she is, so the
     poling rate is the scroll rate: hard scrolling poles hard, easing
     off slows her, stopping stops her mid-stride, and scrolling back up
     skis her backwards. */

  /* One ring at the marker as it arrives, so the eye is told which year
     just landed rather than having to catch it. */
  .jr-node[data-lit="true"] .jr-dot::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: 2px solid var(--color-accent);
    animation: jr-ping 900ms var(--ease-out) 1 both;
  }

  @keyframes jr-ping {
    from { transform: scale(1); opacity: 0.75; }
    to { transform: scale(3.4); opacity: 0; }
  }

  .jr-body {
    fill: var(--color-accent);
    stroke: var(--color-paper);
    stroke-width: 1.4;
  }

  .jr-fade-a {
    stop-color: var(--color-accent);
    stop-opacity: 0.75;
  }

  .jr-fade-b {
    stop-color: var(--color-accent);
    stop-opacity: 0;
  }

  .jr-nodes {
    position: absolute;
    inset: 0;
  }

  /* At the foot of the track, in line with the years above it. */
  .jr-tail-note {
    position: absolute;
    bottom: -1.5rem;
    left: calc(var(--jr-xc, 30%) + 1.8rem);
    padding-left: 0;
  }

  /* Full bleed: the scenery reaches the edges of the screen, not the
     edges of the column. 100vw counts the scrollbar, so the few pixels
     that overhang are clipped by the host rather than left to widen the
     page. The host is everything below the header, so the wash runs
     behind the map as well as the timeline. */
  .jr-host {
    position: relative;
    overflow: clip;
  }

  .jr-scenery {
    display: block;
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(50% - 50vw);
    width: 100vw;
    z-index: -1;
    pointer-events: none;
    /* Script moves this from 0 to 1 across the section, so the scenery
       drifts against the route as the page scrolls. */
    --jr-p: 0;
    transform: translate3d(0, calc(var(--jr-p) * -70px), 0);
    will-change: transform;
  }

  .jr-scene {
    position: absolute;
    /* In rem, not per cent. The builder knows the step and the frame
       height; it does not know how tall the host will be. */
    top: var(--top);
    width: min(34rem, 34vw);
    /* 90rem tall, stepped 60rem apart by the builder. The 30rem
       difference is the crossfade, which is why these stops are at a
       third and two thirds: a frame one and a half times its own step
       overlaps by a third at each end. One frame's fade-out then lands
       exactly on the next one's fade-in, the ramps are complementary,
       and two half-strength layers composite to less than one at full
       strength. The ratio is what matters; move one number and the
       other has to move with it. */
    height: 90rem;
    -webkit-mask-image: linear-gradient(to bottom, transparent 0%,
      #000 33.34%, #000 66.66%, transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0%,
      #000 33.34%, #000 66.66%, transparent 100%);
  }

  .jr-scene[data-edge="l"] { left: 0; }
  .jr-scene[data-edge="r"] { right: 0; }

  /* The middle column sits behind the route, between the two card
     columns, which is the band she pointed at as bare. */
  .jr-scene[data-edge="c"] {
    left: 50%;
    /* Centred by transform, not by a negative margin. min(-23rem,-20vw)
       and min(46rem,40vw) pick from different arms of their clamps at
       most widths, so the margin was not half the width and the column
       sat eighty pixels left of centre at 1440. */
    transform: translateX(-50%);
    width: min(46rem, 40vw);
  }

  /* The filter is legibility, not taste. Flattening the range means the
     darkest pixel in any frame lands around 111 of 255 instead of 0, so
     no part of a photograph can pull the paper below the contrast floor
     however wide the window is and however strong the mask happens to
     be at that point. Faint and cold is also the look her reference
     has. */
  .jr-scene img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0.4;
    filter: saturate(0.45) contrast(0.42) brightness(1.42);
  }

  /* Weaker than the outer columns. Nothing in the middle band carries
     text, so the constraint is not contrast against type, it is the
     route line staying readable over it. */
  .jr-scene[data-edge="c"] img {
    opacity: 0.3;
  }

  /* Faded at both its own edges, so it meets the outer columns without a
     seam and leaves the cards on the ground they were measured against. */
  .jr-scene[data-edge="c"] img {
    -webkit-mask-image: linear-gradient(to right, transparent 0%,
      rgb(0 0 0 / 0.85) 26%, rgb(0 0 0 / 0.85) 74%, transparent 100%);
    mask-image: linear-gradient(to right, transparent 0%,
      rgb(0 0 0 / 0.85) 26%, rgb(0 0 0 / 0.85) 74%, transparent 100%);
  }

  /* Strongest at the screen edge, mostly gone by the card column. The
     mask shapes it and the filter guarantees it, and the ramp is capped
     at 0.9 rather than solid so that no point on the page, not even one
     no card currently reaches, can put text under the contrast floor. */
  .jr-scene[data-edge="l"] img {
    -webkit-mask-image: linear-gradient(to right, rgb(0 0 0 / 0.9) 0%,
      rgb(0 0 0 / 0.68) 22%, rgb(0 0 0 / 0.28) 55%, transparent 100%);
    mask-image: linear-gradient(to right, rgb(0 0 0 / 0.9) 0%,
      rgb(0 0 0 / 0.68) 22%, rgb(0 0 0 / 0.28) 55%, transparent 100%);
  }

  .jr-scene[data-edge="r"] img {
    -webkit-mask-image: linear-gradient(to left, rgb(0 0 0 / 0.9) 0%,
      rgb(0 0 0 / 0.68) 22%, rgb(0 0 0 / 0.28) 55%, transparent 100%);
    mask-image: linear-gradient(to left, rgb(0 0 0 / 0.9) 0%,
      rgb(0 0 0 / 0.68) 22%, rgb(0 0 0 / 0.28) 55%, transparent 100%);
  }

  /* The panel opens over the scenery, so it keeps its own ground. */
  .jr-pop {
    background: var(--color-paper);
  }

  /* Each year spans the full width of the track, so its card can be
     sized against the container rather than against a 14px marker. A
     full-width row would swallow every pointer event on its band, so it
     takes none and the three things inside it take them back; :hover
     still reaches the row from any of them. */
  .jr-node {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--y);
    height: 0;
    padding: 0;
    pointer-events: none;
  }

  .jr-node::before {
    display: none;
  }

  .jr-hit,
  .jr-dot,
  .jr-pop {
    pointer-events: auto;
  }

  .jr-dot {
    left: calc(var(--x) - 0.45rem);
    top: -0.45rem;
  }

  /* Every year in one column beside the track, the way her reference
     has it. Alternating sides existed to give a photo card somewhere to
     sit either side of a big swing; with the swing gone and the card
     down to a year and a line, there is nothing to alternate around. */
  /* Fixed, not shrink-to-fit. Measured down the page the column ran
     between 110px and 384px depending on how long that year's tagline
     happened to be, and that rag is what made the column feel
     unsettled. */
  .jr-hit {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    left: calc(var(--x) + 1.8rem);
    width: 22rem;
    text-align: left;
  }

  /* The panel opens further right again, clear of the column, in the
     width the track leaves empty on that side. */
  .jr-pop {
    display: flex;
    position: absolute;
    z-index: 4;
    left: calc(var(--x) + 28rem);
    width: 22rem;
    max-width: calc(96% - var(--x) - 28rem + 22rem);
    margin: 0;
    opacity: 0;
    visibility: hidden;
    --pop-y: 0px;
    transform: translateY(calc(var(--pop-y) + 8px));
    transition: opacity var(--dur-fast) var(--ease-out),
      visibility var(--dur-fast) var(--ease-out),
      transform var(--dur-fast) var(--ease-out);
  }

  .jr-node[data-vpos="mid"] .jr-pop {
    top: 50%;
    --pop-y: -50%;
  }

  .jr-node[data-vpos="top"] .jr-pop {
    top: -1rem;
  }

  .jr-node[data-vpos="bottom"] .jr-pop {
    bottom: -1rem;
  }

  .jr-hit:focus-visible ~ .jr-pop,
  .jr-node[data-open] .jr-pop {
    opacity: 1;
    visibility: visible;
    transform: translateY(var(--pop-y));
  }

  .jr-node:hover,
  .jr-node[data-open],
  .jr-node:focus-within {
    z-index: 5;
  }

  /* Nothing arrives before it is scrolled to. Script moves data-lit as
     the drawing front passes each marker; a reader without script never
     matches this rule and gets the whole route at once, which is the
     right way for it to fail. */
  .js .jr-node[data-lit="false"] .jr-hit,
  .js .jr-node[data-lit="false"] .jr-dot {
    opacity: 0;
    transform: translateY(14px);
  }

  .js .jr-node[data-lit="false"] .jr-hit {
    transform: translateY(calc(-50% + 14px));
  }

  /* The panel is capped against the viewport, so it never needs to
     scroll on a desktop unless the write-up is a long one. 40rem rather
     than 32 since 7 Sep: the write-ups are her document's paragraphs and
     run longer than the ones they replaced, and at the old cap 2014 and
     2026 scrolled inside themselves. The tallest panel measures 548px,
     so this clears it by 28. */
  .jr-pop {
    max-height: min(36rem, 76vh);
  }

  .js .jr-node .jr-hit,
  .js .jr-node .jr-dot {
    transition: opacity 700ms var(--ease-out),
      transform 700ms var(--ease-out);
  }
}

/* Hover opens the panel only where there is a pointer that can hover. On
   a touch screen the same rule leaves it stuck open after a tap. */
@media (min-width: 900px) and (hover: hover) {
  .jr-node:hover .jr-pop {
    opacity: 1;
    visibility: visible;
    transform: translateY(var(--pop-y));
  }
}

@media (prefers-reduced-motion: reduce) {
  .jr-pop,
  .jr-dot {
    transition: none;
  }

  /* No stride, no ping, no fresh cut. The route and the years stand as
     they are. */
  .jr-node[data-lit="true"] .jr-dot::after {
    animation: none;
  }

  .jr-node[data-lit="true"] .jr-dot::after {
    display: none;
  }

  .jr-fresh {
    display: none;
  }

  /* The drift is decoration. Without it the scenery simply sits still. */
  .jr-scenery {
    transform: none;
  }

  .js .jr-node[data-lit="false"] .jr-hit,
  .js .jr-node[data-lit="false"] .jr-dot {
    opacity: 1;
    transform: none;
  }

  .js .jr-node[data-lit="false"] .jr-hit {
    transform: translateY(-50%);
  }

  .js .jr-node[data-lit="false"]::before {
    transform: none;
  }
}


/* --------------------------------- and below the breakpoint, its own ground

   There is no field beside her on a narrow screen. The panel is taller
   than it is wide, so the crop is a slice centred on her and the words sit
   over the middle of the picture whatever anyone does. They keep the
   site's own arrangement and stay white, and only the grade under them is
   retuned.

   The foot grade was cut for pictures whose lower half was dark. This one
   is a lit valley: measured under the copy it reaches 0.44 on a phone and
   0.23 on a tablet, and the words came out at 2.1:1 and 3.8:1. White needs
   the ground under 0.172, which on 0.44 takes a veil of 0.64, so the grade
   is carried at close to full strength up through the band the copy
   actually occupies rather than fading out below it. */

@media (max-width: 899px) {
  .panel[data-size="hero"] .panel-shot::after {
    background: linear-gradient(
      to top,
      rgba(3, 9, 20, 0.9) 0%,
      rgba(3, 9, 20, 0.88) 30%,
      rgba(3, 9, 20, 0.82) 48%,
      rgba(3, 9, 20, 0.5) 62%,
      rgba(3, 9, 20, 0.14) 78%,
      rgba(3, 9, 20, 0.26) 100%
    );
  }

  /* One step lighter, because the ground under it moved. */
  .panel[data-size="hero"] .panel-body .hero-punch .hl {
    color: oklch(87% 0.09 245);
  }
}


/* ------------------------------------ the hero is the photograph, full bleed

   Edge to edge and behind the bar, with the words on it, and the type
   keeping the grade this design was built with.

   Ink with nothing under it was tried first and measured 1.0:1. This
   frame has no region large enough and even enough for unaided type:
   under the copy the ground runs 0.014 to 0.53, deep rock shadow and lit
   snow inside the same block of text, so ink fails on the dark pixels and
   white fails on the light ones. The same fact forced a plate two frames
   ago.

   Full bleed was the instruction, so it wins, and the least obtrusive
   thing that makes the words readable inside it is a corner darkened
   photographically rather than a panel laid over them. That is the site's
   own device and carried this hero for every frame before the last two,
   so the type, the grade and the bar all go back to it and this block
   only does what the picture itself needs. */

@media (min-width: 900px) {
  /* 4:3, the squarest frame yet, so a wide screen has to lose a third of
     the height. Her head is 9.5% down it, which leaves nothing to spend
     off the top: the whole loss comes off the foot and she clears the bar
     by 70px. What it costs is everything below her knees on a wide
     screen, boots and skis included. A tall panel shows the frame whole,
     so a phone and a tablet still get the skis.

     The horizontal is her head at 56%: it does nothing where the crop is
     vertical, and it is the whole thing on a tall panel. */
  .panel[data-size="hero"] {
    --hero-pos: 56% 0%;
    /* The panel pads itself to the site column, which put the record link
       463px short of the frame edge while the bar's CONTACT sits 46px
       from it. The right gutter comes in to match the bar, so the link
       lands directly under it and the hero's foot spans the frame the way
       its head does. The copy itself does not move: it is left-aligned
       and the headline is capped at 13ch. */
    padding-right: clamp(1.1rem, 2.4vw, 2.875rem);
  }

  /* Full bleed: the shot covers the panel again. */
  .panel[data-size="hero"] .panel-shot {
    right: 0;
  }

  /* The same corner grade the site has always used, four points deeper
     across the corner the copy sits in. The frame it was cut for was a
     near-black forest; this one is lit snow, so at the old strength the
     marked words came out at 2.8:1 at 1440 against a floor of 3. Deeper
     only where the words are, and clear of her by 46% across. */
  .panel[data-size="hero"] .panel-shot::after {
    background:
      linear-gradient(
        95deg,
        rgba(3, 9, 20, 0.88) 0%,
        rgba(3, 9, 20, 0.84) 14%,
        rgba(3, 9, 20, 0.7) 30%,
        rgba(3, 9, 20, 0.42) 46%,
        rgba(3, 9, 20, 0.16) 64%,
        /* The tail carries the record link, which sits at the far right
           of the foot. On this frame the ground under it is 0.18 and at
           0.24 the link measured 4.50:1 against a floor of 4.5. The lift
           is on the last stop only, so it reaches the right edge and
           leaves her, and the middle of the picture, alone. */
        rgba(3, 9, 20, 0.36) 100%
      ),
      linear-gradient(
        to top,
        rgba(3, 9, 20, 0.55) 0%,
        rgba(3, 9, 20, 0.12) 34%,
        rgba(3, 9, 20, 0) 60%
      );
  }

  /* The block sits where the panel puts it, bottom left on the site
     column, so every override the split and the right-hand layouts needed
     comes off. */
  .panel[data-size="hero"] .hero-min {
    /* The wrap caps itself at the site column and then insets 4rem each
       side, which is what held the foot back. The hero takes the padded
       box whole and lets the punch's own measure do the limiting. */
    width: 100%;
    max-width: none;
    margin-inline: 0;
    justify-self: start;
    grid-template-columns: minmax(0, 100%);
    justify-items: start;
  }

  .panel[data-size="hero"] .hero-min .hero-punch {
    max-width: 13ch;
  }

  .panel[data-size="hero"] .hero-min .hero-punch .punch-big {
    font-size: clamp(2.4rem, 5.6vw, 3.9rem);
  }

  /* A flex row whose box ran the full measure while its words stopped a
     third of the way along, so it was judged on ground its own type never
     sits on. */
  .panel[data-size="hero"] .hero-min .hero-punch .punch-eyebrow {
    width: fit-content;
  }

  /* The marked words go one step lighter. The site's light blue was set
     against a near-black forest; on this frame the grade only has to lift
     the ground to 0.16 for the white lines to clear their floor, and at
     80% lightness the blue measured 2.72:1 on that against a floor of 3.
     Lightening the word is the surgical fix. Deepening the grade instead
     would darken the whole corner of the picture to rescue two words. */
  .panel[data-size="hero"] .panel-body .hero-punch .hl {
    color: oklch(87% 0.09 245);
  }

  /* An older rule runs the foot 128px past its column to reach the panel
     edge, which on a bottom-left block puts the record link out over the
     middle of the picture, where she is. */
  .panel[data-size="hero"] .hero-foot {
    width: 100%;
    min-width: 0;
    flex-wrap: wrap;
    row-gap: var(--space-sm);
    /* The plate at one end, the link at the other, spanning the frame. */
    justify-content: space-between;
    column-gap: var(--space-lg);
    align-items: center;
    background: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border-left: none;
    box-shadow: none;
    padding: 0;
  }

  .panel[data-size="hero"] .hero-chip {
    min-width: 0;
  }

  /* The bar turns to ink. Its own dark gradient was cut for a near-black
     photograph; the top of this frame is sky and lit peak at 0.40 to 0.73,
     and measured there the light links came out at 1.0:1 and the wordmark
     at 1.8:1. Ink on that ground is 5.1:1 and up. index.html is the only
     page that sets data-glass, so nothing else moves. */
  .nav[data-glass="true"]:not([data-scrolled="true"]) {
    /* 0.70, not 0.62. The bar sits on whatever the top of the frame
       happens to be, and on this one the patch under the last link is
       darker than the rest: it measured 4.46:1 against a floor of 4.5 at
       1280 while every other item passed. Eight points on the wash lifts
       that one to 4.7:1 and costs the sky nothing anyone will see. */
    background: linear-gradient(
      to bottom,
      oklch(99% 0.003 250 / 0.7),
      oklch(99% 0.003 250 / 0)
    );
  }

  .nav[data-glass="true"]:not([data-scrolled="true"]) .wordmark,
  .nav[data-glass="true"]:not([data-scrolled="true"]) .nav-toggle {
    color: var(--color-ink);
  }

  .nav[data-glass="true"]:not([data-scrolled="true"]) .nav-links a {
    color: var(--color-ink-body);
  }

  .nav[data-glass="true"]:not([data-scrolled="true"]) .nav-links a:hover,
  .nav[data-glass="true"]:not([data-scrolled="true"])
    .nav-links a[aria-current="page"] {
    color: oklch(40% 0.165 252);
  }

  .nav[data-glass="true"]:not([data-scrolled="true"]) .nav-links .nav-work {
    color: var(--color-ink);
    border-color: oklch(24% 0.035 255 / 0.4);
  }

  .nav[data-glass="true"]:not([data-scrolled="true"])
    .nav-links .nav-work:hover {
    border-color: oklch(40% 0.165 252);
    color: oklch(40% 0.165 252);
    background: oklch(24% 0.035 255 / 0.05);
  }

  /* The coordinate rail is fixed and ink-coloured, and over the hero it is
     either invisible or debris depending on the frame. It waits for the
     page instead, and the bar already knows whether the hero is under it. */
  .nav[data-glass="true"]:not([data-scrolled="true"]) ~ .lat-rail {
    opacity: 0;
  }

  .lat-rail {
    transition: opacity var(--dur-mid, 320ms) var(--ease-out);
  }
}

/* ------------------------------------------- the film, played in place

   Sending a reader to YouTube is sending them somewhere they may not come
   back from, so the film plays on the page. What loads is a poster, a
   control and a caption: no iframe, no YouTube script, no cookie, no
   request to Google at all until somebody asks for one.

   The control is a real link to the video. With no JavaScript that is
   exactly what it does, so the page never carries a dead button; with
   JavaScript the click is caught and the iframe takes the poster's place.

   The poster is one of her own frames rather than YouTube's thumbnail,
   for the same reason: fetching the thumbnail would put a request to
   Google on the page before anyone had clicked. The caption says YouTube,
   so nothing is disguised. */

.watch {
  display: grid;
  gap: var(--space-md);
  margin-top: var(--space-md);
}

@media (min-width: 900px) {
  .watch {
    grid-template-columns: minmax(0, 1.9fr) minmax(0, 1fr);
    align-items: start;
    gap: var(--space-lg);
  }
}

.player {
  position: relative;
  display: block;
  isolation: isolate;
  overflow: hidden;
  background: var(--color-paper-deep);
  aspect-ratio: 16 / 9;
  color: var(--color-ink-on-deep);
  text-decoration: none;
}

.player-shot,
.player-shot img,
.player iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  border: 0;
}

.player-shot img {
  object-fit: cover;
}

.player::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(3, 9, 20, 0.82) 0%,
    rgba(3, 9, 20, 0.34) 46%,
    rgba(3, 9, 20, 0.12) 100%
  );
}

/* A disc with a triangle in it, drawn rather than fetched. */
.player-go {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 2;
  width: clamp(3.6rem, 6vw, 5rem);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: oklch(99% 0.003 250 / 0.92);
  box-shadow: 0 10px 30px oklch(12% 0.03 255 / 0.4);
  transition: transform var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out);
}

.player-go::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 54%;
  transform: translate(-50%, -50%);
  border-style: solid;
  border-width: 0.62em 0 0.62em 1.02em;
  border-color: transparent transparent transparent var(--color-accent);
}

.player:hover .player-go,
.player:focus-visible .player-go {
  transform: translate(-50%, -50%) scale(1.06);
  background: oklch(100% 0 0);
}

.player-say {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  display: grid;
  gap: var(--space-3xs);
  padding: var(--space-md);
}

.player-say b {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.15;
}

.player-say .caption {
  color: oklch(88% 0.09 245);
}

/* Once it is playing the poster's furniture is in the way of the film. */
.player[data-playing] .player-go,
.player[data-playing] .player-say,
.player[data-playing]::after {
  display: none;
}

.press-note {
  display: grid;
  gap: var(--space-3xs);
  align-content: start;
  padding: var(--space-md);
  background: var(--color-paper-2);
  border-left: 3px solid var(--color-accent);
  color: var(--color-ink);
  text-decoration: none;
}

.press-note b {
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
}

.press-note span:not(.caption) {
  font-family: var(--font-body);
  color: var(--color-ink-body);
}

.press-note:hover {
  background: var(--color-paper-3);
}


/* ---------------------------------------- where your support goes, beta

   The four areas as a scroll rather than a grid. On a wide screen a
   photograph holds still on the left while the areas pass on the right;
   the one in the middle of the screen is lit, the rest dim, and the
   photograph crossfades to that area's frame. A counter under the picture
   keeps the reader's place.

   On a phone there is no room to hold a picture beside full-width text,
   so each area carries its own frame above it and nothing dims.

   Nothing here hides content without script. The dimming is behind .js,
   and with no script every area is simply there at full strength with
   the first photograph beside them. */

.funds-scroll {
  margin-top: var(--space-lg);
}

.fs-media {
  display: none;
}

.fs-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-xl);
}

.fs-step {
  display: grid;
  gap: var(--space-2xs);
  border-top: 2px solid var(--color-ink);
  padding-top: var(--space-md);
}

.fs-inline {
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  margin-bottom: var(--space-sm);
  background: var(--color-paper-3);
}

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

.fs-n {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(2.4rem, 4vw, 3.6rem);
  line-height: 0.9;
  letter-spacing: -0.04em;
  color: var(--color-accent);
  font-variant-numeric: tabular-nums;
}

.fs-step h3 {
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 1.6vw, 1.4rem);
  font-weight: 400;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  text-transform: uppercase;
  color: var(--color-ink);
}

.fs-step p {
  font-family: var(--font-body);
  font-size: clamp(1.02rem, 0.3vw + 0.95rem, 1.15rem);
  line-height: 1.6;
  color: var(--color-ink-body);
  max-width: 46ch;
  margin: 0;
}

@media (min-width: 1000px) {
  .funds-scroll {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-2xl);
    align-items: start;
  }

  /* The picture holds still while the areas pass. top clears the bar. */
  .fs-media {
    display: block;
    position: sticky;
    top: calc(var(--nav-h, 5rem) + var(--space-lg));
  }

  .fs-frames {
    position: relative;
    aspect-ratio: 4 / 5;
    max-height: 72vh;
    width: 100%;
    overflow: hidden;
    background: var(--color-paper-3);
  }

  .fs-shot {
    position: absolute;
    inset: 0;
    margin: 0;
    opacity: 0;
    transition: opacity 600ms var(--ease-out);
  }

  .fs-shot[data-on] {
    opacity: 1;
  }

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

  .fs-count {
    margin-top: var(--space-sm);
    color: var(--color-ink-3);
    font-variant-numeric: tabular-nums;
  }

  .fs-count b {
    color: var(--color-ink);
  }

  /* Each area gets enough height that scrolling moves through them one
     at a time, and the last one can still reach the middle of the
     screen. */
  .fs-steps {
    gap: 0;
    padding-bottom: 20vh;
  }

  .fs-step {
    min-height: 62vh;
    align-content: center;
    border-top: none;
    padding-top: 0;
  }

  .fs-inline {
    display: none;
  }

  .js .fs-step {
    opacity: 0.28;
    transition: opacity 500ms var(--ease-out);
  }

  .js .fs-step[data-on] {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .fs-shot,
  .js .fs-step {
    transition: none;
  }
}


/* A photographer's credit under a journey frame that is not hers. Small,
   quiet, and on the paper rather than over the picture. */
.jr-credit {
  margin: 0;
  padding: var(--space-3xs) var(--space-md) 0;
  color: var(--color-ink-3);
  font-size: 0.68rem;
}


/* -------------------------------------- where your support goes, panels

   Four photographs side by side. The one you point at, tap or tab to
   opens to roughly three times the width of the others and tells its
   story over the foot of the frame; the rest stay as tall slivers with
   their number and name, so all four are always on screen. On a phone
   they stack and every one is open.

   The copy sits over the picture here, so the grade runs high and dark.
   Without script every panel is the same width and every story shows. */

.fp {
  margin-top: var(--space-lg);
  display: grid;
  gap: var(--space-sm);
}

.fp-panel {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  min-height: 24rem;
  display: grid;
  align-content: end;
  padding: var(--space-lg);
  color: var(--color-ink-on-deep);
  background: var(--color-paper-deep);
  outline-offset: 3px;
}

.fp-img,
.fp-img img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

.fp-img {
  z-index: -2;
}

.fp-img img {
  object-fit: cover;
  transition: transform 900ms var(--ease-out);
}

.fp-panel::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    to top,
    rgba(3, 9, 20, 0.93) 0%,
    rgba(3, 9, 20, 0.87) 46%,
    rgba(3, 9, 20, 0.5) 72%,
    rgba(3, 9, 20, 0.2) 100%
  );
}

.fp-n {
  position: absolute;
  top: var(--space-md);
  left: var(--space-lg);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(2.6rem, 4.4vw, 4rem);
  line-height: 1;
  letter-spacing: -0.04em;
  color: oklch(88% 0.09 245);
  font-variant-numeric: tabular-nums;
}

/* The column is the panel's width, not the story's. The story keeps a
   fixed width so it does not rewrap as the panel grows, and when the
   column sized itself to that, a closed panel laid its name out for an
   open one and cut it off at the edge. */
.fp-copy {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-xs);
  min-width: 0;
}

.fp-copy h3 {
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 1.5vw, 1.3rem);
  font-weight: 400;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  text-transform: uppercase;
  color: inherit;
  max-width: 18ch;
  /* One line-height in both states, so the name growing does not also
     jump its line spacing. */
  line-height: 1.25;
}

.fp-say p {
  font-family: var(--font-body);
  line-height: 1.55;
  margin: 0;
  max-width: 44ch;
  color: oklch(94% 0.01 250);
}

.fp-say p + p {
  margin-top: var(--space-2xs);
}

@media (min-width: 1000px) {
  .fp {
    display: flex;
    height: clamp(30rem, 40vw, 36rem);
  }

  .fp-panel {
    flex: 1 1 0;
    min-width: 0;
    min-height: 0;
    cursor: pointer;
    transition: flex-grow 650ms var(--ease-out);
  }

  /* The story is a fixed width, so the text does not rewrap as the panel
     grows; it simply comes into view. */
  .fp-say {
    width: min(26rem, 30vw);
  }

  /* Opening, in order. The panel widens over 650ms. The name rises to its
     open place and grows to its open size over 600ms, carried up by the
     story's row opening beneath it. The paragraph only fades in from
     380ms, once the name has nearly arrived. Closing reverses it, the
     paragraph leaving first. */
  .js .fp-panel {
    flex-grow: 1;
    padding-inline: var(--space-md);
    transition: flex-grow 650ms var(--ease-out),
      padding 600ms var(--ease-out);
  }

  .js .fp-panel[data-open] {
    flex-grow: 3.2;
    padding-inline: var(--space-lg);
    cursor: default;
  }

  .js .fp-n {
    left: var(--space-md);
    transition: left 600ms var(--ease-out);
  }

  .js .fp-panel[data-open] .fp-n {
    left: var(--space-lg);
  }

  /* Name over story, and the story's row goes 0fr to 1fr. A fraction
     track animates to the height the paragraph actually needs, which a
     max-height cannot: animated to a guessed ceiling it ends early or
     late depending on the paragraph's length, and left untransitioned,
     as it was, it opened in one frame and shoved the name up the photo.
     No gap between the rows, or a closed panel keeps a sliver of it. */
  .js .fp-copy {
    grid-template-rows: auto 0fr;
    row-gap: 0;
    transition: grid-template-rows 600ms var(--ease-out);
  }

  .js .fp-panel[data-open] .fp-copy {
    grid-template-rows: auto 1fr;
  }

  .js .fp-copy h3 {
    font-size: clamp(0.78rem, 0.95vw, 1rem);
    letter-spacing: 0.07em;
    text-indent: 0;
    transition: font-size 600ms var(--ease-out),
      letter-spacing 600ms var(--ease-out);
  }

  .js .fp-panel[data-open] .fp-copy h3 {
    font-size: clamp(1.05rem, 1.5vw, 1.3rem);
    letter-spacing: 0.1em;
  }

  .js .fp-say {
    min-height: 0;
    overflow: hidden;
    opacity: 0;
    transform: translate3d(0, 10px, 0);
    transition: opacity 180ms var(--ease-out),
      transform 180ms var(--ease-out);
  }

  /* The space between name and story lives inside the story, so it
     opens with it rather than sitting there when closed. */
  .js .fp-say > p:first-child {
    padding-top: var(--space-xs);
  }

  .js .fp-panel[data-open] .fp-say {
    opacity: 1;
    transform: none;
    transition: opacity 360ms var(--ease-out) 380ms,
      transform 360ms var(--ease-out) 380ms;
  }

  .js .fp-panel[data-open] .fp-img img {
    transform: scale(1.04);
  }
}

@media (prefers-reduced-motion: reduce) {
  .fp-panel,
  .js .fp-panel,
  .js .fp-n,
  .js .fp-copy,
  .js .fp-copy h3,
  .js .fp-say,
  .js .fp-panel[data-open] .fp-say,
  .fp-img img {
    transition: none;
  }
}


/* --------------------------------- where your support goes, beta 3: season

   The four areas laid over her racing year. Her season, October to
   March, is drawn as five bands from the content file, each naming what
   it needs; pick an area and the bands it pays for stay lit while the
   rest fall back. A sponsor sees when the money is spent as well as on
   what, which is the question a sponsor actually asks next.

   Nothing is dimmed until something is picked, so the chart reads whole
   on arrival and without script. */

.ss {
  margin-top: var(--space-lg);
  display: grid;
  gap: var(--space-xl);
}

.ss-areas {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-sm);
}

@media (min-width: 720px) {
  .ss-areas {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1100px) {
  .ss-areas {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.ss-area {
  all: unset;
  box-sizing: border-box;
  display: grid;
  align-content: start;
  gap: var(--space-2xs);
  width: 100%;
  height: 100%;
  padding: var(--space-md);
  background: var(--color-paper-2);
  border-top: 3px solid transparent;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
}

.ss-area:hover,
.ss-area:focus-visible {
  background: var(--color-paper-3);
}

.ss-area:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.ss-area[aria-pressed="true"] {
  border-top-color: var(--color-accent);
  background: var(--color-paper-3);
}

.ss-n {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(2rem, 3.2vw, 2.8rem);
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--color-accent);
  font-variant-numeric: tabular-nums;
}

.ss-area b {
  font-family: var(--font-display);
  font-size: clamp(1rem, 1.3vw, 1.15rem);
  font-weight: 400;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  text-transform: uppercase;
  color: var(--color-ink);
}

.ss-say {
  font-family: var(--font-body);
  font-size: 0.98rem;
  line-height: 1.55;
  color: var(--color-ink-body);
}

/* The season, on the deep ground the site uses for its record. */
.ss-season {
  margin: 0;
  padding: var(--space-lg);
  background: var(--color-paper-deep);
  color: var(--color-ink-on-deep);
}

.ss-head {
  margin: 0 0 var(--space-md);
  color: oklch(88% 0.09 245);
}

.ss-months {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  border-bottom: var(--rule-hair) solid oklch(100% 0 0 / 0.25);
  padding-bottom: var(--space-2xs);
  font-family: var(--font-display);
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: oklch(100% 0 0 / 0.7);
}

.ss-bands {
  list-style: none;
  margin: var(--space-sm) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-sm);
}

.ss-band {
  position: relative;
  display: grid;
  gap: 6px;
  transition: opacity 400ms var(--ease-out);
}

.ss-bar {
  display: block;
  height: 14px;
  margin-left: var(--l);
  width: var(--w);
  border-radius: 2px;
  background: var(--color-accent);
  transition: background 400ms var(--ease-out);
}

.ss-what {
  margin-left: var(--l);
  font-family: var(--font-body);
  font-size: 0.9rem;
  line-height: 1.35;
  color: oklch(100% 0 0 / 0.72);
}

.ss-what b {
  color: var(--color-ink-on-deep);
  font-weight: 600;
}

/* A label that starts late in the season would run off the right edge,
   so from the middle onwards it ends where its bar ends instead. */
.ss-band[data-late] .ss-what {
  margin-left: 0;
  text-align: right;
  padding-right: calc(100% - var(--l) - var(--w));
}

.ss[data-pick] .ss-band {
  opacity: 0.22;
}

.ss[data-pick] .ss-band[data-lit] {
  opacity: 1;
}

.ss[data-pick] .ss-band[data-lit] .ss-bar {
  background: oklch(78% 0.13 245);
}

@media (prefers-reduced-motion: reduce) {
  .ss-band,
  .ss-bar {
    transition: none;
  }
}

