/* The White Bull · Oracle Red Bull Racing, 2025 Japanese livery
   Dark ink chrome over a photographic WebGL stage, ending on a paper-white
   footer. One accent: Red Bull red. Radius rule: interactive = pill, surfaces
   = 2px.

   The interface is broadcast furniture, not a magazine layout: mono tabular
   data, sector marks, a lap bar, a timing tower. It also has to stay off the
   car. Both of those pull in opposite directions, so every element that could
   sit over the subject is held in a gutter and its placement is measured, not
   eyeballed. */

:root {
  --ink:        #0b0d12;
  --ink-2:      #12151c;
  --paper:      #f4f5f7;
  --paper-mid:  rgba(244, 245, 247, .74);
  --paper-dim:  rgba(244, 245, 247, .52);
  --red:        #e2001a;
  /* The one colour on this page that is not red, and it earns it the same way
     the Messenger blue rule does: it is a SIGNAL, not decoration. In timing, a
     sector that has gone green means finished, and no other colour says that.
     It appears on completed sectors and nowhere else. */
  --go:         #35d07f;
  --edge:       rgba(244, 245, 247, .13);
  --edge-hi:    rgba(244, 245, 247, .28);

  /* footer only: red on paper measures 4.53:1, which clears AA for normal
     text, but the legal row is the smallest type on the page and gets ink */
  --foot-ink:   #16191f;
  /* The footer's hover colour, and the number is measured rather than chosen.
     merus1894.com holds its footer links at rgb(255,51,0) and takes them to
     rgb(66,13,0) on hover: the same hue, driven down to about 26% of its
     lightness. The point is that it DEEPENS -- the link goes darker and richer,
     not greyer -- so it reads as pressed in rather than switched off. Our red at
     that same 26% is this. 16.27:1 on the paper, so it is the safest colour in
     the footer as well as the most emphatic. */
  --foot-deep:  #3a0007;

  --r-pill: 999px;
  --r-edge: 2px;

  --gutter: clamp(1.25rem, 4vw, 4.5rem);
  --measure: 44ch;
  --nav-h: 68px;
  /* Space reserved on the right of every .wrap for the timing tower. The tower
     is position: fixed, so without this reservation right-aligned content runs
     underneath it: measured at 1280, chapter 01's plate ended 85px inside the
     tower and the labels drew on top of the photograph. Zero where the tower is
     hidden; the media queries below step it with the tower's own width. */
  --rail-w: 0px;

  /* THE TYPE SYSTEM IS ONE FILE.
     Saira is a two-axis variable font (wdth 50-125, wght 100-900), so the width
     axis carries three voices out of a single 96 KB download. Set them with
     font-stretch + font-weight, never font-variation-settings: the registered
     axes already map to those properties, and the high-level properties keep
     inheriting and animating the way the rest of CSS expects.

       display  wdth 122 / wght 800  wide and planted. Headlines, big numerals.
       data     wdth  68 / wght 600  narrow uppercase. Broadcast lower-third.
       body     wdth  96 / wght 400  paragraphs. Slightly condensed: Saira at
                                     its natural width ran the measure wider
                                     than Archivo did and pushed chapter 01's
                                     copy further across the car.

     There is deliberately no monospace. F1 timing screens are a condensed sans
     with tabular figures, not a typewriter, and the system mono stack was a
     large part of what made this page read as generic. */
  --wd-display: 122%;
  --wd-data: 68%;
  --wd-body: 96%;

  --e-out: cubic-bezier(.16, 1, .3, 1);
  /* copy sits on the world rather than on a box, so legibility is carried in
     the type itself: see the scrim note below */
  --lift: 0 1px 2px rgba(4, 6, 10, .9), 0 2px 16px rgba(4, 6, 10, .72);
}

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

html {
  background: var(--ink);
  color-scheme: dark;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--ink);
  color: var(--paper);
  font-family: 'Saira', ui-sans-serif, system-ui, 'Segoe UI', sans-serif;
  font-stretch: var(--wd-body);
  font-size: 1rem;
  line-height: 1.6;
  font-synthesis-weight: none;
  overflow-x: hidden;
}
body.is-locked { overflow: hidden; }

h1, h2, h3 { margin: 0; font-weight: 600; }
p { margin: 0; text-wrap: pretty; }
/* figure and blockquote carry a UA margin of 40px PER SIDE. Unreset, every
   figure cell in a grid track came out 80px narrower than its column: measured
   79.5px inside a 159.5px track. */
figure, blockquote, dl, dd { margin: 0; }
ul { margin: 0; }
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }

.jp, [lang="ja"] {
  font-family: 'Zen Kaku Gothic New', 'Saira', sans-serif;
  font-weight: 500;
  font-stretch: normal;
  letter-spacing: .04em;
  white-space: nowrap;
}

/* every numeral on the page is a timing readout */
/* The data voice. Every timing readout, index, key and label on the page. */
.mono, .mark__n, .mark__t, .bento__k, .boot__label, .boot__pct,
.rail em, .rail b, .foot__cols dt, .foot__legal,
.tribute figcaption, .strip__frame figcaption {
  font-stretch: var(--wd-data);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-transform: uppercase;
}
/* The display voice. */
.h, .bento__v, .menu__nav a, .foot__mark i, .quote p {
  font-stretch: var(--wd-display);
  font-variant-numeric: tabular-nums;
}

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

.skip {
  position: fixed;
  top: .5rem; left: .5rem;
  z-index: 90;
  padding: .7rem 1.1rem;
  background: var(--paper);
  color: var(--ink);
  border-radius: var(--r-pill);
  font-weight: 700;
  font-size: .85rem;
  text-decoration: none;
  transform: translateY(-160%);
  transition: transform .18s var(--e-out);
}
/* clicked, not tabbed: :focus is deliberate here */
.skip:focus { transform: translateY(0); }

/* ---------- stage ---------- */

/* 100lvh, not inset:0. A fixed stage sized to the small viewport resizes the
   canvas every time a mobile toolbar collapses, which reads as a jitter bug. */
.stage {
  position: fixed;
  top: 0; left: 0; right: 0;
  /* A LOCKED PIXEL HEIGHT via --lvh, with the real unit only as the pre-JS
     fallback. EVERY height on this page that used to be in `lvh` now goes
     through that one variable, the stage and the acts alike -- locking only the
     stage fixed the background but left the DOM still measured in a live unit,
     so the copy still jumped down on the first scroll of a phone while the
     world behind it sat still.
     100lvh alone was not enough. It stops the stage resizing on browsers that
     honour the spec, where lvh is constant, but Brave on iOS recomputes lvh when
     its bottom chrome collapses. The canvas box then changes height mid-scroll,
     the camera's aspect changes with it, and because the vertical field of view
     is fixed a taller frame renders the same angle over more pixels -- which is
     read, correctly, as the world zooming in.
     main.js writes --lvh once and only grows it, and only remeasures on a
     width or orientation change, so a toolbar sliding away cannot touch it. */
  height: calc(var(--lvh, 1lvh) * 100);
  z-index: 0;
  background: var(--ink);
}

#gl { display: block; width: 100%; height: 100%; }

.stage__grade {
  position: absolute; inset: 0;
  pointer-events: none;
  /* corners and a floor only. The copy scrim used to live here too and it is
     gone: nothing in this layer may darken the middle of the frame. */
  background:
    radial-gradient(135% 100% at 50% 38%, transparent 62%, rgba(10, 16, 26, .26) 100%),
    linear-gradient(to bottom,
      rgba(8, 12, 20, .30) 0%,
      transparent 15%,
      transparent 66%,
      rgba(8, 12, 20, .26) 86%,
      rgba(8, 12, 20, .52) 100%);
}

.stage__grain {
  position: absolute; inset: 0;
  pointer-events: none;
  opacity: .05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- boot ---------- */

.boot {
  position: fixed; inset: 0;
  z-index: 80;
  display: grid;
  /* centred, not pinned to the bottom: it was sitting on the bottom edge where
     a phone's browser chrome crops it */
  place-items: center;
  padding: 0 var(--gutter);
  background: var(--ink);
  transition: opacity .8s var(--e-out), visibility .8s;
}
.boot.is-done { opacity: 0; visibility: hidden; }

.boot__inner {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: .55rem 1rem;
  width: min(420px, 100%);
}
/* the step line spans the full width on its own row, under the bar */
.boot__step {
  grid-column: 1 / -1;
  grid-row: 2;
  order: 2;
  font-stretch: var(--wd-data);
  font-size: .64rem;
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--paper-dim);
  min-height: 1em;
  transition: opacity .25s var(--e-out);
}
.boot__step.is-swapping { opacity: 0; }
.boot__label { font-size: .66rem; text-transform: uppercase; letter-spacing: .34em; color: var(--paper-dim); }
.boot__bar { flex: 1; height: 2px; background: var(--edge); overflow: hidden; }
.boot__bar i { display: block; height: 100%; width: 100%; background: var(--red);
  transform: scaleX(0); transform-origin: left; transition: transform .35s var(--e-out); }
.boot__pct { font-size: .74rem; color: var(--paper-mid); min-width: 2.5ch; text-align: right; }

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

.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 60;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  gap: clamp(1rem, 4vw, 3rem);
  padding: 0 var(--gutter);
  /* Transparent. The scrim that used to be here is gone, so the legibility has
     to come from the type: every child carries --lift. The stuck state keeps a
     light blur and nothing else, because a fully transparent bar over the white
     car at chapter 03 is genuinely unreadable, not merely a style choice. */
  background: transparent;
  transition: background .4s var(--e-out), backdrop-filter .4s var(--e-out);
}
.nav > * { text-shadow: var(--lift); }
.nav.is-stuck {
  background: rgba(9, 11, 16, .34);
  backdrop-filter: blur(12px) saturate(130%);
  -webkit-backdrop-filter: blur(12px) saturate(130%);
}

.nav__mark { display: flex; align-items: center; gap: .7rem; text-decoration: none; margin-right: auto; }
/* The real mark, keyed off its white background by scripts/derive-refs.py.
   Height-driven so the intrinsic 2.26 aspect sets the width. */
.nav__bull {
  width: auto;
  height: 22px;
  filter: drop-shadow(0 1px 3px rgba(4, 6, 10, .7));
}
.nav__wordmark {
  font-stretch: var(--wd-data);
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  white-space: nowrap;
}

.nav__links { display: flex; gap: clamp(.25rem, 1vw, .8rem); }
.nav__links a {
  position: relative;
  /* Symmetric padding, not a bare text box. Without it each item's hit area and
     its underline were exactly its own text width, so four items of different
     lengths sat on a visually uneven rhythm and the hover underline ran hard up
     against its neighbour. The padding carries the spacing now; the flex gap
     only tops it up. */
  padding: .45rem .6rem;
  font-stretch: var(--wd-data);
  font-size: .76rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--paper-dim);
  white-space: nowrap;
  transition: color .2s;
}
/* the underline is a sector bar, so it wipes rather than fades */
.nav__links a::after {
  content: "";
  /* inset to the padding, so the bar underlines the word rather than the box */
  position: absolute; left: .6rem; right: .6rem; bottom: .15rem;
  height: 2px;
  background: var(--red);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .28s var(--e-out);
}
.nav__links a:hover, .nav__links a.is-current { color: var(--paper); }
.nav__links a:hover::after, .nav__links a.is-current::after { transform: scaleX(1); }

/* lap bar: the whole scroll as one red sector line under the chrome */
/* Five sectors instead of one continuous line. Same 2px line it always was --
   the segmentation is the only change, so nothing in the layout moves. */
.lap {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 1px;
  background: transparent;
}
.lap s {
  display: block;
  height: 100%;
  background: rgba(244, 245, 247, .10);
  overflow: hidden;
  text-decoration: none;
}
/* A finished sector goes green and stays green. */
.lap s.is-done { background: rgba(53, 208, 127, .28); }
.lap s.is-done i { background: var(--go); }
.lap i {
  display: block;
  height: 100%;
  background: var(--red);
  transform: scaleX(0);
  transform-origin: left;
  will-change: transform;
}

/* ---------- telemetry ---------- */

/* The readout at the foot of the tower. It is driven by how fast the reader is
   actually moving through the page, so it reports something true rather than
   playing an animation: park the scroll and it falls to zero and drops out of
   gear, exactly as it should. */
.tele {
  margin-top: .5rem;
  padding-top: .5rem;
  border-top: 1px solid var(--edge);
  display: grid;
  gap: .3rem;
}
.tele__spd { display: flex; align-items: baseline; gap: .22rem; }
.tele__spd b {
  font-stretch: var(--wd-display);
  font-size: 1.25rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -.01em;
  font-variant-numeric: tabular-nums;
  color: var(--paper);
  /* 3ch so 8, 88 and 288 do not shunt the unit sideways every frame */
  min-width: 3ch;
}
.tele__spd em {
  font-style: normal;
  font-stretch: var(--wd-data);
  font-size: .5rem;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--paper-dim);
}
.tele__row { display: flex; gap: .7rem; }
.tele__row span { display: flex; align-items: baseline; gap: .22rem; }
.tele__row em {
  font-style: normal;
  font-stretch: var(--wd-data);
  font-size: .48rem;
  font-weight: 600;
  letter-spacing: .14em;
  color: var(--paper-dim);
}
.tele__row b {
  font-stretch: var(--wd-data);
  font-size: .62rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--paper-mid);
  min-width: 2.5ch;
}
.tele__bar {
  height: 2px;
  background: rgba(244, 245, 247, .14);
  overflow: hidden;
}
.tele__bar i {
  display: block;
  height: 100%;
  background: var(--red);
  transform: scaleX(0);
  transform-origin: left;
  will-change: transform;
}

/* ---------- buttons ---------- */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  padding: .72rem 1.35rem;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-stretch: var(--wd-data);
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: transform .18s var(--e-out), background .2s;
}
/* a skewed sweep, transform only: nothing here repaints on hover */
.btn::before {
  content: "";
  position: absolute;
  top: -20%; bottom: -20%; left: 0;
  width: 55%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .34), transparent);
  transform: translateX(-190%) skewX(-18deg);
  transition: transform .55s var(--e-out);
}
.btn:hover::before { transform: translateX(320%) skewX(-18deg); }
.btn > * { position: relative; }
.btn--primary {
  background: var(--red);
  color: #fff;
  /* Glass, ported by hand. The reference component that asked for this shipped
     its markup and Tailwind classes but none of the .glass-button CSS, so this
     is authored rather than copied. Three layers do the work:
       1. a 1px inner light border, which is the edge of the pane
       2. a highlight along the TOP edge only — the physical tell. A ring all the
          way round reads as a plastic bevel; light collects on one edge.
       3. a red-tinted drop shadow rather than a neutral one, so the button
          appears to be lit by its own colour. */
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, .22),
    inset 0 1px 0 rgba(255, 255, 255, .40),
    inset 0 -6px 12px -6px rgba(0, 0, 0, .45),
    0 6px 20px -6px rgba(226, 0, 26, .55),
    0 2px 6px -2px rgba(4, 6, 10, .6);
  transition: transform .18s var(--e-out), background .2s, box-shadow .25s var(--e-out);
}
.btn--primary:hover {
  background: #f5121f;
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, .30),
    inset 0 1px 0 rgba(255, 255, 255, .52),
    inset 0 -6px 12px -6px rgba(0, 0, 0, .45),
    0 10px 28px -8px rgba(226, 0, 26, .70),
    0 2px 6px -2px rgba(4, 6, 10, .6);
}
.btn--primary:active { transform: translateY(1px) scale(.985); }
.btn--lg { padding: .9rem 1.8rem; font-size: .86rem; }

/* The CTA says the whole thing at every width. It used to fall back to "The Art
   Of" below 1180px, which is where the half title on phones came from. The full
   name fits: at 375px the row is the bull mark, this button and the burger, and
   the type steps down below 560 to buy the room. */
.nav__cta { white-space: nowrap; }
@media (max-width: 560px) {
  .nav__cta { padding: .6rem .85rem; font-size: .66rem; letter-spacing: .04em; }
}

/* ---------- layout ---------- */

main { position: relative; z-index: 10; }

.act {
  position: relative;
  min-height: calc(var(--lvh, 1lvh) * 100);
  display: flex;
  align-items: flex-end;
  padding: calc(var(--nav-h) + 2rem) 0 clamp(3rem, 9vh, 7rem);
  pointer-events: none;
}

/* Extra scroll length so the camera journey is deliberate rather than a flick.
   NOT the lede: its copy is bottom-anchored, so a 135lvh lede pushes the
   headline and CTA below the fold. Chapter 03 now carries six figure cells and
   chapter 04 is a 52 m climb, so both get the dwell. */
/* These heights are not taste. The conductor puts a chapter's composed camera
   pose at that section's midpoint, so a section much taller than the viewport
   reaches its pose after its own copy has scrolled off the top, and the lens
   arrives at an empty frame. Sticky absorbs some of that, but only as much as
   (section height - content height) allows. Tuned by measuring the chapter mark
   at the anchor at 1280x720 and 1440x900; see the note in HANDOFF.md.

   Chapter 03 was 230lvh when its content was a 1018px figure grid and sticky
   needed that much room to pin. The pit-wall band is 632px, so the same pin
   works in 150 and the strip's 275px pan is no longer stretched over two and a
   third screens of scrolling. */
.act--arrival { min-height: calc(var(--lvh, 1lvh) * 132); }
.act--speed { min-height: calc(var(--lvh, 1lvh) * 180); }
.act--circuit { min-height: calc(var(--lvh, 1lvh) * 138); }
.act > * { pointer-events: auto; position: relative; z-index: 1; }

/* An act taller than the viewport reaches its composed camera pose at its own
   midpoint, which is past the point where bottom-anchored copy has scrolled
   away: the lens arrives at a frame with nothing on it. So the three tall acts
   top-align and stick, and their content holds while the camera travels.
   This is why `.act` cannot have `overflow: hidden` any more; the scrim above
   no longer overflows its act, so it does not need to. */
.act--arrival, .act--speed, .act--circuit { align-items: flex-start; }
.act--arrival > .wrap,
.act--speed > .wrap,
.act--circuit > .wrap {
  position: sticky;
  top: calc(var(--nav-h) + clamp(1.5rem, 5vh, 3.5rem));
}

/* THE SCRIM RULE.
   Earlier builds put a wide radial (and on two acts a full-cover wash) over the
   frame to hold copy. That works and it also puts a grey sheet over the car,
   which is the thing the page exists to show. So: the scrim is now a left-edge
   ramp no wider than the copy column, fully transparent well before mid-frame,
   and the last of the legibility is carried by --lift on the type itself.
   Nothing in this file may darken the centre of the viewport. */
.act::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: min(60ch, 52%);
  pointer-events: none;
  background: linear-gradient(to right,
    rgba(6, 8, 12, .80) 0%,
    rgba(6, 8, 12, .62) 30%,
    rgba(6, 8, 12, .28) 62%,
    transparent 100%);
}
/* chapter 04's grid is wider than a copy column, so its ramp is too */
.act--circuit::before { width: min(74ch, 62%); }

.wrap {
  width: 100%;
  max-width: 1400px;
  /* Flush left, not centred. `margin: 0 auto` left 181px of dead space before
     the 72px gutter at 1920, so the copy started 325px in and the page had no
     consistent left edge. max-width still holds the measure; the gutter alone
     now sets the inset, at every width and in every chapter. */
  margin: 0 auto 0 0;
  padding: 0 calc(var(--gutter) + var(--rail-w)) 0 var(--gutter);
}

/* ---------- chapter mark ---------- */

.mark {
  display: flex;
  align-items: center;
  gap: .7rem;
  margin-bottom: 1.4rem;
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .26em;
  text-transform: uppercase;
}
/* the sector block: a skewed red slab, the way a broadcast marshalling flag
   is drawn into a lower third */
.mark::before {
  content: "";
  width: 12px; height: 12px;
  flex: none;
  background: var(--red);
  transform: skewX(-14deg);
}
.mark__n { color: var(--paper); }
.mark__t { color: var(--paper-mid); }
/* three sector ticks rather than a fading rule */
.mark::after {
  content: "";
  flex: 1;
  height: 6px;
  max-width: 20vw;
  background-image: linear-gradient(to right, var(--edge-hi) 0 100%);
  background-size: 22% 2px;
  background-position: 0 50%, 30% 50%, 60% 50%;
  background-repeat: no-repeat;
  box-shadow: none;
}

/* vertical Japanese chapter mark, right edge */
.vmark {
  position: absolute;
  right: clamp(.75rem, 2.6vw, 2.6rem);
  top: 20vh;
  z-index: 1;
  writing-mode: vertical-rl;
  font-size: clamp(.8rem, 1.1vw, 1rem);
  letter-spacing: .42em;
  color: var(--paper-dim);
  text-shadow: var(--lift);
  pointer-events: none;
}

/* ---------- headings and body ---------- */

.h {
  font-size: clamp(1.5rem, 2.9vw, 2.35rem);
  line-height: 1.04;
  letter-spacing: -.01em;
  text-transform: uppercase;
  font-weight: 800;
  max-width: 19ch;
  text-wrap: balance;
  text-shadow: var(--lift);
}
.h--lede {
  font-size: clamp(1.9rem, 3.7vw, 3.1rem);
  font-weight: 900;
  max-width: 13ch;
}

.body {
  margin-top: 1.1rem;
  display: grid;
  gap: .9rem;
  max-width: var(--measure);
  font-size: .95rem;
  color: var(--paper-mid);
  text-shadow: var(--lift);
}

/* ---------- reveals ---------- */

[data-reveal] { will-change: transform, opacity, clip-path; }

/* The hidden state is gated on a class main.js sets, so a module that never runs
   leaves the copy on screen instead of blanking the page. */
.reveal-ready [data-reveal] {
  opacity: 0;
  clip-path: inset(0 100% 0 0);
  transform: translateX(-14px);
  transition:
    opacity .5s var(--e-out),
    clip-path .62s var(--e-out),
    transform .62s var(--e-out);
}
.reveal-ready [data-reveal].is-in {
  opacity: 1;
  clip-path: inset(0 0 0 0);
  transform: none;
}

/* Headings do not use the wipe. main.js splits them into per-word masks and
   they rise in sequence, which reads far more deliberate than a block fading.
   The wrappers keep their layout size, so unlike the clip-path state above this
   one leaves real area for IntersectionObserver to see. */
.reveal-ready .h--split,
.reveal-ready .strip {
  opacity: 1;
  clip-path: none;
  transform: none;
}
.h--split .w {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  /* the mask has to clear the descenders or they are shaved off at rest */
  padding-bottom: .08em;
  margin-bottom: -.08em;
}
.h--split .w i {
  display: inline-block;
  font-style: normal;
  transform: translateY(110%);
  transition: transform .72s var(--e-out);
  transition-delay: calc(var(--i) * 62ms);
}
.reveal-ready .h--split.is-in .w i { transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  .reveal-ready [data-reveal] {
    transform: none;
    clip-path: none;
    transition: opacity .4s linear;
  }
}

/* ---------- 00 lede ---------- */

.lede__cta { margin-top: 1.9rem; }

/* ---------- 01 livery ---------- */

.act--livery { align-items: center; }
.panels {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(2rem, 5vw, 4rem) 1.5rem;
  align-items: center;
}
/* Four columns, not five. Chapter 01's lens is a tight front wing shot in which
   the car fills 15% to 92% of the frame, so this is the one chapter where the
   copy column width is the only lever on obstruction. */
.panel { grid-column: 1 / span 7; }
.panel .body { max-width: 46ch; }
/* Held in the right gutter and capped, because chapter 01 is a front wing shot
   and the car occupies the middle of that frame. */
/* ---------- bento (chapter 04's stat grid) ----------
   Capped tighter than it looks like it needs to be. The Saira display face is
   wider and heavier than what preceded it, which pushed chapter 04's heading
   taller and the grid down onto the car; sized to fit the frame it comes back. */

.bento { display: grid; grid-template-columns: repeat(6, 1fr); gap: .8rem; max-width: 41rem; }
.bento__cell {
  position: relative;
  grid-column: span 2;
  min-height: 7.6rem;
  padding: 1.1rem;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: .35rem;
  border: 1px solid var(--edge);
  border-radius: var(--r-edge);
  background: rgba(9, 11, 16, .62);
  backdrop-filter: blur(9px);
  -webkit-backdrop-filter: blur(9px);
  overflow: hidden;
}
.bento__cell--wide {
  grid-column: span 4;
  grid-row: span 2;
  background:
    radial-gradient(90% 120% at 8% 100%, rgba(226, 0, 26, .26), transparent 62%),
    rgba(9, 11, 16, .70);
}
.bento__cell--wide p { margin-top: .7rem; color: var(--paper-mid); max-width: 42ch; font-size: .86rem; line-height: 1.5; }
/* Four columns, not the full six. Spanning the whole grid put this strip across
   64% of the car's visible silhouette in the bird's eye. */
.bento__cell--img { grid-column: 1 / span 4; grid-row: 3; min-height: 8.5rem; padding: 0; }
.bento__cell--img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.bento__cell--img figcaption {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 1.5rem 1.2rem .9rem;
  font-size: .74rem;
  color: var(--paper-mid);
  background: linear-gradient(to top, rgba(6, 8, 12, .9), transparent);
}
.bento__k { font-size: .6rem; letter-spacing: .2em; text-transform: uppercase; color: var(--paper-dim); }
.bento__v { font-size: clamp(2.2rem, 4vw, 3.4rem); font-weight: 900; letter-spacing: -.03em; line-height: .92; }
.bento__v small {
  font-stretch: var(--wd-data);
  font-size: .3em; font-weight: 600; letter-spacing: .14em;
  text-transform: uppercase; margin-left: .5em; color: var(--red);
}

/* ---------- 01: the tribute column ----------
   One column, three shapes: a 3:2 frame, a letterbox strip, and the peek card.
   The version before this overlapped all three and read as a pile of
   rectangles. They now share a left edge and a width, and the rhythm comes from
   the aspect ratios rather than from scattering them. */

/* A row under the copy, not a column beside it. Stacked in the right gutter
   these three read as a sidebar competing with the car; on one line, starting
   at the copy's own left edge, they read as a caption strip belonging to the
   text above them. */
.tribute {
  grid-column: 1 / -1;
  justify-self: start;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: start;
  gap: .7rem;
  width: 100%;
  max-width: 46rem;
}

.tribute figure { margin: 0; }

.tribute__shot,
.tribute__strip {
  overflow: hidden;
  border: 1px solid var(--edge-hi);
  border-radius: var(--r-edge);
  background: rgba(9, 11, 16, .55);
}
.tribute__shot img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
/* a letterbox crop of the same car: a different shape doing different work,
   rather than a second square photograph competing with the first */
.tribute__strip img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; object-position: 50% 55%; }

.tribute figcaption {
  padding: .55rem .8rem .6rem;
  font-stretch: var(--wd-data);
  font-size: .62rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--paper-mid);
  border-top: 1px solid var(--edge);
  background: rgba(9, 11, 16, .72);
}
.tribute figcaption .mono { color: var(--red); margin-right: .5em; }

/* ---------- the Suzuka plate, chapter 02 ----------

   Two portrait 4:5 frames. The chapter had no imagery at all before, and these
   are the photographs of the thing the 3D scene is a reconstruction of, so they
   belong here rather than anywhere prettier. 4:5 is also the only aspect on the
   page that fills a phone held upright, which is why they stay side by side
   rather than stacking on narrow: two 46vw portraits read as a spread, one
   full-width portrait would push the chapter's copy off the screen. */
/* The cap is vh-aware because this box's HEIGHT is what overflows: a 4:5
   portrait is 1.25x its own width plus a caption, so a fixed rem cap makes a
   fixed 374px block whatever the viewport is. At 1280x720 -- one of the two
   sizes on the verification checklist -- that put the photograph and its
   caption 50px below the fold at chapter 02's own anchor, and the act is
   sticky, so it stayed there for the whole chapter rather than scrolling into
   view. 26vh holds the full 17rem picture from about 1045px of viewport height
   upward and gives the height back below that. Verified clear at the chapter's
   own anchor at 1920x1080, 1440x900, 1280x720, 1440x700, 1280x640, 1366x600 and
   1024x640 -- measure bottom against innerHeight, do not eyeball it. */
.plate {
  width: 100%;
  max-width: clamp(9rem, 26vh, 17rem);
  margin-top: clamp(.9rem, 3vh, 2.4rem);
}
.plate figure { margin: 0; }
.plate__shot {
  overflow: hidden;
  border: 1px solid var(--edge-hi);
  border-radius: var(--r-edge);
  background: rgba(9, 11, 16, .55);
}
.plate__shot img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }
.plate figcaption {
  padding: .55rem .8rem .6rem;
  font-stretch: var(--wd-data);
  font-size: .58rem;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--paper-mid);
  border-top: 1px solid var(--edge);
  background: rgba(9, 11, 16, .72);
}
.plate figcaption .mono { color: var(--red); margin-right: .5em; }

/* ---------- the peek card ----------
   Kage renders this box live from the NEXT chapter's camera, through a second
   render pass. Ours is a still: a second pass costs frame time every frame, the
   draw-call budget is already over the guideline, and the play triangle was
   never going to play anything either way. It scrolls to the next chapter. */
.peek {
  display: block;
  text-decoration: none;
  border: 1px solid var(--edge-hi);
  border-radius: var(--r-edge);
  overflow: hidden;
  background: rgba(9, 11, 16, .72);
  transition: transform .3s var(--e-out), border-color .3s;
}
.peek:hover, .peek:focus-visible { transform: translateY(-3px); border-color: var(--red); }
/* 16:9, not 21:9. As a letterbox strip this card was the smallest thing in the
   column and read as a footnote to the two frames above it, when it is actually
   the link into the next chapter. */
.peek__fr { display: block; position: relative; aspect-ratio: 4 / 3; overflow: hidden; }
.peek__fr img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: .92; }
.peek__play {
  position: absolute;
  top: 50%; left: 50%;
  width: 46px; height: 46px;
  margin: -23px 0 0 -23px;
  display: grid;
  place-items: center;
  border: 1px solid rgba(244, 245, 247, .6);
  border-radius: var(--r-pill);
  background: rgba(6, 8, 12, .45);
  color: var(--paper);
  transition: background .3s var(--e-out), border-color .3s;
}
.peek__play svg { width: 17px; height: 17px; margin-left: 2px; }
.peek:hover .peek__play, .peek:focus-visible .peek__play { background: var(--red); border-color: var(--red); }
.peek__cap {
  display: flex;
  align-items: baseline;
  gap: .55rem;
  padding: .55rem .8rem .6rem;
  border-top: 1px solid var(--edge);
}
.peek__cap b { font-size: .85rem; font-weight: 500; color: var(--paper); }
.peek__cap i {
  font-stretch: var(--wd-data);
  font-style: normal;
  font-size: .58rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--paper-dim);
}

/* ---------- 03: the pit wall ----------
   Replaces the six-cell figure grid that used to sit here. That grid was a
   rectangle of opaque cards covering 19.6% of the car's silhouette, and it read
   as a stock bento. This is a band: one row of frames, panned by this chapter's
   own scroll progress, so roughly two and a half frames are on screen at a time.
   There is no card surface at all. The photograph is the card, and the only
   chrome is a red bracket at two corners. */

/* The band carries the reveal, not the frames. `overflow: hidden` means a frame
   parked outside the band has a zero-area intersection rect, so an observer on
   the frames themselves never fires for the ones still off to the right. */
.strip {
  width: min(38rem, 100%);
  /* The band had no top margin at all. On a laptop the quote's `max-width: 34ch`
     left incidental room and it passed; on a phone `.quote { max-width: none }`
     runs the attribution line the full width of the column and it collided with
     the first row of frames -- cite and photograph touching, which reads as
     suffocated rather than dense. The gap is real spacing now, not a side effect
     of a measure. */
  margin-top: clamp(1.5rem, 3.4vh, 2.4rem);
  overflow: hidden;
  /* the panned row runs past both edges, so fade them rather than cutting the
     frames off against a hard line */
  mask-image: linear-gradient(to right, transparent, #000 3%, #000 94%, transparent);
  -webkit-mask-image: linear-gradient(to right, transparent, #000 3%, #000 94%, transparent);
}

.strip__track {
  display: flex;
  align-items: flex-end;
  gap: .75rem;
  will-change: transform;
  /* main.js writes the transform. A CSS transition here would fight the scrub,
     which is already smoothed by the conductor's own damping. */
}

/* Fixed height, natural width: the frames keep their own aspect ratios, which
   is what stops five photographs in a row reading as a template. */
.strip__frame {
  position: relative;
  flex: 0 0 auto;
  height: clamp(11rem, 26vh, 17rem);
  overflow: hidden;
  background: rgba(9, 11, 16, .5);
}
.strip__frame img { height: 100%; width: auto; max-width: none; object-fit: cover; }

/* Two corner brackets, not a border. They draw in on reveal. */
.strip__frame::before,
.strip__frame::after {
  content: "";
  position: absolute;
  width: 18px; height: 18px;
  border: 2px solid var(--red);
  transition: opacity .4s var(--e-out), transform .5s var(--e-out);
  opacity: 0;
}
.strip__frame::before { top: 0; left: 0; border-right: 0; border-bottom: 0; transform: translate(-6px, -6px); }
.strip__frame::after { bottom: 0; right: 0; border-left: 0; border-top: 0; transform: translate(6px, 6px); }
.reveal-ready .strip.is-in .strip__frame::before,
.reveal-ready .strip.is-in .strip__frame::after { opacity: 1; transform: translate(0, 0); }
/* staggered along the row, so the brackets snap on in sequence */
.strip__frame:nth-child(1)::before, .strip__frame:nth-child(1)::after { transition-delay: .10s; }
.strip__frame:nth-child(2)::before, .strip__frame:nth-child(2)::after { transition-delay: .18s; }
.strip__frame:nth-child(3)::before, .strip__frame:nth-child(3)::after { transition-delay: .26s; }
.strip__frame:nth-child(4)::before, .strip__frame:nth-child(4)::after { transition-delay: .34s; }
.strip__frame:nth-child(5)::before, .strip__frame:nth-child(5)::after { transition-delay: .42s; }

.strip__frame figcaption {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 1.6rem .7rem .55rem;
  font-size: .62rem;
  letter-spacing: .14em;
  white-space: nowrap;
  color: var(--paper-mid);
  background: linear-gradient(to top, rgba(6, 8, 12, .92), transparent);
}
.strip__frame figcaption .mono { color: var(--red); margin-right: .45em; }

/* ---------- the pull quote ---------- */
/* Out of the grid entirely. It is text; it never needed a card. */
.quote {
  margin-top: clamp(1.4rem, 3vh, 2.2rem);
  padding-left: 1.1rem;
  border-left: 2px solid var(--red);
  max-width: 34ch;
}
.quote p {
  font-size: clamp(1.05rem, 1.7vw, 1.45rem);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -.01em;
  text-wrap: balance;
  text-shadow: var(--lift);
}
.quote cite { display: block; margin-top: .7rem; font-style: normal; }
.quote cite b {
  display: block;
  font-stretch: var(--wd-data);
  font-size: .74rem; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase;
}
.quote cite span { font-size: .72rem; color: var(--paper-dim); }

/* ---------- 04 circuit ---------- */

.act--circuit .h { margin-bottom: clamp(1.8rem, 4vh, 2.8rem); }

/* ---------- 05 end ---------- */

.act--end { align-items: flex-end; padding-bottom: clamp(4rem, 14vh, 9rem); }
.end {
  display: grid;
  grid-template-columns: 1.1fr .9fr;
  gap: clamp(2rem, 6vw, 5rem);
  align-items: end;
}
.end__note { margin-top: 1rem; font-size: .76rem; color: var(--paper-dim); max-width: 40ch; text-shadow: var(--lift); }

/* ---------- footer: the page turns to paper ---------- */

.foot {
  position: relative;
  z-index: 10;
  background: var(--paper);
  color: var(--red);
  border-top: 2px solid var(--red);
  padding: clamp(3rem, 8vh, 6rem) 0 1.6rem;
  /* the stage behind is fixed and ink: this keeps bottom overscroll paper */
  box-shadow: 0 50vh 0 var(--paper);
}

.foot__wrap {
  width: 100%;
  max-width: 1400px;
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.foot__cols {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 2rem;
}
.foot__cols dl { margin: 0; }
.foot__cols dt {
  margin-bottom: 1rem;
  font-size: .62rem;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--foot-ink);
}
.foot__cols dd { margin: 0; }
.foot__cols a {
  display: inline-block;
  padding: .18rem 0;
  font-size: clamp(1rem, 1.6vw, 1.3rem);
  font-weight: 700;
  letter-spacing: -.005em;
  text-decoration: none;
  transition: color .3s var(--e-out);
}
/* Deepen, do not grey out.
   This used to land on --foot-ink, which is a cold blue-black: the link left the
   red family entirely and read as disabled rather than as hovered. Merus keeps
   the hue and drives the lightness down, which is the whole effect. Focus-visible
   takes the identical treatment there, so a keyboard reader gets what a mouse
   reader gets. */
.foot__cols a:hover,
.foot__cols a:focus-visible { color: var(--foot-deep); }

/* The legal row starts at ink rather than red, so deepening it further would be
   invisible: ink and the deep red are both near-black and the swap would not
   read at 11px. It moves the other way, to the accent, which serves the same
   idea -- hovering a footer link commits it to the brand colour.
   Measured on the paper, so the accessibility statement stays true: ink at rest
   is 16.14:1 and red on hover is 4.53:1. That clears AA for normal-size text
   and it is the tightest ratio on the page, which is exactly why the RESTING
   colour of this row stays ink. A transient hover may sit at 4.53; a paragraph
   of 11px legal text may not. */
.foot__legal a {
  color: inherit;
  text-decoration: none;
  transition: color .3s var(--e-out);
}
.foot__legal a:hover,
.foot__legal a:focus-visible { color: var(--red); }

.foot__ext { font-size: .62em; vertical-align: .5em; margin-left: .25em; }

/* RED BULL, edge to edge.
   The starting size below is only a pre-JS floor. Letter advances depend on
   which font actually loaded, and a vw constant that fits Saira overflows the
   fallback, so main.js measures the line and sets the real size. `space-between`
   is the safety net: whatever is left over spreads instead of overflowing.
   This sits outside .foot__wrap so it spans the page, not the 1400px measure. */
.foot__mark {
  position: relative;   /* the hover line below is positioned against this */
  margin: clamp(2.5rem, 7vh, 5rem) 0 0;
  padding: 0 var(--gutter);
  line-height: .8;
}
.foot__mark > span:not(.foot__since) {
  display: flex;
  justify-content: space-between;
  font-size: 17vw;
  /* clipping happens at the padding edge, which is what gives the letters a
     runway to rise from without cropping their caps */
  overflow: hidden;
  padding-block: .08em;
}
.foot__mark > span:not(.foot__since) i {
  display: block;
  font-style: normal;
  font-size: inherit;
  font-weight: 900;
  letter-spacing: -.025em;
  transform: translateY(105%);
  transition: transform .8s var(--e-out);
  transition-delay: calc(var(--i) * 38ms);
  will-change: transform;
}
.foot__mark.is-in > span:not(.foot__since) i { transform: translateY(0); }
/* The word break is an em-sized box, not a percentage and not a space glyph.
   A percentage would not scale with the font size, which would make the fit
   measurement in main.js non-linear and leave the line short. */
.foot__gap { flex: 0 0 auto; width: .34em; }

/* Hover peek: RED BULL leaves through the top of its own clip band, SINCE 2005
   drops in from above, both letter by letter left to right, and after a two
   second hold the whole thing puts itself back. Measured off the Merus footer:
   0.72s out, 2.0s held, 0.72s back, and it completes whether or not the pointer
   is still there -- it is a peek, not a hover state, which is why this is one
   keyframe cycle per letter rather than a :hover transition. main.js starts it
   on mouseenter and takes the class off again when the last letter lands.

   RED BULL comes back from BELOW, not back down the way it left, so the letters
   only ever travel upwards. The jump from top to bottom happens at 84.9%, while
   they are outside the clip and nobody can see it. */
.foot__since {
  position: absolute;
  /* its own clip box, sized to the small text -- the RED BULL band is far
     taller, so clipping there would leave these letters visible at rest */
  top: 50%;
  left: 0;
  right: 0;
  height: 1em;
  margin-top: -.5em;
  display: flex;
  justify-content: center;
  overflow: hidden;
  line-height: 1;
  /* font-size is set by fitWordmark() from the measured wordmark size */
  font-size: 4.6vw;
  pointer-events: none;
}
.foot__since i {
  display: block;
  font-style: normal;
  font-weight: 900;
  letter-spacing: .06em;
  transform: translateY(-120%);
  will-change: transform;
}

/* One 3.3s cycle, `both` so the letter sits on the first frame through its own
   stagger delay and on the last frame afterwards -- both of which are the rest
   position, so removing the class never shows a jump. */
.foot__mark.is-swap > span:not(.foot__since) i {
  animation: mark-peek 3.5s var(--e-out) both;
  animation-delay: calc(var(--i) * 40ms);
}
.foot__mark.is-swap .foot__since i {
  animation: since-peek 3.5s var(--e-out) both;
  animation-delay: calc(var(--i) * 30ms);
}

/* Percentages of 3.5s. Named in seconds so the shape survives a retune:
   out by .42, held until 3.00, back home by 3.42. */
@keyframes mark-peek {
  0%     { transform: translateY(0); }
  12%    { transform: translateY(-118%); }   /* out through the top, .42s */
  85.7%  { transform: translateY(-118%); }   /* held while SINCE 2005 shows */
  85.8%  { transform: translateY(118%); }    /* the unseen jump, off-clip */
  97.7%  { transform: translateY(0); }       /* back up into place from below */
  100%   { transform: translateY(0); }
}
/* in by .51, held until 2.72 -- a full two seconds on screen -- out by 3.14,
   which clears the band before RED BULL starts climbing back into it. */
@keyframes since-peek {
  0%     { transform: translateY(-120%); }
  2.6%   { transform: translateY(-120%); }   /* lets RED BULL break first */
  14.6%  { transform: translateY(0); }
  77.7%  { transform: translateY(0); }
  89.7%  { transform: translateY(-120%); }
  100%   { transform: translateY(-120%); }
}

.foot__legal {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 2rem;
  justify-content: space-between;
  align-items: baseline;
  margin-top: clamp(1.6rem, 4vh, 2.6rem);
  padding-top: 1.2rem;
  border-top: 1px solid rgba(22, 25, 31, .18);
  font-size: .7rem;
  color: var(--foot-ink);
}
.foot__legal ul { display: flex; flex-wrap: wrap; gap: .4rem 1.4rem; margin: 0; padding: 0; list-style: none; }

/* the footer is paper, so the focus ring has to be too */
.foot :focus-visible { outline-color: var(--foot-ink); }

/* ---------- scrambling labels ---------- */

/* The ghost is the whole trick. A scrambling label swaps in random glyphs of
   different widths on every frame, so without something holding the row open it
   jitters the entire menu. The ghost is the real text at zero opacity carrying
   the layout; the live copy floats over it and is the only thing that changes.
   Taken from KPRVERSE, which does exactly this with a .spacer and an .animation. */
.sc { position: relative; display: inline-block; }
.sc__ghost { opacity: 0; font-style: normal; }
.sc__live {
  position: absolute;
  inset: 0;
  font-style: normal;
  white-space: nowrap;
}

/* ---------- burger and full-screen menu ---------- */

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

.burger {
  width: 40px; height: 40px;
  margin-left: .4rem;
  display: grid; place-items: center;
  background: none; border: 0; cursor: pointer;
  color: var(--paper);
}
.burger__bars, .burger__bars::before, .burger__bars::after {
  content: "";
  display: block;
  width: 22px; height: 1.5px;
  background: currentColor;
  transition: transform .3s var(--e-out), opacity .2s;
}
.burger__bars { position: relative; }
.burger__bars::before { position: absolute; top: -6px; }
.burger__bars::after  { position: absolute; top:  6px; }
[aria-expanded="true"] .burger__bars { background: transparent; }
[aria-expanded="true"] .burger__bars::before { transform: translateY(6px) rotate(45deg); }
[aria-expanded="true"] .burger__bars::after  { transform: translateY(-6px) rotate(-45deg); }

.menu {
  position: fixed;
  inset: 0;
  z-index: 70;
  visibility: hidden;
  /* Delay the HIDE until the slide finishes, and make the SHOW instant. A plain
     `transition: visibility .5s` keeps the subtree hidden for half a second
     after the class lands, and an element inside a visibility:hidden subtree
     cannot take focus — so the focus move on open silently did nothing and the
     keyboard was left outside the dialog. */
  transition: visibility 0s linear .62s;
}
.menu.is-open { visibility: visible; transition: visibility 0s linear 0s; }

.menu__scrim {
  position: absolute;
  inset: 0;
  background: rgba(6, 8, 12, .72);
  opacity: 0;
  transition: opacity .5s var(--e-out);
}
.menu.is-open .menu__scrim { opacity: 1; }

/* Two nested translates. The panel comes in from the left and the content
   inside it travels the opposite way at the same time, so the content resolves
   into place rather than being carried in bodily. One translate alone reads as
   a drawer; two read as a reveal. KPRVERSE does this with content-outer and
   content-inner and it is the whole character of the transition. */
.menu__panel {
  position: absolute;
  top: 0; bottom: 0; left: 0;
  /* 40rem, not 30. Chapter 05 is now "The Art Of Livery Design", which is 493px
     of nowrap display type -- .sc__live is absolutely positioned and cannot
     wrap, so a row that does not fit is silently clipped by the overflow below
     rather than reflowing. This width plus the eased type scale in .menu__nav
     leaves every row, its number and its meta label inside the panel. */
  width: min(40rem, 94vw);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  background: var(--ink);
  border-right: 1px solid var(--edge);
  transform: translate3d(-100%, 0, 0);
  transition: transform .62s var(--e-out);
  overflow: hidden;
}
.menu.is-open .menu__panel { transform: translate3d(0, 0, 0); }

/* Sits in the panel's own top-right, inside the paper, at a 44px touch target. */
.menu__close {
  position: absolute;
  top: clamp(.9rem, 2.5vh, 1.5rem);
  right: clamp(.9rem, 3vw, 1.6rem);
  z-index: 2;
  width: 44px; height: 44px;
  display: grid;
  place-items: center;
  padding: 0;
  background: none;
  border: 1px solid rgba(22, 25, 31, .18);
  border-radius: var(--r-pill);
  color: var(--foot-ink);
  cursor: pointer;
  transition: background .25s var(--e-out), border-color .25s var(--e-out), color .25s;
}
.menu__close:hover, .menu__close:focus-visible {
  background: var(--red);
  border-color: var(--red);
  color: var(--paper);
}
/* the same X the burger morphs into, drawn directly rather than animated */
.menu__close-x { position: relative; display: block; width: 16px; height: 16px; }
.menu__close-x::before, .menu__close-x::after {
  content: "";
  position: absolute;
  top: 50%; left: 0;
  width: 100%; height: 1.5px;
  background: currentColor;
}
.menu__close-x::before { transform: rotate(45deg); }
.menu__close-x::after  { transform: rotate(-45deg); }

.menu__inner {
  padding: calc(var(--nav-h) + clamp(1.5rem, 5vh, 3rem)) var(--gutter) 0;
  transform: translate3d(38%, 0, 0);
  opacity: 0;
  transition: transform .62s var(--e-out), opacity .4s var(--e-out);
}
.menu.is-open .menu__inner { transform: translate3d(0, 0, 0); opacity: 1; }

.menu__label {
  margin: 0 0 1rem;
  font-stretch: var(--wd-data);
  font-size: .6rem;
  font-weight: 600;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--paper-dim);
}
.menu__label::before {
  content: "";
  display: inline-block;
  width: 6px; height: 6px;
  margin-right: .7rem;
  vertical-align: middle;
  background: var(--red);
  transform: skewX(-14deg);
}

.menu__nav { display: grid; gap: .1rem; margin-bottom: clamp(2rem, 6vh, 3.5rem); }
.menu__nav a {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: .9rem;
  padding: .3rem .7rem;
  margin-left: -.7rem;
  font-stretch: var(--wd-display);
  /* Eased down from 2.1rem so the longest chapter name fits on one line at the
     panel width above. Measured: the row needs number + gap + label + gap +
     meta to sit inside 525px of inner width. */
  font-size: clamp(1.15rem, 2.9vw, 1.62rem);
  font-weight: 800;
  line-height: 1.12;
  letter-spacing: -.015em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--paper-mid);
  transition: color .25s var(--e-out);
}
/* the highlight block, drawn behind the label and wiped in from the left */
.menu__nav a::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--paper);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .34s var(--e-out);
}
.menu__nav a:hover,
.menu__nav a:focus-visible { color: var(--ink); }
.menu__nav a:hover::before,
.menu__nav a:focus-visible::before { transform: scaleX(1); }

.menu__no {
  font-stretch: var(--wd-data);
  font-style: normal;
  font-size: .58em;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--red);
  transition: color .25s;
}
.menu__nav a:hover .menu__no { color: var(--red); }

/* the small meta label that fades in beside the hovered row */
.menu__meta {
  margin-left: auto;
  font-stretch: var(--wd-data);
  font-size: .5em;
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0;
  transform: translateX(-6px);
  transition: opacity .3s var(--e-out), transform .3s var(--e-out);
}
.menu__nav a:hover .menu__meta,
.menu__nav a:focus-visible .menu__meta { opacity: .55; transform: translateX(0); }

.menu__links { display: grid; gap: .45rem; }
.menu__links a {
  font-stretch: var(--wd-data);
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--paper-dim);
  transition: color .25s;
}
.menu__links a:hover, .menu__links a:focus-visible { color: var(--red); }

.menu__foot {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem 1.5rem;
  justify-content: space-between;
  padding: 1.4rem var(--gutter);
  border-top: 1px solid var(--edge);
  font-stretch: var(--wd-data);
  font-size: .6rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--paper-dim);
}

@media (prefers-reduced-motion: reduce) {
  /* a real fallback: the panel is simply there, not slid */
  .menu__panel, .menu__inner { transition: none; transform: none; }
  .menu:not(.is-open) .menu__panel { transform: translate3d(-100%, 0, 0); }
}


/* ---------- the menu is paper ----------

   The panel flips from ink to paper and takes the footer's colour logic with
   it: red for anything large, ink for anything small.

   That split is not a style choice. Red on paper measures 4.53:1, which clears
   AA for normal text and fails nothing at display size -- but it is close
   enough to the line that the smallest type in the panel (the section labels,
   the row meta, the footer row) takes --foot-ink instead, exactly as the page
   footer already does for its legal row.

   Scoped to the panel throughout. --paper and --red are global tokens used by
   the whole page, so nothing here reassigns them. */

.menu__panel {
  background: var(--paper);
  border-right: 1px solid rgba(22, 25, 31, .16);
}

/* section labels: smallest type in the panel, so ink */
.menu__panel .menu__label { color: var(--foot-ink); }

/* the chapter rows: display size, so red is comfortable */
.menu__panel .menu__nav a { color: var(--red); }
/* the wipe block was paper on ink; inverted, it is ink on paper */
.menu__panel .menu__nav a::before { background: var(--foot-ink); }
.menu__panel .menu__nav a:hover,
.menu__panel .menu__nav a:focus-visible { color: var(--paper); }
/* the number rides the row, so it inverts with it rather than staying red on ink */
.menu__panel .menu__nav a:hover .menu__no,
.menu__panel .menu__nav a:focus-visible .menu__no { color: var(--paper); }
/* meta only ever shows on a hovered row, which is now ink: paper, not ink */
.menu__panel .menu__meta { color: var(--paper); }

.menu__panel .menu__links a { color: var(--foot-ink); }
.menu__panel .menu__links a:hover,
.menu__panel .menu__links a:focus-visible { color: var(--red); }

.menu__panel .menu__foot {
  color: var(--foot-ink);
  border-top: 1px solid rgba(22, 25, 31, .16);
}

/* The burger is the close button while the panel is open. It is white by
   default, so wherever it lands on paper it would be invisible. The state lives
   on the button itself, and .nav sits before .menu in the DOM, so this cannot
   be a sibling selector off .menu.is-open. */
.burger[aria-expanded="true"] { color: var(--foot-ink); }

/* ---------- timing tower ---------- */

/* Broadcast furniture, held entirely in the right gutter. Labels are the part
   most likely to reach the car, so they are the part that collapses first. */
.rail {
  position: fixed;
  right: clamp(.9rem, 2vw, 1.7rem);
  top: 50%;
  transform: translateY(-50%);
  z-index: 55;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: .6rem .55rem;
  border-left: 2px solid var(--red);
  background: linear-gradient(to right, rgba(6, 8, 12, .58), rgba(6, 8, 12, .30));
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity .5s var(--e-out);
}
.rail.is-on { opacity: 1; }
/* The tower is furniture for the dark stage. Over the paper footer it is a dark
   panel floating on white, and it lands on the RED BULL wordmark. The chapter
   index cannot answer this: `exact` tops out around 4.96, because the last
   chapter's anchor sits inside the final viewport of scroll, so the
   `i < rail.length` test in main.js never fires. main.js watches the paper
   itself instead. Must stay after .is-on: same specificity, source order wins. */
.rail.is-paper { opacity: 0; pointer-events: none; }

.rail a {
  display: grid;
  /* number, label, sector bar, sector time */
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: .55rem;
  padding: .28rem .1rem;
  text-decoration: none;
  border-top: 1px solid transparent;
  transition: border-color .25s;
}
.rail a + a { border-top-color: var(--edge); }

/* The sector time. Locked in when the sector completes and then held, so the
   tower reads as a timing screen that has been somewhere rather than a progress
   bar. Tabular figures because the column must not jitter as digits change. */
.rail u {
  font-stretch: var(--wd-data);
  font-size: .56rem;
  font-weight: 600;
  letter-spacing: .04em;
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  color: var(--paper-dim);
  opacity: 0;
  min-width: 4.2ch;
  text-align: right;
  transition: opacity .3s var(--e-out), color .3s;
}
.rail a.is-done u { opacity: 1; color: var(--go); }
.rail a.is-done span i { background: var(--go); }

.rail em {
  font-style: normal;
  font-size: .6rem;
  letter-spacing: .06em;
  color: var(--paper-dim);
  transition: color .25s;
}
.rail b {
  font-size: .58rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--paper-dim);
  transition: color .25s;
}
/* the sector bar */
.rail span {
  display: block;
  width: 34px; height: 3px;
  background: rgba(244, 245, 247, .16);
  overflow: hidden;
}
.rail span i {
  display: block;
  height: 100%;
  background: var(--paper-dim);
  transform: scaleX(0);
  transform-origin: left;
  will-change: transform;
}
.rail a.is-current em, .rail a.is-current b { color: var(--paper); }
.rail a.is-current span i { background: var(--red); }

/* The live row breathes. One element, one keyframe, current chapter only, so
   there is no perpetual animation running on the four rows nobody is reading. */
.rail a.is-current em::before {
  content: "";
  display: inline-block;
  width: 5px; height: 5px;
  margin-right: .4rem;
  vertical-align: middle;
  border-radius: 50%;
  background: var(--red);
  animation: rail-live 1.9s var(--e-out) infinite;
}
@keyframes rail-live {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: .35; transform: scale(.7); }
}

/* A red bar crosses the row as the chapter changes. main.js adds .is-cut for
   one animation cycle; it is transform-only and never touches layout. */
.rail a { position: relative; overflow: hidden; }
.rail a::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(226, 0, 26, .55), transparent);
  transform: translateX(-110%);
  pointer-events: none;
}
.rail a.is-cut::after { animation: sector-cut .55s var(--e-out) 1; }
@keyframes sector-cut {
  from { transform: translateX(-110%); }
  to   { transform: translateX(110%); }
}
.rail a:hover em, .rail a:hover b { color: var(--paper); }

/* ---------- no-webgl fallback: the plates become real backgrounds ---------- */

.no-webgl .stage { background: var(--ink); }
.no-webgl #gl, .no-webgl .stage__grain { display: none; }
.no-webgl .act {
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}
.no-webgl .act::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgba(8, 10, 15, .72) 0%, rgba(8, 10, 15, .35) 40%, rgba(8, 10, 15, .88) 100%);
}
.no-webgl .act--lede    { background-image: url('assets/layers/helmet-1024.webp'); }
.no-webgl .act--livery  { background-image: url('assets/layers/body-1024.webp'); }
.no-webgl .act--arrival { background-image: url('assets/layers/far-1024.webp'); }
.no-webgl .act--speed   { background-image: url('assets/layers/wingfront-1024.webp'); }
.no-webgl .act--circuit { background-image: url('assets/layers/outro-1024.webp'); }
.no-webgl .act--end     { background-image: url('assets/layers/wingrear-1024.webp'); }

/* ---------- responsive ---------- */

/* the tower is visible from 768 up, and carries labels above 1200 */
@media (min-width: 768px)  { :root { --rail-w: 4.5rem; } }
/* 11rem, not 9: the tower carries sector times above 1200px now and measured
   162px wide against the 144px that was being reserved, which would have let
   right-aligned copy run under it. The reservation follows the furniture. */
@media (min-width: 1201px) { :root { --rail-w: 11rem; } }

@media (max-width: 1200px) {
  /* the tower's labels are the first thing to reach the car on a narrower
     frame, so they go before the tower itself does. The sector times go with
     them: a bare number with no label beside it is not a timing screen. */
  .rail b, .rail u { display: none; }
  .rail a { grid-template-columns: auto 1fr auto; }
  .tele { display: none; }
}

@media (max-width: 1024px) {
  /* The chapter links used to survive down to 767px, and between 768 and about
     910 they were the reason two things broke at once: the brand wordmark ran
     18px into the first link, and the CTA -- which is `overflow: hidden` for the
     glass edge -- clipped its own label to "HE ART OF LIVERY DESIG". Measured at
     768: scrollWidth 38px past clientWidth, wordmark-to-links gap -20px. Below
     1024 the row is therefore the mark, the button and the burger, which is the
     phone arrangement that already fits, and the burger carries the same five
     chapters. Shortening the CTA label instead was tried and reverted once
     already; see the note above .nav__cta. */
  .nav__links { display: none; }
  .panel, .tribute { grid-column: 1 / -1; }
  .end { grid-template-columns: 1fr; }
  .bento__cell { grid-column: span 3; }
  .bento__cell--wide { grid-column: span 6; grid-row: auto; }
  /* 16rem flat was 256px at every height, and below 1025px wide the grid stacks
     instead of nesting, so chapter 04 ran 60 to 72px past the fold at 768, 900
     and 1024 wide at EVERY height up to 800. The act is sticky, so it stayed
     cut. Measured, not eyeballed: the figcaption inside is absolutely
     positioned, so this cell is the whole height. */
  .bento__cell--img { grid-column: span 6; grid-row: auto; min-height: clamp(7rem, 22vh, 16rem); }
  .act::before, .act--circuit::before { width: min(78ch, 88%); }
}

/* Narrow AND not tall. Below 1025px the bento stops nesting and stacks into
   three full-width rows, which is a much taller block than the desktop grid it
   replaces -- so chapter 04 ran 23 to 72px past the fold at 768, 900 and 1024
   wide at every height from 681 to 800, and the act is sticky so it stayed cut.
   Height alone does not describe that case and neither does width, which is why
   this is the one rule in the file gated on both. Placed BEFORE the short block
   below on purpose: that one goes further, and later wins. */
@media (max-width: 1024px) and (max-height: 820px) {
  .bento { gap: .55rem; }
  .bento__cell { padding: .8rem; }
  .bento__cell--img { min-height: clamp(6rem, 16vh, 12rem); }
  .bento__v { font-size: clamp(1.9rem, 3.4vw, 2.6rem); }
}

@media (max-width: 767px) {
  :root { --nav-h: 60px; }

  /* The sticky acts were sized for a laptop and shipped unchanged to phones,
     where the wrap is nearly as tall as the viewport, so almost the whole act
     became pin. A reader scrolling through it sees the copy stop dead while the
     camera keeps scrubbing, and reports it as lag. It is not lag; it is the
     pin, and it was measured at 375x812:

       act               section  wrap   pinned   reported as stalling
       .act--arrival       1072    613    459px   yes
       .act--speed         1462    917    545px   yes
       .act--circuit       1121    890    231px   NO

     The two that stall are the two long pins and the one nobody noticed is the
     short one, so the threshold sits between 231 and 459. These heights put
     both long acts near that 231, which already reads as a beat rather than a
     stop: 613 + 230 = 843 (104% of 812), 917 + 230 = 1147 (141%).

     The pin is kept rather than dropped because it is load-bearing -- an act
     reaches its camera pose at its own MIDPOINT, and without the pin the copy
     has scrolled past by the time the composed frame arrives. Two consequences,
     both accepted: the mobile page is about 11% shorter, and each chapter's
     camera move now happens over less scroll, so the lens travels faster per
     swipe. If that ever reads as rushed, these two numbers go up. */
  .act--arrival { min-height: calc(var(--lvh, 1lvh) * 104); }
  .act--speed   { min-height: calc(var(--lvh, 1lvh) * 141); }
  /* Chapter 04 was going to be left alone, on a 231px reading. Re-measured on a
     fully loaded page at 390x844 -- the iPhone 12 Pro's actual CSS viewport --
     it pins 344px, which made it the worst of the three the moment the other
     two came down. Left as it was it would simply have become the next thing
     reported. 821 + 230 = 1051, which is 124% of 844. */
  .act--circuit { min-height: calc(var(--lvh, 1lvh) * 124); }

  .nav__wordmark { font-size: .6rem; letter-spacing: .16em; }
  .nav__bull { height: 18px; }
  .btn { padding: .7rem 1.1rem; }
  .rail { display: none; }
  .vmark { top: 14vh; font-size: .74rem; }
  .bento { grid-template-columns: 1fr; }
  .bento__cell, .bento__cell--wide, .bento__cell--img { grid-column: 1 / -1; }
  /* A scrubbed strip fights native momentum scrolling on touch, so the band
     becomes a plain two-column grid. main.js writes no transform because the
     track then has no overflow to pan: the layout decides, not a media query
     duplicated in JS. */
  /* and more of it on a phone, where the cite line above runs full width and so
     ends directly over the first frame rather than beside it */
  .strip { width: 100%; margin-top: 2rem; mask-image: none; -webkit-mask-image: none; }
  .strip__track { display: grid; grid-template-columns: repeat(2, 1fr); gap: .6rem; }
  .strip__frame { height: auto; aspect-ratio: 3 / 4; }
  /* Five frames in two columns leaves the fifth alone in its own row, which is
     what read as lopsided. It spans instead, so the block resolves 2 + 2 + 1
     wide: a deliberate composition rather than an orphan. Its own aspect goes
     landscape because a full-width 3:4 portrait would be enormous. */
  .strip__frame:last-child { grid-column: 1 / -1; aspect-ratio: 16 / 9; }
  .strip__frame img { width: 100%; height: 100%; }
  /* Three across is unreadable at 375px, so the row folds: the two stills side
     by side, the peek card spanning beneath them as the link out of the chapter. */
  .tribute { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tribute > .peek { grid-column: 1 / -1; }
  .peek__fr { aspect-ratio: 16 / 9; }
  .quote { max-width: none; }
  .mark::after { max-width: 12vw; }
  .act { padding-bottom: clamp(2rem, 6vh, 4rem); }
  /* THE SCRIM RULE, on a phone.
     This was `width: 100%` at .78 -> .50 and never reached transparent, so
     every pixel of the scene sat behind an average of 64% black. That, not the
     lighting, is what made mobile look two to three times darker than desktop.

     The win here is the RIGHT side, not the left. The old ramp sat at .50 black
     across the entire right half of the frame -- over the car's flank, the
     wheel and the tail of the wordmark, where no copy has ever been. That half
     now clears completely, which is most of the brightness back.

     The left is only slightly lighter than the .78 it was, and deliberately so.
     Attempts to solve the copy-side alpha by sampling the WebGL buffer and
     computing WCAG contrast per pixel gave inconsistent answers over an
     animated scene, so this side is NOT set from that: it stays near the value
     that was already shipping and known legible, and it was checked by looking
     at every chapter rendered on a phone frame.

     It still has to be wide. The copy is not a band on a phone: the sticky
     chapters put it across 6% to 123% of the viewport height, so there is no
     clear horizontal strip to protect. What there is, is a clear right edge --
     the copy column never passes about 62% of the width -- which is where the
     car's flank, the wheel and the tail of the wordmark live. That side now
     goes fully transparent. */
  .act::before, .act--circuit::before {
    width: 100%;
    background: linear-gradient(to right,
      rgba(6, 8, 12, .70) 0%,
      rgba(6, 8, 12, .60) 45%,
      rgba(6, 8, 12, .18) 74%,
      transparent 100%);
  }
  /* NO vignette at all on a phone.
     It was already reduced by a third here, on the reasoning that the bands eat
     a larger share of a tall frame than a wide one. That reasoning was right and
     did not go far enough: on a small bright screen held at arm's length the
     bands only make a night scene look murky, and they stack on top of the copy
     scrim, which is doing the legibility work by itself. Desktop keeps the full
     vignette, where a large frame genuinely needs its corners closed down. */
  .stage__grade { background: none; }
  .foot__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2rem 1.5rem; }
  .foot__cols dl:last-child { grid-column: 1 / -1; }
  .foot__legal { flex-direction: column; }
}

/* below 560 the full wordmark cannot share one line with the CTA, so the mark
   carries the brand and the name stays available to screen readers */
@media (max-width: 559px) {
  .nav__wordmark {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .nav__bull { width: 34px; height: 15px; }
}

/* Short desktop viewport -- a 1366x768 laptop with browser chrome lands at
   about 600px, which is common enough to be a target rather than an edge case.
   Two acts are sized to fit the frame and both ran past the fold there, measured
   at 1366x600: chapter 03's filmstrip finished 19px below it, so the frame
   captions were cut in half, and chapter 04's stat grid finished 49px below it.
   Neither is visible as an error -- the content simply stops -- and both were
   clean at 1280x720, which is why the checklist's two sizes missed it.

   The cause in 03 is the clamp FLOOR, not the preferred value: at 26vh the strip
   is already scaling with the viewport, but 11rem is 176px and takes over below
   677px of height, so the band stops shrinking exactly when it needs to most.
   Lower the floor and the vh term keeps working. 04 is the grid's own minimum
   image height plus two row gaps.

   Judge this by measuring `getBoundingClientRect().bottom` against innerHeight
   at each chapter's anchor, not by looking -- a band that ends below the fold
   looks like a band that ends. */
@media (min-width: 768px) and (max-height: 680px) {
  .strip__frame { height: clamp(7.5rem, 24vh, 17rem); }
  .bento { gap: .55rem; }
  /* 900x600 -- a narrow short window -- still ran the grid 16px past the fold
     on the padding alone, so the cells give that back too. --img is already
     padding 0, so this only touches the stat cells. */
  .bento__cell { padding: .75rem; }
  /* vh-driven rather than a constant, for the same reason .plate is: a fixed
     rem floor stops the grid shrinking exactly where the viewport needs it to.
     The figcaption inside is absolutely positioned, so this cell IS the height. */
  .bento__cell--img { min-height: clamp(3.6rem, 12vh, 8.5rem); }
  .bento__v { font-size: clamp(1.9rem, 3.4vw, 2.8rem); }
  /* chapter 02's plate is a 4:5 portrait, so its height is its width times
     1.25 plus the caption: narrowing it is the only lever that shortens it.
     Measured 68px past the fold at 1366x600 before this. Narrowing alone was
     not enough (68 -> 42), so the vertical rhythm above it gives back the rest
     -- the sticky offset, the space under the heading and the gap between the
     two paragraphs. Type SIZE is deliberately untouched: 0.95rem body at a
     squat viewport is still 0.95rem body, and shrinking it to win 18px would
     trade a measured overflow for an unmeasured legibility problem. */
  .plate { max-width: clamp(8rem, 25vh, 11rem); margin-top: clamp(.4rem, 1.2vh, 1.4rem); }
  .body { margin-top: .75rem; gap: .7rem; }
  .h { font-size: clamp(1.35rem, 2.4vw, 2rem); }
  .mark { margin-bottom: .6rem; }
  .act--arrival > .wrap,
  .act--speed > .wrap,
  .act--circuit > .wrap { top: calc(var(--nav-h) + clamp(.7rem, 2.5vh, 3.5rem)); }
}

/* Landscape phone. The frame is around 390px tall and the timing tower wants
   154px of it, 39% of the screen, so it goes for the same reason it goes below
   767px wide: it is furniture sized for a tall viewport. --rail-w has to go
   with it, or right-aligned copy keeps a reservation for a tower that is not
   there. Placed after the min-width rules above so it wins on source order. */
@media (max-height: 480px) and (orientation: landscape) {
  :root { --nav-h: 56px; --rail-w: 0px; }
  .rail { display: none; }
}

@media (prefers-reduced-transparency: reduce) {
  .bento__cell, .nav.is-stuck, .tribute figcaption, .rail {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: rgba(9, 11, 16, .93);
  }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; scroll-behavior: auto !important; }
  .stage__grain { display: none; }
  /* real fallbacks, not disabled animations: everything still arrives */
  .foot__mark > span:not(.foot__since) i { transform: none; }
  .h--split .w i { transform: none; }
  .rail a.is-current em::before { animation: none; }
  .rail a.is-cut::after { animation: none; }
  .strip__frame::before, .strip__frame::after { opacity: 1; transform: translate(0, 0); transition-delay: 0s; }
}
