/* lint-allow-raw: the design tokens, site/styles/tokens.css on one line */
@font-face { font-family: "Overpass"; src: url("../fonts/overpass-var.woff2") format("woff2"); font-weight: 100 900; font-style: normal; font-display: swap; } @font-face { font-family: "Overpass Mono"; src: url("../fonts/overpass-mono-var.woff2") format("woff2"); font-weight: 300 700; font-style: normal; font-display: swap; } @font-face { font-family: "Public Sans"; src: url("../fonts/public-sans-var.woff2") format("woff2"); font-weight: 100 900; font-style: normal; font-display: swap; } @font-face { font-family: "Public Sans Fallback"; src: local("Arial"); size-adjust: 102.1%; ascent-override: 93%; descent-override: 22%; line-gap-override: 0%; } @font-face { font-family: "Overpass Fallback"; src: local("Arial"); size-adjust: 100.6%; ascent-override: 87.8%; descent-override: 38.1%; line-gap-override: 0%; } @font-face { font-family: "Overpass Mono Fallback"; src: local("Consolas"); size-adjust: 112%; ascent-override: 78.8%; descent-override: 34.2%; line-gap-override: 0%; } :root { --asphalt-950: #0e1113; --asphalt-900: #121518; --asphalt-800: #1b1f23; --asphalt-700: #262b30; --asphalt-600: #353b41; --asphalt-500: #464d54; --kerb-500: #5f656c; --kerb-400: #6e747b; --kerb-300: #a9aeb3; --kerb-350: #868c92; --lane-200: #d3d6d2; --road-100: #e6e8e4; --road-50: #f2f3f1; --white: #ffffff; --signal-amber: #f2a900; --signal-amber-soft: rgb(242 169 0 / 0.16); --surface: var(--road-50); --surface-raised: var(--white); --text: var(--asphalt-800); --text-muted: var(--kerb-500); --text-soft: #3f454b; --rule: var(--lane-200); --control-edge: var(--asphalt-800); --hairline-mid: var(--kerb-350); --error: #b42318; --figure-edge: #737980; --figure-mid: var(--hairline-mid); --figure-lo: var(--lane-200); --figure-edge-road: var(--kerb-400); --figure-mid-road: var(--asphalt-500); --figure-lo-road: var(--asphalt-700); --ink: var(--scene-ground); --road-surface: var(--ink); --road-surface-deep: var(--ink); --header-ground: var(--road-surface); --header-pinned: var(--asphalt-800); --road-raised: var(--asphalt-700); --road-text: var(--road-50); --road-text-muted: var(--kerb-300); --road-rule: var(--asphalt-600); --road-dash: var(--road-50); --track-idle: var(--kerb-400); --seam: transparent; --accent: var(--signal-amber); --accent-soft: var(--signal-amber-soft); --focus-outer: var(--white); --focus-inner: var(--asphalt-900); --selection-bg: var(--asphalt-800); --selection-text: var(--road-50); --selection-bg-dark: var(--road-50); --selection-text-dark: var(--asphalt-900); --font-display: "Overpass", "Overpass Fallback", "Segoe UI", system-ui, sans-serif; --font-body: "Public Sans", "Public Sans Fallback", "Segoe UI", system-ui, sans-serif; --font-mono: "Overpass Mono", "Overpass Mono Fallback", ui-monospace, Consolas, monospace; --step--1: clamp(0.875rem, 0.85rem + 0.1vw, 0.9375rem); --step-0: clamp(1rem, 0.97rem + 0.15vw, 1.0625rem); --step-1: clamp(1.1875rem, 1.13rem + 0.3vw, 1.375rem); --step-2: clamp(1.4375rem, 1.3rem + 0.65vw, 1.875rem); --step-3: clamp(1.75rem, 1.5rem + 1.25vw, 2.625rem); --step-4: clamp(2.125rem, 1.7rem + 2.2vw, 3.75rem); --step-5: clamp(2.5rem, 1.8rem + 3.6vw, 5rem); --step-hero: clamp(2.4rem, 1.6rem + 2.6vw, 3.9rem); --step-numeral: clamp(4.5rem, 3rem + 7vw, 9.5rem); --step-mega: clamp(9rem, 5rem + 18vw, 22rem); --step-badge: clamp(3rem, 2rem + 3.2vw, 5.5rem); --scene-ground: #0b0d0f; --scene-scrim: rgb(11 13 15 / 0.86); --scene-scrim-clear: rgb(11 13 15 / 0); --scene-label-bg: rgb(11 13 15 / 0.82); --scene-label-edge: rgb(242 243 241 / 0.22); --scene-label-type: 0.75rem; --scene-label-type-owner: 0.8125rem; --scene-label-pad: 0.3125rem 0.5rem; --scene-label-offset: 0.625rem; --scene-label-dy: 0px; --scene-bleed-left: 0px; --scene-bleed-width: 100%; --scene-bleed-top: 0px; --scene-label-dot: 5px; --scene-label-gap: 6px; --scene-frame-ratio: 16 / 9; --scene-frame-width: 100cqw; --scene-frame-fade: linear-gradient(to bottom, transparent, black 9%, black 91%, transparent); --scene-stage-fade: linear-gradient(to bottom, transparent, black 22%, black 80%, transparent); --scene-frame-phone: 200cqw; --scene-frame-phone-height: 112.5cqw; --scene-frame-focus: 0.74; --scene-frame-focus-y: 0.49; --hero-strip-height: clamp(9.5rem, 40vw, 22rem); --hero-strip-room: 10rem; --hero-strip-overlap: calc(-1 * var(--space-2xs)); --scene-phone-crop: 4 / 3; --scene-phone-crop-height: 75cqw; --scene-phone-focus-y: 0.47; --scene-text-column: 26rem; --scene-stage-gap: 55vh; --scene-tail: calc(var(--space-3xl) + 20vh); --scene-stage-narrow: max(10rem, calc(68svh - var(--header-height))); --scene-stage-narrow-foot: var(--space-l); --scene-stage-gap-narrow: 40svh; --scene-tail-narrow: calc(var(--space-2xl) + 30svh); --hero-scene-height: calc(100svh - var(--header-height)); --hero-copy-max: min(36rem, 44vw); --door-still: 15rem; --door-still-narrow: 11rem; --door-still-ratio-phone: 13 / 5; --scene-frame-fade-x: linear-gradient(to right, transparent, black 9%, black 91%, transparent); --frame-fade: max(14%, 2.25rem); --frame-fade-y: linear-gradient(to bottom, transparent, black var(--frame-fade), black calc(100% - var(--frame-fade)), transparent); --frame-fade-x: linear-gradient(to right, transparent, black var(--frame-fade), black calc(100% - var(--frame-fade)), transparent); --hero-doors-ground: linear-gradient(to bottom, var(--scene-scrim-clear), var(--scene-scrim) 34%, var(--scene-ground) 52%); --scene-still-width: 80%; --scene-still-scrim: linear-gradient(90deg, var(--scene-ground) 0%, var(--scene-scrim) 22%, var(--scene-scrim-clear) 46%); --scene-tag-dim: 0.4; --lane-opacity: 0.55; --scene-floor-line: 0.4388; --scene-floor-clear: 3rem; --scene-top-line: 0.18; --scene-hero-sky-clear: 4rem; --scene-hero-sky-fade: linear-gradient(to bottom, transparent, black var(--scene-hero-sky-clear)); --scene-frame-fit: calc((100cqh - var(--scene-floor-clear) - var(--scene-hero-sky-clear)) / (var(--scene-floor-line) - var(--scene-top-line))); --scene-floor-fade: linear-gradient(to bottom, black calc(100% - var(--scene-floor-clear)), transparent); --scene-sky-clear: 10rem; --scene-sky-fade: linear-gradient(to bottom, transparent, black var(--scene-sky-clear)); --scene-foot-clear: 8rem; --scene-foot-fade: linear-gradient(to bottom, black calc(100% - var(--scene-foot-clear)), transparent); --scene-leader: var(--lane-200); --scene-leader-opacity: 0.6; --scene-leader-length: 2.5rem; --scene-chip-pad: 0.4375rem 0.5rem; --scene-leader-tick: 4px; --frame-ratio: 4 / 3; --frames-stage-gap: 10vh; --frames-tail: 5vh; --station-frame-beside: 55%; --scene-leader-beside: 1rem; --station-drop-none: 0rem; --station-drop: var(--station-drop-none); --station-count-mid: calc(var(--step--1) * var(--leading-body) / 2); --station-picture: calc(100cqw - var(--node-size-large) - var(--space-l)); --station-drop-frame: calc(var(--station-picture) / (var(--frame-ratio)) + var(--space-m) + var(--station-count-mid) - var(--node-size-large) / 2); --station-drop-figure: calc(min(var(--station-picture), var(--figure-chapter-max)) / (var(--figure-ratio)) + var(--space-m) + var(--station-count-mid) - var(--node-size-large) / 2); --dur-numeral: 240ms; --numeral-swap-rise: 0.25em; --measure-title: 30ch; --items-gap: var(--space-m); --items-tick: var(--space-s); --page-hero-peek: 7.5rem; --page-hero-height: calc(var(--hero-scene-height) - var(--page-hero-peek)); --page-hero-still-ratio: 16 / 10; --page-hero-still-phone: 118vw; --page-hero-lede: 30rem; --page-hero-lede-narrow: 22rem; --page-hero-still-inset: 62%; --page-hero-leader: 2.25rem; --page-hero-scrim: linear-gradient(90deg, var(--ink) 0%, var(--scene-scrim) 30%, var(--scene-scrim-clear) 54%); --checks-hang: calc(var(--space-l) + var(--space-xs)); --ledger-row: 5.25rem; --ledger-total: var(--step-5); --sticky-top: calc(var(--header-height) + var(--space-l)); --weight-regular: 400; --weight-medium: 500; --weight-semibold: 600; --weight-bold: 700; --weight-black: 800; --weight-display: 700; --weight-display-s: 600; --weight-figure: 500; --weight-nav: 500; --leading-tight: 1.04; --leading-snug: 1.2; --leading-body: 1.6; --leading-paper: 1.5; --leading-lede: 1.45; --tracking-display: -0.02em; --tracking-display-xl: -0.03em; --tracking-display-l: -0.025em; --tracking-display-m: -0.015em; --tracking-label: 0.06em; --tracking-label-hero: 0.12em; --measure: 62ch; --measure-lede: 36rem; --optical-stem: -0.065em; --arrow-size: 0.875em; --space-3xs: 0.25rem; --space-2xs: 0.5rem; --space-xs: 0.75rem; --space-s: 1rem; --space-m: 1.5rem; --space-l: 2rem; --space-xl: 3rem; --space-2xl: clamp(3.5rem, 2.5rem + 4vw, 6rem); --space-3xl: clamp(3.5rem, 2.25rem + 3.5vw, 5.5rem); --gutter: clamp(1rem, 0.5rem + 2.5vw, 2.5rem); --page-max: 78rem; --content-edge-room: calc(max(var(--gutter), (100vw - var(--page-max)) / 2) - var(--gutter) / 2); --header-height: 4.25rem; --details-label-width: 14rem; --hero-figure-max: 34rem; --split: minmax(0, 5fr) minmax(0, 7fr); --col-gap: var(--space-2xl); --hairline: 1px; --hairline-stroke-l: 1.25; --figure-stroke: 1; --figure-stroke-wide: var(--hairline-stroke-l); --figure-ratio: 5 / 4; --figure-dash: 1 3; --figure-chapter-max: 22rem; --figure-chapter-max-phone: 20rem; --figure-beside-max: 28rem; --eyebrow-block: calc(var(--step--1) * var(--leading-body) + var(--space-s)); --line: 2px; --track-width: 4px; --dash-length: 2.25rem; --dash-gap: 1.5rem; --dash-lead: 20%; --dash-tail: 80%; --node-size: 1.25rem; --node-size-large: 2.75rem; --logo-height: clamp(1.25rem, 0.9rem + 1.2vw, 1.75rem); --logo-aspect: 21988 / 2100; --radius-s: 2px; --radius-round: 999px; --focus-width: 3px; --focus-offset: 2px; --menu-icon-width: 1.375rem; --tap-min: 2.75rem; --text-link-reach: calc((var(--tap-min) - 1lh) / 2); --scroll-foot: calc(2 * var(--focus-width) + var(--focus-offset) + var(--space-s)); --menu-end: var(--kerb-500); --tap-row: 3rem; --footer-heading-height: 2lh; --footer-heading-height-narrow: 3lh; --nav-current-bar: 3px; --mark-dash-w: 12px; --mark-dash-h: 3px; --duration-quick: 160ms; --duration-base: 320ms; --duration-draw: 1400ms; --duration-slow: 700ms; --duration-pulse: 2400ms; --duration-scene: 900ms; --dur-press: 80ms; --dur-exit: 200ms; --dur-header: 280ms; --dur-handover: 400ms; --dur-reveal: 640ms; --dur-rule: 720ms; --dur-media: 840ms; --dur-page-out: 140ms; --dur-page-in: 260ms; --reveal-step: 80ms; --reveal-rise: 20px; --rule-lead: 120ms; --press-depth: 1px; --header-rail: 2px; --render-drift: 2%; --duration-crossfade: 300ms; --ease-out: cubic-bezier(0.22, 1, 0.36, 1); --ease-in: cubic-bezier(0.55, 0, 1, 0.45); --ease-reveal: cubic-bezier(0.33, 1, 0.68, 1); --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); } @media (prefers-color-scheme: dark) { :root { --surface: var(--asphalt-900); --surface-raised: var(--asphalt-800); --text: var(--road-50); --text-muted: var(--kerb-300); --rule: var(--asphalt-500); --control-edge: var(--road-50); --road-surface: var(--asphalt-700); --road-surface-deep: var(--asphalt-700); --road-raised: var(--asphalt-600); --road-rule: var(--asphalt-500); --seam: var(--asphalt-500); } } @media (prefers-color-scheme: dark) { :root { --text-soft: var(--kerb-300); --error: #ff9b8f; --header-pinned: var(--road-surface); --selection-bg: var(--selection-bg-dark); --selection-text: var(--selection-text-dark); } } @media (prefers-color-scheme: dark) { :root { --figure-edge: var(--kerb-350); --figure-mid: var(--kerb-400); --figure-lo: var(--asphalt-700); --figure-edge-road: var(--kerb-300); --figure-mid-road: #737980; --figure-lo-road: var(--asphalt-600); --figure-stroke-wide: var(--figure-stroke); } } @media (prefers-contrast: more) { :root { --text-muted: var(--text); --text-soft: var(--text); --rule: var(--control-edge); --road-text-muted: var(--road-text); --hairline: 2px; --figure-mid: var(--figure-edge); --figure-mid-road: var(--figure-edge-road); } }

/* Generated by tools/build.mjs from src/. Do not edit. */
/* Techrise Africa. Every value comes from tokens.css. */

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

html {
  color-scheme: light dark;
  -webkit-text-size-adjust: 100%;
  scroll-padding-block: var(--header-height) var(--scroll-foot);
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: var(--leading-body);
  text-rendering: optimizeLegibility;
}

/* The dark grounds: native controls and scrollbars in their dark form, and greyscale smoothing for light type on
   dark, which subpixel smoothing would make heavy. Paper keeps the system's smoothing. */
:is(.road, .hero, .site-header, .site-footer) {
  color-scheme: dark;
  -webkit-font-smoothing: antialiased;
}

/* Selected text: ink on paper, paper on the dark grounds. Never amber. */
::selection {
  background: var(--selection-bg);
  color: var(--selection-text);
}

:is(.road, .hero, .site-header, .site-footer) ::selection {
  background: var(--selection-bg-dark);
  color: var(--selection-text-dark);
}

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

h1,
h2,
h3,
h4 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-display);
  text-wrap: balance;
}

p,
ul,
ol,
dl,
dd {
  margin: 0;
}

p {
  text-wrap: pretty;
}

a {
  color: inherit;
}

:focus-visible {
  outline: var(--focus-width) solid var(--focus-inner);
  outline-offset: var(--focus-offset);
  box-shadow: 0 0 0 calc(var(--focus-width) + var(--focus-offset) + var(--focus-width)) var(--focus-outer);
}

.visually-hidden {
  position: absolute;
  width: 1px; /* lint-allow-raw: the standard visually-hidden pattern needs a 1px box */
  height: 1px; /* lint-allow-raw: the standard visually-hidden pattern needs a 1px box */
  margin: -1px; /* lint-allow-raw: the standard visually-hidden pattern needs a 1px box */
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: var(--space-s);
  top: var(--space-s);
  z-index: 100;
  padding: var(--space-xs) var(--space-s);
  background: var(--road-50);
  color: var(--asphalt-900);
  font-weight: var(--weight-semibold);
  transform: translateY(-200%);
}

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

.wrap {
  width: min(100% - 2 * var(--gutter), var(--page-max));
  margin-inline: auto;
}

/* ---------- Road chapters (dark ground in both schemes) ---------- */
.road {
  --text: var(--road-text);
  --text-muted: var(--road-text-muted);
  --text-soft: var(--road-text-muted);
  --rule: var(--road-rule);
  --surface: var(--road-surface);
  --focus-inner: var(--road-50);
  --focus-outer: var(--asphalt-950);
  background: var(--road-surface);
  color: var(--road-text);
}

.road--deep {
  --surface: var(--road-surface-deep);
  background: var(--road-surface-deep);
}

/* Band seams: a rule along each edge where a road band meets another band, and along the top of the footer. A border,
   so the scene and the stills inside a band never cover it; --seam is transparent in the light scheme, where the band's
   own ground shows through (audit problem 15). */
main > .road:not(:first-child),
.site-footer {
  border-top: var(--hairline) solid var(--seam);
}

main > .road:not(:last-child) {
  border-bottom: var(--hairline) solid var(--seam);
}

/* The lane marking sits on the content grid: whole dash cycles only, the first dash on the left content edge and the
   last ending on the right one. Each cycle is half a gap, a dash and half a gap; the element reaches half a gap past
   each edge, and "round" fits a whole number of cycles by scaling them a fraction, so the dashes keep the grid at any
   width. Outside a .wrap, hold it to the grid with inset-inline: max(var(--gutter), (100% - var(--page-max)) / 2). */
.lane-marking {
  height: var(--line);
  margin-inline: calc(var(--dash-gap) / -2);
  background-image: linear-gradient(90deg, transparent 0 var(--dash-lead), var(--road-dash) var(--dash-lead) var(--dash-tail), transparent var(--dash-tail));
  background-size: calc(var(--dash-length) + var(--dash-gap)) 100%;
  background-repeat: round no-repeat;
  opacity: 0.55;
}

@media print {
  .skip-link,
  .lane-marking {
    display: none;
  }
}

/* ---------- Type helpers ---------- */
.eyebrow {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* The dash sits on the first line when an eyebrow wraps. */
.eyebrow::before {
  content: "";
  width: var(--dash-length);
  height: var(--line);
  margin-top: calc(0.5lh - var(--line) / 2);
  background: currentColor;
}

/* The approved hero frame keeps its eyebrow as it was: the wider label tracking, and its own wrap (decision 0010). */
.hero .eyebrow {
  align-items: center;
  letter-spacing: var(--tracking-label-hero);
  text-wrap: pretty;
}

.hero .eyebrow::before {
  margin-top: 0;
}

/* From 900px the hero's eyebrow is one line: it may run past the copy's measure, over the scene's dark side. */
@media (min-width: 900px) {
  .hero .eyebrow {
    white-space: nowrap;
  }

  .hero .eyebrow::before {
    flex: none;
  }
}

/* An eyebrow directly above a heading, outside the grid heads that already space it. */
:not(.chapter__head) > .eyebrow + * {
  margin-top: var(--space-s);
}

.lede {
  max-width: var(--measure);
  font-size: var(--step-1);
  line-height: var(--leading-body);
  color: var(--text-muted);
}

.prose {
  max-width: var(--measure);
}

/* Running text on paper is ink, set a little tighter, with one lede measure (decision 0010). Eyebrows, labels, notes
   and captions keep the muted grey; the road bands keep their own light text and leading. */
.chapter:not(.road) {
  --leading-body: var(--leading-paper);
  line-height: var(--leading-body);
}

.chapter:not(.road) :is(.lede, .chapter__intro) {
  max-width: var(--measure-lede);
  line-height: var(--leading-lede);
}

.chapter:not(.road) :is(.lede, .chapter__intro, .statement-grid__side p, .door__text, .ledger__row, .checks__item, .station__text, .prose p, .split__text, .badge__text, .contact__entity) {
  color: var(--text-soft);
}

/* Prose links: a quiet rule at rest; on hover a full line draws in from the left and leaves to the right. */
.text-link {
  font-weight: var(--weight-semibold);
  text-decoration: none;
  background-image: linear-gradient(currentColor, currentColor), linear-gradient(var(--rule), var(--rule));
  background-size: 0 var(--line), 100% var(--hairline);
  background-position: 100% 100%, 0 100%;
  background-repeat: no-repeat;
  padding-bottom: var(--space-3xs);
  transition: background-size var(--dur-exit) var(--ease-in);
}

/* A text link that stands on its own (beside the mandate button, under a statement) is a full finger's height: the
   box reaches above and below the line without moving it, and the rule stays under the text. */
:is(.cta, .statement-grid__side) .text-link {
  display: inline-block;
  padding-block: var(--text-link-reach);
  margin-block: calc(-1 * var(--text-link-reach));
  background-origin: content-box;
  background-position: 100% calc(100% + var(--space-3xs)), 0 calc(100% + var(--space-3xs));
}

@media (hover: hover) and (pointer: fine) {
  .text-link:hover {
    background-size: 100% var(--line), 100% var(--hairline);
    background-position: 0 100%, 0 100%;
    transition-duration: var(--duration-base);
    transition-timing-function: var(--ease-out);
  }

  :is(.cta, .statement-grid__side) .text-link:hover {
    background-position: 0 calc(100% + var(--space-3xs)), 0 calc(100% + var(--space-3xs));
  }
}

.placeholder {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--text-muted);
}

/* ---------- Glyph finish ---------- */
/* A display line that opens on a straight stem moves back by the letter's side bearing, so it meets the text edge. */
[data-optical="stem"] {
  margin-inline-start: var(--optical-stem);
}

.facts__note,
.door__text,
.split__text,
.station__text,
.ledger__row,
.details dd,
li {
  text-wrap: pretty;
}

.facts__note,
.door__title,
.station__title,
.split__title,
.eyebrow {
  text-wrap: balance;
}

.nobr {
  white-space: nowrap;
}

/* ---------- Buttons ---------- */
.button {
  --button-bg: var(--text);
  --button-text: var(--surface);
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  min-height: var(--space-xl);
  padding: var(--space-xs) var(--space-m);
  border: var(--line) solid var(--button-bg);
  border-radius: var(--radius-s);
  background: var(--button-bg);
  color: var(--button-text);
  font-family: var(--font-display);
  font-size: var(--step-0);
  font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
  text-decoration: none;
  text-box: trim-both cap alphabetic; /* the label sits on the cap height's centre, not the font's line box */
  transition: background-color var(--duration-quick) var(--ease-out), color var(--duration-quick) var(--ease-out), transform var(--dur-press) var(--ease-out);
}

/* A press goes down a pixel at once; no scale, no shadow. */
.button:active {
  transform: translateY(var(--press-depth));
  transition-duration: var(--dur-press);
}

.button--ghost {
  background: transparent;
  color: var(--button-bg);
}

/* The arrow: an SVG in the brand stroke, drawn at build time (tools/build.mjs), since no brand face has U+2192. */
.arrow {
  flex: none;
  width: var(--arrow-size);
  height: var(--arrow-size);
}

.button__arrow,
.button .arrow {
  transition: transform var(--duration-base) var(--ease-out);
}

/* Hover only where there is hover, so a tapped button on a phone returns to its rest colours. */
@media (hover: hover) and (pointer: fine) {
  .button:hover {
    background: transparent;
    color: var(--button-bg);
  }

  .button--ghost:hover {
    background: var(--button-bg);
    color: var(--button-text);
  }

  .button:hover :is(.button__arrow, .arrow) {
    transform: translateX(var(--space-3xs));
  }
}

/* On paper a button is ink on white with a hairline edge: a printer drops its fill, so its own colours would leave
   white text on nothing. */
@media print {
  .button {
    border: var(--hairline) solid var(--asphalt-900);
    background: var(--white);
    color: var(--asphalt-900);
  }
}

/* The mandate button, with an optional quiet link beside it (the cta include in tools/build.mjs). */
.cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-m) var(--space-l);
}

/* ---------- Header ----------
   At rest it stands on the scene's own ground, so it does not sit as a lighter bar over the hero; once the page moves
   it pins on asphalt with a rule, and on a longer read it folds away to a 2px rail that carries the reading progress
   (decision 0010). Solid colours only: no blur, no transparency. The state is set by src/scripts/main/menu.js. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  --header-bg: var(--header-ground);
  border-bottom: var(--hairline) solid transparent;
  background: var(--header-bg);
  transition: transform var(--dur-header) var(--ease-reveal), background-color var(--dur-header) var(--ease-out), border-color var(--dur-header) var(--ease-out);
  view-transition-name: site-header; /* the header holds still while the page changes under it */
}

.site-header[data-state="pinned"],
.site-header[data-state="folded"] {
  --header-bg: var(--header-pinned);
  border-bottom-color: var(--road-rule);
}

.site-header[data-state="folded"] {
  transform: translateY(calc(-100% + var(--header-rail)));
}

/* Reading progress, along the header's bottom edge, so the folded rail carries it. */
.site-header__progress {
  position: absolute;
  inset: auto 0 0 0;
  height: var(--header-rail);
  background: var(--road-text);
  transform: scaleX(var(--progress, 0));
  transform-origin: 0 50%;
  pointer-events: none;
}

@supports (animation-timeline: scroll()) {
  .site-header__progress {
    transform: scaleX(0);
    animation: header-progress linear both;
    animation-timeline: scroll(root block);
  }
}

@keyframes header-progress {
  to {
    transform: scaleX(1);
  }
}

.site-header__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-m);
  min-height: var(--header-height);
}

/* The link is a full finger's height; the drawing keeps its own size, centred in it. */
.wordmark {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  color: var(--road-text);
  text-decoration: none;
}

.wordmark__logo {
  flex: none;
  width: auto;
  height: var(--logo-height);
  aspect-ratio: var(--logo-aspect);
}

.wordmark__letters {
  fill: currentColor;
}

.wordmark__africa {
  fill: var(--accent); /* signature: Africa lit in the logo */
}

.site-nav__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs) var(--space-m);
  padding: 0;
  list-style: none;
}

/* Nav links: the pass-through underline of the prose links (10-type.css); the current page's line is drawn in full
   by its own mark, which slides to the next page's link on a page change. */
.site-nav__link {
  position: relative;
  display: inline-block;
  padding-block: var(--space-2xs);
  font-family: var(--font-display);
  font-weight: var(--weight-nav);
  text-decoration: none;
  color: var(--text-muted);
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0 var(--line);
  background-position: 100% 100%;
  background-repeat: no-repeat;
  transition: color var(--duration-quick) var(--ease-out), background-size var(--dur-exit) var(--ease-in);
}

.site-nav__link[aria-current="page"] {
  color: var(--text);
}

.site-nav__mark {
  position: absolute;
  inset: auto 0 0 0;
  height: var(--line);
  background: currentColor;
  view-transition-name: nav-mark;
}

@media (hover: hover) and (pointer: fine) {
  .site-nav__link:not([aria-current="page"]):hover {
    color: var(--text);
    background-size: 100% var(--line);
    background-position: 0 100%;
    transition-duration: var(--duration-quick), var(--duration-base);
    transition-timing-function: var(--ease-out);
  }
}

/* The header's Contact: a ghost button on the dark ground. The page's own "Discuss a mandate" stays solid. */
.site-nav__cta,
.site-header__contact {
  --button-bg: var(--road-text);
  --button-text: var(--header-ground);
  border: var(--line) solid var(--road-text);
  background: transparent;
  color: var(--road-text);
}

.site-nav__cta {
  min-height: auto;
  padding: var(--space-2xs) var(--space-s);
}

.site-nav__cta,
.site-header__contact,
.menu-button {
  text-box: trim-both cap alphabetic;
}

.site-nav__cta:active,
.site-header__contact:active,
.menu-button:active {
  transform: translateY(var(--press-depth));
}

@media (hover: hover) and (pointer: fine) {
  .site-nav__cta:hover,
  .site-header__contact:hover {
    background: var(--road-text);
    color: var(--header-ground);
  }
}

.site-header__contact {
  display: none;
}

.menu-button {
  display: none;
}

@media (max-width: 1079px) {
  .menu-button {
    display: none;
  }

  /* Contact stays one tap away on phones: in the bar, between the wordmark and the menu button, not in the menu. */
  .site-header__contact {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap-min);
    padding-inline: var(--space-s);
    border-radius: var(--radius-s);
    font-family: var(--font-display);
    font-weight: var(--weight-bold);
    text-decoration: none;
    transition: background-color var(--duration-quick) var(--ease-out), color var(--duration-quick) var(--ease-out);
  }

  .site-nav__cta-item {
    display: none;
  }

  .js .menu-button {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    min-height: var(--tap-min);
    padding: var(--space-2xs) var(--space-s);
    border: var(--line) solid var(--road-text);
    border-radius: var(--radius-s);
    background: transparent;
    color: var(--road-text);
    font-family: var(--font-display);
    font-size: var(--step-0);
    font-weight: var(--weight-bold);
    cursor: pointer;
  }

  .menu-button__icon {
    display: grid;
    gap: var(--space-3xs);
    width: var(--menu-icon-width);
  }

  .menu-button__icon span {
    height: var(--line);
    background: currentColor;
    transition: transform var(--duration-quick) var(--ease-out), opacity var(--duration-quick) var(--ease-out);
  }

  .menu-button[aria-expanded="true"] .menu-button__icon span:nth-child(1) {
    transform: translateY(calc(var(--space-3xs) + var(--line))) rotate(45deg);
  }

  .menu-button[aria-expanded="true"] .menu-button__icon span:nth-child(2) {
    opacity: 0;
  }

  .menu-button[aria-expanded="true"] .menu-button__icon span:nth-child(3) {
    transform: translateY(calc(-1 * (var(--space-3xs) + var(--line)))) rotate(-45deg);
  }

  /* Without the script the navigation is a plain list that wraps under the bar. */
  .site-header__bar {
    flex-wrap: wrap;
    gap: var(--space-xs);
  }

  .js .site-header__bar {
    flex-wrap: nowrap;
  }

  /* The wordmark gives way first when the bar is tight: its drawing scales down inside its box. */
  .wordmark {
    flex: 0 1 auto;
    min-width: 0;
    margin-inline-end: auto;
  }

  .wordmark__logo {
    max-width: 100%;
  }

  .site-header__contact,
  .menu-button {
    flex: none;
  }

  .site-nav {
    flex-basis: 100%;
    padding-bottom: var(--space-s);
  }

  /* The open menu: a panel that unrolls from under the bar, on the header's own ground, and scrolls on its own when
     the screen is short (audit problem 13). It opens over the base duration and closes faster, with no stagger on
     the way out; a browser without @starting-style opens it at once. */
  .js .site-nav {
    position: absolute;
    inset: 100% 0 auto 0;
    display: none;
    padding: 0 max(var(--gutter), (100% - var(--page-max)) / 2) var(--space-l);
    border-bottom: var(--line) solid var(--menu-end); /* the panel ends on its own rule, not into the page */
    background: var(--header-bg);
    max-height: calc(100svh - var(--header-height));
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-color: var(--kerb-500) transparent;
    clip-path: inset(0 0 100% 0);
    transition: clip-path var(--dur-exit) var(--ease-in), display var(--dur-exit) allow-discrete;
  }

  .js .site-nav[data-open] {
    display: block;
    clip-path: inset(0);
    transition: clip-path var(--duration-base) var(--ease-out), display var(--duration-base) allow-discrete;
  }

  .js .site-nav li {
    transition: opacity var(--dur-exit) var(--ease-in);
  }

  .js .site-nav[data-open] li {
    opacity: 1;
    transition: opacity var(--duration-base) var(--ease-out) calc(var(--i, 0) * var(--reveal-step));
  }

  @starting-style {
    .js .site-nav[data-open] {
      clip-path: inset(0 0 100% 0);
    }

    .js .site-nav[data-open] li {
      opacity: 0;
    }
  }

  .site-nav__list {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }

  .site-nav__list li {
    border-top: var(--hairline) solid var(--rule);
  }

  /* The last row is closed by a rule too (the Contact row, when it is in the menu, stands first). */
  .js .site-nav__list li:nth-last-child(1 of :not(.site-nav__cta-item)) {
    border-bottom: var(--hairline) solid var(--rule);
  }

  .site-nav__link {
    display: block;
    padding-block: var(--space-s);
    padding-inline-start: var(--space-s);
    font-size: var(--step-1);
    background-image: none;
  }

  /* The current page: an inset bar at the start of its row. */
  .site-nav__mark {
    inset: var(--space-s) auto var(--space-s) 0;
    width: var(--nav-current-bar);
    height: auto;
  }
}

/* Phones: the menu button keeps its name for screen readers but shows only its icon, so the bar stays one line. */
@media (max-width: 30rem) {
  .site-header__bar {
    gap: var(--space-2xs);
  }

  .site-header__contact {
    padding-inline: var(--space-xs);
  }

  .menu-button__label {
    position: absolute;
    width: var(--hairline);
    height: var(--hairline);
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .js .menu-button {
    justify-content: center;
    min-width: var(--tap-min);
    padding-inline: var(--space-xs);
  }
}

/* Below 360px the bar has no room for Contact beside the wordmark, so it goes back to the top of the menu. */
@media (max-width: 22.4375rem) {
  .site-header__contact {
    display: none;
  }

  .site-nav__cta-item {
    display: block;
    order: -1;
    padding-bottom: var(--space-s);
    border-top: 0;
  }

  .site-nav__cta {
    justify-content: center;
    width: 100%;
    min-height: var(--tap-row);
  }
}

/* Short viewports (a phone on its side, or a page zoomed to 400%): the header must not take a third of the screen,
   so it folds away for good once the page moves (menu.js), and anchor jumps keep only a small margin. */
@media (max-height: 32rem) {
  html {
    scroll-padding-top: var(--space-s);
  }
}

/* Under reduced motion the header never folds, and the progress line is not drawn. */
@media (prefers-reduced-motion: reduce) {
  .site-header__progress {
    display: none;
  }
}

@media print {
  .site-nav,
  .menu-button,
  .js .menu-button,
  .site-header__contact,
  .site-header__progress {
    display: none;
  }

  .site-header {
    position: static;
    transform: none;
  }
}

/* ---------- Hero ---------- */
.hero {
  position: relative;
  overflow: hidden;
  display: grid;
  align-items: center;
  min-height: var(--hero-scene-height);
  padding-block: var(--space-2xl) calc(var(--space-xl) + var(--space-2xl));
  background: var(--scene-ground);
}

.hero__inner {
  position: relative;
  z-index: 1;
}

.hero__copy {
  max-width: var(--hero-copy-max);
}

.hero__copy > * + * {
  margin-top: var(--space-m);
}

.hero__title {
  font-size: var(--step-hero);
  font-weight: var(--weight-black);
  line-height: var(--leading-tight);
}

/* The hero's lane marking does not run behind the service doors (premium plan item 14). */
.hero:has(.hero-doors)::after {
  display: none;
}

/* Below 900px the hero stacks as approved key frame 5 has it (audit problem 12): a strip of the cluster directly under
   the header, then the copy, then the doors (decision 0009). The stage comes last in the markup, so a column puts it
   first (45-scene.css). */
@media (max-width: 899px) {
  .hero {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    min-height: 0;
    padding-bottom: var(--space-xl);
  }

  .hero__copy {
    max-width: none;
  }
}

/* On a phone the copy is set closer, so both doors' names and links still end the first screen under the strip. */
@media (max-width: 600px) {
  .hero__copy > * + * {
    margin-top: var(--space-s);
  }
}

@media print {
  .hero {
    min-height: 0;
    padding-block: var(--space-l);
  }
}

/* ---------- Service doors under the home hero ----------
   Owned by the home workstream (debt focus doors, docs/decisions/0009; premium plan item 7). Two equal doors end the
   first screen, on the hero's own ground: no panel and no fill, one 1px rule across their top and one between them,
   and no edge round the stills. The "Both services" strip under them states the terms the two services share.
   The door's own grammar (title, underline, arrow, the still's lift) is in 80-statement-doors.css. */

/* The hero with doors: the copy centred in the room above, the doors on its foot. */
.hero:has(.hero-doors) {
  grid-template-rows: minmax(0, 1fr) auto;
  padding-block: var(--space-xl);
}

/* The copy stays above the ground that rises under the doors. */
.hero:has(.hero-doors) .hero__inner {
  z-index: 2;
}

/* The facts card follows the doors instead of covering the hero's foot. */
.hero:has(.hero-doors) + .facts {
  margin-top: 0;
  padding-top: var(--space-xl);
}

.hero-doors {
  position: relative;
  z-index: 1;
  margin-top: var(--space-l);
  border-top: var(--hairline) solid var(--road-rule);
}

/* The ground comes up under the doors across the full width, so whatever part of the scene reaches this low falls
   back into the floor, and the doors' text and stills always stand on the scene ground itself. */
.hero-doors::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: calc(-1 * var(--space-2xl));
  bottom: calc(-1 * var(--space-xl));
  left: calc(50% - 50vw);
  right: calc(50% - 50vw);
  background: var(--hero-doors-ground);
  pointer-events: none;
}

.hero-doors__row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.hero-doors .door {
  padding: var(--space-m) var(--space-l) var(--space-m) 0;
}

.hero-doors .door + .door {
  padding: var(--space-m) 0 var(--space-m) var(--space-l);
  border-left: var(--hairline) solid var(--road-rule);
}

.hero-doors .door__body {
  gap: var(--space-2xs);
}

.hero-doors .door__title {
  font-size: var(--step-1);
  line-height: var(--leading-snug);
}

.hero-doors .door__text {
  max-width: none;
  color: var(--road-text-muted);
}

.hero-doors .door__go {
  margin-top: var(--space-2xs);
}

/* Side by side (900px and wider), the two doors share their text rows: index, name, line and go line each start on
   one line in both doors, however many lines either name takes, and the go lines stay right under the taller text
   rather than drop to the foot of the still. The last row takes whatever height the still has beyond the text. */
@media screen and (min-width: 900px) {
  .hero-doors__row {
    grid-template-rows: repeat(4, auto) 1fr;
  }

  .hero-doors .door {
    grid-row: span 5;
    grid-template-rows: subgrid;
    row-gap: 0;
  }

  .hero-doors .door__still {
    grid-column: 1;
    grid-row: 1 / -1;
  }

  .hero-doors .door__body {
    display: contents;
  }

  .hero-doors .door__index,
  .hero-doors .door__title,
  .hero-doors .door__text,
  .hero-doors .door__go {
    grid-column: 2;
    justify-self: start;
  }

  .hero-doors .door__index {
    grid-row: 1;
  }

  .hero-doors .door__title {
    grid-row: 2;
    margin-top: var(--space-2xs);
  }

  .hero-doors .door__text {
    grid-row: 3;
    margin-top: var(--space-2xs);
  }

  .hero-doors .door__go {
    grid-row: 4;
    margin-top: var(--space-s);
  }
}

/* The stills meet the scene ground at their edges; a fade on all four keeps the floor grid from ending on a line. */
.hero-doors .render {
  -webkit-mask-image: var(--scene-frame-fade), var(--scene-frame-fade-x);
  mask-image: var(--scene-frame-fade), var(--scene-frame-fade-x);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

/* Both services: the facts card's grammar, one mono label and four cells parted by rules. */
.hero-doors__both {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  border-top: var(--hairline) solid var(--road-rule);
}

.hero-doors__label {
  padding-right: var(--space-m);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--road-text-muted);
}

.hero-doors__terms {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  padding: 0;
  list-style: none;
}

/* Equal cells, however many terms there are; a term that has to wrap wraps evenly. */
.hero-doors__terms li {
  display: flex;
  align-items: center;
  text-wrap: balance;
  padding: var(--space-s);
  border-left: var(--hairline) solid var(--road-rule);
  font-size: var(--step--1);
  line-height: var(--leading-snug);
  color: var(--road-text);
}

/* Narrower desktops: the still keeps its 240px and the title takes the room it needs. */
@media (max-width: 1200px) {
  .hero-doors__both {
    grid-template-columns: minmax(0, 1fr);
  }

  .hero-doors__label {
    padding-top: var(--space-s);
  }

  .hero-doors__terms li:first-child {
    padding-left: 0;
    border-left: 0;
  }
}

/* Tablets on their side and small laptops (900 to 1099px): a smaller still, so the text beside it is more than a few
   words wide and the go line stays whole. */
@media (min-width: 900px) and (max-width: 1099px) {
  .hero-doors .door--still {
    grid-template-columns: var(--door-still-narrow) minmax(0, 1fr);
  }

  .hero-doors .door__go {
    white-space: nowrap;
  }
}

/* Short laptop screens (1366 by 768, 1280 by 720): the facts line waits for the service page, so both doors' names
   and links still end the first screen. The hero keeps room for the whole strip and its foot below the fold, so the
   first screen ends on the doors' bottom rule and never on half of the strip. */
@media (min-width: 900px) and (max-height: 820px) {
  .hero:has(.hero-doors) {
    min-height: calc(var(--hero-scene-height) + var(--hero-strip-room));
    padding-top: var(--space-l);
  }

  .hero-doors .door,
  .hero-doors .door + .door {
    padding-top: var(--space-s);
  }

  .hero-doors .door__text {
    display: none;
  }
}

/* Below 900px the hero stacks under the strip of the cluster (45-scene.css): the doors follow the lede, side by side,
   each still above its text at the column's full width, so a phone's first screen ends on two pictures and two names.
   Nothing of the scene follows them. */
@media (max-width: 899px) {
  .hero:has(.hero-doors) {
    padding-block: 0 var(--space-l);
  }

  .hero-doors {
    margin-top: var(--space-l);
  }

  .hero-doors::before {
    content: none;
  }

  /* The two doors share their rows, so both stills, both labels and both names start on one line whatever the
     length of either name. */
  .hero-doors__row {
    grid-template-rows: auto auto;
  }

  .hero-doors .door {
    grid-row: span 2;
    grid-template-rows: subgrid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-s);
    padding: var(--space-m) var(--space-s) var(--space-m) 0;
  }

  .hero-doors .door + .door {
    padding: var(--space-m) 0 var(--space-m) var(--space-s);
    border-top: 0;
  }

}

/* On a phone the terms run down the column, one to a row, parted by rules, whatever their number. */
@media (max-width: 599px) {
  .hero-doors__terms {
    grid-auto-flow: row;
    grid-template-columns: minmax(0, 1fr);
  }

  .hero-doors__terms li {
    padding-inline: 0;
    border-left: 0;
  }

  .hero-doors__terms li + li {
    border-top: var(--hairline) solid var(--road-rule);
  }
}

/* On a phone the facts line waits for the service page; the name and the link fit the first screen. */
@media (max-width: 600px) {
  .hero-doors {
    margin-top: var(--space-s);
  }

  /* The still stands on the doors' top rule: its own faded edge is the space above it. */
  .hero-doors .door,
  .hero-doors .door + .door {
    padding-top: 0;
  }

  .hero-doors .door {
    gap: var(--space-2xs);
  }

  .hero-doors .door__go {
    margin-top: 0;
  }

  /* A shallow crop: the model and its arc fill the frame, and the link stays in the first screen. */
  .hero-doors .render {
    aspect-ratio: var(--door-still-ratio-phone);
    object-fit: cover;
  }

  .hero-doors .door__text {
    display: none;
  }

  /* The go line keeps its words and arrow on one line wherever the door is wide enough for it (380px and wider). */
  .hero-doors .door__go {
    white-space: nowrap;
  }

  .hero-doors__label {
    padding-top: var(--space-s);
  }
}

/* Narrower, both go lines step down a size, so "The three stages" keeps to one line beside "The six stages" on the
   common small phones (360 and 375); on narrower still it wraps as text rather than run out of the door, and the
   last word and the arrow stay together (.door__go-end, which takes no box of its own until then). */
.door__go-end {
  display: contents;
}

@media (max-width: 379px) {
  .hero-doors .door__go {
    display: inline-block;
    white-space: normal;
    font-size: var(--step--1);
  }

  .hero-doors .door__go-end {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    white-space: nowrap;
  }
}

/* Short phones (360 by 800, 375 by 667): the hero's foot and the step from the lede to the doors close up, so the
   doors' names still start in the first screen. */
@media (max-width: 899px) and (max-height: 820px) {
  .hero:has(.hero-doors) {
    padding-bottom: var(--space-m);
  }

  .hero-doors {
    margin-top: var(--space-2xs);
  }
}

@media print {
  .hero-doors .render {
    display: none;
  }

  .hero-doors .door {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---------- The 3D scene (docs/decisions/0005) ----------
   The stage holds a 16:9 frame scaled like a cover image and pinned to the right, so the poster, its labels and
   the live render (src/scripts/hero3d.js reads the same frame) share one composition at every width. */
.hero__stage {
  position: absolute;
  inset: 0;
  overflow: hidden;
  container-type: size;
}

.hero__frame {
  position: absolute;
  top: 50%;
  right: 0;
  width: var(--scene-frame-width);
  aspect-ratio: var(--scene-frame-ratio);
  transform: translateY(-50%);
}

/* Above the service doors the scene has the room over them to itself (decision 0009): its stage is the hero's
   first row, up to the hero's top edge, and the frame stands on the stage's floor, so the lowest tile and pin sit
   --scene-floor-clear above the doors' top rule at every window size. The frame narrows, pinned right, when the room
   is short, so the cluster never rises into the header or the canvas's sky fade under it (on a short laptop window
   the back row used to sit in that fade and grey out). The live camera composes on this same frame, so poster and
   scene move together. */
@media (min-width: 900px) {
  .hero:has(.hero-doors) .hero__stage {
    grid-row: 1 / 2; /* both lines, or an absolute box reaches the padding edge */
    top: calc(-1 * var(--space-xl));
    -webkit-mask-image: var(--scene-floor-fade), var(--scene-hero-sky-fade);
    mask-image: var(--scene-floor-fade), var(--scene-hero-sky-fade);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
  }

  .hero:has(.hero-doors) .hero__frame {
    --scene-frame-w: min(100cqw, var(--scene-frame-fit));
    top: calc(100cqh - var(--scene-floor-clear) - var(--scene-floor-line) * var(--scene-frame-w));
    width: var(--scene-frame-w);
    transform: none;
  }
}

.hero__frame > picture {
  display: contents;
}

.hero__poster {
  width: 100%;
  height: 100%;
  -webkit-mask-image: var(--scene-frame-fade);
  mask-image: var(--scene-frame-fade);
  transition: opacity var(--dur-handover) var(--ease-out);
}

/* The copy side of the frame is darkened so the headline always reads. */
.hero__stage::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, var(--scene-ground) 0%, var(--scene-scrim) 30%, var(--scene-scrim-clear) 54%);
  pointer-events: none;
}

.hero__labels {
  position: absolute;
  inset: 0;
  z-index: 1;
  margin: 0;
  padding: 0;
  list-style: none;
}

.hero__label {
  position: absolute;
  left: var(--x);
  top: var(--y);
  transform: translate(var(--scene-label-offset), calc(-50% + var(--scene-label-dy)));
  padding: var(--scene-label-pad);
  border: var(--hairline) solid var(--scene-label-edge);
  background: var(--scene-label-bg);
  color: var(--road-text);
  font-family: var(--font-mono);
  font-size: var(--scene-label-type);
  font-weight: var(--weight-medium);
  line-height: 1;
  letter-spacing: var(--tracking-label-hero); /* the approved key frames keep their label tracking */
  text-transform: uppercase;
  white-space: nowrap;
  transition: opacity var(--duration-slow) var(--ease-out);
}

.hero__label::before {
  content: "";
  position: absolute;
  left: calc(-1 * var(--scene-label-offset));
  top: calc(50% - var(--scene-label-dy)); /* a label moved clear of another keeps its dot on its point */
  width: var(--scene-label-dot);
  height: var(--scene-label-dot);
  border-radius: var(--radius-round);
  background: currentColor;
  transform: translateY(-50%);
}

/* On the poster below 1100px the frame is too narrow for Road sign, flipped to the left of its point, to clear Asset
   owner on its row, and there is no room above it under Stormwater: the poster leaves the lesser tag out (review
   VD-6). The live scene shows it again, set clear of the others by --scene-label-gap. */
@media (max-width: 1099px) {
  :root:not(.has-scene) .hero__label[data-anchor="sign"] {
    display: none;
  }
}

/* Near the right edge a label sits to the left of its point. */
.hero__label--flip {
  transform: translate(calc(-100% - var(--scene-label-offset)), calc(-50% + var(--scene-label-dy)));
}

.hero__label--flip::before {
  left: auto;
  right: calc(-1 * var(--scene-label-offset));
}

.hero__label--owner {
  font-size: var(--scene-label-type-owner);
  border-color: var(--accent); /* signature */
  color: var(--accent); /* signature */
}

/* While the scene runs it places every label with the translate property, in pixels from the frame's corner, so a
   moving label costs no layout (premium plan item 16). The poster's own positions (--x, --y) come back with it. */
.has-scene [data-hero3d] .hero__label,
.track__stage .hero__label {
  left: 0;
  top: 0;
}

.scene-canvas {
  display: none;
}

.track__stage {
  display: none;
}

/* Stage tags are placed by the scene, one stretch of the dive at a time. Each stands off its point on a 1px leader
   that ends in a square tick on the point (src/scripts/hero3d.js draws the leaders), and its text sits on the cap
   height in the middle of its chip (premium plan items 13 and 19). */
.track__stage .hero__label {
  --scene-label-offset: var(--scene-leader-length);
  padding: var(--scene-chip-pad);
  text-box: trim-both cap alphabetic;
}

.track__stage .hero__label::before {
  display: none;
}

.track__stage .hero__label:not(.is-on) {
  opacity: 0;
}

.scene-leaders {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  overflow: visible;
  fill: var(--scene-leader);
  stroke: var(--scene-leader);
  stroke-width: var(--hairline);
  opacity: var(--scene-leader-opacity);
  shape-rendering: crispEdges;
  pointer-events: none;
}

.has-scene .track__stage .hero__labels {
  z-index: 2;
}

/* One canvas, inside the section that shows it: the hero stage, then the track's sticky stage. It scrolls with the
   page, so the picture, the copy and the labels always move together (src/scripts/hero3d.js moves it between them).
   It runs at every width where motion is allowed (decision 0014): phones and narrow windows get the live hero and the
   live six stages too. */
.scene-canvas {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-handover) var(--ease-out);
}

.has-scene .scene-canvas {
  opacity: 1;
}

.has-scene [data-hero3d] .hero__poster {
  opacity: 0;
}

/* The home hero's own labels only: a page hero's labels and the frames' tags are not the scene's to light. */
.has-scene [data-hero3d] .hero__label:not(.is-on) {
  opacity: 0;
}

/* The six stages in scene mode, at every width: the band is the scene's own ground. */
.has-scene .track[data-scene] {
  position: relative;
  background: var(--scene-ground);
}

/* The canvas runs up over the track's top border, so the band seam is drawn again above it (00-base.css). */
.has-scene .track[data-scene]::before {
  content: "";
  position: absolute;
  top: calc(-1 * var(--hairline));
  left: 0;
  right: 0;
  z-index: 1;
  height: var(--hairline);
  background: var(--seam);
  pointer-events: none;
}

.has-scene .track[data-scene] .track__counter,
.has-scene .track[data-scene] .track__current-name,
.has-scene .track[data-scene] .track__mini {
  display: none;
}

.has-scene .track[data-scene] .track__rail {
  position: relative;
  z-index: 1;
}

.scene-lost .scene-canvas {
  display: none;
}

.scene-lost [data-hero3d] .hero__poster {
  opacity: 1;
}

.scene-lost .hero,
.scene-lost .track[data-scene] {
  background: var(--scene-ground);
}

.scene-lost .track__stage .hero__labels {
  display: none;
}

@media (min-width: 900px) {
  /* The track in scene mode: the stages on the left, a sticky stage on the right that the camera composes on. */
  .has-scene .track[data-scene] .track__grid {
    position: relative;
    grid-template-columns: minmax(0, var(--scene-text-column)) minmax(0, 1fr);
    gap: var(--space-xl);
  }

  .has-scene .track[data-scene] .track__panel {
    position: relative;
    top: auto;
    z-index: 1;
  }

  /* The tail keeps the stage pinned while the last view holds, after stage 6 is reached. */
  .has-scene .track[data-scene] .track__rail {
    grid-column: 1;
    padding-bottom: var(--scene-tail);
  }

  .has-scene .track[data-scene] .track__stations {
    gap: var(--scene-stage-gap);
  }

  .has-scene .track[data-scene] .track__stage {
    position: sticky;
    top: var(--header-height);
    z-index: 0;
    display: block;
    grid-column: 2;
    grid-row: 1 / span 2;
    height: calc(100vh - var(--header-height));
  }

  /* In the track the canvas and its scrim run the full page width under the stage list, and up to the top of the
     track (both offsets set by the scene). */
  .track__stage .scene-canvas,
  .has-scene .track[data-scene] .track__stage::after {
    top: calc(-1 * var(--scene-bleed-top));
    left: var(--scene-bleed-left);
    width: var(--scene-bleed-width);
    height: calc(100% + var(--scene-bleed-top));
  }

  /* The canvas's sky and its foot fade into the band, so neither the floor's far edge nor the stage's end shows as a
     line when the track runs out (review VD-2). */
  .track__stage .scene-canvas {
    -webkit-mask-image: var(--scene-sky-fade), var(--scene-foot-fade);
    mask-image: var(--scene-sky-fade), var(--scene-foot-fade);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
  }

  .has-scene .track[data-scene] .track__stage::after {
    content: "";
    position: absolute;
    background: linear-gradient(90deg, var(--scene-ground) 0%, var(--scene-scrim) 34%, var(--scene-scrim-clear) 52%);
    pointer-events: none;
  }
}

/* Below 900px the six stages stack: the title, then a stage pinned under the header that the camera composes on, then
   the stages, which pass under the stage's faded foot as the reader scrolls and are read just below it (the scene's
   reading line is at 60% of the screen, under the stage). The canvas runs the full width of the window. */
@media (max-width: 899px) {
  .has-scene .track[data-scene] .track__grid {
    row-gap: var(--space-l);
  }

  .has-scene .track[data-scene] .track__stage {
    position: sticky;
    top: var(--header-height);
    z-index: 2;
    display: block;
    grid-row: 2;
    height: var(--scene-stage-narrow);
    background: var(--scene-ground);
    transition: top var(--dur-header) var(--ease-reveal);
  }

  /* When the header folds away to its rail, the stage rises with it, so no stage text shows through above it. */
  .has-scene:has(.site-header[data-state="folded"]) .track[data-scene] .track__stage {
    top: var(--header-rail);
  }

  /* The stage's foot: the ground fades out below it, so a stage passing under it goes softly, not at a hard line. */
  .has-scene .track[data-scene] .track__stage::after {
    content: "";
    position: absolute;
    top: 100%;
    left: var(--scene-bleed-left);
    width: var(--scene-bleed-width);
    height: var(--scene-stage-narrow-foot);
    background: linear-gradient(to bottom, var(--scene-ground), var(--scene-scrim-clear));
    pointer-events: none;
  }

  .track__stage .scene-canvas {
    left: var(--scene-bleed-left);
    width: var(--scene-bleed-width);
    -webkit-mask-image: var(--scene-sky-fade), var(--scene-foot-fade);
    mask-image: var(--scene-sky-fade), var(--scene-foot-fade);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
  }

  .has-scene .track[data-scene] .track__rail {
    grid-row: 3;
    padding-bottom: var(--scene-tail-narrow);
  }

  .has-scene .track[data-scene] .track__stations {
    gap: var(--scene-stage-gap-narrow);
  }
}

/* Below 900px the stage is a shallow strip of the cluster at the top of the hero, directly under the header (approved
   key frame 5, audit problem 12), faded into the ground above and below; the copy and the doors follow it. */
@media (max-width: 899px) {
  .hero__stage {
    position: relative;
    inset: auto;
    order: -1;
    flex: none;
    height: var(--hero-strip-height);
    margin-top: 0;
    margin-bottom: var(--hero-strip-overlap); /* the eyebrow starts on the strip's faded foot */
    -webkit-mask-image: var(--scene-stage-fade);
    mask-image: var(--scene-stage-fade);
  }

  .hero__stage::after {
    display: none;
  }

  /* Zoom to the cluster: the frame is twice the stage's width, centred on the middle of the seven cases. */
  .hero__frame {
    top: calc(50cqh - var(--scene-frame-focus-y) * var(--scene-frame-phone-height));
    right: auto;
    left: calc(50cqw - var(--scene-frame-focus) * var(--scene-frame-phone));
    width: var(--scene-frame-phone);
    transform: none;
  }
}

@media (max-width: 600px) {
  /* Too small to label legibly; the list of damaged items follows further down the page. The home page's live six
     stages keep their few tags, one or two at a time (decision 0014). */
  .hero__labels {
    display: none;
  }

  .track[data-scene] .track__stage .hero__labels {
    display: block;
  }
}

/* On a phone the stage shows the dedicated 4:3 crop of the cluster (home-hero-phone, audit problem 12), drawn at the
   phone's own resolution, not the wide poster zoomed past its pixels, at the screen's full width with the strip
   centred on the cluster; the stage's two-sided fade sets it into the ground above and below. Its labels are hidden
   at this width, so nothing has to follow the crop. */
@media (max-width: 599px) {
  .hero__frame {
    top: calc(50cqh - var(--scene-phone-focus-y) * var(--scene-phone-crop-height));
    left: 0;
    width: 100%;
    aspect-ratio: var(--scene-phone-crop);
  }

  .hero__poster {
    -webkit-mask-image: none;
    mask-image: none;
  }
}

@media print {
  .scene-canvas,
  .hero__stage {
    display: none;
  }
}

/* ?capture=stage-N (docs/contract-renders.md, section 3): the canvas alone, filling the window on the scene ground,
   for the track frames. Nothing else is drawn. */
.is-capture,
.is-capture body {
  overflow: hidden;
  scrollbar-gutter: auto;
  background: var(--scene-ground);
}

.is-capture body > :not(.scene-canvas) {
  visibility: hidden;
}

.is-capture .scene-canvas {
  position: fixed;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  opacity: 1;
  transition: none;
}

/* ---------- Page heroes (inner pages and the 404) ----------
   Each inner page opens on a still of the same world as the home scene (decision 0006, approved key frames 2 and 3).
   The hero stands on the ink, the scene's own ground, in both schemes, as the home hero does; every still meets
   that ground at its edges, so it shows no rectangle (premium plan item 2). Markup:
   <section class="road page-hero">
     <div class="page-hero__still" style="--focus: <subject x as a share of the still>">
       <!-- include:render:<name>:hero:first -->
       <ul class="hero__labels page-hero__labels"> chips, as on the home scene </ul>
     </div>
     <div class="wrap page-hero__inner"> eyebrow, title, lede, <!-- include:cta --> </div>
   </section> */
.page-hero {
  --road-surface: var(--ink);
  --surface: var(--ink);
  position: relative;
  overflow: hidden;
  display: grid;
  align-items: center;
  min-height: var(--page-hero-height); /* the first screen ends on the facts card's first values, not on the hero */
  padding-block: var(--space-2xl) var(--space-3xl);
  background: var(--ink);
  isolation: isolate;
}

/* The still keeps its own 16:10 frame, the hero's height, pinned to the right and centred on the hero's height, so a
   chip stays on its point at every width. On a tall window (a portrait tablet, a narrow laptop window) that frame
   would be wider than the window and bring its subject under the copy, so there it is the window's width instead,
   and the floor runs out above and below into the ink. Its top and bottom fade into the ink, and its copy side is
   darkened so the title always reads, as the home scene's frame is (45-scene.css). */
.page-hero__still {
  position: absolute;
  z-index: -1;
  top: 50%;
  right: 0;
  width: min(100%, calc(var(--page-hero-height) * var(--page-hero-still-ratio)));
  aspect-ratio: var(--page-hero-still-ratio);
  translate: 0 -50%;
}

/* A plan view keeps clear of the copy: a narrower frame, whose floor runs out above and below into the ink. */
.page-hero__still--inset {
  width: var(--page-hero-still-inset);
}

.page-hero__still .render {
  display: block;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
  -webkit-mask-image: var(--scene-frame-fade);
  mask-image: var(--scene-frame-fade);
}

/* The plan view's floor is a shade above the ink, so its frame fades on all four edges. */
.page-hero__still--inset .render {
  -webkit-mask-image: var(--scene-frame-fade), var(--scene-frame-fade-x);
  mask-image: var(--scene-frame-fade), var(--scene-frame-fade-x);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

.page-hero__still::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--page-hero-scrim);
  pointer-events: none;
}

.page-hero__labels--phone {
  display: none;
}

/* A chip that hangs below its point on a short leader, so it sits on the dark floor beside a building and clears the
   arc. --anchor is where along the chip the leader meets it. */
.page-hero__label--below {
  --anchor: 50%;
  transform: translate(calc(-1 * var(--anchor)), var(--page-hero-leader));
}

.page-hero__label--below::before {
  left: var(--anchor);
  top: calc(-1 * var(--page-hero-leader));
  transform: translate(-50%, -50%);
}

.page-hero__label--below::after {
  content: "";
  position: absolute;
  left: var(--anchor);
  top: calc(-1 * var(--page-hero-leader));
  width: var(--hairline);
  height: var(--page-hero-leader);
  background: currentColor;
  transform: translateX(-50%);
}

.page-hero__inner {
  position: relative;
  z-index: 1;
}

.page-hero__inner > * {
  max-width: var(--hero-copy-max);
}

.page-hero__inner > * + * {
  margin-top: var(--space-m);
}

.page-hero__inner > .lede {
  max-width: var(--page-hero-lede);
}

.page-hero__inner > .cta {
  padding-top: var(--space-xs);
}

.page-hero .button {
  --button-bg: var(--road-50);
  --button-text: var(--asphalt-900);
}

/* The page title keeps the approved key frames' weight and tracking, at the home hero's size. */
.page-hero__title {
  max-width: var(--hero-copy-max);
  text-wrap: balance;
  font-size: var(--step-hero);
  font-weight: var(--weight-black);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display);
}

/* One sentence per line in a two-sentence title. */
.line {
  display: block;
}

/* A lane marking runs along the foot of a road hero, held to the content grid like .lane-marking (00-base.css): whole
   dash cycles, the first dash on the left content edge and the last on the right one. */
.hero::after,
.page-hero::after {
  content: "";
  position: absolute;
  z-index: 1;
  bottom: var(--space-l);
  inset-inline: calc(max(var(--gutter), (100% - var(--page-max)) / 2) - var(--dash-gap) / 2);
  height: var(--line);
  background-image: linear-gradient(90deg, transparent 0 var(--dash-lead), var(--road-dash) var(--dash-lead) var(--dash-tail), transparent var(--dash-tail));
  background-size: calc(var(--dash-length) + var(--dash-gap)) 100%;
  background-repeat: round no-repeat;
  opacity: var(--lane-opacity);
}

/* Where the facts card follows, its top rule is the seam: no dash runs in fragments either side of it (premium plan
   item 14). */
.page-hero:has(+ .facts)::after {
  display: none;
}

/* Where it does run, the copy keeps a full step clear of it. */
.page-hero:not(:has(+ .facts)) {
  padding-bottom: calc(var(--space-3xl) + var(--space-l));
}

/* The print line at the foot of each inner page: "Printed from <address> on <day>", filled by the page script
   (src/scripts/main/print.js). Paper only. */
.print-only {
  display: none;
}

/* Laptop and tablet landscape: the still is wider than the room beside the copy. It keeps its right edge, so its
   subject, arc end and chips stay whole, and runs out on the left under the scrim, while the lede and button keep to
   a narrower column (key frame 2). */
@media (max-width: 1199px) and (min-width: 900px) {
  .page-hero__inner > .lede {
    max-width: var(--page-hero-lede-narrow);
  }
}

/* Phones: the still sits above the title, a little wider than the screen and centred on its subject (--focus), its
   four edges faded into the ink, so the cut at the screen edge shows no seam. Its phone crop, where the manifest has
   one, is chosen by the picture's own source. */
@media (max-width: 899px) {
  .page-hero {
    display: block;
    min-height: 0;
    padding-top: 0;
  }

  .page-hero__still,
  .page-hero__still--inset {
    position: relative;
    top: auto;
    translate: none;
    z-index: 0;
    width: var(--page-hero-still-phone);
    height: auto;
    aspect-ratio: auto;
    margin-left: calc(50vw - var(--focus, 0.5) * var(--page-hero-still-phone));
    margin-bottom: calc(-1 * var(--space-xl));
  }

  .page-hero__still .render {
    height: auto;
    -webkit-mask-image: var(--scene-stage-fade), var(--scene-frame-fade-x);
    mask-image: var(--scene-stage-fade), var(--scene-frame-fade-x);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
  }

  .page-hero__still::after {
    display: none;
  }

  .page-hero__labels--wide {
    display: none;
  }

  /* 45-scene.css hides the home scene's seven labels under 600px; a page still carries one chip, which stays. */
  .page-hero .page-hero__labels--phone {
    display: block;
  }

  .page-hero__inner > * {
    max-width: none;
  }

  .page-hero .cta .button {
    width: 100%;
    justify-content: center;
  }
}

/* A page hero with no still, of its own height (message-sent.html, the page a visitor reaches after sending the
   call-back form without script): the copy alone on the ink. The page ends on it, so the footer's own top rule is
   the one seam. */
.page-hero--short {
  min-height: 0;
  padding-top: var(--space-3xl);
}

main > .page-hero--short.road:last-child {
  border-bottom: 0;
}

@media (max-width: 899px) {
  .page-hero--short {
    padding-top: var(--space-2xl);
  }
}

@media print {
  .hero::after,
  .page-hero::after,
  .page-hero__still {
    display: none;
  }

  .page-hero,
  .page-hero:not(:has(+ .facts)) {
    --road-surface: var(--white);
    --surface: var(--white);
    display: block;
    min-height: 0;
    padding-block: var(--space-l);
    background: var(--white);
  }

  .page-hero .cta {
    display: none;
  }

  .print-only {
    display: block;
    width: min(100% - 2 * var(--gutter), var(--page-max));
    margin: var(--space-l) auto 0;
    padding-top: var(--space-s);
    border-top: var(--hairline) solid var(--rule);
    font-family: var(--font-mono);
    font-size: var(--step--1);
    color: var(--text-muted);
    overflow-wrap: anywhere;
  }
}

/* ---------- Chapters ---------- */
.chapter {
  padding-block: var(--space-3xl);
}

.chapter--tight {
  padding-block: var(--space-2xl);
}

/* A chapter that holds one statement gets the most air on the page (premium plan, item 5). */
.chapter:has(.statement-grid) {
  padding-block: calc(var(--space-3xl) + var(--space-xl));
}

.chapter__head {
  display: grid;
  grid-template-columns: var(--split);
  gap: var(--space-l) var(--col-gap);
  align-items: end;
  margin-bottom: var(--space-2xl);
}

.chapter__head--solo {
  margin-bottom: 0;
}

.chapter__head > .eyebrow {
  grid-column: 1 / -1;
}

/* A two-tone title (a statement and its muted answer) runs across both columns. */
.chapter__head--wide > .chapter__title {
  grid-column: 1 / -1;
  max-width: var(--measure-title);
}

.chapter__title {
  font-size: var(--step-3);
  font-weight: var(--weight-display);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display-l);
}

.chapter__intro {
  color: var(--text-muted);
  font-size: var(--step-1);
}

/* The muted second sentence of a two-tone title, in the same face and on the same line (audit problem 18). */
.title-quiet {
  color: var(--text-muted);
}

.rule-top {
  border-top: var(--hairline) solid var(--rule);
}

/* ---------- The home scene bands ----------
   The items band, the six-stage track, the closing chapter and the arrears band stand on the scene's own ground in
   both schemes, so stills and figures on them never show as rectangles (decision 0009, premium plan item 2). The
   items band, the track and the closing chapter are one band, with no seam between them (audit problem 5). */
.scene-band,
.scene-band + .track[data-scene] {
  --road-surface: var(--scene-ground);
  position: relative;
}

.scene-band:has(+ .track[data-scene]),
.track[data-scene]:has(+ .scene-band) {
  border-bottom-color: transparent;
}

.scene-band + .track[data-scene],
.track[data-scene] + .scene-band {
  border-top-color: transparent;
}

.has-scene .scene-band + .track[data-scene]::before {
  content: none;
}

/* In the dark scheme paper and the scene are near-blacks, so where they meet the seam is the dashed lane line, held to
   the content grid, not a 1px rule (premium plan, item 14). The light scheme parts them by colour alone. */
@media (prefers-color-scheme: dark) {
  main > .scene-band:not(.track[data-scene] + .scene-band) {
    border-top-color: transparent;
  }

  main > .scene-band:not(:has(+ .track[data-scene])) {
    border-bottom-color: transparent;
  }

  .scene-band:not(.track[data-scene] + .scene-band)::before,
  .scene-band:not(:has(+ .track[data-scene]))::after {
    content: "";
    position: absolute;
    left: calc(max(var(--gutter), (100% - var(--page-max)) / 2) - var(--dash-gap) / 2);
    right: calc(max(var(--gutter), (100% - var(--page-max)) / 2) - var(--dash-gap) / 2);
    height: var(--line);
    background-image: linear-gradient(90deg, transparent 0 var(--dash-lead), var(--road-dash) var(--dash-lead) var(--dash-tail), transparent var(--dash-tail));
    background-size: calc(var(--dash-length) + var(--dash-gap)) 100%;
    background-repeat: round no-repeat;
    opacity: var(--lane-opacity);
    pointer-events: none;
  }

  /* Over the band's own (transparent) border, so the dash sits on the edge itself. */
  .scene-band::before {
    top: calc(-1 * var(--hairline));
  }

  .scene-band::after {
    bottom: calc(-1 * var(--hairline));
  }
}

/* The items band opens the track: the track's own padding is the space below it. */
.items-band {
  padding-bottom: 0;
}

/* ---------- A dark chapter with a still (the closing mandate chapter and the arrears band) ----------
   The copy takes the first column; the still sits under it, set to the right, its empty floor under the copy behind
   a scrim, as the hero's scene is. */
.scene-chapter__grid {
  display: grid;
  grid-template-columns: var(--split);
  column-gap: var(--col-gap);
}

.scene-chapter__copy {
  position: relative;
  z-index: 1;
  grid-column: 1;
  grid-row: 1;
}

.scene-chapter__still {
  position: relative;
  grid-column: 1 / -1;
  grid-row: 1;
  justify-self: end;
  align-self: center;
  width: var(--scene-still-width);
}

.scene-chapter__still .render {
  width: 100%;
  height: auto;
  -webkit-mask-image: var(--scene-frame-fade), var(--scene-frame-fade-x);
  mask-image: var(--scene-frame-fade), var(--scene-frame-fade-x);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

.scene-chapter__still::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--scene-still-scrim);
  pointer-events: none;
}

/* Tags on a still: the scene's own label grammar, placed where each object sits in the render (--x, --y). */
.scene-tags {
  position: absolute;
  inset: 0;
  z-index: 1;
  margin: 0;
  padding: 0;
  list-style: none;
}

.scene-tag {
  position: absolute;
  left: var(--x);
  top: var(--y);
  transform: translate(var(--scene-label-offset), -50%);
  padding: var(--scene-label-pad);
  border: var(--hairline) solid var(--scene-label-edge);
  background: var(--scene-label-bg);
  color: var(--road-text);
  font-family: var(--font-mono);
  font-size: var(--scene-label-type);
  font-weight: var(--weight-medium);
  line-height: 1;
  letter-spacing: var(--tracking-label-hero);
  text-transform: uppercase;
  white-space: nowrap;
  transition: opacity var(--duration-base) var(--ease-out), border-color var(--duration-base) var(--ease-out);
}

.scene-tag::before {
  content: "";
  position: absolute;
  left: calc(-1 * var(--scene-label-offset));
  top: 50%;
  width: var(--scene-label-dot);
  height: var(--scene-label-dot);
  border-radius: var(--radius-round);
  background: currentColor;
  transform: translateY(-50%);
}

.scene-tag--flip {
  transform: translate(calc(-100% - var(--scene-label-offset)), -50%);
}

.scene-tag--flip::before {
  left: auto;
  right: calc(-1 * var(--scene-label-offset));
}

/* The arc to the client and its tag (DESIGN.md Colour, decision 0009). */
.scene-tag--arc {
  border-color: var(--accent); /* signature */
  color: var(--accent); /* signature */
}

/* ---------- The closing mandate chapter (key frame 4) ----------
   It continues the track's ground after stage 6; the lane marking at its top says the road goes on. */
.closing {
  padding-top: var(--space-3xl);
}

.closing .scene-chapter__grid {
  margin-top: var(--space-2xl);
}

.closing__head > * + * {
  margin-top: var(--space-m);
}

/* The one heading set at the hero's size, as its bookend (audit problem 20). */
.closing__title {
  font-size: var(--step-hero);
  font-weight: var(--weight-display);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display-xl);
  text-wrap: balance;
}

/* Beside the still the copy column is narrow on a laptop, so the title may run on into the column gap, over the
   still's dark side, and "everything else." keeps to one line. */
@media (min-width: 901px) and (max-width: 1199px) {
  .closing__title {
    margin-inline-end: calc(-1 * var(--col-gap));
  }
}

.tally {
  max-width: var(--measure-lede);
  margin-top: var(--space-l);
  border-top: var(--hairline) solid var(--rule);
}

.tally__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-s);
  padding-block: var(--space-xs);
  border-bottom: var(--hairline) solid var(--rule);
}

.tally dt {
  color: var(--text-muted);
}

.tally dd {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: var(--weight-display-s);
}

.closing .cta {
  margin-top: var(--space-l);
}

/* ---------- The arrears band (decision 0009, graft a) ----------
   The three stages on the track's rail, lit as the reader scrolls (src/scripts/main/track.js reads data-track); the
   still stays beside them on wide screens, and the current stage's tag on it is the bright one. */
.arrears__head > * + * {
  margin-top: var(--space-m);
}

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

.arrears .track__stations {
  gap: var(--space-xl);
}

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

.arrears__still {
  position: sticky;
  top: calc(var(--header-height) + var(--space-xl));
  align-self: start;
}

.arrears[data-live] .scene-tag[data-stage] {
  opacity: var(--scene-tag-dim);
}

.arrears[data-live]:has(.station:nth-child(1).is-lit):not(:has(.station:nth-child(2).is-lit)) .scene-tag[data-stage="1"],
.arrears[data-live]:has(.station:nth-child(2).is-lit):not(:has(.station:nth-child(3).is-lit)) .scene-tag[data-stage="2"],
.arrears[data-live]:has(.station:nth-child(3).is-lit) .scene-tag[data-stage="3"] {
  opacity: 1;
  border-color: var(--road-text);
}

@media (max-width: 900px) {
  .chapter__head {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Phones: the still above the copy, at the column's width, with no scrim to need. */
  .scene-chapter__grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .scene-chapter__still,
  .arrears__still {
    position: relative;
    top: auto;
    grid-column: 1;
    grid-row: 1;
    width: 100%;
    margin-bottom: var(--space-l);
  }

  .scene-chapter__copy {
    grid-row: 2;
  }

  .scene-chapter__still::after {
    content: none;
  }
}

@media (max-width: 600px) {
  /* Too small to tag legibly; the stages follow in the list. */
  .scene-tags {
    display: none;
  }
}

@media print {
  .chapter,
  .chapter:has(.statement-grid) {
    min-height: 0;
    padding-block: var(--space-l);
  }

  /* Paper, like every road band in print (99-print.css), so figures and buttons take ink on white. */
  .scene-band,
  .scene-band + .track[data-scene] {
    --road-surface: var(--white);
  }

  .scene-chapter__still,
  .scene-band::before,
  .scene-band::after {
    display: none;
  }

  .scene-chapter__grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---------- Damaged items index ----------
   The seven Hairline figures held to the content grid, seven equal columns (premium plan, item 18). Each label hangs
   from its figure on a 1px leader, centred, two lines tall at least and set to its foot, so every label shares one top
   and one last baseline. No chip boxes: chips belong to the scene. */
.items {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--space-l) var(--items-gap);
  margin-top: var(--space-xl);
  padding: 0;
  list-style: none;
}

.items__item {
  display: grid;
  grid-template-rows: auto auto;
}

.items__name {
  position: relative;
  display: grid;
  place-items: end center;
  min-height: calc(2lh + var(--items-tick) + var(--space-2xs));
  padding-top: calc(var(--items-tick) + var(--space-2xs));
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-label);
  text-align: center;
  text-transform: uppercase;
  text-wrap: balance;
}

.items__name::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: var(--hairline);
  height: var(--items-tick);
  background: var(--rule);
}

/* The home page's two points under the problem title, side by side. */
.split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-xl);
  margin-top: var(--space-2xl);
}

.split__item {
  padding-top: var(--space-m);
  border-top: var(--line) solid var(--text);
}

.split__title {
  font-size: var(--step-2);
  font-weight: var(--weight-display-s);
  letter-spacing: var(--tracking-display-m);
  margin-bottom: var(--space-xs);
}

.split__text {
  color: var(--text-muted);
}

/* Four to a row on eight half columns, each figure two of them wide, so the three on the last row stand centred
   under the four above. */
@media (max-width: 1100px) {
  .items {
    grid-template-columns: repeat(8, minmax(0, 1fr));
  }

  .items__item {
    grid-column: span 2;
  }

  .items__item:nth-child(4n + 1):nth-last-child(3) {
    grid-column: 2 / span 2;
  }
}

@media (max-width: 900px) {
  .split {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-l);
  }
}

@media (max-width: 600px) {
  .items {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .items__item,
  .items__item:nth-child(4n + 1):nth-last-child(3) {
    grid-column: auto;
  }

  /* The seventh figure stands alone on the last row, centred under the pairs. */
  .items__item:last-child:nth-child(odd) {
    grid-column: 1 / -1;
    justify-self: center;
    width: calc(50% - var(--items-gap) / 2);
  }
}

@media print {
  .items__item {
    break-inside: avoid;
  }
}

/* ---------- Hairline figures: the hosts for the moving line figures ----------
   Owned by the figures workstream (docs/decisions/0008). A host is <div class="figure" data-figure="...">, built by
   tools/build.mjs with the figure's still inside it: its rest pose, which is all there is under reduced motion, with
   no script and in print. src/scripts/figures mounts the moving figure over the still and adds is-live, which hides
   the still; the two are drawn alike, so the change does not show.
   The figure's plate (the fill that lets a nearer part cover a farther one) is the ground it stands on: --surface,
   which every band sets to its own background. A band on the scene ground sets --surface: var(--scene-ground) along
   with its background. No border, no chip box, so a figure never shows as a rectangle. Its bright stroke is the
   ground's --text; its other tones come from the --figure-* tokens, by ground. */
.figure {
  --hairline-plate: var(--surface);
  --hairline-hi: var(--text);
  --hairline-edge: var(--figure-edge);
  --hairline-mid: var(--figure-mid);
  --hairline-lo: var(--figure-lo);
  --hairline-stroke: var(--figure-stroke);
  --figure-stroke-host: var(--figure-stroke-wide);
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: var(--figure-ratio);
  container-type: inline-size;
  background: var(--surface);
}

.road .figure {
  --hairline-edge: var(--figure-edge-road);
  --hairline-mid: var(--figure-mid-road);
  --hairline-lo: var(--figure-lo-road);
  --figure-stroke-host: var(--figure-stroke);
}

/* The stroke: one unit, and on paper a quarter more once the host is 240px wide or more (premium plan, item 18), so a
   figure holds its own beside the type. Both drawings read it: the moving one (the package's own variable) and the
   still. */
.figure > svg {
  --hl-sw: var(--hairline-stroke);
}

/* The moving drawing's tone changes take the site's own easing, not the package's, so the page keeps its three
   curves (premium plan, the easing gate). */
.figure > svg :where(path, polygon, ellipse, line, circle) {
  transition-timing-function: var(--ease-out);
}

@container (min-width: 240px) {
  .figure > svg {
    --hl-sw: var(--figure-stroke-host);
  }
}

/* The still: the package's own drawing rules, read from the same tones. */
.figure__still {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

.figure__still :where(path, polygon, ellipse, line) {
  fill: var(--hairline-plate);
  stroke: var(--hairline-mid);
  stroke-width: var(--hl-sw);
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.figure__still .nf {
  fill: none;
}

.figure__still .fo {
  stroke: none;
}

.figure__still .sil {
  stroke: var(--hairline-edge);
}

.figure__still .hi {
  stroke: var(--hairline-hi);
}

.figure__still .lo {
  stroke: var(--hairline-lo);
}

.figure__still .dash {
  stroke-dasharray: var(--figure-dash);
}

.figure__still .dot {
  stroke: none;
  fill: var(--hairline-hi);
}

.figure__still .dot.m {
  fill: var(--hairline-edge);
}

.figure__still .dot.off {
  fill: var(--hairline-lo);
}

.figure.is-live > .figure__still {
  display: none;
}

/* Held by the Pause motion control (window.techriseFigures.setPaused): a held figure answers nothing. */
.figures-held .figure {
  pointer-events: none;
}

/* A figure beside a chapter's text, or above a stage of the debt track (its column and spacing are the track's own,
   70-track.css): at a size where the drawing reads, and never wider than the column. */
/* The build sets --figure-left on these (the share of the 400 by 320 box left of the drawing), so the drawing, not
   its empty margin, starts on the text's edge. */
.figure--chapter,
.station__figure {
  max-width: var(--figure-chapter-max);
  translate: calc(-1 * var(--figure-left, 0%)) 0;
}

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

/* In a chapter head the figure takes the right column beside the title, and an intro after it follows below it. */
.chapter__head > .figure--chapter {
  grid-column: 2;
  margin-top: 0;
}

.chapter__head:has(> .figure--chapter) {
  align-items: start;
}

.chapter__head > .figure--chapter + .chapter__intro {
  grid-column: 2;
}

/* A figure beside a governance statement (home, how we work), as the hub stands beside its title: in the right column
   at the hub's size, its top on the statement's first line (under the eyebrow), the list following under it. */
@media (min-width: 901px) {
  :is(.statement-grid, .two-col):has(> .figure--chapter) {
    row-gap: var(--space-l);
    align-items: start;
  }

  :is(.statement-grid, .two-col) > .figure--chapter {
    grid-column: 2;
    grid-row: 1;
    max-width: var(--figure-beside-max);
    margin-top: calc(var(--eyebrow-block) - min(100%, var(--figure-beside-max)) / (var(--figure-ratio)) * var(--figure-top, 0));
  }

  /* The statement stays over the figure's empty margin, which reaches back across the column gap. */
  :is(.statement-grid, .two-col):has(> .figure--chapter) > :first-child {
    position: relative;
    z-index: 1;
    grid-column: 1;
    grid-row: 1 / span 2;
  }

  :is(.statement-grid, .two-col):has(> .figure--chapter) > .checks {
    grid-column: 2;
    grid-row: 2;
  }
}

@media (max-width: 900px) {
  .figure--chapter {
    max-width: var(--figure-chapter-max-phone);
    margin-top: var(--space-l);
  }

  .chapter__head > .figure--chapter,
  .chapter__head > .figure--chapter + .chapter__intro {
    grid-column: auto;
  }
}

/* Forced colours: the plate takes the forced ground and every line the forced text colour. */
@media (forced-colors: active) {
  .figure {
    --hairline-plate: Canvas;
    --hairline-hi: CanvasText;
    --hairline-edge: CanvasText;
    --hairline-mid: CanvasText;
    --hairline-lo: GrayText;
    background: Canvas;
  }
}

/* Print: the still, in ink on white, whatever the ground on screen. */
@media print {
  .figure,
  .road .figure {
    --hairline-hi: var(--asphalt-900);
    --hairline-edge: var(--kerb-500);
    --hairline-mid: var(--kerb-350);
    --hairline-lo: var(--lane-200);
    break-inside: avoid;
  }

  .figure.is-live > svg {
    display: none;
  }

  .figure.is-live > .figure__still {
    display: block;
  }
}

/* ---------- The case track (signature) ---------- */
.track__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: var(--space-2xl);
  align-items: start;
}

.track__panel {
  position: sticky;
  top: calc(var(--header-height) + var(--space-xl));
}

.track__panel > * + * {
  margin-top: var(--space-m);
}

.track__title {
  font-size: var(--step-4);
  font-weight: var(--weight-display);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display-l);
}

.track__counter {
  display: flex;
  align-items: baseline;
  gap: var(--space-s);
  font-family: var(--font-display);
}

.track__numeral {
  font-size: var(--step-numeral);
  font-weight: var(--weight-black);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-display);
}

.track__of {
  font-family: var(--font-mono);
  font-size: var(--step-1);
  color: var(--text-muted);
}

.track__current-name {
  min-height: calc(2 * var(--leading-snug) * var(--step-2));
  font-size: var(--step-2);
}

/* The live counter only exists while the track follows the scroll. */
.track:not([data-live]) .track__counter,
.track:not([data-live]) .track__current-name,
.track:not([data-live]) .track__mini {
  display: none;
}

.track__mini {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0;
  list-style: none;
}

.track__mini::before,
.track__mini::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: var(--track-width);
  transform: translateY(-50%);
  background: var(--track-idle);
}

.track__mini::after {
  background: var(--accent); /* signature */
  transform-origin: left center;
  transform: translateY(-50%) scaleX(var(--progress, 1));
}

.track__mini-node {
  position: relative;
  z-index: 1;
  width: var(--node-size);
  height: var(--node-size);
  border: var(--track-width) solid var(--accent); /* signature */
  border-radius: var(--radius-round);
  background: var(--accent); /* signature */
  transition: background-color var(--duration-base) var(--ease-out), border-color var(--duration-base) var(--ease-out);
}

/* The rail is a container, so a station's picture height, and the drop of its node to the "Stage n of N" row under
   that picture, can be read from the rail's width (--station-drop, tokens.css). */
.track__rail {
  position: relative;
  container-type: inline-size;
}

.track__stations {
  display: grid;
  gap: var(--space-2xl);
  padding: 0 0 0 calc(var(--node-size-large) + var(--space-l));
  list-style: none;
}

.track__rail::before,
.track__fill {
  content: "";
  position: absolute;
  top: calc(var(--node-size-large) / 2 + var(--station-drop));
  bottom: calc(var(--node-size-large) / 2);
  left: calc(var(--node-size-large) / 2 - var(--track-width) / 2);
  width: var(--track-width);
  background: var(--track-idle);
}

.track__fill {
  background: var(--accent); /* signature */
  transform-origin: center top;
  transform: scaleY(var(--progress, 1));
}

.station {
  position: relative;
  min-height: var(--node-size-large);
}

/* Where a frame or figure stands above a station's text, the node drops to the "Stage n of N" row, so the number and
   the text read together (DESIGN.md Accessibility). */
.station__node {
  position: absolute;
  left: calc(-1 * (var(--node-size-large) + var(--space-l)));
  top: var(--station-drop);
  display: grid;
  place-items: center;
  width: var(--node-size-large);
  height: var(--node-size-large);
  border: var(--track-width) solid var(--accent); /* signature */
  border-radius: var(--radius-round);
  background: var(--accent); /* signature */
  color: var(--asphalt-900);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: var(--weight-bold);
  transition: background-color var(--duration-base) var(--ease-out), border-color var(--duration-base) var(--ease-out), color var(--duration-base) var(--ease-out);
}

.station__count {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.station__title {
  margin-top: var(--space-2xs);
  font-size: var(--step-2);
  font-weight: var(--weight-display-s);
  letter-spacing: var(--tracking-display-m);
}

.station__text {
  margin-top: var(--space-xs);
  max-width: 46ch;
  color: var(--text-muted);
}


/* Unlit stations only exist once the script has taken over. Without it, every station shows lit. */
.js .track[data-live] .station:not(.is-lit) .station__node {
  border-color: var(--track-idle);
  background: var(--road-surface);
  color: var(--road-text-muted);
}

.js .track[data-live] .track__mini-node:not(.is-lit) {
  border-color: var(--track-idle);
  background: var(--road-surface);
}

/* ---------- The current stage (audit problem 17) ----------
   Once the script follows the scroll, the last lit station is the current one: its title keeps full weight while
   the others step back to muted (colour, not opacity, so contrast holds), stages passed keep an amber ring, and the
   current node fills amber. The numeral swaps in a short rise when the stage changes. */
.js .track[data-live] .station:not(.is-current) .station__title {
  color: var(--text-muted);
  transition: color var(--duration-base) var(--ease-out);
}

.js .track[data-live] .station.is-lit:not(.is-current) .station__node {
  background: var(--road-surface);
  color: var(--accent); /* signature: a station marker */
}

/* Numerals and node text sit on the cap height in the middle of their circles (premium plan item 13). */
.station__node,
.track__mini-node {
  text-box: trim-both cap alphabetic;
}

.track__numeral.is-swapping,
.track__caption-numeral.is-swapping {
  animation: numeral-swap var(--dur-numeral) var(--ease-out);
}

@keyframes numeral-swap {
  from {
    opacity: 0;
    translate: 0 var(--numeral-swap-rise);
  }
}

/* ---------- The track's ground ----------
   In the light scheme every track stands on the ink, so the frames and the scene never show as rectangles (premium
   plan item 2). In the dark scheme a scene or frames track after a paper chapter takes the dashed lane line as its
   seam, as the scene bands do (premium plan item 14). */
@media not (prefers-color-scheme: dark) {
  .track {
    --road-surface: var(--ink);
    background: var(--ink);
  }
}

@media (prefers-color-scheme: dark) {
  main > :not(.road) + .track:is([data-scene], [data-frames]) {
    position: relative;
    border-top-color: transparent;
  }

  main > :not(.road) + .track:is([data-scene], [data-frames])::after {
    content: "";
    position: absolute;
    top: calc(-1 * var(--hairline));
    left: calc(max(var(--gutter), (100% - var(--page-max)) / 2) - var(--dash-gap) / 2);
    right: calc(max(var(--gutter), (100% - var(--page-max)) / 2) - var(--dash-gap) / 2);
    z-index: 1;
    height: var(--line);
    background-image: linear-gradient(90deg, transparent 0 var(--dash-lead), var(--road-dash) var(--dash-lead) var(--dash-tail), transparent var(--dash-tail));
    background-size: calc(var(--dash-length) + var(--dash-gap)) 100%;
    background-repeat: round no-repeat;
    opacity: var(--lane-opacity);
    pointer-events: none;
  }
}

/* ---------- Frames from the 3D track (audit problem 4; the Infrastructure Cost Recovery page) ----------
   Each station carries its own frame inline above its text: on phones, under reduced motion and without script.
   At 900px and wider, while the script follows the scroll, the frames stand on a sticky stage beside the stations
   instead, one at a time: the incoming frame fades in on top over --duration-crossfade and the old one goes after,
   so the picture never dips to the ground between them. The frames carry no text; the stage's tags are set over
   them, each off its point on a leader, as in the live track. */
/* No ground of its own either: an ink box would show as a rectangle on the dark scheme's road (review VD-2). */
.station__frame {
  position: relative;
  margin: 0 0 var(--space-m);
}

/* The frames stand inline above their stations wherever the sticky stage is not used: on phones, under reduced motion
   and without script. */
.track[data-frames]:not([data-live]) {
  --station-drop: var(--station-drop-frame);
}

@media (max-width: 899px) {
  .track[data-frames] {
    --station-drop: var(--station-drop-frame);
  }
}

.station__frame img,
.track__frame img {
  display: block;
  width: 100%;
  height: auto;
}

/* The frame's four edges dissolve into the band, ink in the light scheme and the road in the dark, so it never reads
   as a rectangle; the frame itself already fades to the ink at its edges (tools/hero3d/capture-frames.mjs). */
.station__frame img,
.track__frame img {
  -webkit-mask-image: var(--frame-fade-y), var(--frame-fade-x);
  mask-image: var(--frame-fade-y), var(--frame-fade-x);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

.track__frames {
  display: none;
}

.station__frame .hero__label,
.track__frame .hero__label {
  --scene-label-offset: var(--scene-leader-length);
  padding: var(--scene-chip-pad);
  text-box: trim-both cap alphabetic;
}

/* The leader runs from the point to the chip, and a square tick marks the point. */
.station__frame .hero__label::before,
.track__frame .hero__label::before {
  width: var(--scene-label-offset);
  height: var(--hairline);
  border-radius: 0;
  background: var(--scene-leader);
  opacity: var(--scene-leader-opacity);
  transform: translateY(-50%);
}

.station__frame .hero__label::after,
.track__frame .hero__label::after {
  content: "";
  position: absolute;
  top: 50%;
  left: calc(-1 * var(--scene-label-offset) - var(--scene-leader-tick) / 2);
  width: var(--scene-leader-tick);
  height: var(--scene-leader-tick);
  background: var(--scene-leader);
  opacity: var(--scene-leader-opacity);
  transform: translateY(-50%);
}

.station__frame .hero__label--flip::after,
.track__frame .hero__label--flip::after {
  left: auto;
  right: calc(-1 * var(--scene-label-offset) - var(--scene-leader-tick) / 2);
}

@media (min-width: 900px) {
  .js .track[data-live][data-frames] .track__grid {
    grid-template-columns: minmax(0, var(--scene-text-column)) minmax(0, 1fr);
    gap: var(--space-xl) var(--col-gap);
  }

  .js .track[data-live][data-frames] .track__panel {
    position: relative;
    top: auto;
  }

  .js .track[data-live][data-frames] .track__rail {
    grid-column: 1;
  }

  .js .track[data-live][data-frames] .station__frame {
    display: none;
  }

  .js .track[data-live][data-frames] .track__stations {
    gap: var(--frames-stage-gap);
    padding-bottom: var(--frames-tail);
  }

  /* Without the sticky stage (reduced motion, no script), each frame stands beside its station's text instead of
     above it, so six stages do not become six screens of ink; the node stays on the stage's first row. */
  .track[data-frames]:not([data-live]) {
    --station-drop: var(--station-drop-none);
  }

  .track[data-frames]:not([data-live]) .track__stations {
    gap: var(--space-xl);
  }

  .track[data-frames]:not([data-live]) .station {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, var(--station-frame-beside));
    grid-template-rows: auto auto 1fr;
    column-gap: var(--space-m);
    align-items: start;
  }

  .track[data-frames]:not([data-live]) .station > :not(.station__frame, .station__node) {
    grid-column: 1;
  }

  .track[data-frames]:not([data-live]) .station__frame {
    grid-column: 2;
    grid-row: 1 / span 3;
    margin: 0;
  }

  /* Beside its text the frame is smaller and ends on the content edge: its tags take a shorter leader, and a tag set
     to the right of its point wraps rather than run off the page (900 to about 1365px wide), reaching at most to half
     a gutter short of the window edge (--content-edge-room). */
  .track[data-frames]:not([data-live]) .station__frame .hero__label {
    --scene-label-offset: var(--scene-leader-beside);
  }

  .track[data-frames]:not([data-live]) .station__frame .hero__label:not(.hero__label--flip) {
    width: max-content;
    max-width: calc(100% - var(--x) - var(--scene-label-offset) + var(--content-edge-room));
    white-space: normal;
    text-wrap: balance;
  }

  /* The sticky stage ends on the content edge too: just over 900px wide, its tags wrap in the same way. */
  .js .track[data-live][data-frames] .track__frame .hero__label:not(.hero__label--flip) {
    width: max-content;
    max-width: calc(100% - var(--x) - var(--scene-label-offset) + var(--content-edge-room));
    white-space: normal;
    text-wrap: balance;
  }

  .js .track[data-live][data-frames] .track__frames {
    position: sticky;
    top: var(--header-height);
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--space-s);
    grid-column: 2;
    grid-row: 1 / span 2;
    height: calc(100vh - var(--header-height));
  }

  /* No ground of its own: the frame's masked edges dissolve into the band itself, ink in the light scheme and the
     dark scheme's own ground, so the stage never shows as a rectangle. */
  .track__frames-view {
    position: relative;
    aspect-ratio: var(--frame-ratio);
  }

  .track__frame {
    position: absolute;
    inset: 0;
    margin: 0;
    opacity: 0;
  }

  .track__frame.is-current {
    z-index: 2;
    opacity: 1;
    transition: opacity var(--duration-crossfade) var(--ease-out);
  }

  .track__frame.is-leaving {
    z-index: 1;
    opacity: 1;
  }

  /* The stage's caption: which stage the frame shows, in the track's own grammar. */
  .track__caption {
    display: flex;
    align-items: baseline;
    gap: var(--space-s);
    font-family: var(--font-mono);
    font-size: var(--step--1);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-muted);
  }

  .track__caption-count {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }

  .track__caption-numeral {
    display: inline-block;
    color: var(--text);
  }
}

/* Once the live scene runs on this page's track (decision 0014), it is the picture at every width: the frames, inline
   and on their sticky stage, step aside, and the stations take the live track's spacing (45-scene.css). The frames
   stay for reduced motion, saved data, no WebGL and no script, and in print. */
.has-scene .track[data-scene][data-frames] {
  --station-drop: var(--station-drop-none);
}

.has-scene .track[data-scene][data-frames] .station__frame,
.has-scene .track[data-scene][data-frames] .track__frames {
  display: none;
}

@media (min-width: 900px) {
  .has-scene .track[data-scene][data-frames] .track__stations {
    gap: var(--scene-stage-gap);
    padding-bottom: 0;
  }
}

@media (max-width: 899px) {
  .has-scene .track[data-scene][data-frames] .track__stations {
    gap: var(--scene-stage-gap-narrow);
  }
}

/* ---------- The compact track (audit problem 9; the Debt Collection & Credit Control page) ----------
   One row of three stages at 900px and wider, each with its Hairline figure above its amber node, then the stage's
   label, title and text; the rail runs from node to node and ends at the last one, its amber filling stage by
   stage. On phones the stages stack and the rail runs down beside them; each figure stands above its own stage, as
   the infrastructure frames do, with the node on its "Stage n of N" row. */
.track__head > * + * {
  margin-top: var(--space-m);
}

.track--compact .track__rail {
  margin-top: var(--space-xl);
}

.track--compact .track__rail::before,
.track--compact .track__fill {
  display: none;
}

.track--compact .station {
  display: flex;
  flex-direction: column;
}

.track--compact .station__figure {
  order: 0;
  margin: 0 0 var(--space-m);
}

@media (max-width: 899px) {
  .track--compact {
    --station-drop: var(--station-drop-figure);
  }
}

/* One rail segment from each node down to the next; amber once the next stage is lit. */
.track--compact .station:not(:last-child)::before,
.track--compact .station:not(:last-child)::after {
  content: "";
  position: absolute;
  top: calc(var(--node-size-large) + var(--station-drop));
  bottom: calc(-1 * (var(--space-2xl) + var(--station-drop)));
  left: calc(-1 * (var(--node-size-large) + var(--space-l)) + var(--node-size-large) / 2 - var(--track-width) / 2);
  z-index: 1; /* over the figure's plate, which reaches back across the rail */
  width: var(--track-width);
  background: var(--track-idle);
}

.track--compact .station:not(:last-child)::after {
  background: var(--accent); /* signature */
  transform-origin: center top;
  transition: transform var(--duration-base) var(--ease-out);
}

.js .track--compact[data-live] .station:not(:has(+ .station.is-lit))::after {
  transform: scaleY(0);
}

@media (min-width: 900px) {
  .track--compact .track__stations {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--col-gap);
    padding-left: 0;
  }

  .track--compact .station {
    container-type: inline-size;
  }

  .track--compact .station__figure {
    order: 0;
    margin: 0 0 var(--space-m);
  }

  .track--compact .station__node {
    position: relative;
    top: auto;
    left: auto;
    flex: none;
    margin-bottom: var(--space-m);
  }

  /* Across the row: from the node's right edge to the next node's left edge. */
  .track--compact .station:not(:last-child)::before,
  .track--compact .station:not(:last-child)::after {
    content: none;
  }

  .track--compact .station:not(:last-child) .station__node::before,
  .track--compact .station:not(:last-child) .station__node::after {
    content: "";
    position: absolute;
    top: calc(50% - var(--track-width) / 2);
    left: calc(100% + var(--track-width));
    width: calc(100cqw + var(--col-gap) - var(--node-size-large) - 2 * var(--track-width));
    height: var(--track-width);
    background: var(--track-idle);
  }

  .track--compact .station:not(:last-child) .station__node::after {
    background: var(--accent); /* signature */
    transform-origin: left center;
    transition: transform var(--duration-base) var(--ease-out);
  }

  .js .track--compact[data-live] .station:not(:has(+ .station.is-lit)) .station__node::after {
    transform: scaleX(0);
  }
}

@media (max-width: 900px) {
  .track__grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .track__panel {
    position: static;
  }

  .track__mini,
  .track__counter,
  .track__current-name {
    display: none;
  }
}

@media print {
  /* Paper, not ink: the track's own ground (the ink in the light scheme) gives way, and the frames from the 3D track,
     their tags and the stage tags stay on screen, so nothing prints as a black box. */
  .track,
  .track[data-scene] {
    --road-surface: var(--white);
    --road-surface-deep: var(--white);
  }

  .track[data-frames] {
    --station-drop: var(--station-drop-none);
  }

  .track__panel .track__counter,
  .track__panel .track__current-name,
  .track__mini,
  .station__frame,
  .track__frames,
  .track__frame,
  .hero__labels,
  .scene-tag {
    display: none;
  }

  .station__figure {
    background: none;
  }

  .station {
    break-inside: avoid;
  }

  .track__panel {
    position: static;
  }
}

/* ---------- Ledger: who carries what ----------
   Two columns of equal rows, each row its label with any note on a muted line under it, and the total under both:
   "None", set as a figure under a heavy ink rule (audit problem 22). No amber. The columns are the shared split
   with the shared gap (audit problem 21), so the right column starts where every other right column does; the rule
   between them runs down the middle of the gap. */
.ledger {
  display: grid;
  grid-template-columns: var(--split);
  column-gap: var(--col-gap);
  border-top: var(--line) solid var(--text);
}

.ledger__col {
  display: flex;
  flex-direction: column;
  padding-top: var(--space-l);
}

.ledger__col + .ledger__col {
  position: relative;
}

.ledger__col + .ledger__col::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(-1 * var(--col-gap) / 2);
  width: var(--hairline);
  background: var(--rule);
}

.ledger__head {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.ledger__list {
  flex: 1;
  margin-top: var(--space-m);
  padding: 0;
  list-style: none;
}

.ledger__row {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-3xs);
  padding-block: var(--space-s);
  border-bottom: var(--hairline) solid var(--rule);
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: var(--weight-display-s);
  line-height: var(--leading-snug);
}

.ledger__tick {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: var(--weight-regular);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* The total sits on the ledger's own columns, so "None" stands under the right column's rows. */
.ledger__total {
  display: grid;
  grid-template-columns: var(--split);
  column-gap: var(--col-gap);
  align-items: baseline;
  margin-top: var(--space-xl);
  padding-top: var(--space-m);
  border-top: calc(2 * var(--line)) solid var(--text);
}

.ledger__total-label {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.ledger__total-value {
  font-family: var(--font-display);
  font-size: var(--ledger-total);
  font-weight: var(--weight-figure);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display-xl);
}

/* Side by side, the two columns keep one row height, so their rules line up. */
@media (min-width: 901px) {
  .ledger__row {
    min-height: var(--ledger-row);
  }
}

/* Wide enough that every row keeps to one row height: the client's column carries on as empty ruled lines down to
   the foot of the longer one, so the two columns end together, as a ledger page does. */
@media (min-width: 1100px) {
  .ledger__col:first-child .ledger__list {
    background-image: repeating-linear-gradient(to bottom, transparent 0 calc(var(--ledger-row) - var(--hairline)), var(--rule) calc(var(--ledger-row) - var(--hairline)) var(--ledger-row));
  }
}

/* ---------- Fees band (service pages) ----------
   The fee promise said once, whole, in the ledger's grammar (audit problem 10): who provides what, three rows, the
   quiet line and the mandate button. Fees never move (decision 0010). */
.fees__grid {
  display: grid;
  grid-template-columns: var(--split);
  gap: var(--col-gap);
  align-items: start;
}

.fees__rows {
  border-top: var(--line) solid var(--text);
}

.fees__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-s);
  padding-block: var(--space-s);
  border-bottom: var(--hairline) solid var(--rule);
}

.fees__row dt {
  color: var(--text-soft);
}

.fees__row dd {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: var(--weight-display);
  text-align: right;
}

.fees__quiet {
  margin-top: var(--space-m);
  color: var(--text-muted);
}

.fees__body > .cta {
  margin-top: var(--space-l);
}

@media (max-width: 900px) {
  .ledger,
  .ledger__total {
    grid-template-columns: minmax(0, 1fr);
  }

  .ledger__col + .ledger__col {
    margin-top: var(--space-l);
    border-top: var(--hairline) solid var(--rule);
  }

  .ledger__col + .ledger__col::before {
    content: none;
  }

  .fees__grid {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-l);
  }
}

@media print {
  .ledger__row,
  .fees__row,
  .ledger__total {
    break-inside: avoid;
  }
}

/* ---------- Statement ----------
   One sentence set large, with an optional muted second sentence in the same face (the house two-tone pattern,
   audit problem 20). Shared by the home page and the inner pages. */
.statement {
  max-width: 24ch;
  font-family: var(--font-display);
  font-size: var(--step-3);
  font-weight: var(--weight-display);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display-l);
  text-wrap: balance;
}

.statement__quiet {
  display: block;
  color: var(--text-muted);
  text-wrap: balance;
}

.statement-grid {
  display: grid;
  grid-template-columns: var(--split);
  gap: var(--col-gap);
  align-items: end;
}

/* A statement with nothing beside it takes the whole row. */
.statement-grid:has(> :only-child) {
  grid-template-columns: minmax(0, 1fr);
}

.statement-grid__side > * + * {
  margin-top: var(--space-s);
}

/* ---------- Service doors ----------
   A door is one link: a mono index, the title, one line and the go line. Markup:
   <a class="door" href="..." aria-labelledby="<id>">
     <span class="door__index">...</span>
     <span class="door__title" id="<id>"><span class="door__name">...</span></span>
     <span class="door__text">...</span>
     <span class="door__go">... <span class="door__go-arrow" aria-hidden="true">→</span></span>
   </a>
   With a still (.door--still), the still comes first and the rest goes in <span class="door__body">:
   <span class="door__still"><!-- include:render:<name>:door --></span><span class="door__body">...</span> */
.doors {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border-top: var(--hairline) solid var(--rule);
}

.door {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-s);
  padding: var(--space-xl) var(--space-xl) var(--space-xl) 0;
  color: inherit;
  text-decoration: none;
}

.door + .door {
  padding-left: var(--space-xl);
  border-left: var(--hairline) solid var(--rule);
}

.door__index {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.door__title {
  font-family: var(--font-display);
  font-size: var(--step-2);
  font-weight: var(--weight-display);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display-m);
}

/* The title's own text carries the underline, line by line, so a title on two lines underlines both at once. It
   passes through in the reading direction: in from the left on hover, out to the right after. */
.door__name {
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0 var(--line);
  background-position: 100% 100%;
  background-repeat: no-repeat;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  transition: background-size var(--dur-exit) var(--ease-in);
}

.door__text {
  max-width: 44ch;
  color: var(--text-muted);
  text-wrap: pretty;
}

/* The go line: a quiet rule at rest, as every prose link has. */
.door__go {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  margin-top: auto;
  padding-bottom: var(--space-3xs);
  background-image: linear-gradient(var(--rule), var(--rule));
  background-size: 100% var(--hairline);
  background-position: 0 100%;
  background-repeat: no-repeat;
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
}

.door__go-arrow {
  display: inline-flex;
  transition: transform var(--duration-base) var(--ease-out);
}

.door__still {
  display: block;
  width: 100%;
  transition: transform var(--dur-media) var(--ease-out);
}

.door__still .render {
  width: 100%;
  height: auto;
}

/* On a dark band (the inner pages' door bands) the still meets the ground at its edges; the fade on all four keeps
   the floor grid from ending on a line, as in the home hero's doors (42-hero-doors.css). */
.road .door__still .render {
  -webkit-mask-image: var(--scene-frame-fade), var(--scene-frame-fade-x);
  mask-image: var(--scene-frame-fade), var(--scene-frame-fade-x);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

/* A door with a still: the still beside the text at 240px (from door-*-480.webp), the text in a body of its own. */
.door--still {
  display: grid;
  grid-template-columns: var(--door-still) minmax(0, 1fr);
  align-items: start;
  gap: var(--space-m);
}

.door__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-s);
  height: 100%;
}

/* Hover only where there is hover: the title underlines, the arrow steps on and the still lifts. Nothing else moves. */
@media (hover: hover) and (pointer: fine) {
  .door:hover .door__name {
    background-size: 100% var(--line);
    background-position: 0 100%;
    transition-duration: var(--duration-base);
    transition-timing-function: var(--ease-out);
  }

  .door:hover .door__go-arrow {
    transform: translateX(var(--space-2xs));
  }

  .door:hover .door__still {
    transform: translateY(calc(-1 * var(--space-3xs)));
  }
}

@media (max-width: 900px) {
  .statement-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .doors {
    grid-template-columns: minmax(0, 1fr);
  }

  .door,
  .door + .door {
    padding: var(--space-l) 0;
    border-left: 0;
  }

  .door + .door {
    border-top: var(--hairline) solid var(--rule);
  }
}

/* Two doors side by side on a laptop: beside a 240px still the text would be a column a few words wide, so the
   still stands above it instead, at the same width. */
@media (min-width: 901px) and (max-width: 1199px) {
  .doors .door--still {
    grid-template-columns: minmax(0, 1fr);
  }

  .doors .door__still {
    max-width: var(--door-still);
  }
}

@media (max-width: 600px) {
  .door--still {
    grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
    gap: var(--space-s);
  }
}

@media print {
  .door__still {
    display: none;
  }
}

/* ---------- Plain lists and definition grids ---------- */
.checks {
  display: grid;
  gap: var(--space-s);
  padding: 0;
  list-style: none;
}

.checks__item {
  display: grid;
  grid-template-columns: var(--space-l) minmax(0, 1fr);
  gap: var(--space-xs);
}

/* The rule runs under the text only, so it starts where the text does when the dashes hang in the margin. */
.checks__item > :last-child {
  padding-bottom: var(--space-s);
  border-bottom: var(--hairline) solid var(--rule);
}

/* A list that is not a sequence is marked with a dash, never a numeral (audit problem 16): the dash sits on the first
   line's middle, at the end of its column, next to the text. */
.checks__mark {
  justify-self: end;
  width: var(--mark-dash-w);
  height: var(--mark-dash-h);
  margin-top: calc(0.5lh - var(--mark-dash-h) / 2);
  background: var(--text-muted);
}

.two-col {
  display: grid;
  grid-template-columns: var(--split);
  gap: var(--col-gap);
  align-items: start;
}

/* A heading block in a two-column section: eyebrow, title, then its intro a step below (audit problem 25). */
.two-col__head > .chapter__title + * {
  margin-top: var(--space-m);
}

/* Who keeps what: two titled lists, one under the other, beside the heading. */
.roles > .split__title + .checks {
  margin-top: var(--space-m);
}

.roles > .checks + .split__title {
  margin-top: var(--space-2xl);
}

/* On wide screens the dashes hang in the margin, so every line of list text starts on the heading's left edge
   (premium plan item 13); a heading block stays in view beside a long list (audit problem 19). From 901px, the width
   where .two-col has two columns: at exactly 900 the one-column layout has no margin for the dashes. */
@media (min-width: 901px) {
  .checks {
    margin-inline-start: calc(-1 * var(--checks-hang));
  }

  .two-col__head--sticky {
    position: sticky;
    top: var(--sticky-top);
  }
}

@media (max-width: 900px) {
  .two-col {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media print {
  .checks__item {
    break-inside: avoid;
  }

  .checks {
    margin-inline-start: 0;
  }
}

/* ---------- Definition grids and credential badges ---------- */
.details {
  display: grid;
  grid-template-columns: minmax(0, var(--details-label-width)) minmax(0, 1fr);
  border-top: var(--line) solid var(--text);
}

.details dt,
.details dd {
  padding-block: var(--space-s);
  border-bottom: var(--hairline) solid var(--rule);
}

.details dt {
  padding-right: var(--space-m);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.details dd {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: var(--weight-semibold);
}

/* Two figures, Level 2 and 125%, each with its caption (audit problem 19). They keep the four-column badge size
   (no ownership share or EME, decision 0015). Credentials never move (decision 0010) and never stand beside a
   Hairline figure. */
.badge-row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-l);
  margin-top: var(--space-2xl);
}

.badge {
  padding-top: var(--space-m);
  border-top: var(--line) solid var(--text);
}

.badge__value {
  font-family: var(--font-display);
  font-size: var(--step-badge);
  white-space: nowrap;
  letter-spacing: var(--tracking-display-xl);
  font-weight: var(--weight-figure);
  line-height: var(--leading-tight);
}

.badge__text {
  margin-top: var(--space-xs);
  color: var(--text-muted);
}

.details .address {
  font-style: normal;
}

/* The company's closing band: its facts, then the two ways on, level with the heading. */
.company-close {
  align-items: start;
}

.company-close .cta {
  margin-top: var(--space-l);
}

@media (max-width: 1100px) {
  .badge-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 600px) {
  .badge-row {
    gap: var(--space-l) var(--space-m);
  }
}

@media (max-width: 600px) {
  .details {
    grid-template-columns: minmax(0, 1fr);
  }

  .details dt {
    padding-bottom: 0;
    border-bottom: 0;
  }

  .details dd {
    padding-top: var(--space-3xs);
  }
}

@media print {
  .company-close .cta {
    display: none;
  }

  .badge,
  .details dt,
  .details dd {
    break-inside: avoid;
  }

  /* On paper each label stands over its value: Chrome will not keep a heading with the first row of a grid, so as a
     grid the list let its heading end a page alone. */
  .details {
    display: block;
  }

  .details dt {
    padding-bottom: 0;
    border-bottom: 0;
    break-after: avoid;
  }

  .details dd {
    padding-top: var(--space-3xs);
  }
}

/* ---------- Legal pages (decision 0017) ----------
   The privacy notice, the PAIA manual, "If we have contacted you" and the accessibility statement. Markup:
   <section class="road page-hero page-hero--legal"> eyebrow, title, lede, <p class="legal__updated"> </section>
   <section class="chapter"><div class="wrap legal">
     <nav class="legal__toc"> <p class="legal__toc-title"> <ol> links to the sections </ol> </nav>
     <div class="legal__body"> h2, h3, p, ul, ol, dl.legal__contacts, table.legal__table (the build wraps each table
       in a .legal__table-wrap region named by its caption) </div>
   </div></section>
   then the contact block (<!-- include:contact:general --> or :account). */

/* The hero: the inner-page hero's ink, type and lane marking, without a still, so it is the copy's own height and the
   text starts on the first screen of a laptop. The lede has the whole measure, since nothing stands beside it; the copy
   keeps a full step clear of the lane marking. */
.page-hero.page-hero--legal {
  min-height: 0;
  padding-block: var(--space-xl) calc(var(--space-xl) + var(--space-l));
}

.page-hero--legal .page-hero__title {
  font-size: var(--step-4);
}

.page-hero--legal .page-hero__inner > .lede {
  max-width: var(--measure);
}

.legal__updated {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

.page-hero__inner > .legal__updated {
  margin-top: var(--space-l);
}

/* The body: the list of sections first, then the text, at one readable measure. The sections are numbered in the
   site's mono numerals, since they run in order. */
/* The text follows the hero closely: the hero has already set the page apart. */
.chapter:has(> .legal) {
  padding-top: var(--space-xl);
}

.legal {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-xl);
}

.legal__toc-title {
  padding-top: var(--space-s);
  border-top: var(--hairline) solid var(--rule);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.legal__toc ol {
  margin-top: var(--space-xs);
  padding: 0;
  list-style: none;
  counter-reset: legal-toc;
}

.legal__toc li {
  break-inside: avoid;
  counter-increment: legal-toc;
  display: grid;
  grid-template-columns: var(--space-l) minmax(0, 1fr);
  align-items: baseline;
  font-size: var(--step--1);
  line-height: var(--leading-snug);
}

.legal__toc li::before {
  content: counter(legal-toc, decimal-leading-zero);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

/* Each link is a row tall enough to hit (24px and more), its rule drawn under the text on hover. */
.legal__toc a {
  display: block;
  padding-block: var(--space-3xs);
  color: var(--text-soft);
  font-weight: var(--weight-medium);
  text-decoration: none;
}

@media (hover: hover) and (pointer: fine) {
  .legal__toc a:hover {
    color: var(--text);
    text-decoration: underline;
    text-decoration-thickness: var(--hairline);
    text-underline-offset: var(--space-3xs);
  }
}

/* The text. A long address in a sentence breaks rather than widening the page. */
.legal__body {
  container-type: inline-size;
  max-width: var(--measure);
  color: var(--text-soft);
  overflow-wrap: break-word;
}

.legal__body > * + * {
  margin-top: var(--space-s);
}

/* An anchored heading clears the pinned header (html's scroll padding) by one more step. */
.legal__body h2,
.legal__body h3 {
  color: var(--text);
  scroll-margin-top: var(--space-l);
}

.legal__body h2 {
  padding-top: var(--space-s);
  border-top: var(--hairline) solid var(--rule);
  font-size: var(--step-2);
  font-weight: var(--weight-display);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-display-m);
}

.legal__body > h2:not(:first-child) {
  margin-top: var(--space-2xl);
}

.legal__body h3 {
  font-size: var(--step-1);
  font-weight: var(--weight-display-s);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-display-m);
}

.legal__body > h3:not(:first-child) {
  margin-top: var(--space-l);
}

.legal__body > :is(h2, h3) + * {
  margin-top: var(--space-s);
}

.legal__body strong {
  color: var(--text);
  font-weight: var(--weight-semibold);
}

/* Lists: an unordered item is marked with the site's dash, an ordered one with its numeral, each in a column of its
   own beside the text. */
.legal__body ul,
.legal__body ol {
  padding: 0;
  list-style: none;
}

.legal__body li + li {
  margin-top: var(--space-2xs);
}

.legal__body ul > li {
  position: relative;
  padding-inline-start: var(--space-l);
}

.legal__body ul > li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: calc(0.5lh - var(--mark-dash-h) / 2);
  width: var(--mark-dash-w);
  height: var(--mark-dash-h);
  background: var(--text-muted);
}

.legal__body ol {
  counter-reset: legal-step;
}

.legal__body ol > li {
  counter-increment: legal-step;
  display: grid;
  grid-template-columns: var(--space-l) minmax(0, 1fr);
  align-items: baseline;
}

.legal__body ol > li::before {
  content: counter(legal-step, decimal-leading-zero);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

/* Contact lists: label and value rows, as in the contact block, with the label in the block's mono. Side by side
   where the text column has room, the label over its value where it has not. The label's line box is the value's, so
   the two first lines share a baseline (below). */
.legal__contacts {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  border-top: var(--hairline) solid var(--rule);
}

.legal__body > .legal__contacts {
  margin-top: var(--space-m);
}

.legal__contacts dt {
  padding-top: var(--space-xs);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.legal__contacts dd {
  padding-block: var(--space-3xs) var(--space-xs);
  border-bottom: var(--hairline) solid var(--rule);
  color: var(--text);
  overflow-wrap: anywhere;
}

@container (min-width: 34rem) {
  .legal__contacts {
    grid-template-columns: minmax(0, var(--details-label-width)) minmax(0, 1fr);
  }

  .legal__contacts dt,
  .legal__contacts dd {
    padding-block: var(--space-xs);
    border-bottom: var(--hairline) solid var(--rule);
  }

  /* The label's first line sits on the value's: it is pushed down by half the difference of their line boxes, and a
     label that wraps keeps its own tight leading. */
  .legal__contacts dt {
    padding-inline-end: var(--space-m);
    padding-top: calc(var(--space-xs) + (var(--step-0) * var(--leading-paper) - var(--step--1) * var(--leading-snug)) / 2);
    line-height: var(--leading-snug);
  }
}

/* Tables: hairline rules, mono column labels as on the facts card, tabular figures, the caption as the table's
   title. The region around a table scrolls sideways on its own if a screen is too narrow for it, so the page never
   does; it takes focus for that (tools/build.mjs legalMarkup). */
.legal__body > .legal__table-wrap {
  margin-top: var(--space-m);
}

/* The region is a ring's width wider than the text on each side, its padding giving that width back, so its focus
   ring does not touch the first column's glyphs nor the scroller cut it. */
.legal__table-wrap {
  --ring-room: calc(2 * var(--focus-width) + var(--focus-offset));
  overflow-x: auto;
  margin-inline: calc(-1 * var(--ring-room));
  padding-inline: var(--ring-room);
}

.legal__table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
  line-height: var(--leading-lede);
}

.legal__table caption {
  caption-side: top;
  padding-bottom: var(--space-xs);
  text-align: start;
  font-family: var(--font-display);
  font-weight: var(--weight-display-s);
  line-height: var(--leading-snug);
  color: var(--text);
}

.legal__table th,
.legal__table td {
  padding: var(--space-xs) var(--space-m) var(--space-xs) 0;
  text-align: start;
  vertical-align: top;
}

.legal__table :is(th, td):last-child {
  padding-inline-end: 0;
}

.legal__table thead th {
  border-top: var(--hairline) solid var(--rule);
  border-bottom: var(--hairline) solid var(--rule);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
  vertical-align: bottom;
}

.legal__table td {
  border-bottom: var(--hairline) solid var(--rule);
}

/* The first column names the row (a row header, tools/build.mjs legalMarkup), so it reads in the text colour, set as
   the text; the second is its value. */
.legal__table tbody th {
  border-bottom: var(--hairline) solid var(--rule);
  font-weight: var(--weight-regular);
  color: var(--text);
}

/* Below the two columns the hero is a step shorter, so the text is near on a short window, and from 600px the list of
   sections runs in two columns, half its height. */
@media (max-width: 899px) {
  .page-hero.page-hero--legal {
    padding-block: var(--space-l) calc(var(--space-l) + var(--space-l));
  }

  .page-hero__inner > .legal__updated {
    margin-top: var(--space-m);
  }
}

@media (min-width: 600px) and (max-width: 899px) {
  .legal__toc ol {
    column-count: 2;
    column-gap: var(--space-l);
  }
}

@media (min-width: 900px) {
  /* The site's two columns: the list of sections in the narrow one, staying in view below the header as the text
     runs on beside it, and scrolling itself if a short window cannot show it whole. */
  .legal {
    grid-template-columns: var(--split);
    column-gap: var(--col-gap);
    align-items: start;
  }

  /* A scroller cuts what overflows it, focus rings included, so the list is a ring's width larger than its text on
     every side, its padding giving that width back: a focused link's ring shows whole. */
  .legal__toc {
    --ring-room: calc(2 * var(--focus-width) + var(--focus-offset));
    position: sticky;
    top: calc(var(--sticky-top) - var(--ring-room));
    max-height: calc(100vh - var(--sticky-top) - var(--space-l) + 2 * var(--ring-room));
    margin: calc(-1 * var(--ring-room));
    padding: var(--ring-room);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* Windows contrast themes: the dash marks are drawn with a background, so they take the theme's text colour. */
@media (forced-colors: active) {
  .legal__body ul > li::before {
    forced-color-adjust: none;
    background: CanvasText;
  }
}

/* Paper: the text alone, at full width, with its links underlined. The list of sections and the form stay off it
   (89-form.css); a contact list's values and a link that shows its own address are not spelled out again. */
@media print {
  .legal {
    display: block;
  }

  .legal__toc {
    display: none;
  }

  .legal__body {
    max-width: none;
  }

  .legal__body a {
    text-decoration: underline;
    text-decoration-thickness: var(--hairline);
  }

  .legal__body .legal__contacts a::after,
  .legal__body a[data-href-shown]::after {
    content: none;
  }

  /* The dash is a background on screen, which a browser leaves off paper by default: a border prints. */
  .legal__body ul > li::before {
    height: 0;
    border-top: var(--mark-dash-h) solid var(--text-muted);
    background: none;
  }

  .legal__table-wrap {
    overflow: visible;
    margin-inline: 0;
    padding-inline: 0;
  }

  .legal__table tr,
  .legal__contacts dd {
    break-inside: avoid;
  }

  .legal__contacts dt,
  .legal__body h3 {
    break-after: avoid;
  }
}

/* ---------- Contact ----------
   The shared contact block (contact() in tools/build.mjs). The email and phone, once supplied, are the largest type in
   the block and tap rows at least 48px tall (docs/decisions/0007). Contact details never move. */
.contact {
  display: grid;
  grid-template-columns: var(--split);
  gap: var(--col-gap);
  align-items: start;
}

.contact__title {
  font-size: var(--step-3);
  font-weight: var(--weight-display);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display-l);
}

.contact__entity {
  margin-top: var(--space-m);
  max-width: var(--measure-lede);
  color: var(--text-soft);
}

.contact__lines {
  display: grid;
  gap: var(--space-m);
}

.contact__line {
  padding-top: var(--space-s);
  border-top: var(--hairline) solid var(--rule);
}

.contact__label {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.contact__value {
  margin-top: var(--space-3xs);
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: var(--weight-semibold);
  font-style: normal;
}

.contact__value--reach {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs) var(--space-m);
  font-size: var(--step-3);
  letter-spacing: var(--tracking-display-m);
}

.contact__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-row);
  overflow-wrap: anywhere;
}

/* The registered office as one block (the address include, for the company page). */
.address {
  font-style: normal;
}

/* Copy buttons, added by src/scripts/main/contact.js only where the clipboard works. */
.contact__copy {
  min-height: var(--tap-min);
  padding: var(--space-2xs) var(--space-s);
  border: var(--hairline) solid var(--control-edge);
  border-radius: var(--radius-s);
  background: transparent;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  cursor: pointer;
  transition: background-color var(--duration-quick) var(--ease-out), color var(--duration-quick) var(--ease-out);
}

.contact__copy:active {
  transform: translateY(var(--press-depth));
}

@media (hover: hover) and (pointer: fine) {
  .contact__copy:hover {
    background: var(--text);
    color: var(--surface);
  }
}

@media (max-width: 900px) {
  .contact {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media print {
  .contact__copy {
    display: none;
  }
}

/* ---------- The call-back form ----------
   In the contact block's right column, above the registered office (callbackForm() in tools/build.mjs; decision
   0016). Labels sit above their fields; every field is a tap row at least 48px tall with a hairline edge in the control
   colour, as the copy buttons have; a field that needs attention takes a heavier edge and a message under it
   (src/scripts/main/contact-form.js), so the change is a shape as well as a colour. Two columns from 900px. */
.contact__intro {
  margin-top: var(--space-s);
  max-width: var(--measure-lede);
  color: var(--text-soft);
}

.callback {
  margin-top: var(--space-s);
}

.callback__fields {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-m);
}

.field__label {
  display: block;
  margin-bottom: var(--space-2xs);
  font-size: var(--step--1);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}

/* A hint between a label and its field: the label's size, in the softer text colour. */
.field__hint {
  margin-bottom: var(--space-2xs);
  max-width: var(--measure-lede);
  color: var(--text-soft);
  font-size: var(--step--1);
  line-height: var(--leading-snug);
}

.field__optional {
  font-weight: var(--weight-regular);
  color: var(--text-muted);
}

.field__input {
  display: block;
  width: 100%;
  min-height: var(--tap-row);
  margin: 0;
  padding: var(--space-xs) var(--space-xs);
  border: var(--hairline) solid var(--control-edge);
  border-radius: var(--radius-s);
  background: var(--surface-raised);
  color: var(--text);
  font: inherit;
  font-size: var(--step-0);
  line-height: var(--leading-snug);
}

.field__area {
  min-height: calc(3 * var(--tap-row));
  resize: vertical;
  line-height: var(--leading-paper);
}

.field__select {
  cursor: pointer;
}

/* A field that needs attention: the heavier line and the message colour, set only by the script. */
.field__input[aria-invalid="true"],
.field__check[aria-invalid="true"] {
  border-color: var(--error);
  box-shadow: inset 0 0 0 var(--hairline) var(--error);
}

.field__input[aria-invalid="true"]:focus-visible {
  box-shadow: 0 0 0 calc(var(--focus-width) + var(--focus-offset) + var(--focus-width)) var(--focus-outer);
}

.field__error {
  margin-top: var(--space-2xs);
  color: var(--error);
  font-size: var(--step--1);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}

/* The consent box: the box on the first line of its text, and the whole label a target as tall as every other row
   (the privacy-notice link inside it is an inline link in a sentence). */
.field--check {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: var(--space-xs);
  align-items: start;
}

.field__check {
  width: var(--space-m);
  height: var(--space-m);
  margin: 0; /* its 24px centre sits on the centre of the label's first line */
  accent-color: var(--text);
  cursor: pointer;
}

.field__check-label {
  min-height: var(--tap-row);
  color: var(--text-soft);
  font-size: var(--step--1);
  line-height: var(--leading-paper);
  cursor: pointer;
}

.field--check .field__error {
  grid-column: 2;
  margin-top: var(--space-3xs);
}

.callback__send {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-s) var(--space-m);
  margin-top: var(--space-l);
}

/* Disabled in a preview build with no form key, and aria-disabled while it sends (so it keeps focus): a muted fill
   that answers no hover or press. */
.callback .button:disabled,
.callback .button[aria-disabled="true"] {
  --button-bg: var(--text-muted);
  cursor: not-allowed;
}

.callback .button:disabled:active,
.callback .button[aria-disabled="true"]:active {
  transform: none;
}

@media (hover: hover) and (pointer: fine) {
  .callback .button:disabled:hover,
  .callback .button[aria-disabled="true"]:hover {
    background: var(--button-bg);
    color: var(--button-text);
  }

  .callback .button:disabled:hover .arrow,
  .callback .button[aria-disabled="true"]:hover .arrow {
    transform: none;
  }
}

/* The live region: "Sending" in the soft text colour, a failure in the message colour. */
.callback__status:not(:empty) {
  margin-top: var(--space-s);
  color: var(--text-soft);
  font-weight: var(--weight-semibold);
}

.callback__status--error:not(:empty) {
  color: var(--error);
}

/* The thank-you that takes the form's place: the block's value type, so it reads as the answer to the form. */
.callback__done {
  margin-top: var(--space-s);
  max-width: var(--measure-lede);
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}

@media (min-width: 900px) {
  .callback__fields {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-m);
  }

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

/* Windows contrast themes: the fields keep the theme's own edges; the disabled button takes its grey text colour. */
@media (forced-colors: active) {
  .field__input[aria-invalid="true"] {
    border-width: var(--line);
    box-shadow: none;
  }

  .callback .button:disabled,
  .callback .button[aria-disabled="true"] {
    border-color: GrayText;
    color: GrayText;
  }
}

/* Paper: no form to fill in. The registered office and the rest of the block print as before. */
@media print {
  .contact__intro,
  .contact__line--form {
    display: none;
  }
}

/* ---------- Footer ----------
   The logo signs off above the lane marking, the header's lockup at the header's height (decision 0011); below it the footer is split by audience (decision 0009, graft d):
   each service with its stages, then the company links, the legal line and the legal pages. */
.site-footer {
  padding-block: var(--space-xl) var(--space-l);
}

.site-footer__mark {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  margin-bottom: var(--space-s);
  color: var(--road-text);
}

.site-footer__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) repeat(3, minmax(0, 1fr));
  gap: var(--space-xl);
  padding-top: var(--space-l);
}

.site-footer__group {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* Each group's heading takes two lines, so the first links of all three groups start on one line. */
.site-footer__heading {
  min-height: var(--footer-heading-height);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.site-footer__about {
  max-width: 40ch;
  color: var(--text-muted);
}

/* Footer links: a tall enough target, and the pass-through underline of the prose links. */
.site-footer__service,
.site-footer__list a,
.site-footer__docs a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  text-decoration: none;
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0 var(--hairline);
  background-position: 100% calc(100% - var(--space-2xs));
  background-repeat: no-repeat;
  transition: background-size var(--dur-exit) var(--ease-in);
}

@media (hover: hover) and (pointer: fine) {
  .site-footer__service:hover,
  .site-footer__list a:hover,
  .site-footer__docs a:hover {
    background-size: 100% var(--hairline);
    background-position: 0 calc(100% - var(--space-2xs));
    transition-duration: var(--duration-base);
    transition-timing-function: var(--ease-out);
  }
}

.site-footer__service {
  margin-top: var(--space-2xs);
  font-family: var(--font-display);
  font-weight: var(--weight-display-s);
}

.site-footer__stages,
.site-footer__list {
  padding: 0;
  list-style: none;
}

.site-footer__stages {
  display: grid;
  gap: var(--space-3xs);
  line-height: var(--leading-snug);
  margin-top: var(--space-2xs);
  font-size: var(--step--1);
  color: var(--text-muted);
}

.site-footer__list {
  margin-top: var(--space-2xs);
}

.site-footer__legal {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-3xs) var(--space-xl);
  margin-top: var(--space-xl);
  padding-top: var(--space-m);
  border-top: var(--hairline) solid var(--rule);
  font-size: var(--step--1);
  color: var(--text-muted);
}

.site-footer__legal-end {
  text-align: end;
}

/* The legal row breaks at one width, whatever its copy: side by side above it, stacked and flush left below it, so
   the Pause motion line never right-aligns inside a shrink-wrapped block. */
@media (min-width: 1140px) {
  .site-footer__legal {
    flex-wrap: nowrap;
  }

  .site-footer__legal-end {
    flex-shrink: 0;
  }
}

@media (max-width: 1139px) {
  .site-footer__legal {
    flex-direction: column;
  }

  .site-footer__legal-end {
    text-align: start;
  }
}

/* The legal pages: one quiet row, placeholders until each page exists. */
.site-footer__docs {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-l);
  margin-top: var(--space-xs);
  padding: 0;
  list-style: none;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--text-muted);
}

/* The legal page being read: full text colour, its rule drawn. */
.site-footer__docs a[aria-current="page"] {
  color: var(--text);
  background-size: 100% var(--hairline);
  background-position: 0 calc(100% - var(--space-2xs));
}

.site-footer__docs .placeholder {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
}

@media (max-width: 1100px) {
  .site-footer__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .site-footer__brand {
    grid-column: 1 / -1;
  }
}

/* Three narrow columns: the longest heading takes a third line. */
@media (max-width: 900px) and (min-width: 761px) {
  .site-footer__heading {
    min-height: var(--footer-heading-height-narrow);
  }
}

@media (max-width: 760px) {
  .site-footer__grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-l);
  }
}

@media (max-width: 480px) {
  .site-footer__grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .site-footer__heading {
    min-height: 0;
  }
}

/* Print: no navigation, only the company's own lines, kept together. */
@media print {
  .site-footer__mark,
  .site-footer__grid,
  .site-footer__docs {
    display: none;
  }

  .site-footer {
    padding-block: var(--space-m) 0;
  }

  .site-footer__legal {
    display: block;
    margin-top: 0;
    break-inside: avoid;
  }

  .site-footer__legal-end {
    text-align: start;
  }
}

/* ---------- Reveal on scroll (src/scripts/main/reveal.js; premium plan item 8) ----------
   Only under .motion, which the script sets when reduced motion is off: without it every unit is simply there.
   A rule draws from the left, then type settles on it; a ledger row fades in place; a render rises once decoded.
   Transform and opacity only, apart from the drawn rule, which is the border repainted as a growing line. */
.motion .rv {
  transition:
    opacity var(--dur-reveal) var(--ease-reveal) var(--rv-delay, 0ms),
    transform var(--dur-reveal) var(--ease-reveal) var(--rv-delay, 0ms);
}

.motion .rv:not(.is-in):not(.rv-rule):not(.rv-line) {
  opacity: 0;
  transform: translateY(var(--reveal-rise));
}

.motion .rv-fade:not(.is-in) {
  transform: none;
}

.motion .rv-media {
  transition-duration: var(--dur-media);
}

/* The element's own top or bottom border, repainted as a line that grows from the left edge. */
.motion .rv-rule {
  background-image: linear-gradient(var(--rv-rule-color), var(--rv-rule-color));
  background-repeat: no-repeat;
  background-origin: border-box;
  background-clip: border-box;
  background-size: 100% var(--hairline);
  transition: background-size var(--dur-rule) var(--ease-reveal) var(--rv-delay, 0ms);
}

.motion .rv-rule--top {
  border-top-color: transparent;
  background-position: left top;
}

.motion .rv-rule--bottom {
  border-bottom-color: transparent;
  background-position: left bottom;
}

.motion .rv-rule:not(.is-in) {
  background-size: 0% var(--hairline);
}

.motion .rv-line {
  transform-origin: left center;
  transition: transform var(--dur-rule) var(--ease-reveal) var(--rv-delay, 0ms);
}

.motion .rv-line:not(.is-in) {
  transform: scaleX(0);
}

/* Already where the reader is (in view at load, past mid-screen on a fast scroll, above an anchor): no show. */
.motion .rv-now {
  transition: none;
}

/* ---------- Inner page arrival (premium plan item 8) ----------
   The lane line under the page hero draws from the left as the page arrives; the hero still develops once decoded. */
@media (prefers-reduced-motion: no-preference) {
  .page-hero::after {
    transform-origin: left center;
    animation: lane-draw var(--duration-draw) var(--ease-reveal) var(--rule-lead) both;
  }
}

@keyframes lane-draw {
  from {
    transform: scaleX(0);
  }
}

.rv-develop {
  transition:
    opacity var(--dur-media) var(--ease-reveal),
    transform var(--dur-media) var(--ease-reveal);
}

.rv-develop--wait {
  opacity: 0;
  transform: translateY(var(--reveal-rise));
}

/* ---------- Page changes (premium plan item 10) ----------
   The header (site-header) and the current page's mark (nav-mark) hold still; the page fades out quickly and the
   next one fades up into place. A door's still grows into the next page's hero still (page-render, set by
   src/scripts/main/transitions.js on the outgoing page). */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}

::view-transition-old(root) {
  animation: var(--dur-page-out) var(--ease-in) both page-out;
}

::view-transition-new(root) {
  animation: var(--dur-page-in) var(--ease-out) var(--dur-page-out) both page-in;
}

@keyframes page-out {
  to {
    opacity: 0;
  }
}

@keyframes page-in {
  from {
    opacity: 0;
    transform: translateY(var(--reveal-rise));
  }
}

.page-hero img {
  view-transition-name: page-render;
}

::view-transition-group(page-render) {
  animation-duration: var(--dur-media);
  animation-timing-function: var(--ease-reveal);
}

/* ---------- Pause motion (src/scripts/main/motion.js; WCAG 2.2.2) ----------
   A text button in the scene's chip style, never amber. The scene's own sits bottom right over the scene, just
   above the doors' top rule, while the scene is live; the footer's sits in the footer's last line. On a page with
   figures a third one follows the skip link, out of sight until it has focus, as the skip link is (review AM-9). */
.motion-toggle {
  min-height: var(--tap-min);
  padding: 0;
  border: 0;
  background: none;
  color: var(--road-text-muted);
  font-family: var(--font-mono);
  font-size: var(--scene-label-type);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  cursor: pointer;
}

.motion-toggle--scene {
  position: absolute;
  right: 0;
  bottom: calc(100% + var(--space-s));
  z-index: 2;
  display: none;
  min-height: 0;
  padding: var(--scene-chip-pad);
  border: var(--hairline) solid var(--scene-label-edge);
  background: var(--scene-label-bg);
  color: var(--road-text);
  text-box: trim-both cap alphabetic;
}

.has-scene:not(.scene-lost) .motion-toggle--scene {
  display: block;
}

/* Below 900px the copy runs right down to the doors, so the scene's control takes its own line after them, at the
   right, where it covers nothing. */
@media (max-width: 899px) {
  .motion-toggle--scene {
    position: static;
    margin: var(--space-s) 0 0 auto;
  }
}

/* After the skip link it takes the skip link's place and face when focused, so it is the second tab stop. */
.motion-toggle--skip {
  position: absolute;
  left: var(--space-s);
  top: var(--space-s);
  z-index: 100;
  min-height: 0;
  padding: var(--space-xs) var(--space-s);
  background: var(--road-50);
  color: var(--asphalt-900);
  font-family: var(--font-body);
  font-size: var(--step-0);
  font-weight: var(--weight-semibold);
  letter-spacing: normal;
  text-transform: none;
  transform: translateY(-200%);
}

.motion-toggle--skip:focus {
  transform: none;
}

/* In the footer it is one more line of the legal row, in the docs row's own face. */
.motion-toggle--footer {
  font-size: var(--step--1);
  font-weight: var(--weight-regular);
  letter-spacing: normal;
  text-transform: none;
}

@media (hover: hover) and (pointer: fine) {
  .motion-toggle:hover {
    color: var(--road-text);
  }

  .motion-toggle--scene:hover {
    border-color: var(--road-text-muted);
  }
}

/* ---------- Reduced motion: everything resolves to a still ---------- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }

  html {
    scroll-behavior: auto;
  }

  .motion-toggle {
    display: none !important;
  }
}

@media print {
  .rv,
  .rv-develop--wait {
    opacity: 1 !important;
    transform: none !important;
  }

  .rv-rule {
    background-image: none !important;
  }

  .rv-rule--top {
    border-top-color: var(--rv-rule-color) !important;
  }

  .rv-rule--bottom {
    border-bottom-color: var(--rv-rule-color) !important;
  }

  .rv-line {
    transform: none !important;
  }

  .motion-toggle,
  .site-footer__motion {
    display: none;
  }
}

/* A render offered as AVIF first sits in a <picture> (tools/build.mjs render()); the wrapper takes no box, so the
   image lays out, reveals and transitions exactly as a bare render does. */
.render-picture {
  display: contents;
}

/* ---------- 404 ----------
   A page hero of its own height (not the first screen's): one empty tile, its lane line stopping at the tile's edge,
   "Page not found." and the pages of the site as a plain list (audit problem 24). */
.lost {
  min-height: 0;
  padding-top: var(--space-3xl);
}

/* The page ends on the hero (the print line after it is paper only), so the footer's own top rule is the one seam. */
main > .lost.road {
  border-bottom: 0;
}

.lost__pages {
  padding-top: var(--space-s);
}

.lost__label {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.lost__list {
  margin-top: var(--space-s);
  padding: 0;
  list-style: none;
  border-top: var(--hairline) solid var(--rule);
}

.lost__link {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-s);
  min-height: var(--tap-row);
  border-bottom: var(--hairline) solid var(--rule);
  color: inherit;
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: var(--weight-display-s);
  text-decoration: none;
}

.lost__link .arrow {
  transition: transform var(--duration-base) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .lost__link:hover .arrow {
    transform: translateX(var(--space-2xs));
  }
}

@media (max-width: 899px) {
  .lost {
    padding-top: 0;
  }
}

@media print {
  .lost {
    padding-block: var(--space-l);
  }

  .lost__link .arrow {
    display: none;
  }
}

/* ---------- Forced colours (Windows contrast themes) ----------
   The shapes drawn only with a background would turn into the page colour and vanish, so each keeps its shape in the
   theme's text colour; the lit part of the logo takes the theme's highlight (decision 0010). */
@media (forced-colors: active) {
  .eyebrow::before,
  .track__rail::before,
  .track__fill,
  .track__mini::before,
  .track__mini::after,
  .lane-marking,
  .hero__label::before,
  .road::after,
  .site-nav__mark,
  .site-header__progress,
  .menu-button__icon span {
    forced-color-adjust: none;
    background: CanvasText;
  }

  .lane-marking {
    background: linear-gradient(90deg, transparent 0 var(--dash-lead), CanvasText var(--dash-lead) var(--dash-tail), transparent var(--dash-tail));
    background-size: calc(var(--dash-length) + var(--dash-gap)) 100%;
    background-repeat: round no-repeat;
  }

  .wordmark__africa {
    forced-color-adjust: none;
    fill: Highlight;
  }
}

/* ---------- Print: for procurement files. Paper, ink, no navigation, links spelled out. ----------
   Global rules only; each component keeps its own print rules in its partial. */
/* A4 with filing margins, the company's name on every page, and page numbers (decision 0010).
   Margin boxes do not see the page's custom properties, so their values are written here. */
@page {
  size: A4;
  margin: 16mm 15mm 20mm; /* lint-allow-raw: page margins for filing; margin boxes cannot read tokens */

  @bottom-left {
    content: "Techrise Africa (Pty) Ltd";
    font: 8pt/1 sans-serif; /* lint-allow-raw: page footer; margin boxes cannot read tokens */
    color: #444; /* lint-allow-raw: page footer; margin boxes cannot read tokens */
  }

  @bottom-right {
    content: "Page " counter(page) " of " counter(pages);
    font: 8pt/1 sans-serif; /* lint-allow-raw: page footer; margin boxes cannot read tokens */
    color: #444; /* lint-allow-raw: page footer; margin boxes cannot read tokens */
  }
}

@media print {
  :root {
    --surface: var(--white);
    --surface-raised: var(--white);
    --text: var(--asphalt-900);
    --text-muted: var(--asphalt-700);
    --text-soft: var(--asphalt-700); /* the dark scheme's grey running text prints as ink too */
    --rule: var(--lane-200);
    --road-surface: var(--white);
    --road-surface-deep: var(--white);
    --road-text: var(--asphalt-900);
    --road-text-muted: var(--asphalt-700);
    --road-rule: var(--lane-200);
    --track-idle: var(--lane-200);
    --seam: transparent;
  }

  body,
  .road,
  .road--deep {
    background: var(--white);
    color: var(--asphalt-900);
  }

  h1,
  h2,
  h3 {
    break-after: avoid;
  }

  /* No page ends on a heading, a label or half a block. */
  .chapter__head,
  .closing__head,
  .arrears__head,
  .track__head,
  .statement-grid,
  .contact,
  .contact__line,
  .facts__card,
  .items,
  .two-col__head,
  .station,
  .door {
    break-inside: avoid;
  }

  .chapter__head,
  .track__head,
  .track__panel,
  .closing__head,
  .arrears__head,
  .two-col__head,
  .eyebrow {
    break-after: avoid;
  }

  /* A two-column section prints as one column of blocks: its heading keeps with the start of what follows, which
     may run on to the next page between its rows, so no page ends on a heading over an empty foot. */
  .two-col {
    display: block;
  }

  .two-col > * + * {
    margin-top: var(--space-l);
  }

  /* A statement chapter is one or two sentences: it moves to the next page whole. */
  .chapter--statement {
    break-inside: avoid-page;
  }

  /* Whatever the reveal script has or has not done, print shows everything. */
  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
  }

  a[href^="http"]::after,
  a[href^="mailto:"]::after,
  a[href^="tel:"]::after {
    content: " (" attr(href) ")";
    font-family: var(--font-mono);
    font-size: var(--step--1);
  }
}
