/* ============================================================
   Co-Bot — cinematic one-pager
   Type: Clash Display (display) · Satoshi (body) · system mono (HUD)
   The WebGL hero owns its own grade (bloom/grain/vignette in-shader);
   this sheet owns layout, typography, and the DOM grade.
   ============================================================ */

:root {
  color-scheme: dark;
  --bg: #050607;
  --ink: #f4f6f0;
  --muted: #9aa6a0;
  --soft: #cfd8d2;
  --line: rgba(244, 246, 240, 0.14);
  --accent: #01a2f6;
  --font-display: "Clash Display", "Avenir Next", "Segoe UI", sans-serif;
  --font-body: "Satoshi", "Segoe UI", system-ui, sans-serif;
  --font-mono: "Cascadia Code", "JetBrains Mono", "IBM Plex Mono", ui-monospace, Consolas, monospace;
  --pad-x: max(24px, calc((100vw - 1240px) / 2));
  --ease-expo: cubic-bezier(0.16, 1, 0.3, 1);
}

* {
  box-sizing: border-box;
}

html {
  background: var(--bg);
  overflow-x: clip;
  scrollbar-color: rgba(244, 246, 240, 0.25) #0a0c0d;
  scrollbar-width: thin;
}

::-webkit-scrollbar {
  width: 9px;
}

::-webkit-scrollbar-track {
  background: #0a0c0d;
}

::-webkit-scrollbar-thumb {
  background: rgba(244, 246, 240, 0.22);
  border-radius: 99px;
}

body {
  margin: 0;
  min-width: 320px;
  overflow-x: clip;
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.6;
  color: var(--ink);
  background: var(--bg);
}

body[data-loading] {
  overflow: hidden;
}

::selection {
  color: #07100b;
  background: var(--accent);
}

a {
  color: inherit;
  text-decoration: none;
}

img {
  display: block;
  max-width: 100%;
}

h1,
h2,
h3,
p,
ul {
  margin: 0;
  padding: 0;
}

ul {
  list-style: none;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

.mono {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* ---------- split-text plumbing ---------- */
.split-word {
  display: inline-block;
  white-space: nowrap;
}

.split-mask {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-bottom: 0.09em;
  margin-bottom: -0.09em;
}

.split-inner {
  display: inline-block;
  will-change: transform;
}

/* ============================================================
   PAGE CHROME
   ============================================================ */

.grain {
  position: fixed;
  inset: -60%;
  z-index: 200;
  pointer-events: none;
  width: 220%;
  height: 220%;
  opacity: 0.06;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  animation: grain-jump 1.1s steps(1) infinite;
}

@keyframes grain-jump {
  0% { transform: translate(0, 0); }
  13% { transform: translate(-3%, -5%); }
  26% { transform: translate(4%, -2%); }
  39% { transform: translate(-2%, 4%); }
  52% { transform: translate(5%, 3%); }
  65% { transform: translate(-5%, 1%); }
  78% { transform: translate(2%, -4%); }
  91% { transform: translate(-1%, 2%); }
}

.progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 140;
  height: 2px;
  pointer-events: none;
}

.progress span {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--accent), #2df4cf);
  transform: scaleX(0);
  transform-origin: left center;
}

.cursor-light {
  position: fixed;
  left: -13px;
  top: -13px;
  z-index: 220;
  width: 26px;
  height: 26px;
  border: 1px solid rgba(244, 246, 240, 0.75);
  border-radius: 999px;
  pointer-events: none;
  mix-blend-mode: difference;
  opacity: 0;
  transition: width 200ms var(--ease-expo), height 200ms var(--ease-expo),
    margin 200ms var(--ease-expo), border-color 200ms ease, opacity 200ms ease;
}

.cursor-light.is-on {
  opacity: 1;
}

.cursor-light.is-hot {
  width: 54px;
  height: 54px;
  margin: -14px 0 0 -14px;
  border-color: var(--accent);
}

/* ============================================================
   PRELOADER
   ============================================================ */

.preloader {
  position: fixed;
  inset: 0;
  z-index: 300;
}

.preloader-shutter {
  position: absolute;
  left: 0;
  right: 0;
  height: 50.25%;
  background: #07090a;
}

.preloader-shutter--top {
  top: 0;
  border-bottom: 1px solid rgba(244, 246, 240, 0.06);
}

.preloader-shutter--bottom {
  bottom: 0;
}

.preloader-core {
  position: absolute;
  inset: 0;
  z-index: 2;
  padding: clamp(1.4rem, 4vw, 3rem);
}

.preloader-label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.preloader-label span {
  color: var(--accent);
}

.preloader-count {
  position: absolute;
  left: clamp(1.4rem, 4vw, 3rem);
  bottom: clamp(2.6rem, 7vh, 4.6rem);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(4.6rem, 14vw, 11rem);
  line-height: 0.85;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.preloader-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(1.5rem, 4.4vh, 2.6rem);
  height: 1px;
  margin-inline: clamp(1.4rem, 4vw, 3rem);
  background: rgba(244, 246, 240, 0.12);
}

.preloader-bar span {
  display: block;
  height: 100%;
  background: var(--accent);
  transform-origin: left center;
  box-shadow: 0 0 18px color-mix(in srgb, var(--accent) 70%, transparent);
}

.preloader-status {
  position: absolute;
  right: clamp(1.4rem, 4vw, 3rem);
  bottom: clamp(2.6rem, 7vh, 4.6rem);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}

body.no-motion .preloader {
  display: none;
}

/* ============================================================
   HEADER
   ============================================================ */

.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 120;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.05rem var(--pad-x) 1.5rem;
  background: linear-gradient(180deg, rgba(5, 6, 7, 0.82), rgba(5, 6, 7, 0.35) 64%, transparent);
  pointer-events: none;
}

.site-header > * {
  pointer-events: auto;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
}

.brand-mark {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 8px;
  color: #07100b;
  background: var(--accent);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.82rem;
  transition: background 300ms ease;
}

.brand-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.05rem;
  letter-spacing: 0.01em;
}

.site-header nav {
  display: flex;
  gap: clamp(1rem, 2.6vw, 2.2rem);
}

.site-header nav a {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  transition: color 200ms ease;
}

.site-header nav a i {
  margin-right: 0.45em;
  font-style: normal;
  color: var(--accent);
  opacity: 0.55;
  transition: opacity 200ms ease;
}

.site-header nav a.is-active,
.site-header nav a:hover,
.site-header nav a:focus-visible {
  color: var(--ink);
}

.site-header nav a.is-active i {
  opacity: 1;
}

.demo-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 40px;
  padding: 0.6rem 1.15rem;
  border-radius: 999px;
  color: #07100b;
  background: var(--ink);
  font-weight: 700;
  font-size: 0.9rem;
  transition: transform 250ms var(--ease-expo), background 250ms ease;
}

.demo-link:hover,
.demo-link:focus-visible {
  transform: translateY(-2px);
  background: var(--accent);
}

/* ============================================================
   HERO — the film
   ============================================================ */

.hero {
  position: relative;
  height: 600vh;
  height: 600svh;
  background: var(--bg);
}

/* mobile act stops — rendered only in the <=720px block. Their --sp offsets in
   index.html are holdProgress x (mobile .hero height - 100svh): recompute them
   there if the <=720px height below changes. */
.hero-snap {
  display: none;
}

.hero-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}

.hero-canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

.hero-fallback {
  position: absolute;
  inset: 0;
  display: none;
  place-items: center;
  background:
    radial-gradient(circle at 50% 64%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 36rem),
    var(--bg);
}

.hero-fallback img {
  width: min(480px, 70vw);
  filter: drop-shadow(0 50px 50px rgba(0, 0, 0, 0.55))
    drop-shadow(0 0 38px color-mix(in srgb, var(--accent) 24%, transparent));
}

body.no-webgl .hero-canvas {
  display: none;
}

body.no-webgl .hero-fallback {
  display: grid;
}

/* --- act 0: title --- */
.hero-intro {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: grid;
  place-content: center;
  justify-items: center;
  text-align: center;
  pointer-events: none;
  padding: 0 16px;
}

.hero-kicker {
  color: var(--accent);
  margin-bottom: clamp(1rem, 2.6vh, 1.8rem);
}

.hero-wordmark {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(4.6rem, 16.5vw, 15rem);
  line-height: 0.88;
  letter-spacing: -0.015em;
}

.hero-tagline {
  margin-top: clamp(1rem, 2.6vh, 1.7rem);
  font-size: clamp(1rem, 1.5vw, 1.2rem);
  color: var(--soft);
}

.hero-scrollcue {
  position: absolute;
  left: 50%;
  bottom: clamp(1.6rem, 5vh, 3rem);
  z-index: 4;
  display: grid;
  justify-items: center;
  gap: 0.7rem;
  transform: translateX(-50%);
  color: var(--muted);
  pointer-events: none;
}

.scrollcue-line {
  position: relative;
  width: 1px;
  height: 52px;
  overflow: hidden;
  background: rgba(244, 246, 240, 0.14);
}

.scrollcue-line::before {
  content: "";
  position: absolute;
  left: 0;
  top: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(180deg, transparent, var(--accent));
  animation: cue-drop 2.1s var(--ease-expo) infinite;
}

@keyframes cue-drop {
  0% { transform: translateY(0); }
  62%, 100% { transform: translateY(200%); }
}

/* --- acts 1–4: beats --- */
.beat {
  position: absolute;
  z-index: 5;
  max-width: min(33rem, 40vw);
  pointer-events: none;
}

/* soft scrim so copy survives bright pixels behind it */
.beat--left::before,
.beat--right::before {
  content: "";
  position: absolute;
  inset: -22% -16%;
  z-index: -1;
  background: radial-gradient(closest-side, rgba(5, 6, 7, 0.62), transparent);
  filter: blur(6px);
}

.beat--left {
  left: clamp(1.4rem, 7vw, 8rem);
  top: 50%;
  transform: translateY(-50%);
}

.beat--right {
  right: clamp(1.4rem, 7vw, 8rem);
  top: 50%;
  transform: translateY(-50%);
}

.beat--center {
  left: 50%;
  bottom: clamp(3rem, 11svh, 7rem);
  transform: translateX(-50%);
  max-width: min(44rem, 88vw);
  text-align: center;
}

.beat-meta {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  color: var(--accent);
  margin-bottom: 1.1rem;
}

.beat--center .beat-meta {
  justify-content: center;
}

.beat-meta span {
  display: inline-grid;
  place-items: center;
  padding: 0.22rem 0.5rem;
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  border-radius: 4px;
}

.beat-line {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(2.3rem, 4.6vw, 4.5rem);
  line-height: 0.98;
  letter-spacing: -0.01em;
}

.beat-copy {
  margin-top: 1.2rem;
  max-width: 29rem;
  font-size: clamp(0.95rem, 1.15vw, 1.06rem);
  line-height: 1.7;
  color: var(--soft);
}

.beat--center .beat-copy {
  margin-inline: auto;
}

.beat-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1.5rem;
  color: var(--ink);
}

.beat--center .beat-chips {
  justify-content: center;
}

.beat-chips li {
  padding: 0.52rem 0.78rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(5, 6, 7, 0.55);
  backdrop-filter: blur(10px);
}

.beat-cue {
  margin-top: 1.7rem;
  color: var(--accent);
}

.beat-cue::after {
  content: " ↓";
}

/* --- HUD --- */
.hero-hud {
  position: absolute;
  left: var(--pad-x);
  right: var(--pad-x);
  bottom: 1.5rem;
  z-index: 6;
  display: flex;
  align-items: center;
  justify-content: space-between;
  color: var(--muted);
  pointer-events: none;
}

.hud-readout {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
}

.hud-dot {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--accent);
  animation: hud-pulse 2.2s ease-out infinite;
}

@keyframes hud-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
  100% { box-shadow: 0 0 0 12px transparent; }
}

/* --- LiDAR aperture portal (hero → scrubber) ---
   A dark-teal iris with concentric scan rings + a thin glowing leading edge.
   Sized by --iris (a length): grows from the sensor point to fill the screen
   on the hero's last frame, then contracts away into the airport scene. The
   centre stays dark, so it reads as diving through an aperture — not a flash. */
/* a brief dark-teal dip that bridges the hero cleanup into the next section
   (the robot-sweeps-the-hero choreography is the real transition) */
.warp-iris {
  position: absolute;
  inset: 0;
  z-index: 12;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(circle at 50% 45%, #062a1e 0%, #04120d 70%, #030b08 100%);
}

/* ============================================================
   CHAPTERS 02–04
   ============================================================ */

/* Dwell. THE ONLY PART OF THIS NUMBER THAT IS READING TIME IS THE MIDDLE.
   The copy reveal is viewport-relative (`top bottom` +=185%), so it always
   ends 85vh past the sticky pin however tall this is; the exit is a fraction
   (dim at 47%) plus the fixed 100svh cover overlap. So:
       entrance = 1.85 screens   (fixed — does not scale)
       hold     = 0.47*H - 85vh  (this is what the number buys, ~1:1)
       exit     = 0.53*H - 100vh
   680vh gave 1.85 / 2.55 / 2.40 = 6.8 screens and, over six chapters, a
   ~50-screen page. 500vh gives 1.85 / 1.70 / 1.45 = 5.0 with the story clips
   carrying their own narrative on the video's clock (8s loops) rather than on
   scroll. Do not go below ~480vh: at 460 the hold drops under 1.5 screens and
   the un-draw (51%→65%) no longer clears the cover climb. */
.chapter {
  position: relative;
  height: 500vh;
  height: 500svh;
  background: var(--bg);
}

/* stacked-cover transitions: each later section overlaps the previous and
   paints over it (negative margin = overlap, z-index = paint order) so the
   incoming section can slide / zoom over the one that stays pinned behind it */
.hero {
  z-index: 1;
}

/* paint order comes from build.py (--z), so re-ordering products.json can't
   scramble the stack the way hardcoded ids did */
.chapter {
  z-index: var(--z, 2);
}

/* "cover" sections must be TRANSPARENT — otherwise their own dark background
   paints over the still-pinned section behind them during the overlap, which
   showed up as a black band. Every chapter after the first is a cover now: the
   old "zoom" variant (an opaque black panel that rose, then a stage that zoomed
   forward out of a blur) was written for a static scene photo and has been
   removed — see build.py's entrance_for(). */
.chapter[data-entrance="cover"] {
  margin-top: -100vh;
  margin-top: -100svh;
  background: transparent;
}

.finale {
  position: relative;
  z-index: 20;
}

/* the "vertical line" that opens section 2 — a sibling of the (clipped) stage
   so the stage's clip-path doesn't hide it; sticky so it holds at viewport
   centre while the section is pinned */
.reveal-line {
  position: sticky;
  top: 10vh;
  top: 10svh;
  z-index: 11;
  display: block;
  width: 2px;
  height: 80vh;
  height: 80svh;
  margin-inline: auto;
  transform: scaleY(0);
  transform-origin: 50% 50%;
  background: linear-gradient(180deg, transparent, var(--accent) 14%, var(--accent) 86%, transparent);
  box-shadow: 0 0 24px 3px color-mix(in srgb, var(--accent) 75%, transparent);
  opacity: 0;
  pointer-events: none;
}

/* The seam-bridging survey line. FIXED, not sticky: it must hold steady in
   the viewport across the hero's emptied last screen AND the first chapter's
   opening — a span no single section owns. Scroll-driven from script.js. */
.handoff-line {
  position: fixed;
  top: 10vh;
  top: 10svh;
  left: 50%;
  z-index: 15;
  width: 2px;
  height: 80vh;
  height: 80svh;
  margin-left: -1px;
  transform: scaleY(0);
  transform-origin: 50% 50%;
  background: linear-gradient(180deg, transparent, #01a2f6 14%, #01a2f6 86%, transparent);
  box-shadow: 0 0 24px 3px rgba(1, 162, 246, 0.75);
  opacity: 0;
  pointer-events: none;
}

.chapter-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  isolation: isolate;
}

/* --scene-pos / --scene-zoom are the per-product framing knob (products.json
   "stage" -> build.py -> inline custom properties on the <section>).

   POSITION: the scene photos are square, so on a landscape stage `cover`
   overflows vertically and background-position picks the band; on a portrait
   phone it overflows horizontally and picks the column. Default 50% 50% is the
   old `center`.

   ZOOM: `cover` recomputes against the element box, so zoom cannot be a
   background-size (there is nothing to size against on the short axis). It is
   the negative inset instead — the element grows around the stage, .chapter-
   stage clips it, and the visible crop tightens. 1 keeps the historical -3%,
   which is the headroom the parallax in script.js needs. That parallax is a
   transform on this element (scale 1.18 -> 1.02, yPercent -5 -> 5); it
   composes on top of both knobs and is untouched. */
.chapter-bg {
  position: absolute;
  inset: calc(-3% - (var(--scene-zoom, 1) - 1) * 50%);
  z-index: 1;
  background-image: var(--scene);
  background-size: cover;
  background-position: var(--scene-pos, 50% 50%);
  filter: saturate(0.92) contrast(1.05);
}

.chapter-veil {
  position: absolute;
  inset: 0;
  z-index: 2;
  background:
    linear-gradient(90deg, rgba(5, 6, 7, 0.9), rgba(5, 6, 7, 0.34) 52%, rgba(5, 6, 7, 0.14)),
    linear-gradient(180deg, rgba(5, 6, 7, 0.5), transparent 34%, rgba(5, 6, 7, 0.8));
}

.chapter-veil::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(
    circle at 70% 40%,
    color-mix(in srgb, var(--accent) 9%, transparent),
    transparent 26rem
  );
  mix-blend-mode: screen;
}

.chapter--flip .chapter-veil {
  background:
    linear-gradient(270deg, rgba(5, 6, 7, 0.9), rgba(5, 6, 7, 0.34) 52%, rgba(5, 6, 7, 0.14)),
    linear-gradient(180deg, rgba(5, 6, 7, 0.5), transparent 34%, rgba(5, 6, 7, 0.8));
}

.chapter-inner {
  position: relative;
  z-index: 3;
  display: grid;
  grid-template-columns: minmax(300px, 0.92fr) minmax(360px, 1.08fr);
  align-items: center;
  gap: clamp(1.6rem, 4vw, 4rem);
  height: 100%;
  padding: 6.5rem var(--pad-x) 3.5rem;
}

.chapter--flip .chapter-copy {
  order: 2;
}

.chapter--flip .chapter-visual {
  order: 1;
}

.chapter-meta {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  color: var(--accent);
  margin-bottom: 1.2rem;
}

.chapter-meta span {
  display: inline-grid;
  place-items: center;
  padding: 0.22rem 0.5rem;
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  border-radius: 4px;
}

.chapter-title {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(3.2rem, 8.5vw, 7.5rem);
  line-height: 0.94;
  letter-spacing: -0.01em;
}

.chapter-lede {
  margin-top: 1.4rem;
  max-width: 33rem;
  font-size: clamp(0.98rem, 1.2vw, 1.08rem);
  line-height: 1.72;
  color: var(--soft);
}

.chapter-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 2rem;
  color: var(--muted);
}

.chapter-stats li {
  display: grid;
  gap: 0.18rem;
  min-width: 120px;
  padding: 0.8rem 0.95rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: rgba(5, 6, 7, 0.5);
  backdrop-filter: blur(12px);
}

.chapter-stats strong {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.12rem;
  letter-spacing: 0.01em;
  color: var(--ink);
}

/* MONO-CAPS REDUCTION (2026-09-06). Uppercase mono is reserved for eyebrows
   and telemetry/callouts. The chip VALUE keeps Clash Display as above; the
   label under it drops out of the mono system entirely - it is a caption, and
   three shouted captions per chapter over film was most of the page's noise.
   The <ul> keeps .mono so the chips still inherit the tracking for the value. */
.chapter-stats .stat-label {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 0.76rem;
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.35;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 55%, transparent);
}

.chapter-visual {
  position: relative;
  display: grid;
  place-items: center;
  min-height: min(78vh, 640px);
  perspective: 1100px;
}

/* always opaque — the canvas content is transparent (alpha renderer) and the
   robot sits off-screen until it drives in, so there's nothing to fade. A fade
   here would "materialise" the robot over the photo when the model loads late. */
.chapter-canvas {
  position: absolute;
  inset: 0;
  z-index: 3;
  width: 100%;
  height: 100%;
}

.chapter-visual.is-3d-ready .visual-ring,
.chapter-visual.is-3d-ready .chapter-render {
  opacity: 0;
}

.visual-ring {
  position: absolute;
  width: min(560px, 86%);
  aspect-ratio: 1;
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
  border-radius: 50%;
  transform: rotateX(72deg) rotateZ(0deg);
  transition: opacity 500ms ease;
  animation: ring-spin 30s linear infinite;
}

.visual-ring::after {
  content: "";
  position: absolute;
  inset: -14%;
  border: 1px solid rgba(244, 246, 240, 0.1);
  border-radius: 50%;
}

@keyframes ring-spin {
  to { transform: rotateX(72deg) rotateZ(360deg); }
}

.chapter-render {
  position: relative;
  z-index: 2;
  width: min(520px, 84%);
  max-height: 62vh;
  max-height: 62svh;
  object-fit: contain;
  filter: drop-shadow(0 44px 44px rgba(0, 0, 0, 0.5))
    drop-shadow(0 0 34px color-mix(in srgb, var(--accent) 20%, transparent));
  transition: opacity 500ms ease;
}

/* ============================================================
   FINALE
   ============================================================ */

.finale {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 100vh;
  min-height: 100svh;
  padding: 8rem 24px;
  overflow: hidden;
  background:
    radial-gradient(circle at 72% 36%, rgba(45, 244, 207, 0.12), transparent 26rem),
    radial-gradient(circle at 24% 70%, rgba(255, 184, 77, 0.1), transparent 24rem),
    var(--bg);
}

.finale-marquee {
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  transform: translateY(-50%);
  white-space: nowrap;
  pointer-events: none;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(5rem, 17vw, 15rem);
  letter-spacing: 0;
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1px rgba(244, 246, 240, 0.07);
}

.marquee-track {
  display: inline-flex;
  animation: marquee 46s linear infinite;
}

@keyframes marquee {
  to { transform: translateX(-50%); }
}

.finale-core {
  position: relative;
  z-index: 2;
  display: grid;
  justify-items: center;
  width: min(900px, 100%);
  text-align: center;
}

.finale-core .chapter-meta {
  justify-content: center;
}

.finale-title {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(2.7rem, 6.4vw, 5.4rem);
  line-height: 1.02;
  letter-spacing: -0.01em;
}

.finale-lede {
  margin-top: 1.5rem;
  max-width: 36rem;
  color: var(--soft);
  line-height: 1.72;
}

.finale-actions {
  display: flex;
  align-items: center;
  gap: 1.4rem;
  margin-top: 2.6rem;
}

.demo-link--large {
  min-height: 52px;
  padding: 0.85rem 1.8rem;
  font-size: 1rem;
}

.ghost-link {
  color: var(--muted);
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.3rem;
  transition: color 200ms ease, border-color 200ms ease;
}

.ghost-link:hover,
.ghost-link:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
}

/* ============================================================
   NO-MOTION FAILSAFE (scripts blocked / CDN down)
   ============================================================ */

body.no-motion .beat,
body.no-motion .hero-intro,
body.no-motion .site-header,
body.no-motion .hero-scrollcue,
body.no-motion .hero-hud {
  opacity: 1 !important;
  visibility: visible !important;
}

body.no-motion .beat {
  position: relative;
  inset: auto;
  transform: none;
  max-width: 36rem;
  margin: 3rem auto;
}

/* ============================================================
   RESPONSIVE
   ============================================================ */

@media (max-width: 1060px) {
  .chapter-inner {
    grid-template-columns: 1fr;
    align-content: center;
    gap: 1.2rem;
    padding-top: 6rem;
  }

  .chapter--flip .chapter-copy {
    order: 1;
  }

  .chapter--flip .chapter-visual {
    order: 2;
  }

  .chapter-visual {
    min-height: 300px;
  }

  .chapter-render {
    max-height: 38vh;
    max-height: 38svh;
  }

  .beat {
    max-width: min(34rem, 62vw);
  }
}

@media (max-width: 720px) {
  .cursor-light {
    display: none;
  }

  .site-header {
    padding: 0.9rem 16px 1.2rem;
  }

  .site-header nav {
    gap: 0.9rem;
  }

  .site-header nav a {
    font-size: 0;
  }

  .site-header nav a i {
    font-size: 0.7rem;
    margin: 0;
  }

  .brand-name {
    display: none;
  }

  .demo-link {
    min-height: 36px;
    padding: 0.5rem 0.9rem;
    font-size: 0.82rem;
  }

  .hero-wordmark {
    font-size: clamp(3.4rem, 21vw, 6.4rem);
  }

  .beat,
  .beat--left,
  .beat--right,
  .beat--center {
    left: 16px;
    right: 16px;
    top: auto;
    bottom: 9svh;
    transform: none;
    max-width: none;
    text-align: left;
  }

  .beat--center .beat-meta,
  .beat--center .beat-chips {
    justify-content: flex-start;
  }

  .beat--center .beat-copy {
    margin-inline: 0;
  }

  .beat-line {
    font-size: clamp(1.9rem, 8.6vw, 2.6rem);
  }

  .beat-copy {
    font-size: 0.94rem;
    line-height: 1.62;
  }

  .hero-hud {
    display: none;
  }

  .preloader-count {
    font-size: clamp(4rem, 22vw, 7rem);
  }

  .chapter-inner {
    padding-inline: 16px;
  }

  .chapter-stats li {
    min-width: calc(50% - 0.3rem);
  }

  .finale-actions {
    flex-direction: column;
    gap: 1.1rem;
  }
}

@media (hover: none) {
  .cursor-light {
    display: none;
  }
}

/* ============================================================
   MOBILE CHAPTER RECOMPOSITION (<=720px)
   The desktop composition drowned the machine on phones: title + lede +
   three stacked stat cards + note overflowed the 100svh sticky stage, the
   title parked under the fixed header for the whole hold and the machine
   sat below the fold. Mobile now leads with the MACHINE in a clear top
   band (under the header), then title, lede and a compact 3-across stat
   row. The note is cut — the chips carry it.
   ============================================================ */
@media (max-width: 720px) {
  .chapter-inner {
    align-content: start;
    gap: 0.4rem;
    padding-top: 4.6rem;
  }

  .chapter-copy,
  .chapter--flip .chapter-copy {
    order: 2;
  }

  .chapter-visual,
  .chapter--flip .chapter-visual {
    order: 1;
    min-height: 31svh;
    max-height: 31svh;
  }

  .chapter-render {
    max-height: 29svh;
  }

  .chapter-title {
    font-size: clamp(2.3rem, 10.5vw, 3rem);
  }

  .chapter-lede {
    margin-top: 0.7rem;
    font-size: 0.92rem;
    line-height: 1.55;
  }

  .chapter-stats {
    gap: 0.45rem;
    margin-top: 1rem;
  }

  .chapter-stats li {
    flex: 1 1 0;
    min-width: 0;
    padding: 0.55rem 0.55rem;
  }

  .chapter-stats strong {
    font-size: 1rem;
  }

  .chapter-stats li,
  .chapter-stats span {
    font-size: 0.62rem;
  }

  /* sentence-case Satoshi needs a touch more size than the mono caps did to
     stay legible at this scale on the deck */
  .chapter-stats .stat-label {
    font-size: 0.68rem;
    line-height: 1.25;
  }

  .chapter-note {
    display: none;
  }

  .spec-toggle {
    margin-top: 1.1rem;
  }

  /* Touch grammar: the page is a DECK with HARD STOPS. Every chapter is
     exactly one screen and every swipe lands the next section fully in the
     viewport (mandatory snap + snap-stop, on the root scroller — Lenis
     leaves touch native so CSS snap applies). Animations play themselves on
     arrival (script.js/hero.js `coarse` paths). Inside the hero, invisible
     .hero-snap markers make each ACT a stop of its own: swipe -> camera
     glides to the next hold -> stops -> the beat reads -> swipe again. */
  html {
    scroll-snap-type: y mandatory;
  }

  .hero {
    height: 550vh;
    height: 550svh;
  }

  .hero-snap {
    display: block;
    position: absolute;
    left: 0;
    top: calc(var(--sp) * 1vh);
    top: calc(var(--sp) * 1svh);
    width: 1px;
    height: 1px;
    scroll-snap-align: start;
    scroll-snap-stop: always;
    pointer-events: none;
  }

  .chapter {
    height: 100vh;
    height: 100svh;
    scroll-snap-align: start;
    scroll-snap-stop: always;
  }

  /* the stacked-cover overlap is a scrub gesture — a snapped deck has no
     partial overlap state, so the chapters must not eat each other */
  .chapter[data-entrance="cover"] {
    margin-top: 0;
  }

  .finale,
  #scale,
  #sustainability,
  #docking,
  #why,
  #demo {
    scroll-snap-align: start;
    scroll-snap-stop: always;
  }

  .site-footer,
  footer {
    scroll-snap-align: end;
  }

  /* the pinned horizontal walk fights mandatory snapping (a pinned section
     leaves the scroll coordinate space) — scale.js skips the pin on touch,
     and the fleet line becomes a sideways finger-scroll: see the override
     block at the END of this file (it must come after the base .scale-*
     rules or the cascade eats it). */
}

/* ============================================================
   REDUCED MOTION
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  .grain,
  .scrollcue-line::before,
  .hud-dot,
  .visual-ring,
  .marquee-track {
    animation: none !important;
  }

  html {
    scroll-behavior: auto;
  }
}

/* ---------------- chapter film (scroll-scrubbed) ---------------- */
/* Sits exactly where .chapter-bg sits, over the CSS photo. The photo stays
   underneath as the never-black fallback while the film loads or if it fails. */
/* Exactly 100%, not 106%. The oversize was copied from .chapter-bg, which needs
   the extra so it has room to parallax — the film does not parallax, so all the
   oversize did was crop 6% off every shot for nothing. Every film is 16:9, so at
   100% on a 16:9 viewport the whole frame is visible. Any further movement is
   done with a transform (see script.js), which scales from the centre and can be
   eased, rather than being baked into the layout. */
.chapter-film {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  filter: saturate(0.92) contrast(1.05);
  opacity: 0;
  transition: opacity 520ms ease;
  pointer-events: none;
  will-change: transform;
}

.chapter.is-film-ready .chapter-film { opacity: 1; }

/* film chapters carry the product in-frame, so the copy gets a single column
   with room to breathe instead of sharing the grid with a render panel */
.chapter--film .chapter-inner {
  grid-template-columns: minmax(0, 52ch);
  align-content: center;
}

.chapter--film.chapter--flip .chapter-inner {
  justify-content: end;
  text-align: right;
}

.chapter--film.chapter--flip .chapter-meta { justify-content: flex-end; }
.chapter--film.chapter--flip .chapter-stats { justify-content: flex-end; }

@media (max-width: 860px) {
  .chapter--film .chapter-inner,
  .chapter--film.chapter--flip .chapter-inner {
    grid-template-columns: minmax(0, 1fr);
    justify-content: start;
    text-align: left;
  }
  .chapter--film.chapter--flip .chapter-meta,
  .chapter--film.chapter--flip .chapter-stats { justify-content: flex-start; }
}

/* brand lockup — the real i-cobots wordmark replaces the mark+text placeholder */
.brand-logo {
  display: block;
  width: clamp(104px, 9vw, 136px);
  height: auto;
}

/* supporting detail under the stat chips — the "why it matters" line */
.chapter-note {
  margin-top: 1.35rem;
  max-width: 46ch;
  font-size: 0.92rem;
  line-height: 1.65;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 62%, transparent);
}

.chapter--flip .chapter-note { margin-left: auto; }

@media (max-width: 860px) {
  .chapter-note { font-size: 0.86rem; }
  .chapter--flip .chapter-note { margin-left: 0; }
}

/* three chips always sit as one row on film chapters — ragged wrapping made the
   right-aligned (flip) sections look accidental */
.chapter--film .chapter-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.6rem;
}

.chapter--film .chapter-stats li { min-width: 0; }

@media (max-width: 700px) {
  .chapter--film .chapter-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---- film chapters on narrow screens ----
   A 16:9 film in a portrait viewport gets centre-cropped hard, and centred copy
   then sits straight on top of the machine. Push the copy to the bottom third
   and weight the scrim to match, so the upper half stays clean film. */
@media (max-width: 700px) {
  .chapter--film .chapter-inner,
  .chapter--film.chapter--flip .chapter-inner {
    align-content: end;
    padding-bottom: 5rem;
  }

  .chapter--film .chapter-veil,
  .chapter--film.chapter--flip .chapter-veil {
    background: linear-gradient(
      180deg,
      rgba(5, 6, 7, 0.42) 0%,
      rgba(5, 6, 7, 0.12) 26%,
      rgba(5, 6, 7, 0.82) 62%,
      rgba(5, 6, 7, 0.95) 100%
    );
  }

  /* bias the crop upward so the machine sits in the visible band, not under
     the copy */
  .chapter-film { object-position: 50% 34%; }

  .chapter--film .chapter-note { display: none; } /* spec chips carry it on mobile */
}

/* ---------------- brochure specs ---------------- */

.spec-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 1.5rem;
  padding: 0.62rem 1.15rem 0.62rem 0.95rem;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 9%, rgba(5, 6, 7, 0.5));
  color: var(--ink, #f4f6f0);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 220ms ease, border-color 220ms ease, transform 220ms ease;
}

.spec-toggle:hover {
  background: color-mix(in srgb, var(--accent) 20%, rgba(5, 6, 7, 0.5));
  border-color: var(--accent);
}

.spec-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* a "+" drawn from two bars so it can rotate into an "x" when open */
.spec-toggle-mark {
  position: relative;
  width: 11px;
  height: 11px;
  flex: none;
}
.spec-toggle-mark::before,
.spec-toggle-mark::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto 0;
  height: 1.5px;
  background: var(--accent);
  transition: transform 260ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
.spec-toggle-mark::after { transform: rotate(90deg); }
.spec-toggle[aria-expanded="true"] .spec-toggle-mark::before { transform: rotate(135deg); }
.spec-toggle[aria-expanded="true"] .spec-toggle-mark::after { transform: rotate(45deg); }

/* The panel sits INSIDE .chapter-stage and above .chapter-inner, so opening it
   changes nothing about document flow — the sticky stages and their stacked
   overlap transitions stay exactly as they were. */
.spec-panel {
  position: absolute;
  inset: 0;
  z-index: 8;
  display: grid;
  place-items: center;
  padding: 5.5rem var(--pad-x) 3rem;
  background: rgba(4, 6, 8, 0.9);
  backdrop-filter: blur(18px) saturate(0.9);
  opacity: 0;
  transition: opacity 300ms ease;
}

.spec-panel[hidden] { display: none; }
.spec-panel.is-open { opacity: 1; }

.spec-panel-inner {
  width: min(100%, 62rem);
  max-height: 100%;
  overflow-y: auto;
  transform: translateY(14px);
  transition: transform 380ms cubic-bezier(0.2, 0.7, 0.3, 1);
}

.spec-panel.is-open .spec-panel-inner { transform: none; }

.spec-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 0.9rem;
  margin-bottom: 1.4rem;
  border-bottom: 1px solid rgba(244, 246, 240, 0.14);
}

.spec-panel-head p {
  color: var(--accent);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.spec-close {
  flex: none;
  width: 38px;
  height: 38px;
  border: 1px solid rgba(244, 246, 240, 0.2);
  border-radius: 50%;
  background: transparent;
  color: var(--ink, #f4f6f0);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  transition: background 200ms ease, border-color 200ms ease;
}
.spec-close:hover { background: rgba(244, 246, 240, 0.1); border-color: var(--accent); }
.spec-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.spec-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.15rem 2.6rem;
}

.spec-row {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: 1rem;
  align-items: baseline;
  padding: 0.72rem 0;
  border-bottom: 1px solid rgba(244, 246, 240, 0.08);
}

.spec-row dt {
  font-size: 0.76rem;
  letter-spacing: 0.04em;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 55%, transparent);
}

.spec-row dd {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.45;
  color: var(--ink, #f4f6f0);
}

.spec-source {
  margin-top: 1.5rem;
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 34%, transparent);
}

@media (max-width: 860px) {
  .spec-grid { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .spec-panel { padding: 4.5rem 1.25rem 2rem; }
  .spec-row { grid-template-columns: minmax(0, 1fr); gap: 0.15rem; padding: 0.6rem 0; }
}

/* ================= substance sections (post-film) ================= */
/* These are ordinary flow sections — deliberately NOT sticky. The film is the
   pitch; everything below it is for someone who has decided to evaluate, and
   that reader wants to scroll normally and scan. */

.slab {
  position: relative;
  z-index: 21;
  background: var(--bg);
  padding: clamp(5rem, 11vh, 8.5rem) var(--pad-x);
  border-top: 1px solid rgba(244, 246, 240, 0.08);
}

.slab-inner { width: min(100%, 74rem); margin-inline: auto; }

.slab-title {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(2rem, 4.6vw, 3.4rem);
  line-height: 1.04;
  letter-spacing: -0.02em;
  margin-bottom: 1.1rem;
}

.slab-lede {
  max-width: 54ch;
  font-size: clamp(1rem, 1.35vw, 1.12rem);
  line-height: 1.6;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 72%, transparent);
}

.slab-foot {
  margin-top: 2.6rem;
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 34%, transparent);
}

/* ---- resource-use pillars ---- */
.slab-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1px;
  margin-top: 3.4rem;
  background: rgba(244, 246, 240, 0.09);
  border: 1px solid rgba(244, 246, 240, 0.09);
}

.pillar { background: var(--bg); padding: 2.1rem 1.7rem 2.3rem; }

.pillar-figure {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.7rem, 2.7vw, 2.3rem);
  line-height: 1;
  color: var(--accent);
  margin-bottom: 1rem;
}

.pillar h3 {
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  margin-bottom: 0.6rem;
}

.pillar p {
  font-size: 0.88rem;
  line-height: 1.6;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 62%, transparent);
}

/* ---- workstation vs charging station ---- */
.compare {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
  gap: 1.2rem;
  margin-top: 3.4rem;
}

.compare-card {
  padding: 2.2rem 2rem 2.4rem;
  border: 1px solid rgba(244, 246, 240, 0.12);
  border-radius: 14px;
  background: rgba(244, 246, 240, 0.02);
}

/* the workstation is the one that actually removes labour — say so visually */
.compare-card--lead {
  border-color: color-mix(in srgb, var(--accent) 42%, transparent);
  background: color-mix(in srgb, var(--accent) 6%, rgba(244, 246, 240, 0.02));
}

.compare-tag {
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 1rem;
}

.compare-card h3 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.2rem, 2vw, 1.55rem);
  margin-bottom: 0.85rem;
}

.compare-card h3 em { font-style: italic; color: var(--accent); }

.compare-card > p {
  font-size: 0.9rem;
  line-height: 1.62;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 66%, transparent);
}

.compare-list { margin-top: 1.6rem; border-top: 1px solid rgba(244, 246, 240, 0.1); }

.compare-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid rgba(244, 246, 240, 0.07);
  font-size: 0.85rem;
}

.compare-list strong { font-weight: 600; }

.compare-list span {
  font-size: 0.76rem;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 52%, transparent);
  text-align: right;
}

/* ---- why i-cobots ----
   Four compact points, hairline-separated the same way .slab-grid separates
   the resource-use pillars, but lighter: these are a heading and one sentence
   each, not a figure and a paragraph, so they get no border box of their own. */
.why-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 2.2rem 2.6rem;
  margin-top: 3rem;
}

.why-point h3 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.14rem;
  line-height: 1.2;
  letter-spacing: -0.01em;
  padding-top: 1.1rem;
  border-top: 1px solid color-mix(in srgb, var(--accent) 42%, transparent);
}

.why-point p {
  margin-top: 0.7rem;
  max-width: 34ch;
  font-size: 0.92rem;
  line-height: 1.65;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 62%, transparent);
}

/* CLIENT TO SUPPLY. Ships with `hidden` on it, so this only ever paints once
   real customer names/logos/a quote arrive. Same convention as the footer's
   unfilled company details: [data-placeholder] italicises and mutes it. */
.why-proof {
  margin-top: 3.2rem;
  padding-top: 1.6rem;
  border-top: 1px solid rgba(244, 246, 240, 0.09);
}

.why-proof[hidden] { display: none; }

.why-proof-heading {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.9rem;
  margin-bottom: 0.5rem;
  font-style: normal;
}

.why-proof p { font-size: 0.88rem; }

/* ---- demo form ---- */
.demo-form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.2rem 1.6rem;
  margin-top: 3.2rem;
  max-width: 52rem;
}

.field { display: flex; flex-direction: column; gap: 0.45rem; }
.field--wide { grid-column: 1 / -1; }

.field label {
  font-size: 0.76rem;
  letter-spacing: 0.06em;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 62%, transparent);
}

.field .opt { color: color-mix(in srgb, var(--ink, #f4f6f0) 34%, transparent); }

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 0.78rem 0.9rem;
  border: 1px solid rgba(244, 246, 240, 0.16);
  border-radius: 9px;
  background: rgba(244, 246, 240, 0.03);
  color: var(--ink, #f4f6f0);
  font: inherit;
  font-size: 0.94rem;
  transition: border-color 200ms ease, background 200ms ease;
}

.field textarea { resize: vertical; min-height: 6.5rem; }

.field select { appearance: none; cursor: pointer; }
.field select option { background: #0b0f12; color: #f4f6f0; }

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--accent);
  background: rgba(244, 246, 240, 0.06);
}

.field input:user-invalid,
.field select:user-invalid,
.field textarea:user-invalid { border-color: #ff6f59; }

.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.4rem;
  margin-top: 0.6rem;
}

.demo-form button[type="submit"] { border: 0; cursor: pointer; font: inherit; }

/* MONO-CAPS REDUCTION (2026-09-06). Uppercase mono is for eyebrows and
   telemetry. The reassurance under the button, the privacy line and the footer
   column headings are body copy - shouting them made a quiet page loud. The
   `mono` class was removed from all three in index.html; these rules carry the
   size and colour that class used to supply. */
.form-note {
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 50%, transparent);
}

.form-note[data-state="error"] { color: #ff8a78; }
.form-note[data-state="ok"] { color: var(--accent); }

/* its own row in the form grid, under the button + note */
.form-privacy {
  margin: 0.1rem 0 0;
  text-align: center;
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 40%, transparent);
}

/* ================= footer ================= */
.site-footer {
  position: relative;
  z-index: 21;
  background: #04070a;
  border-top: 1px solid rgba(244, 246, 240, 0.1);
  padding: clamp(3.4rem, 7vh, 5rem) var(--pad-x) 2rem;
}

.footer-inner {
  width: min(100%, 74rem);
  margin-inline: auto;
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) repeat(3, minmax(0, 1fr));
  gap: 2.6rem 2rem;
}

.footer-brand .brand-logo { margin-bottom: 1.3rem; }

.footer-line {
  max-width: 34ch;
  font-size: 0.85rem;
  line-height: 1.6;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 58%, transparent);
}

.footer-oem {
  margin-top: 0.9rem;
  font-size: 0.78rem;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 40%, transparent);
}

.footer-heading {
  font-family: var(--font-body);
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--accent);
  margin-bottom: 1.1rem;
}

.footer-nav li,
.footer-contact li { margin-bottom: 0.6rem; font-size: 0.87rem; }

.footer-nav a,
.footer-contact a {
  color: color-mix(in srgb, var(--ink, #f4f6f0) 72%, transparent);
  text-decoration: none;
  transition: color 180ms ease;
}

.footer-nav a:hover,
.footer-contact a:hover { color: var(--ink, #f4f6f0); }

/* unmistakable in the browser, invisible in intent — these are unfilled */
[data-placeholder] {
  color: color-mix(in srgb, var(--ink, #f4f6f0) 34%, transparent);
  font-style: italic;
}

.footer-base {
  width: min(100%, 74rem);
  margin: 3.2rem auto 0;
  padding-top: 1.6rem;
  border-top: 1px solid rgba(244, 246, 240, 0.08);
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 1.6rem;
  justify-content: space-between;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 40%, transparent);
}

/* scroll is frozen while a spec panel is open */
.specs-open { overflow: hidden; }

@media (max-width: 860px) {
  .demo-form { grid-template-columns: minmax(0, 1fr); }
  .footer-inner { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .footer-brand { grid-column: 1 / -1; }
}

@media (max-width: 520px) {
  .footer-inner { grid-template-columns: minmax(0, 1fr); }
}

/* ================= fleet menu ================= */

.nav-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin-left: auto;
  margin-right: 0.9rem;
  padding: 0.55rem 1.05rem;
  border: 1px solid rgba(244, 246, 240, 0.22);
  border-radius: 999px;
  background: rgba(8, 11, 14, 0.5);
  backdrop-filter: blur(8px);
  color: var(--ink, #f4f6f0);
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color 200ms ease, background 200ms ease;
}

.nav-toggle:hover { border-color: var(--accent); background: rgba(8, 11, 14, 0.75); }
.nav-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.nav-toggle-bars { display: grid; gap: 3px; width: 13px; }
.nav-toggle-bars i {
  display: block;
  height: 1.5px;
  background: var(--accent);
  transition: transform 250ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
.nav-toggle[aria-expanded="true"] .nav-toggle-bars i:first-child { transform: translateY(2.2px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars i:last-child { transform: translateY(-2.2px) rotate(-45deg); }

.fleet-menu {
  position: fixed;
  inset: 0;
  z-index: 210;
  display: grid;
  place-items: start center;
  padding: 6.5rem var(--pad-x) 3rem;
  overflow-y: auto;
  background: rgba(4, 6, 8, 0.94);
  backdrop-filter: blur(20px) saturate(0.9);
  opacity: 0;
  transition: opacity 320ms ease;
}

.fleet-menu[hidden] { display: none; }
.fleet-menu.is-open { opacity: 1; }

.fleet-menu-inner {
  width: min(100%, 88rem);
  transform: translateY(16px);
  transition: transform 400ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
.fleet-menu.is-open .fleet-menu-inner { transform: none; }

.fleet-menu-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 1rem;
  margin-bottom: 1.8rem;
  border-bottom: 1px solid rgba(244, 246, 240, 0.14);
}

.fleet-menu-head p {
  color: var(--accent);
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.fleet-close {
  width: 40px;
  height: 40px;
  border: 1px solid rgba(244, 246, 240, 0.2);
  border-radius: 50%;
  background: transparent;
  color: var(--ink, #f4f6f0);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  transition: background 200ms ease, border-color 200ms ease;
}
.fleet-close:hover { background: rgba(244, 246, 240, 0.1); border-color: var(--accent); }
.fleet-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* The menu is a full-viewport overlay, so it should look like one: a contact
   sheet of the fleet, not a row of 120px stamps with two thirds of the screen
   left dark. Each entry is a 16:10 still with the name and the space it suits
   underneath - auto-fill lands on 4 columns at 1600, 3 at 1024, 2 at tablet. */
.fleet-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: clamp(1.1rem, 2vh, 1.9rem) 1.4rem;
}

.fleet-list a {
  display: block;
  text-decoration: none;
  color: var(--ink, #f4f6f0);
  border-radius: 13px;
  outline-offset: 4px;
}

.fleet-shot {
  display: block;
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-radius: 12px;
  background: #0a0e11;
  border: 1px solid rgba(244, 246, 240, 0.09);
  transition: border-color 240ms ease;
}

.fleet-list img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.85) contrast(1.05) brightness(0.86);
  transform: scale(1.001);
  transition: transform 620ms var(--ease-expo), filter 300ms ease;
}

.fleet-num {
  position: absolute;
  top: 0.7rem;
  left: 0.8rem;
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 72%, transparent);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.7);
  transition: color 200ms ease;
}

.fleet-list a:hover .fleet-shot,
.fleet-list a:focus-visible .fleet-shot { border-color: var(--accent); }
.fleet-list a:hover img,
.fleet-list a:focus-visible img {
  transform: scale(1.045);
  filter: saturate(0.95) contrast(1.05) brightness(1);
}
.fleet-list a:hover .fleet-num,
.fleet-list a:focus-visible .fleet-num { color: var(--accent); }
.fleet-list a:hover .fleet-name,
.fleet-list a:focus-visible .fleet-name { color: var(--accent); }

.fleet-name {
  display: block;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.15rem, 1.5vw, 1.42rem);
  line-height: 1.15;
  margin-top: 0.75rem;
  transition: color 200ms ease;
}

.fleet-kicker {
  display: block;
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 46%, transparent);
  margin-top: 0.3rem;
}

.fleet-more {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 2rem;
  margin-top: 2.2rem;
  padding-top: 1.6rem;
  border-top: 1px solid rgba(244, 246, 240, 0.12);
}

.fleet-more a {
  font-size: 0.86rem;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 70%, transparent);
  text-decoration: none;
  transition: color 180ms ease;
}
.fleet-more a:hover { color: var(--accent); }

.menu-open { overflow: hidden; }

@media (max-width: 860px) {
  .fleet-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 620px) {
  .nav-toggle { padding: 0.5rem 0.85rem; font-size: 0.64rem; }
  .fleet-menu { padding: 5.5rem 1.25rem 2.5rem; }
  .fleet-list { grid-template-columns: minmax(0, 1fr); }
  /* one column of 16:10 stills would run seven tall cards; a wider band keeps
     the whole fleet within a couple of thumb-flicks */
  .fleet-shot { aspect-ratio: 2 / 1; }
  .fleet-name { font-size: 1.2rem; margin-top: 0.55rem; }
}


/* Depth dim — painted over an outgoing chapter as the next one covers it, so
   the stack reads as cards receding rather than sections simply vanishing.
   Created by script.js so it needs no markup change. An overlay is used instead
   of filter: brightness() because a filter on the sticky stage would make it a
   containing block and costs far more to composite. */
.chapter-dim {
  position: absolute;
  inset: 0;
  z-index: 9;
  background: #04070a;
  opacity: 0;
  pointer-events: none;
}

/* ---- mid-page CTA (2026-09-06) ----
   Lives INSIDE a chapter's sticky .chapter-stage (build.py's optional "cta"
   block), bottom-anchored. It is deliberately NOT a band between two sections:
   every chapter after the first carries margin-top:-100svh, so a sibling in
   between would become the surface the NEXT chapter's cover entrance climbs
   over and the choreography would break.
   z-index 10 is ABOVE .chapter-dim (9) on purpose — as the chapter recedes the
   picture dims and the invitation stays lit, until the next chapter paints
   over it. Centred with auto margins rather than translateX(-50%) so GSAP's
   y-tween owns the transform outright. Visible by default: script.js sets the
   hidden state, so a no-JS or reduced-motion load still shows it. */
.chapter-cta {
  position: absolute;
  left: var(--pad-x);
  right: var(--pad-x);
  bottom: clamp(20px, 5vh, 48px);
  z-index: 10;
  width: fit-content;
  max-width: min(34rem, 100%);
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.85rem;
  padding: 1.05rem 1.7rem 1.15rem;
  border: 1px solid var(--line);
  border-top: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  border-radius: 14px;
  background: rgba(5, 6, 7, 0.62);
  backdrop-filter: blur(14px);
  text-align: center;
}

.chapter-cta-line {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.05rem, 1.5vw, 1.3rem);
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--ink, #f4f6f0);
}

.chapter-cta-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.5rem 1.3rem;
}

.chapter-cta-more {
  font-size: 0.82rem;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 55%, transparent);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color 180ms ease, border-color 180ms ease;
}

.chapter-cta-more:hover {
  color: var(--ink, #f4f6f0);
  border-bottom-color: color-mix(in srgb, var(--ink, #f4f6f0) 40%, transparent);
}

/* On the deck the stage is a single 100svh panel and the copy runs down it, so
   the card becomes a compact strip pinned to the foot of the panel. */
@media (max-width: 720px) {
  .chapter-cta {
    left: 12px;
    right: 12px;
    bottom: 1.2svh;
    width: auto;
    max-width: none;
    gap: 0.4rem;
    padding: 0.6rem 0.9rem 0.7rem;
  }

  .chapter-cta-line { font-size: 0.98rem; }
  .chapter-cta-more { font-size: 0.78rem; }

  /* The deck panel is exactly 100svh and the copy runs to the foot of it, so a
     chapter that also carries a CTA has to make room for the card or it lands
     on the "Full specifications" pill (1px of clearance, measured). Tighten the
     lead-in under the readout strip - but not past 3.4rem: the strip is
     absolutely positioned and 56px tall, so anything smaller slides the eyebrow
     underneath it and the eyebrow simply disappears. */
  .chapter--story .chapter-stage:has(.chapter-cta) .chapter-inner {
    padding-top: calc(4.6rem + 28svh + 3.4rem);
  }

  .chapter--story .chapter-stage:has(.chapter-cta) .spec-toggle {
    margin-top: 0.85rem;
  }
}

/* ============================================================
   THE FLEET, TO SCALE — the one section on paper.
   Machines are charcoal: they read as hardware on a light ground
   and as silhouettes on a dark one, so this section inverts. The
   fixed header inverts with it via .site-header.on-light (scale.js).
   ============================================================ */

.scale {
  position: relative;
  z-index: 21; /* above the last chapter's stacked-cover stack */
  background: #e7e4dc;
  color: #101318;
  background-image:
    linear-gradient(rgba(1, 162, 246, 0.10) 1px, transparent 1px),
    linear-gradient(90deg, rgba(1, 162, 246, 0.10) 1px, transparent 1px);
  background-size: 72px 72px;
}

.scale-rail {
  --scale-base: 168px; /* floor line height above the rail bottom; read by JS */
  position: relative;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}

.scale-head {
  position: absolute;
  z-index: 3;
  top: clamp(5rem, 12vh, 8rem);
  left: var(--pad-x, 24px);
  right: var(--pad-x, 24px);
  max-width: min(44rem, 66vw);
  pointer-events: none;
}

.scale-kicker {
  font-size: 0.68rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #018fe0;
  margin-bottom: 1.1rem;
}

.scale-title {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(2.6rem, 6vw, 5rem);
  line-height: 0.96;
  letter-spacing: -0.01em;
  color: #101318;
}

.scale-lede {
  margin-top: 1.2rem;
  max-width: 34rem;
  font-size: clamp(0.98rem, 1.15vw, 1.06rem);
  line-height: 1.65;
  color: #4c5158;
}

.scale-track {
  position: absolute;
  left: 0;
  bottom: var(--scale-base);
  display: flex;
  align-items: flex-end;
  gap: clamp(1.75rem, 4.5vw, 5rem);
  padding: 0 var(--pad-x, 24px);
  will-change: transform;
}

.scale-baseline {
  position: absolute;
  left: 0;
  right: 0;
  bottom: var(--scale-base);
  height: 1px;
  background: #101318;
  z-index: 2;
}
.scale-baseline::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 7px;
  background-image: linear-gradient(to right, #101318 1px, transparent 1px);
  background-size: 2.5rem 100%;
  opacity: 0.3;
}

.scale-hint {
  position: absolute;
  right: var(--pad-x, 24px);
  top: clamp(5rem, 12vh, 8rem);
  margin: 0;
  z-index: 4;
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #8a8f96;
}

.scale-unit {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

.scale-art {
  position: relative;
  height: calc(var(--mm) * var(--ppm, 0.3px)); /* real millimetres drive height */
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
.scale-art img {
  position: relative;
  z-index: 1;
  height: 100%;
  width: auto;
  max-width: none;
  filter: drop-shadow(0 6px 5px rgba(15, 17, 20, 0.16));
}
.scale-art svg {
  height: 100%;
  width: auto;
  aspect-ratio: 60 / 180;
  fill: #101318;
  opacity: 0.16;
}
/* contact ellipse — what makes a cutout stand ON the line, not near it */
.scale-unit:not(.scale-unit--human) .scale-art::after {
  content: "";
  position: absolute;
  z-index: 0;
  left: 50%;
  bottom: -2px;
  width: 82%;
  height: 12px;
  transform: translateX(-50%);
  background: radial-gradient(
    ellipse 50% 50% at 50% 50%,
    rgba(15, 17, 20, 0.36) 0%,
    rgba(15, 17, 20, 0.15) 52%,
    transparent 72%
  );
}
/* blue scan ring under whichever machine the caliper is measuring */
.scale-unit:not(.scale-unit--human) .scale-art::before {
  content: "";
  position: absolute;
  z-index: 0;
  left: 50%;
  bottom: -5px;
  width: 108%;
  height: 18px;
  transform: translateX(-50%);
  border-radius: 50%;
  border: 1px solid rgba(1, 162, 246, 0.55);
  opacity: 0;
  transition: opacity 0.3s;
}
.scale-unit.is-active .scale-art::before {
  opacity: 1;
}

.scale-meta {
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  padding-top: 0.8rem;
  text-align: center;
  width: max-content;
  max-width: 12rem;
}

.scale-mm {
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: #018fe0;
  margin: 0;
  font-variant-numeric: tabular-nums;
}
.scale-mm::after {
  content: " MM";
  opacity: 0.55;
}
.scale-name {
  font-family: var(--font-display);
  font-weight: 500;
  text-transform: none;
  font-size: clamp(0.95rem, 1.4vw, 1.15rem);
  margin: 0.25rem 0 0;
  white-space: nowrap;
  color: #8a8f96;
  transition: color 0.3s;
}
.scale-unit.is-active .scale-name {
  color: #101318;
}
.scale-use {
  font-size: 0.52rem;
  line-height: 1.5;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #8a8f96;
  margin: 0.3rem auto 0;
  max-width: 16ch;
}
.scale-unit--human .scale-mm,
.scale-unit--human .scale-name {
  color: #8a8f96;
}

.scale-caliper {
  position: absolute;
  bottom: var(--scale-base);
  z-index: 3;
  pointer-events: none;
  height: 0;
  opacity: 0;
  transform: translateX(var(--capx, 0px));
  transition: opacity 0.25s;
}
.scale-caliper i {
  position: absolute;
  left: 0;
  bottom: 0;
  width: var(--capw, 0px);
  height: 1px;
  background: #01a2f6;
}
.scale-caliper i::before,
.scale-caliper i::after {
  content: "";
  position: absolute;
  bottom: 0;
  width: 1px;
  height: 20px;
  background: #01a2f6;
}
.scale-caliper i::before { left: 0; }
.scale-caliper i::after { right: 0; }

/* the fixed chrome inverts while the paper section is under it */
.site-header.on-light .demo-link,
.site-header.on-light .nav-toggle {
  color: #101318;
  border-color: rgba(16, 19, 24, 0.5);
}
.site-header.on-light .nav-toggle-bars i {
  background: #101318;
}
.progress.on-light {
  background: rgba(16, 19, 24, 0.14);
}

@media (max-width: 720px) {
  .scale-head {
    max-width: none;
  }
  .scale-use {
    display: none;
  }
  .scale-hint {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .scale-rail {
    height: auto;
    overflow-x: auto;
    padding-bottom: 3rem;
  }
  .scale-track {
    position: relative;
    bottom: auto;
    margin-top: 3rem;
  }
  .scale-head {
    position: relative;
    top: auto;
    padding: 5rem 0 0;
  }
  .scale-caliper {
    display: none;
  }
}

/* ============================================================
   MOBILE FLEET-TO-SCALE — must stay AFTER the base .scale-* rules.
   The deck card keeps the desktop composition (head on top, machines on
   the survey line at the bottom of a full-screen rail), but the walk is a
   native sideways finger-scroll: the absolutely-positioned track shrinks
   to the rail's width unless given its real content width, which is why
   width: max-content is load-bearing — without it nothing overflows and
   the fleet beyond the first machines is unreachable.
   ============================================================ */
@media (max-width: 720px) {
  .scale-rail {
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
  }

  .scale-track {
    width: max-content;
  }
}


/* ============================================================
   SKIP LINK, FORM CONFIRMATION, CHAPTER RAIL
   ============================================================ */

/* The page is ~50 screens of scroll-driven film before the form. A keyboard
   visitor gets one tab to jump the whole thing. */
.skip-link {
  position: fixed;
  top: 0;
  left: 50%;
  z-index: 400;
  transform: translate(-50%, -140%);
  padding: 0.75rem 1.4rem;
  border: 1px solid var(--accent);
  border-radius: 0 0 10px 10px;
  background: #050607;
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  transition: transform 220ms var(--ease-expo);
}
.skip-link:focus {
  transform: translate(-50%, 0);
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

#demo:focus { outline: none; }

/* ---------------- demo form: sent ---------------- */
.form-done {
  max-width: 34rem;
  margin-top: 0.5rem;
  padding: 2rem 2rem 1.7rem;
  border: 1px solid rgba(1, 162, 246, 0.4);
  border-radius: 16px;
  background: linear-gradient(
    180deg,
    rgba(1, 162, 246, 0.09),
    rgba(244, 246, 240, 0.02)
  );
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 320ms ease, transform 420ms var(--ease-expo);
}
/* the form is display:grid, which outranks the UA [hidden] rule - without
   this it stays on screen behind the confirmation */
.demo-form[hidden] { display: none; }
.form-done[hidden] { display: none; }
.form-done.is-open { opacity: 1; transform: none; }

.form-done-mark {
  margin: 0 0 0.9rem;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border: 1px solid var(--accent);
  border-radius: 50%;
  color: var(--accent);
  font-size: 0.9rem;
  letter-spacing: 0;
}

.form-done-title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.25rem, 2.2vw, 1.7rem);
  line-height: 1.2;
}

.form-done-lede {
  margin: 0.7rem 0 1.4rem;
  font-size: 0.94rem;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 66%, transparent);
}
.form-done-lede a { color: var(--accent); }

.form-done button[data-form-restart] {
  border: 0;
  padding: 0;
  background: none;
  cursor: pointer;
}

/* mail-fallback confirmation (form.js): reopen the draft / copy its text */
.form-done-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.4rem;
  margin-top: 1.6rem;
}
.form-done-actions button.ghost-link {
  border: 0;
  padding: 0;
  background: none;
  cursor: pointer;
}
.form-done-copy {
  width: 100%;
  min-height: 9rem;
  padding: 0.9rem 1rem;
  border: 1px solid rgba(244, 246, 240, 0.16);
  border-radius: 10px;
  background: rgba(244, 246, 240, 0.04);
  color: var(--soft);
  font: 0.85rem/1.55 var(--font-mono);
  resize: vertical;
}

/* ---------------- chapter rail ----------------
   Built by rail.js from the seven chapter ids. Quiet by design: 6px dots on
   the right edge, the number revealed on hover or focus, accent for the one
   you are in. Desktop only, and it retires once the film is over. */
.chapter-rail {
  position: fixed;
  top: 50%;
  right: clamp(14px, 1.6vw, 26px);
  z-index: 190;
  transform: translateY(-50%);
  opacity: 1;
  transition: opacity 280ms ease;
}

.chapter-rail-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 14px;
  justify-items: end;
}

.chapter-rail-dot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 3px 2px 3px 12px;
  text-decoration: none;
  border-radius: 20px;
}

.chapter-rail-num {
  font-size: 0.58rem;
  letter-spacing: 0.14em;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 55%, transparent);
  opacity: 0;
  transform: translateX(4px);
  transition: opacity 200ms ease, transform 260ms var(--ease-expo), color 200ms ease;
}

.chapter-rail-mark {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(244, 246, 240, 0.28);
  box-shadow: 0 0 0 0 rgba(1, 162, 246, 0);
  transition: background 240ms ease, box-shadow 320ms ease, transform 320ms var(--ease-expo);
}

.chapter-rail-dot:hover .chapter-rail-num,
.chapter-rail-dot:focus-visible .chapter-rail-num { opacity: 1; transform: none; }
.chapter-rail-dot:hover .chapter-rail-mark { background: rgba(244, 246, 240, 0.7); }
.chapter-rail-dot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.chapter-rail-dot.is-active .chapter-rail-mark {
  background: var(--accent);
  transform: scale(1.4);
  box-shadow: 0 0 0 4px rgba(1, 162, 246, 0.16);
}
.chapter-rail-dot.is-active .chapter-rail-num { opacity: 1; transform: none; color: var(--accent); }

/* out of the way of the dialogs (nav.js sets .menu-open, specs.js .specs-open
   on <html>), and gone once the film hands over to the scale strip */
.chapter-rail.is-retired,
.menu-open .chapter-rail,
.specs-open .chapter-rail {
  opacity: 0;
  pointer-events: none;
}

@media (max-width: 1023px) {
  .chapter-rail { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .chapter-rail,
  .chapter-rail-mark,
  .chapter-rail-num,
  .skip-link,
  .form-done { transition: none; }
  .chapter-rail-dot.is-active .chapter-rail-mark { transform: none; }
  .chapter-rail .chapter-rail-num { opacity: 1; transform: none; }
}

/* ============================================================
   STORY CHAPTERS — clip + telemetry overlay
   A story chapter replaces the orbit stage with a full-bleed clip of the
   machine doing a real job, and draws the survey-drawing overlay
   (planned lane / actual track / coverage ribbon / obstacle brackets /
   status readout) on top of it. Markup is emitted by build.py from the
   "story" field in content/products.json; the renderer is story.js and the
   cue-sheet format is documented at the top of that file.

   The stage is the same sticky 100svh .chapter-stage as every other chapter,
   so the stacked-cover entrances, the dim-out and the parallax in script.js
   are untouched. .chapter-bg stays underneath as the never-black fallback —
   build.py points --scene at the clip's own poster frame, so the still and the
   clip's first frame are the same picture and there is no flash on load.
   ============================================================ */

.chapter--story .story-stage {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
}

.story-film {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  /* only the pre-measure state: story.js writes left/top/width/height in px
     from the same cover maths that positions the overlay, so the picture's crop
     and the overlay's transform cannot drift apart (and a `wide.zoom` in the
     cue sheet can push the box past the stage) */
  object-fit: cover;
  object-position: 50% 50%;
  filter: saturate(0.9) contrast(1.06) brightness(0.94);
  opacity: 0;
  transition: opacity 520ms ease;
  pointer-events: none;
}

.chapter--story[data-story-ready] .story-film { opacity: 1; }

/* dark scrim — weighted toward the copy column, plus a global floor so the
   accent strokes stay legible over a bright shelf or a lit shop front */
.story-veil {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    linear-gradient(100deg, rgba(5, 6, 7, 0.9) 0%, rgba(5, 6, 7, 0.62) 34%, rgba(5, 6, 7, 0.1) 62%, rgba(5, 6, 7, 0.42) 100%),
    linear-gradient(180deg, rgba(5, 6, 7, 0.55) 0%, rgba(5, 6, 7, 0.05) 26%, rgba(5, 6, 7, 0.12) 62%, rgba(5, 6, 7, 0.72) 100%);
}

.chapter--story.chapter--flip .story-veil {
  background:
    linear-gradient(260deg, rgba(5, 6, 7, 0.9) 0%, rgba(5, 6, 7, 0.62) 34%, rgba(5, 6, 7, 0.1) 62%, rgba(5, 6, 7, 0.42) 100%),
    linear-gradient(180deg, rgba(5, 6, 7, 0.55) 0%, rgba(5, 6, 7, 0.05) 26%, rgba(5, 6, 7, 0.12) 62%, rgba(5, 6, 7, 0.72) 100%);
}

.story-svg {
  position: absolute;
  inset: 0;
  z-index: 3;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}

/* ---- desktop: keep the copy out of the action area ----
   A story chapter is a film with an overlay drawn on it, not a backdrop. The
   inherited .chapter--film copy column is 52ch wide and vertically centred,
   which put the three stat chips slap across the middle of the frame - on Mira
   they sat on the basket and on the telemetry bracket reading it, which is the
   one thing on screen the chapter exists to show.

   Two moves, both here:
   1. HORIZONTAL - the column is pinned to ~40% of the viewport on its own side
      (left by default, right for --flip), and the chips are forced into ONE row
      inside it rather than wrapping into a wide slab.
   2. VERTICAL - the copy is top-aligned rather than centred. Every one of these
      clips puts its machine, its obstacle and its ground-plane telemetry in the
      LOWER half of the frame, so the copy belongs in the upper half. This is
      what actually clears Mira: the basket is at 16% of frame width and no crop
      can move it out from under a left-hand column, but it starts well below
      the chips.
   The third move is in the cue sheets (`wide`: a small zoom plus an off-centre
   focus, see story.js) which biases the machine away from the copy side.

   Only above 720px: the mobile film-band recomposition below owns that case. */
@media (min-width: 721px) {
  .chapter--story .chapter-inner {
    /* 38vw measured from the page edge, so the column ends at 38% of the
       viewport whatever --pad-x resolves to */
    grid-template-columns: minmax(0, min(46ch, calc(38vw - var(--pad-x))));
    align-content: start;
    align-items: start;
    padding-top: clamp(5.6rem, 11vh, 8rem);
  }

  /* the title is a caption over film here, not a poster: the display size the
     turntable chapters use costs ~30px of vertical clearance and does not fit
     a 38vw column at 1280 anyway */
  .chapter--story .chapter-title {
    font-size: clamp(2.9rem, 7vw, 6.2rem);
  }

  .chapter--story.chapter--flip .chapter-inner {
    justify-content: end;
  }

  /* the column is the limit now — a second, wider max-width just made the
     lede and the chips disagree about where the column ends */
  .chapter--story .chapter-lede {
    max-width: none;
    margin-top: 1.1rem;
  }

  .chapter--story .chapter-stats {
    flex-wrap: nowrap;
    margin-top: 1.35rem;
  }

  .chapter--story .chapter-stats li {
    flex: 1 1 0;
    min-width: 0;
    padding: 0.68rem 0.72rem;
  }

  /* THE SPEC BUTTON GOES ABOVE THE NOTE ON STORY CHAPTERS (2026-09-06).
     At 1600x900 Mira's "Full specifications" pill was landing on the top-left
     corner of the obstacle bracket, and a solid pill on the telemetry is the
     one collision that reads as a mistake. The note crossing that same corner
     is already accepted (thin accent outline under muted body text over the
     veil — see plan.md 2026-09-05) and cannot be cleared without narrowing the
     column past the width the chips need. So the two swap places: the button
     finishes above the action area, and the element that stays low is the one
     that was always going to be there. Pure flex `order` — the markup order
     (note, then button) is what a reader without CSS should get. */
  .chapter--story .chapter-copy {
    display: flex;
    flex-direction: column;
  }

  .chapter--story .spec-toggle {
    order: 1;
    align-self: flex-start;
    margin-top: 1.4rem;
  }

  .chapter--story.chapter--flip .spec-toggle { align-self: flex-end; }

  .chapter--story .chapter-note {
    order: 2;
    max-width: none;
    margin-top: 1.35rem;
  }
}

/* A short desktop viewport (a 1280x720 laptop) is the tight case: the copy
   block does not shrink with the viewport but the machine's obstacle box does
   climb up the frame, so the chips and the box meet. Tighten the stack — a
   height-capped title, less lead-in — so the chips still finish above it. */
@media (min-width: 721px) and (max-height: 820px) {
  .chapter--story .chapter-inner {
    padding-top: 4.7rem;
  }

  .chapter--story .chapter-title {
    font-size: min(7vw, 12vh);
  }

  .chapter--story .chapter-lede {
    margin-top: 0.85rem;
    font-size: 0.95rem;
    line-height: 1.6;
  }

  .chapter--story .chapter-stats {
    margin-top: 1rem;
  }

  .chapter--story .chapter-stats li {
    padding: 0.55rem 0.68rem;
  }

  .chapter--story .chapter-note {
    margin-top: 1.1rem;
  }
}

/* ---- status readout ----
   Opposite the copy column: copy left (default) puts it bottom-right, copy
   right (--flip) puts it bottom-left. */

.story-status {
  position: absolute;
  right: var(--pad-x);
  bottom: clamp(20px, 5vh, 48px);
  z-index: 4;
  display: grid;
  gap: 0.42rem;
  padding: 0.85rem 1rem 0.9rem;
  min-width: 236px;
  border: 1px solid var(--line);
  border-left: 1px solid color-mix(in srgb, var(--accent) 60%, transparent);
  border-radius: 10px;
  background: rgba(5, 6, 7, 0.62);
  backdrop-filter: blur(14px);
  color: var(--muted);
  pointer-events: none;
}

.chapter--story.chapter--flip .story-status {
  right: auto;
  left: var(--pad-x);
}

.story-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.4rem;
}

.story-row--unit { justify-content: flex-start; }

.story-row b {
  font-weight: 500;
  text-transform: none;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.story-state {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin-top: 0.2rem;
  padding-top: 0.55rem;
  border-top: 1px solid var(--line);
  color: var(--accent);
}

.story-dot {
  width: 6px;
  height: 6px;
  border-radius: 99px;
  background: var(--accent);
  animation: story-pulse 1.9s var(--ease-expo) infinite;
  flex: none;
}

.story-status[data-busy="1"] .story-dot { animation-duration: 0.72s; }

/* the honesty line — the overlay is a designed representation, not a live feed
   off a robot. Permanent, and quiet enough that it reads as a caption on the
   instrument rather than as an apology: smallest size in the readout, ~34%
   ink, no rule above it. */
.story-illus {
  margin-top: 0.1rem;
  font-size: 0.58rem;
  letter-spacing: 0.16em;
  color: color-mix(in srgb, var(--ink, #f4f6f0) 34%, transparent);
}

.story-illus-short { display: none; }

@media (max-width: 720px) {
  .story-illus-long { display: none; }
  .story-illus-short { display: inline; }
}

@keyframes story-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
  70% { box-shadow: 0 0 0 7px rgba(1, 162, 246, 0); }
  100% { box-shadow: 0 0 0 0 rgba(1, 162, 246, 0); }
}

/* ---- svg vocabulary ----
   Six marks, all accent, all 1.5px non-scaling strokes, mono labels: closer to
   a survey drawing than a game HUD. Everything inside .story-world is authored
   in VIDEO PIXEL SPACE and carries the cover transform; .story-hud is
   untransformed so type is true size at every viewport. */

.story-world [stroke] { vector-effect: non-scaling-stroke; }

.story-planned {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-dasharray: 9 9;
  stroke-linecap: round;
  opacity: 0.5;
}

.story-replanned {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-dasharray: 14 7;
  stroke-linecap: round;
}

.story-actual {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.85;
}

.story-coverage {
  fill: color-mix(in srgb, var(--accent) 14%, transparent);
  stroke: color-mix(in srgb, var(--accent) 34%, transparent);
  stroke-width: 1;
}

.story-bracket {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-linecap: round;
}

.story-boxfill { fill: rgba(1, 162, 246, 0.07); stroke: none; }

.story-scan {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
}

.story-leader { stroke: var(--accent); stroke-width: 1; opacity: 0.55; }

.story-label {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  fill: var(--accent);
}

.story-label--dim { fill: var(--muted); letter-spacing: 0.1em; }
.story-label--ink { fill: var(--ink); text-transform: none; letter-spacing: 0.08em; }

.story-chip {
  fill: rgba(5, 6, 7, 0.66);
  stroke: color-mix(in srgb, var(--accent) 40%, transparent);
  stroke-width: 1;
}

/* ---- ?storydebug ---- */
.story-debug {
  position: absolute;
  right: 14px;
  top: 14px;
  z-index: 6;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: rgba(5, 6, 7, 0.78);
  color: #7fd4ff;
  font-size: 11px;
  line-height: 1.7;
  letter-spacing: 0.06em;
  text-transform: none;
  white-space: pre;
  pointer-events: none;
}

.story-dbg-pt { fill: #ff3d7f; }
.story-dbg-pt--plan { fill: #ffd400; }
.story-dbg-txt { fill: #ff8fb4; font-family: var(--font-mono); font-size: 9px; }
.story-dbg-grid { stroke: rgba(255, 61, 127, 0.28); stroke-width: 1; fill: none; }

/* ---- mobile deck (<=720px) ----
   object-fit:cover on a full-bleed portrait viewport throws away ~70% of a
   16:9 frame and the telemetry lands off screen, so the clip becomes a
   deliberate film band under the header — the same place the machine sits in
   the mobile recomposition — with the copy below it and the readout at the
   foot. The cue sheet's `focus` keeps the machine inside the band. */
@media (max-width: 720px) {
  .chapter--story .story-stage {
    top: 4.6rem;
    bottom: auto;
    height: 30svh;
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
  }

  .chapter--story .story-veil,
  .chapter--story.chapter--flip .story-veil {
    background:
      linear-gradient(180deg, rgba(5, 6, 7, 0.46) 0%, rgba(5, 6, 7, 0) 32%, rgba(5, 6, 7, 0) 64%, rgba(5, 6, 7, 0.46) 100%),
      linear-gradient(90deg, rgba(5, 6, 7, 0.38) 0%, rgba(5, 6, 7, 0) 28%);
  }

  .chapter--story .chapter-inner,
  .chapter--story.chapter--flip .chapter-inner {
    align-content: start;
    text-align: left;
    padding-top: calc(4.6rem + 30svh + 1.1rem);
    padding-bottom: 1rem;
  }

  .chapter--story .story-status,
  .chapter--story.chapter--flip .story-status {
    left: 16px;
    right: 16px;
    min-width: 0;
    bottom: 2.4svh;
  }

  .story-label { font-size: 9px; letter-spacing: 0.08em; }
}

@media (prefers-reduced-motion: reduce) {
  .story-dot { animation: none; }
  .story-film { transition: none; }
}

/* ---- mobile: the band is a band, not a backdrop ----
   Below 720px .story-stage is only the film band, so .chapter-bg (the poster
   still) was left filling the rest of the sticky stage as a second, blurred
   copy of the same picture behind the copy. Drop it and let the stage be
   ground. The readout also has to move: five rows plus title, lede and three
   stat chips do not fit a 844px phone, so on mobile it becomes a single strip
   pinned under the band carrying the unit and the state line - the numbers are
   the desktop luxury, the state is the story. */
@media (max-width: 720px) {
  .chapter--story .chapter-bg { display: none; }
  .chapter--story .chapter-stage { background: #05080a; }

  .chapter--story .story-stage { height: 28svh; }

  .chapter--story .story-status,
  .chapter--story.chapter--flip .story-status {
    top: calc(4.6rem + 28svh);
    bottom: auto;
    left: 0;
    right: 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.55rem 16px 0.6rem;
    border: 0;
    border-bottom: 1px solid var(--line);
    border-radius: 0;
    background: rgba(5, 6, 7, 0.72);
  }

  .chapter--story .story-row:not(.story-row--unit) { display: none; }

  .chapter--story .story-state {
    margin: 0;
    padding: 0;
    border-top: 0;
  }

  .chapter--story .chapter-inner,
  .chapter--story.chapter--flip .chapter-inner {
    padding-top: calc(4.6rem + 28svh + 3.2rem);
  }
}
