/* OpenFloor site.
   Ground is a cold mid-value grey, not cream and not dark. Mid-value grounds
   are rare on the web because they are unforgiving, which is exactly why they
   do not read as a default. The dark plate is used only inside the machine
   well, so the enclosure is dark and the shop is light, the way real machinery
   reads. The accent is spent twice: the contested path, and the primary
   button. */

/* Self-hosted latin subsets (OFL, see assets/fonts/OFL.txt), so loading the
   site sends requests to no one but this domain. Characters outside the
   subset fall back to the system stacks declared below. */
@font-face {
  font-family: "Anton";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/anton-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 100%;
  font-display: swap;
  src: url("/assets/fonts/archivo-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Martian Mono";
  font-style: normal;
  font-weight: 400 600;
  font-stretch: 100%;
  font-display: swap;
  src: url("/assets/fonts/martian-mono-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Three stocks, one cool hue, three value steps apart. The page is a sheet,
     the alternate bands are a second stock laid on it, and cards are a third
     laid on that. The well keeps the only dark plate, so the enclosure still
     reads as the one machined object on the page.
     The whole family sits on Lab hue 265 at the same L* the warm stocks held:
     the value rhythm that made the layout work is untouched, only the hue
     moved. Warm paper under a terracotta accent is Anthropic's pairing, and
     the page was wearing it; cold rolled steel under signal blue is not. */
  --shop: #c6ccd4;        /* page ground */
  --shop-2: #d3d8df;      /* alternate stock, band-wide */
  --sheet: #dce1e7;       /* cards and panels; still below the screen's value */
  --plate: #0d1319;       /* inside the well only */
  --bone: #d7dadf;        /* type on the plate, nowhere else */
  --ink: #12181f;
  --muted: #3d4652;
  --steel: #4a525c;       /* quiet text: crumb separators, placeholders */
  --rule: #a9b1bd;        /* soft line: ranks rows inside a bounded block */
  --edge: #1e262f;        /* structural hairline: seams, and boxes around cards */
  /* Signal blue, the hue the mark's first participant already wears, so the
     accent and the logo are one system. It ships in three values because one
     blue cannot serve a near-black plate and a light ground at once: the old
     oxblood tried, and burned at 1.88:1 against the plate where the contested
     path lives. */
  --accent: #5B93FF;      /* on the dark plate: contested path, 6.28:1 */
  --accent-ink: #1A3FA6;  /* the same accent as small text: 5.63:1 on the ground */
  --accent-fill: #1B45B8; /* the same accent behind white type: 8.11:1 */
  --btn-ink: #fff;        /* type on the filled button; dark mode flips it */
  --agent-green: #2f8055;  /* agent work in flight, in the diagram only */
  /* site.js draws the wave field on a canvas, which cannot read a token, so
     the colour is published here and the script reads it back. */
  --wave: 30, 38, 47;

  --display: "Anton", Impact, sans-serif;
  --sans: "Archivo", -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
  --mono: "Martian Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --gutter: clamp(20px, 5vw, 68px);
  --nav-h: 62px;          /* the nav's height; the hero's stage sits under it */
  /* The widest a band's content may run. It is not a line length: every text
     block below carries its own ch cap, so this only governs the full-width
     pieces (the product screen, the wire graph, the plans). 1280 was set when
     a laptop was the wide case, and on anything past about 1500 it left the
     page ending in mid-air with the nav and the hero still running edge to
     edge. The ceiling is the product capture: it is 2400px of real pixels, so
     past roughly this width it starts going soft on a retina screen. */
  --measure: 1680px;

  /* Tells the browser which way its own furniture goes: scrollbars, form
     controls, the flash before the first paint. Declared, not sniffed, so it
     follows the toggle rather than the OS. */
  color-scheme: light;
}

/* Dark mode is the same design at the other end of the value scale: one hue,
   the same intervals, inverted. Nothing here is a new colour decision, it is
   the light ramp read backwards, so every rule that already uses a token
   follows without being touched.
   It hangs off an attribute rather than prefers-color-scheme on purpose: the
   theme is the reader's explicit choice, made with the control in the nav and
   kept in localStorage, so the OS setting never speaks for them. theme.js puts
   the attribute on <html> before the first paint. */
:root[data-theme="dark"] {
  --shop: #20262e;
  --shop-2: #292f37;
  --sheet: #313740;
  /* On a dark page the hero is not a second, darker black: its ground is the
     page's, so the scene's fog and floor take this too (field/hero.js reads it). */
  --plate: #20262e;
  --bone: #d7dadf;      /* unchanged: still type on a dark ground */
  --ink: #dfe3e8;
  --muted: #a5acb6;
  --steel: #8a919b;
  --rule: #404751;      /* hairlines now sit above the ground, not below */
  --edge: #4e5560;
  /* The ink blue disappears on a dark ground, so the two light values swap
     roles: the bright accent carries small text, and the fill goes bright
     with dark type on it rather than white. */
  --accent-ink: #7FB0FF;
  --accent-fill: #6EA3FF;
  --btn-ink: #0d1319;
  --agent-green: #3faa72;
  --wave: 190, 200, 214;
  color-scheme: dark;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; scroll-padding-top: 78px; scrollbar-gutter: stable; }

body {
  margin: 0;
  background: var(--shop);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, p, ul, ol, dl, dd, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
code { font-family: var(--mono); font-size: .88em; }
a { color: inherit; }
/* The ring takes the value of the accent that survives the ground under it:
   the ink blue on paper, the bright blue on the plate. One token for both
   would fail on one of them. */
a:focus-visible, button:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 3px; }
.hero :focus-visible, .sticky-cta :focus-visible { outline-color: var(--accent); }
:is(p, li, dd, .nav-links, .foot-links, .cta-contact) a:focus-visible { border-radius: 2px; }

.sr-only, .skip-link:not(:focus) {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: 12px; top: 12px; z-index: 60;
  background: var(--ink); color: var(--shop);
  padding: 10px 14px; font-size: 13px; font-weight: 600; text-decoration: none;
}

/* ── Type ─────────────────────────────────────────────────────────────── */

.eyebrow {
  display: block;
  font: 500 10px/1 var(--mono);
  letter-spacing: .18em; text-transform: uppercase; color: var(--muted);
}

/* The logo sets the wordmark lowercase in a bold grotesk, so Archivo 700 carries
   it here and Anton stays the headline face. The mark is the floor brackets
   holding three participants: brackets take the ink of whatever they sit on,
   the three dots keep the brand hues. */
.wordmark {
  font: 700 19px/1 var(--sans);
  letter-spacing: -.015em;
}
.wordmark.sm { font-size: 15.5px; }
.brand-mark { width: 57.5px; height: 20px; flex: none; color: var(--ink); }
.brand-mark.sm { width: 46px; height: 16px; }

/* ── Controls: one filled button, one demoted link ────────────────────── */

.btn {
  display: inline-flex; align-items: center;
  padding: 13px 22px;
  /* The fill is the deep value of the accent, so white on it clears 4.5:1 with
     room to spare. The bright value is untouched: the contested path in the
     hero still burns at full strength, which is the point of spending it
     twice. */
  border: 1px solid var(--accent-fill); background: var(--accent-fill); color: var(--btn-ink);
  border-radius: 8px;
  font: 600 13px var(--sans); letter-spacing: .01em; text-decoration: none;
  transition: background-color .16s ease, border-color .16s ease;
}
.btn:hover { background: #16389A; border-color: #16389A; }
.btn:active { background: #122E80; border-color: #122E80; }
/* Hover moves away from the ground, so on a dark page it brightens instead of
   darkening; the dark type on it only gains contrast either way. */
:root[data-theme="dark"] .btn:hover { background: #8ABAFF; border-color: #8ABAFF; }
:root[data-theme="dark"] .btn:active { background: #A5C9FF; border-color: #A5C9FF; }

.link {
  font: 500 13.5px var(--sans); color: var(--ink); text-decoration: none;
  border-bottom: 1px solid var(--muted); padding-bottom: 2px;
  transition: border-color .16s ease, color .16s ease;
}
.link:hover { border-color: var(--accent-ink); }
.link.on-dark { color: var(--bone); border-bottom-color: #6e757e; }
.link.on-dark:hover { border-color: var(--accent); }

.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 24px; }

/* ── Nav ──────────────────────────────────────────────────────────────── */

.nav {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: 24px;
  height: 62px; padding: 0 var(--gutter);
  background: var(--shop);
}
/* Past the hero the wordmark collapses and the mark carries the brand alone.
   The gap lives on the wordmark rather than on .brand, so it can collapse with
   it and leave no orphaned space beside the mark. The transition is only armed
   after the first observer callback, so opening /why directly starts collapsed
   instead of animating shut on arrival. */
.brand { display: flex; align-items: center; text-decoration: none; }
.brand .wordmark {
  margin-left: 10px;
  max-width: 7em;
  overflow: hidden;
  white-space: nowrap;
}
.brand.is-armed .wordmark {
  transition: max-width .3s ease, margin-left .3s ease, opacity .2s ease;
}
.brand.is-compact .wordmark { max-width: 0; margin-left: 0; opacity: 0; }
.nav-links { display: flex; gap: 24px; margin-left: auto; }
.nav-links a {
  font: 500 11px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); text-decoration: none;
  padding: 10px 2px; margin: -10px -2px;
  transition: color .16s ease;
}
.nav-links a:hover { color: var(--ink); }
.nav-cta { flex: none; }

/* The theme control. Icon only: a label pushes the nav onto a second line on a
   phone, and aria-pressed already carries the state for anyone not looking at
   the icon. */
.theme-toggle {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  background: none; border: 1px solid var(--edge); color: var(--ink); border-radius: 6px;
  padding: 0; cursor: pointer;
  transition: background-color .16s ease, color .16s ease;
}
.theme-toggle:hover { background: var(--ink); color: var(--shop); }
.theme-toggle svg {
  width: 15px; height: 15px;
  fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}
.theme-toggle .moon { display: none; }
:root[data-theme="dark"] .theme-toggle .sun { display: none; }
:root[data-theme="dark"] .theme-toggle .moon { display: block; }

/* ── Hero well ────────────────────────────────────────────────────────── */

/* ── Hero: the break ─────────────────────────────────────────────────── */
/* One screen under the nav. field/hero.js plays the break on its own clock and
   changes the copy under it. Everything that moves is gated on .is-live, which
   the renderer adds only once it has a WebGL context, so no-JS and no-WebGL
   both get the copy alone on the plate, stacked in order. */
/* The page's wave field runs under the hero too, in the light ink whatever the
   theme, since the ground here is dark in both. The table's floor hides it
   while the break plays; the table leaves and the mark stands on it. Unmasked:
   the bands hold the field back over a left-hand reading column, and the
   hero's words are centred. */
.hero { background: var(--plate); --wave: 190, 200, 214; }
.hero .wave-field { -webkit-mask-image: none; mask-image: none; }
#stage { position: relative; padding: 48px var(--gutter) 56px; }
/* The page is the clock. The section is a scroll track two screens taller
   than the stage, so the break plays over that scroll, and the stage
   sticks under the nav while it does. The track is only as long as the break:
   it ends where the mark is standing, so the next scroll leaves the hero for
   the page rather than holding a finished picture still. overflow: clip, not
   the hidden the other bands take: hidden would make this a scrollport and the
   stage would never stick. --nav-h is the nav's height, and the 560px query
   keeps it in step. */
.hero.is-live { height: calc(300vh - var(--nav-h)); overflow: clip; }
.hero.is-live #stage {
  position: sticky; top: var(--nav-h);
  height: calc(100vh - var(--nav-h)); padding: 0; overflow: hidden;
}
/* The document remains the hero's scroll clock. Proximity snapping lets a
   gesture settle near each copy state without trapping momentum scrolling or
   forcing every stop. The renderer creates and positions these only when the
   scrub is live. */
html.hero-snap-enabled { scroll-snap-type: y proximity; }
.hero-snap-point {
  position: absolute; left: 0; width: 1px; height: 1px; pointer-events: none;
  scroll-snap-align: start;
}
#stage canvas, .vig { display: none; }
.hero.is-live #stage canvas { display: block; position: absolute; inset: 0; width: 100%; height: 100%; touch-action: pan-y; }
/* The standing mark can be dragged round; pan-y leaves vertical swipes to the page. */
#stage canvas.live { cursor: grab; }
#stage canvas.grabbing { cursor: grabbing; }
.hero.is-live .vig {
  display: block; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(120% 90% at 50% 42%, transparent 55%, rgba(5, 7, 10, .5) 100%);
}
/* No vignette on the dark page: its darkened corners would be the seam the shared ground removes. */
:root[data-theme="dark"] .hero.is-live .vig { display: none; }
.layer { max-width: 680px; }
.layer + .layer { margin-top: 48px; }
/* The layers share one box at the bottom left and the script fades them in and
   out as the clip plays. .pe marks the one that carries links: it takes the
   pointer only while it is the one shown. */
.hero.is-live .layer {
  position: absolute; left: var(--gutter); right: var(--gutter); bottom: 11vh; z-index: 3;
  margin: 0; opacity: 0; transform: translateY(12px); pointer-events: none;
}
/* Ranged left: lines that share an edge read as one shape rather than as
   centred fragments. */
h1 {
  display: inline-block; text-align: left; max-width: 20ch;
  font-family: var(--display);
  font-size: clamp(32px, 4.8vw, 70px);
  line-height: .92; letter-spacing: .004em; text-transform: uppercase; font-weight: 400;
  color: var(--bone);
}
/* Four lines, anchored at the bottom of the stage: the height cap is what keeps
   the top line out from under the nav on a short viewport. */
.layer h1 { font-size: clamp(56px, min(10vw, 13.5vh), 132px); line-height: .88; max-width: 9ch; }
.layer h2 {
  font-family: var(--display); font-weight: 400; text-transform: uppercase;
  font-size: clamp(40px, 6.4vw, 84px); line-height: .9; max-width: 16ch;
  color: var(--bone);
}
/* One word crossfades into another as the layer scrolls; both sit in the same
   grid cell so the line does not jump when the longer word takes over. */
.swap { display: inline-grid; grid-template: 1fr / 1fr; vertical-align: bottom; }
.swap-a, .swap-b { grid-area: 1 / 1; }
.swap-b { opacity: 0; }
.layer.fin h2 { font-size: clamp(44px, 7vw, 100px); line-height: .88; max-width: none; }
/* The finale is one screen: the mark stands in the upper half (the camera looks
   at a point below it) and the words sit centred underneath. */
.hero.is-live .layer.fin {
  left: var(--gutter); right: var(--gutter); top: 65%; bottom: auto; max-width: none;
  display: flex; flex-direction: column; align-items: center; text-align: center;
}
.layer.fin .actions { justify-content: center; }
.layer p {
  margin-top: 20px; max-width: 46ch;
  font-size: clamp(17px, 1.5vw, 21px); line-height: 1.45; color: #a6acb4;
  text-wrap: pretty;
}
.layer .actions { margin-top: 28px; justify-content: flex-start; }

.well-caption {
  max-width: 110ch; margin: 12px auto 0;
  padding: 0 var(--gutter);
  font: 400 12px var(--mono); line-height: 1.75; color: var(--muted);
}
.well-caption span {
  display: inline-block; margin-right: 10px;
  font-weight: 600; text-transform: uppercase; letter-spacing: .14em; color: var(--ink);
}
.well-caption code { color: var(--ink); }

/* Agent plates. Each names the person driving and the runtime, and rides the
   agent it belongs to, so nothing has to be cross-referenced against a legend. */
.well-caption {
  max-width: 110ch; margin: 12px auto 0;
  padding: 0 var(--gutter);
  font: 400 12px var(--mono); line-height: 1.75; color: var(--muted);
}
.well-caption span {
  display: inline-block; margin-right: 10px;
  font-weight: 600; text-transform: uppercase; letter-spacing: .14em; color: var(--ink);
}
.well-caption code { color: var(--ink); }

/* Agent plates. Each names the person driving and the runtime, and rides the
   agent it belongs to, so nothing has to be cross-referenced against a legend. */
.well-agents { position: absolute; inset: 0; pointer-events: none; }
.well-agents span {
  position: absolute; display: block; white-space: nowrap;
  padding-left: 9px; border-left: 2px solid var(--who);
  transform: translate3d(-9999px, 0, 0);
}
.well-agents b {
  display: block; color: var(--who);
  font: 600 10.5px var(--mono); letter-spacing: .06em; text-transform: uppercase;
}
.well-agents em {
  display: block; margin-top: 2px; font-style: normal; color: #727982;
  font: 400 9px var(--mono); letter-spacing: .04em;
}
.well-agents .blocked { border-left-color: var(--accent); }
.well-agents .blocked b, .well-agents .blocked em { color: var(--accent); }

/* ── Stocks ───────────────────────────────────────────────────────────── */
/* Alternating stock down the page, the way an editorial spread changes paper
   rather than changing colour. Two stocks only, keyed by section id, so the
   rhythm is felt and not noticed. Subpages carry none of these ids and keep
   the plain ground. */

/* #session is deliberately absent. It carries the product screen, and the screen
   is warm cream: on the alternate stock it separated from the band at 1.30 and
   its rails at 1.03, which is what the deleted plate used to hide. On the ground
   it holds 1.47. */
#why,
#pricing { --stock: var(--shop-2); }



/* ── Runtimes strip ───────────────────────────────────────────────────── */
/* A moving row earns its motion only if it is carrying a list longer than the
   eye wants to read at once, so it stays a plain mono row, pauses on hover and
   on focus within, and stops entirely for anyone who asked motion to stop. The
   duplicate row exists so the -50% loop meets itself; it is hidden from the
   accessibility tree rather than read twice. */

.runtimes {
  border-block: 1px solid var(--edge);
  background: var(--shop-2);
  padding: 12px 0 13px;
  overflow: hidden;
}
/* The label sits on the gutter like every other heading on the page; the track
   below it keeps running edge to edge, which is the whole point of the strip. */
.runtimes-title { padding-inline: var(--gutter); margin-bottom: 11px; }
.runtimes-track { display: flex; width: max-content; animation: runtimes-scroll 30s linear infinite; }
.runtimes:hover .runtimes-track,
.runtimes:focus-within .runtimes-track { animation-play-state: paused; }
.runtimes-row {
  display: flex; align-items: center;
  gap: clamp(30px, 4.5vw, 64px);
  padding-right: clamp(30px, 4.5vw, 64px);
}
.runtimes-row li {
  display: flex; align-items: center; gap: 9px; white-space: nowrap;
  font: 500 13px/1 var(--mono); letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink);
}
/* Vendor marks are their own artwork and keep their own colour; only the size
   is normalised, since each is drawn to a different optical weight. */
.rt-mark { width: auto; height: 18px; flex: none; display: block; }
img.rt-mark { object-fit: contain; }
/* Optical, not metric. Each vendor ships a different amount of padding inside
   its own viewBox, so a shared height renders different-sized marks: the
   OpenAI blossom inks only half its box, Kimi about five sixths, the rest
   nearly all of it. These heights are set so the INK measures the same. */
.rt-mark[src$="openai.svg"] { height: 34px; }
.rt-mark[src$="claude.svg"] { height: 17px; }
.rt-mark[src$="gemini.svg"] { height: 18px; }
.rt-mark[src$="kimi.svg"]   { height: 20px; }
.rt-mark[src$="cursor.svg"] { height: 17px; }
.rt-mark[src$="github.svg"] { height: 17px; }
/* Grok's mark is monochrome, so it is drawn inline and takes the page's ink
   rather than shipping as two files, one per theme: currentColor already
   follows the toggle, and nothing has to be inverted the way the black PNG-era
   marks above do. */
.rt-drawn { width: 20px; height: 20px; fill: currentColor; stroke: none; }
/* Three of these marks are drawn in near-black, which is invisible on a dark
   ground. Kimi is black AND blue, so it takes the hue-rotate as well: the
   inversion turns its blue orange and the rotation puts it back. Claude and
   Gemini are already coloured and are left alone. */
:root[data-theme="dark"] .rt-mark[src$="openai.svg"],
:root[data-theme="dark"] .rt-mark[src$="github.svg"],
:root[data-theme="dark"] .rt-mark[src$="cursor.svg"] { filter: invert(1); }
:root[data-theme="dark"] .rt-mark[src$="kimi.svg"] { filter: invert(1) hue-rotate(180deg); }
/* Eight copies, so one copy is 12.5% of the track: the shift is exactly one
   copy and the seven behind it cover any viewport, which is what stops the row
   from running out mid-screen before it repeats. */
@keyframes runtimes-scroll { to { transform: translateX(-12.5%); } }
@media (prefers-reduced-motion: reduce) {
  .runtimes-track { animation: none; }
  .runtimes { overflow-x: auto; }
}

/* ── Product screen ───────────────────────────────────────────────────── */
/* The screen sits on the ground with a hairline and nothing else. A dark plate
   around it read as a frame around a picture; the point is that this is the
   product, not an illustration of it. It still takes back the band's gutter so
   the interface lands at close to its captured scale and can be read. */

.shot {
  width: min(78%, 1120px);
  margin: 0 auto clamp(26px, 3vw, 40px);
}
.theme-video-stack {
  position: relative;
  aspect-ratio: 8 / 5;
  border: 1px solid var(--edge);
  overflow: hidden;
}
.theme-video {
  position: absolute; inset: 0;
  display: block; width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  pointer-events: none;
}
:root[data-theme="light"] .theme-video[data-video-theme="light"],
:root[data-theme="dark"] .theme-video[data-video-theme="dark"] {
  opacity: 1;
}
.shot .well-caption { margin-inline: 0; padding: 0; max-width: 88ch; }

/* ── The wave field ───────────────────────────────────────────────────── */
/* The canvases are inserted by site.js, one per band. Each band isolates, so a
   negative index puts the field above that band's stock and below its words.
   The ground itself stays plain: this is line work drifting over it, not a
   pattern printed into it. */

.band, .runtimes, .foot, .hero { position: relative; overflow: hidden; isolation: isolate; }
.wave-field {
  position: absolute; inset: 0; z-index: -1;
  width: 100%; height: 100%;
  pointer-events: none; opacity: .5;
  /* The text hangs off the left edge and the right side is mostly open, so the
     field is held back across the reading column and comes up to full strength
     in the margin. The lines still run the whole width; they just stop
     competing with the words on the way through. */
  -webkit-mask-image: linear-gradient(to right, rgba(0, 0, 0, .28) 0, rgba(0, 0, 0, .28) 38%, #000 78%);
  mask-image: linear-gradient(to right, rgba(0, 0, 0, .28) 0, rgba(0, 0, 0, .28) 38%, #000 78%);
}

/* Letters the cursor pushes (site.js splits the big type into these). The
   word keeps its shape; each letter moves on its own and eases back. */
.wd { display: inline-block; white-space: nowrap; }
.ch { display: inline-block; transition: transform .38s cubic-bezier(.2, .8, .2, 1); will-change: transform; }

/* ── Media controls ───────────────────────────────────────────────────── */
/* The looping demo moves on its own and never stops, so it carries a control
   to hold it still, parked in a corner of what it governs.
   [hidden] until the script that drives it says otherwise, so no-JS never shows
   a button that would do nothing. Icon only, swapped by the script that owns
   the state; aria-label carries what pressing will do. */

.media-toggle {
  position: absolute; z-index: 2;
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0;
  background: none; border: 1px solid var(--edge); color: var(--ink); border-radius: 6px;
  cursor: pointer;
  transition: background-color .16s ease, color .16s ease, border-color .16s ease;
}
.media-toggle[hidden] { display: none; }
.media-toggle:hover { background: var(--ink); color: var(--sheet); border-color: var(--ink); }
.media-toggle svg { width: 13px; height: 13px; fill: currentColor; }
.media-toggle svg[hidden] { display: none; }

/* Parked on the caption line rather than over the capture. Anywhere inside the
   frame covers some part of the product, and the one thing this control must
   not do is hide the thing it is there to hold still. */
.shot { position: relative; }
.shot .media-toggle { right: 0; bottom: 0; }

@media (max-width: 760px) {
  /* The caption wraps to several lines on a phone, so the control stops
     floating over the end of it and takes its own row. */
  .shot .media-toggle { position: static; display: block; margin: 10px 0 0 auto; }
}

/* ── Intro ────────────────────────────────────────────────────────────── */
/* A fixed plate over the whole page that plays once per session and then clips
   down onto the well, uncovering the nav and the copy on the way.
   It cannot live inside .hero: that rule isolates and clips (see the wave field
   above), so a full bleed child of it is not full bleed.
   Hidden by default, which is the whole of the no-JS, reduced motion and repeat
   visit story: without the attribute this block never applies and preload="none"
   means the file is never even fetched.
   The plate clips rather than resizes: a box that shrinks would make the
   renderer relay out its composition every frame, and the picture would crawl
   under a frame that is barely moving. Clipping holds the picture still and
   closes the frame onto it.
   The renderer paints the well's own colour as its ground, so there is nothing
   to match up at the edges and nothing to show before the first frame. */

#intro { display: none; }

html[data-intro] #intro {
  display: block;
  /* Above .skip-link at 60, the highest thing on the page today. */
  position: fixed; inset: 0; z-index: 90;
  background: var(--plate);
  clip-path: inset(0px 0px 0px 0px);
  /* absurd.com's curve. The delay on opacity is the clip's own duration, so the
     crossfade to the live field starts the instant the frame stops moving. */
  transition: clip-path .9s cubic-bezier(0.2, 0, 0, 1), opacity .5s ease .9s;
}
html[data-intro] #intro canvas {
  display: block; width: 100%; height: 100%;
}

/* The picture goes as soon as the clone is over it, which is the moment the
   mark settles, not the moment the animation ends. Where no clone was made
   (skipped early) the landing itself still takes the picture out. */
html[data-intro-mark] #intro canvas,
html[data-intro="land"] #intro canvas { opacity: 0; transition: opacity .3s ease .05s; }

/* Locked for the whole life of the plate, not only while it plays. The reveal
   is fixed geometry measured once, inside land(), on the assumption that scroll
   is at the top: the plate is fixed to the viewport, its clip is the well's rect
   from that one frame, and the flying mark is placed from the same frame.
   Nothing is remeasured and nothing listens for scroll. Keying this to "play"
   released the lock on the FIRST frame of the reveal rather than the last, so
   any scroll in the following 1.4 seconds slid the well out from under a band
   that cannot follow it and left an opaque slab across the middle of some other
   section. That needed no skip gesture: a reader who watched the whole thing and
   then scrolled hit it too. The attribute is deleted at teardown in the same
   statement that removes the plate, so the lock now lifts on that beat.
   scrollbar-gutter is set globally so taking the scrollbar away here does not
   shift the page under the reader on Windows. */
html[data-intro] { overflow: hidden; }

/* The clip target is measured in the script, not guessed here: the well's
   height is a clamp and the nav is 62px only until the wordmark collapses. */
/* No pointer-events: none here. The opacity transition is delayed by the clip's
   own .9s, so the plate is still fully painted when this rule lands, and letting
   pointer events through would put an invisible hero button under the second
   half of a double click or under the click a tap synthesises after touchstart
   has already dismissed the intro. The plate is removed at 1400ms, exactly when
   the fade ends, so blocking for its whole life costs nothing. */
html[data-intro="land"] #intro { opacity: 0; }

/* The mark handing itself over. The clip ends on the brand mark, so at the last
   frame a clone of the nav's own SVG is dropped exactly over it and flies to the
   nav's resting box while the plate clips away underneath. Vector rather than
   canvas: the mark is drawn hundreds of pixels wide and lands at 57.5, so
   riding the bitmap down would put a scaled-down raster in the nav.
   The picture goes first and quickly, which is what makes the mark look like it
   detaches: the corridor dissolves into the plate's own ground and the mark,
   now a clone, is the only thing left moving. */
#intro-mark {
  position: fixed; z-index: 91;
  transform-origin: 0 0;
  pointer-events: none;
  /* White in the clip, the nav's ink on the page. Held until the flight is
     mostly done, so the change happens against the nav's ground rather than
     against the plate it is still crossing. */
  color: #fff;
  transition:
    transform .9s cubic-bezier(0.2, 0, 0, 1),
    color .3s ease .55s;
}
html[data-intro="land"] #intro-mark { color: var(--ink); }

/* The real mark stays out while its clone is in the air, then takes over. They
   are the same SVG in the same box, so the swap at teardown has nothing to see. */
html[data-intro-mark] .nav .brand-mark { opacity: 0; }

/* The page arriving. Held off first, then brought in on the same beat as the
   frame closing, which is where the movement actually is: the well is nearly
   the viewport already, so the nav dropping and the copy rising carry it. */
html[data-intro] .nav { transform: translateY(-100%); }
html[data-intro] .brand .wordmark { opacity: 0; }
html[data-intro="land"] .brand .wordmark { opacity: 1; transition: opacity .4s ease .85s; }
html[data-intro] main > :not(.hero),
html[data-intro] .foot { opacity: 0; }

/* The nav rides the clip's own top edge down. */
html[data-intro="land"] .nav {
  transform: none;
  transition: transform .55s cubic-bezier(0.2, 0, 0, 1);
}
html[data-intro="land"] main > :not(.hero),
html[data-intro="land"] .foot { opacity: 1; transition: opacity .5s ease .6s; }

/* ── Back to top ──────────────────────────────────────────────────────── */
/* One control for the whole page, parked in the corner the thumb and the mouse
   both already live in. It stays out of the flow entirely, so nothing on the
   page moves when it appears. */

.to-top {
  position: fixed; z-index: 40;
  right: clamp(16px, 2.4vw, 34px); bottom: clamp(16px, 2.4vw, 34px);
  width: 46px; height: 46px;
  display: flex; align-items: center; justify-content: center;
  background: var(--sheet); border: 1px solid var(--edge); color: var(--ink);
  text-decoration: none; cursor: pointer; padding: 0; font: inherit;
  opacity: 0; visibility: hidden; transform: translateY(8px);
  transition: opacity .18s ease, transform .18s ease, visibility .18s;
}
.to-top.is-shown { opacity: 1; visibility: visible; transform: none; }
.to-top:hover { background: var(--ink); color: var(--sheet); border-color: var(--ink); }
.to-top svg {
  width: 18px; height: 18px;
  fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
@media (prefers-reduced-motion: reduce) {
  .to-top { transition: none; }
}

/* ── The wire graph ───────────────────────────────────────────────────── */
/* Several people open one session; the session runs as many agents as they
   need; every agent's work lands on one candidate branch. The people arrive
   in their own colours, and what leaves the floor is agent work, so it all
   runs green. offset-path repeats each wire's exact d string, so a pulse
   cannot drift off the line it is riding. */

/* The one element that does not take the full band. Everything inside this
   figure, its labels included, is drawn in viewBox units and scales with the
   width, so at the full measure the 10px labels render near 23px and the
   diagram reads as a poster. Held at the width its type was drawn for. */
/* Specificity, not source order: `.band > *` sets the measure on every direct
   child and is declared further down this file, so a plain `.wire-graph` cap
   loses the tie and the figure quietly runs the full band. The others that cap
   themselves (.why-body, .status-line, .plans) only win because they happen to
   come later. */
.band > .wire-graph { max-width: 1280px; }
.wire-graph { margin: clamp(10px, 1.4vw, 18px) 0 0; }
.wire-graph svg { display: block; width: 100%; height: auto; overflow: visible; }
/* One of the two is always display:none, which takes it out of the
   accessibility tree as well, so the pair never reads as one diagram twice.
   Qualified by the figure because `.wire-graph svg` above is a descendant
   selector and outranks a lone class wherever it is declared. */
.wire-graph .wg-portrait { display: none; }
.wg-wire { fill: none; stroke: var(--edge); stroke-width: 1; opacity: .3; }
.wg-person circle { fill: var(--who, var(--ink)); }
.wg-person path { fill: none; stroke: var(--who, var(--ink)); stroke-width: 2.2; }
.wg-agent rect { fill: var(--sheet); stroke: var(--edge); stroke-width: 1.2; }
.wg-agent circle { fill: var(--agent-green); }
/* The brackets take the page ink; the three participants keep the brand hues,
   the same rule the mark follows in the nav. */
.wg-floor path { fill: var(--ink); }
.wg-floor rect { fill: var(--sheet); stroke: var(--edge); stroke-width: 1.6; }
.wg-branch rect { fill: var(--sheet); stroke: var(--edge); stroke-width: 1.2; }
.wg-branch path { fill: none; stroke: var(--edge); stroke-width: 1; opacity: .45; }
.wg-pulse { opacity: 0; fill: var(--agent-green); }
.wg-label { font: 500 10px var(--mono); letter-spacing: .14em; text-transform: uppercase; fill: var(--muted); }

.wg-in0 { fill: #2F6DF0; }
.wg-in1 { fill: #12A594; }
.wg-in2 { fill: #E0457F; }

.wg-in0 { offset-path: path('M84,70 C150,70 190,140 250,140'); animation: k-wg-in0 6.6s linear infinite; }
@keyframes k-wg-in0 {
  0%, 0.0% { offset-distance: 0%; opacity: 0; }
  1.6% { opacity: 1; }
  27.19% { opacity: 1; }
  28.79%, 100% { offset-distance: 100%; opacity: 0; }
}
.wg-in1 { offset-path: path('M84,140 C150,140 190,140 250,140'); animation: k-wg-in1 6.6s linear infinite; }
@keyframes k-wg-in1 {
  0%, 0.0% { offset-distance: 0%; opacity: 0; }
  1.6% { opacity: 1; }
  27.19% { opacity: 1; }
  28.79%, 100% { offset-distance: 100%; opacity: 0; }
}
.wg-in2 { offset-path: path('M84,210 C150,210 190,140 250,140'); animation: k-wg-in2 6.6s linear infinite; }
@keyframes k-wg-in2 {
  0%, 0.0% { offset-distance: 0%; opacity: 0; }
  1.6% { opacity: 1; }
  27.19% { opacity: 1; }
  28.79%, 100% { offset-distance: 100%; opacity: 0; }
}
.wg-mid0 { offset-path: path('M354,140 C420,140 440,36 470,36'); animation: k-wg-mid0 6.6s linear infinite; }
@keyframes k-wg-mid0 {
  0%, 28.79% { offset-distance: 0%; opacity: 0; }
  30.39% { opacity: 1; }
  55.98% { opacity: 1; }
  57.58%, 100% { offset-distance: 100%; opacity: 0; }
}
.wg-mid1 { offset-path: path('M354,140 C420,140 440,88 470,88'); animation: k-wg-mid1 6.6s linear infinite; }
@keyframes k-wg-mid1 {
  0%, 28.79% { offset-distance: 0%; opacity: 0; }
  30.39% { opacity: 1; }
  55.98% { opacity: 1; }
  57.58%, 100% { offset-distance: 100%; opacity: 0; }
}
.wg-mid2 { offset-path: path('M354,140 C420,140 440,140 470,140'); animation: k-wg-mid2 6.6s linear infinite; }
@keyframes k-wg-mid2 {
  0%, 28.79% { offset-distance: 0%; opacity: 0; }
  30.39% { opacity: 1; }
  55.98% { opacity: 1; }
  57.58%, 100% { offset-distance: 100%; opacity: 0; }
}
.wg-mid3 { offset-path: path('M354,140 C420,140 440,192 470,192'); animation: k-wg-mid3 6.6s linear infinite; }
@keyframes k-wg-mid3 {
  0%, 28.79% { offset-distance: 0%; opacity: 0; }
  30.39% { opacity: 1; }
  55.98% { opacity: 1; }
  57.58%, 100% { offset-distance: 100%; opacity: 0; }
}
.wg-mid4 { offset-path: path('M354,140 C420,140 440,244 470,244'); animation: k-wg-mid4 6.6s linear infinite; }
@keyframes k-wg-mid4 {
  0%, 28.79% { offset-distance: 0%; opacity: 0; }
  30.39% { opacity: 1; }
  55.98% { opacity: 1; }
  57.58%, 100% { offset-distance: 100%; opacity: 0; }
}
.wg-out0 { offset-path: path('M528,36 C560,36 570,140 596,140'); animation: k-wg-out0 6.6s linear infinite; }
@keyframes k-wg-out0 {
  0%, 57.58% { offset-distance: 0%; opacity: 0; }
  59.18% { opacity: 1; }
  84.76% { opacity: 1; }
  86.36%, 100% { offset-distance: 100%; opacity: 0; }
}
.wg-out1 { offset-path: path('M528,88 C560,88 570,140 596,140'); animation: k-wg-out1 6.6s linear infinite; }
@keyframes k-wg-out1 {
  0%, 57.58% { offset-distance: 0%; opacity: 0; }
  59.18% { opacity: 1; }
  84.76% { opacity: 1; }
  86.36%, 100% { offset-distance: 100%; opacity: 0; }
}
.wg-out2 { offset-path: path('M528,140 C560,140 570,140 596,140'); animation: k-wg-out2 6.6s linear infinite; }
@keyframes k-wg-out2 {
  0%, 57.58% { offset-distance: 0%; opacity: 0; }
  59.18% { opacity: 1; }
  84.76% { opacity: 1; }
  86.36%, 100% { offset-distance: 100%; opacity: 0; }
}
.wg-out3 { offset-path: path('M528,192 C560,192 570,140 596,140'); animation: k-wg-out3 6.6s linear infinite; }
@keyframes k-wg-out3 {
  0%, 57.58% { offset-distance: 0%; opacity: 0; }
  59.18% { opacity: 1; }
  84.76% { opacity: 1; }
  86.36%, 100% { offset-distance: 100%; opacity: 0; }
}
.wg-out4 { offset-path: path('M528,244 C560,244 570,140 596,140'); animation: k-wg-out4 6.6s linear infinite; }
@keyframes k-wg-out4 {
  0%, 57.58% { offset-distance: 0%; opacity: 0; }
  59.18% { opacity: 1; }
  84.76% { opacity: 1; }
  86.36%, 100% { offset-distance: 100%; opacity: 0; }
}

/* The portrait graph's pulses. The wide one names a keyframe set per wire
   because each wire's timing was authored with it; here the timing is the
   same for every wire of a stage, so three sets carry all thirteen and the
   path each circle rides is written on the circle, next to nothing else. */
.wgp-in { animation: k-wgp-in 6.6s linear infinite; }
@keyframes k-wgp-in {
  0% { offset-distance: 0%; opacity: 0; }
  1.6% { opacity: 1; }
  27.19% { opacity: 1; }
  28.79%, 100% { offset-distance: 100%; opacity: 0; }
}
.wgp-mid { animation: k-wgp-mid 6.6s linear infinite; }
@keyframes k-wgp-mid {
  0%, 28.79% { offset-distance: 0%; opacity: 0; }
  30.39% { opacity: 1; }
  55.98% { opacity: 1; }
  57.58%, 100% { offset-distance: 100%; opacity: 0; }
}
.wgp-out { animation: k-wgp-out 6.6s linear infinite; }
@keyframes k-wgp-out {
  0%, 57.58% { offset-distance: 0%; opacity: 0; }
  59.18% { opacity: 1; }
  84.76% { opacity: 1; }
  86.36%, 100% { offset-distance: 100%; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .wg-pulse { animation: none; opacity: 1; offset-distance: 100%; }
}

/* ── Bands ────────────────────────────────────────────────────────────── */

.band {
  max-width: none; margin-inline: 0;
  padding-block: clamp(34px, 3.6vw, 54px);
  padding-inline: var(--gutter);
  border-top: 1px solid var(--edge);
  background: var(--stock, var(--shop));
}
/* The measure caps the line, it does not centre it: everything on the page
   hangs off the same left edge as the headline and the product screen. */
.band > * { max-width: var(--measure); }
.band-head { margin-bottom: clamp(18px, 2vw, 26px); }
.band-head .eyebrow { margin-bottom: 18px; }
.band-head h2 {
  max-width: 20ch;
  font-size: clamp(26px, 3.2vw, 40px); line-height: 1.12;
  letter-spacing: -.022em; font-weight: 600;
}

/* ── Steps: an editorial list, not a card grid ────────────────────────── */

.steps { display: grid; gap: 0; }
.steps > li {
  display: grid; grid-template-columns: 90px minmax(0, 22ch) minmax(0, 68ch);
  gap: 0 32px; align-items: start;
  padding: 14px 0; border-top: 1px solid var(--rule);
}
.steps > li:last-child { border-bottom: 1px solid var(--rule); }
.idx { font: 500 11px var(--mono); letter-spacing: .14em; color: var(--accent-ink); padding-top: 4px; }
.steps h3 { font-size: 19px; font-weight: 600; letter-spacing: -.018em; }
.steps p { font-size: 15px; line-height: 1.62; color: var(--muted); }
.steps .note {
  grid-column: 3; margin-top: 12px; padding-top: 12px;
  border-top: 1px solid var(--rule);
  font: 400 11.5px var(--mono); line-height: 1.7; color: var(--muted);
}

/* ── Prose bands ──────────────────────────────────────────────────────── */

.why-body { max-width: 68ch; display: grid; gap: 14px; }
.why-body .big {
  font-size: clamp(18px, 1.7vw, 23px); line-height: 1.45;
  letter-spacing: -.015em; color: var(--ink);
}
.why-body p { font-size: 15.5px; line-height: 1.65; color: var(--muted); }
.why-body .note {
  margin-top: 8px; padding-top: 16px; border-top: 1px solid var(--rule);
  font: 400 11.5px var(--mono); line-height: 1.8;
}

/* The three verbs are one sentence, not a list: they are the same act said
   three ways, and three ruled rows made a sequence out of them. */
.verbs {
  max-width: min(100%, 58ch); margin: clamp(26px, 3vw, 40px) auto 0;
  text-align: center; text-wrap: balance;
  font-size: clamp(24px, 2.8vw, 34px); line-height: 1.24;
  letter-spacing: -.02em; font-weight: 600;
}

/* ── Honest status ────────────────────────────────────────────────────── */
/* One line under the three guarantees, in the mono the page uses for anything
   it is being literal about. It replaced a seven-row status table and a list
   of open decisions: two screens of prose that were true and unread. */

.status-line {
  margin-top: clamp(18px, 2vw, 26px);
  max-width: 78ch;
  font: 400 12px var(--mono); line-height: 1.85; color: var(--muted);
}

/* ── Access ───────────────────────────────────────────────────────────── */

/* ── Pricing ──────────────────────────────────────────────────────────── */

.plans { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.plan { background: var(--sheet); border: 1px solid var(--edge); padding: 20px 22px; display: flex; flex-direction: column; gap: 11px; }
.plan h3 { font-size: 17px; font-weight: 600; letter-spacing: -.015em; }
.plan .price { font-size: clamp(26px, 3vw, 34px); font-weight: 600; letter-spacing: -.022em; }
.plan .price span { font: 400 11.5px var(--mono); color: var(--muted); }
/* Ink rather than muted. This is the copy someone reads to decide what to buy,
   so it carries the same weight as the rest of the page's prose; the heading
   still leads on weight and size, so the hierarchy does not need colour too. */
.plan p { font-size: 14.5px; line-height: 1.62; color: var(--ink); }
.plan .btn { align-self: start; margin-top: auto; padding-top: 13px; }

.access { padding-block: clamp(44px, 5vw, 70px); }
.access-inner { max-width: 640px; }
.access .eyebrow { margin-bottom: 18px; }
.access h2 {
  font-size: clamp(30px, 4vw, 50px); line-height: 1.06;
  letter-spacing: -.028em; font-weight: 600;
  /* Optical, not metric. Every box in this stack starts on the gutter, but a
     round capital carries more side bearing than a 16px lede or the flat edge
     of the button, so the headline alone reads inset. The hang is in em, so it
     tracks the clamp. */
  margin-left: -.04em;
}
.access .lede { margin-top: 16px; font-size: 16px; line-height: 1.62; color: var(--muted); }
.constraint {
  margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--rule);
  font: 400 11.5px var(--mono); line-height: 1.85; color: var(--muted);
}
.access .actions { margin-top: 24px; }
.cta-contact { margin-top: 18px; font: 400 11.5px var(--mono); color: var(--muted); }
.cta-contact a { border-bottom: 1px solid var(--muted); text-decoration: none; }

/* ── Legal pages: the prose band, plus the headings and lists terms need ── */

/* The nav already draws this line, so the first band under it drops its own.
   Inert on the home page, where the hero sits between the two. */
.nav + main .band:first-child { border-top: 0; }
.why-body h3 { font-size: 17px; font-weight: 600; letter-spacing: -.015em; margin-top: 14px; color: var(--ink); }
.why-body ul { list-style: disc; padding-left: 20px; display: grid; gap: 8px; }
.why-body li { font-size: 15.5px; line-height: 1.62; color: var(--muted); }

/* ── Footer ───────────────────────────────────────────────────────────── */

.foot {
  display: flex; align-items: center; flex-wrap: wrap; gap: 14px 26px;
  max-width: none; margin-inline: 0;
  padding: 20px var(--gutter);
  border-top: 1px solid var(--edge);
  background: var(--shop-2);
}
.foot-brand { display: flex; align-items: center; gap: 9px; }
.foot-links { display: flex; flex-wrap: wrap; gap: 22px; margin-left: auto; }
.foot-links a {
  font: 400 11px var(--mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); text-decoration: none;
  padding: 10px 2px; margin: -10px -2px;
}
.foot-links a:hover { color: var(--ink); }
.foot small { font: 400 10px var(--mono); letter-spacing: .08em; color: var(--muted); }

/* ── Responsive ───────────────────────────────────────────────────────── */

@media (max-width: 1080px) {
  .nav-links { gap: 16px; overflow-x: auto; scrollbar-width: none; }
  .nav-links::-webkit-scrollbar { display: none; }
  .nav-links a { white-space: nowrap; }
  .plans { grid-template-columns: 1fr; }
  .steps > li, .steps.verbs > li { grid-template-columns: 64px minmax(0, 1fr); }
  .steps h3 { grid-column: 2; }
  .steps p, .steps .note { grid-column: 2; }
  .steps p { margin-top: 8px; }
}

@media (max-width: 760px) {
  .shot { width: 100%; }
  /* The wide graph puts four stages side by side. On a phone it is stood up:
     same stages, one under the next, at type that can be read. */
  .wire-graph .wg-wide { display: none; }
  .wire-graph .wg-portrait { display: block; max-width: 340px; margin-inline: auto; }
  .actions { gap: 16px; }
  .foot-links { margin-left: 0; width: 100%; order: 3; }
}

@media (max-width: 560px) {
  .nav { height: auto; flex-wrap: wrap; padding-top: 12px; }
  .nav-links { order: 3; width: 100%; padding: 12px 0; margin-left: 0; }
  /* The links drop to their own row here, so the toggle takes over pushing the
     call to action to the right edge instead of stranding itself mid-nav. */
  .theme-toggle { margin-left: auto; }
  .nav-cta { margin-left: 16px; }
  html { scroll-padding-top: 110px; }
  :root { --nav-h: 110px; }
  .steps > li, .steps.verbs > li { grid-template-columns: 1fr; gap: 6px; }
  .idx { padding-top: 0; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  /* Nothing to scrub: the scene opens on its ending, so the track is one screen. */
  .hero.is-live { height: calc(100vh - var(--nav-h)); }
  .brand.is-armed .wordmark { transition: none; }
  html[data-intro] #intro,
  html[data-intro] #intro canvas,
  html[data-intro] #intro-mark,
  html[data-intro] .nav { transition-duration: 0s; }
}

/* ── Subpages ─────────────────────────────────────────────────────────── */

.crumbs { max-width: var(--measure); margin: 22px auto 0; padding: 0 var(--gutter); }
.crumbs ol { display: flex; flex-wrap: wrap; gap: 8px; }
.crumbs li { font: 500 10px var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
.crumbs li + li::before { content: "/"; margin-right: 8px; color: var(--steel); }
.crumbs a { color: inherit; text-decoration: none; }
.crumbs a:hover { color: var(--ink); }
.crumbs [aria-current] { color: var(--ink); }

.page-head { border-top: 0; padding-top: clamp(36px, 5vw, 64px); padding-bottom: 0; }
.page-head h1 {
  font-family: var(--display);
  font-size: clamp(34px, 4.2vw, 58px);
  line-height: .95; letter-spacing: .004em; text-transform: uppercase; font-weight: 400;
  color: var(--ink); max-width: 20ch;
}
.page-head .lede { margin-top: 22px; max-width: 62ch; font-size: 16px; line-height: 1.62; color: var(--muted); }
.page-head .eyebrow { margin-bottom: 18px; }
.page-head .actions { margin-top: 30px; }

/* ── Forms ────────────────────────────────────────────────────────────── */

.form { margin-top: 34px; display: grid; gap: 18px; max-width: 560px; }
.field { display: grid; gap: 7px; }
.field label {
  font: 500 10px var(--mono); letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted);
}
.field input, .field textarea {
  background: var(--sheet); border: 1px solid var(--edge); border-radius: 0;
  padding: 12px 14px; font: 400 14.5px var(--sans); color: var(--ink);
  width: 100%;
}
.field textarea { resize: vertical; min-height: 96px; }
.field input::placeholder, .field textarea::placeholder { color: var(--steel); }
.field input:focus-visible, .field textarea:focus-visible {
  outline: 2px solid var(--accent-ink); outline-offset: 2px;
}
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.promise { font: 400 11.5px var(--mono); line-height: 1.8; color: var(--muted); }
.promise a { border-bottom: 1px solid var(--muted); text-decoration: none; }
.banner {
  border: 1px solid var(--accent-ink); background: var(--sheet);
  padding: 12px 16px; font: 400 12px var(--mono); line-height: 1.7; color: var(--ink);
}

/* ── Sticky mobile CTA ────────────────────────────────────────────────── */

.sticky-cta { display: none; }
@media (max-width: 760px) {
  .sticky-cta:not([hidden]) {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
    display: flex; align-items: center; gap: 12px;
    padding: 10px var(--gutter) calc(10px + env(safe-area-inset-bottom));
    background: var(--plate); border-top: 1px solid #2a3038;
  }
  .sticky-cta .btn { flex: 1; justify-content: center; }
  .sticky-cta.out:not([hidden]) { display: none; }
  .sticky-dismiss {
    flex: none; background: none; border: 1px solid #3b414a; color: var(--bone);
    width: 42px; height: 42px; font-size: 18px; line-height: 1; cursor: pointer;
  }
  body:has(.sticky-cta:not([hidden]):not(.out)) { padding-bottom: 76px; }
}

/* ── Comparison studies ─────────────────────────────────────────────────────── */

.study { padding: 28px 0; border-top: 1px solid var(--rule); display: grid; gap: 14px; max-width: 72ch; }
.study:last-of-type { border-bottom: 1px solid var(--rule); }
.study h3 { font-size: 20px; font-weight: 600; letter-spacing: -.018em; }
.study .meta {
  font: 400 10.5px var(--mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted);
}
.study p { font-size: 15px; line-height: 1.65; color: var(--muted); }
.study .proof {
  font: 400 11.5px var(--mono); line-height: 1.7; color: var(--muted);
  padding-top: 12px; border-top: 1px solid var(--rule);
}
.study .proof code { color: var(--ink); }

/* ── Reviews ──────────────────────────────────────────────────────────── */

.quotes { display: grid; max-width: 72ch; }
.quotes blockquote { margin: 0; padding: 24px 0; border-top: 1px solid var(--rule); }
.quotes blockquote:last-child { border-bottom: 1px solid var(--rule); }
.quotes blockquote > p { font-size: 17px; line-height: 1.55; letter-spacing: -.01em; }
.quotes blockquote footer {
  margin-top: 10px;
  font: 400 11px var(--mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted);
}
.empty-state {
  background: var(--sheet); border: 1px solid var(--edge); padding: 26px 28px; max-width: 62ch;
  font-size: 14.5px; line-height: 1.62; color: var(--muted);
}

/* ── Contact: map, directions, team ───────────────────────────────────── */

.map-embed {
  width: 100%; height: clamp(260px, 38vw, 420px);
  border: 1px solid var(--edge); display: block; background: var(--sheet);
}
.address { font: 400 12px var(--mono); line-height: 1.9; color: var(--muted); font-style: normal; }
.map-note { margin-top: 14px; font: 400 11.5px var(--mono); line-height: 1.7; color: var(--muted); }
.directions-links { margin-top: 16px; display: flex; flex-wrap: wrap; gap: 20px; }

/* ── Auth: sign in and account ────────────────────────────────────────── */

.auth-card { max-width: 460px; background: var(--sheet); border: 1px solid var(--edge); padding: 30px; display: grid; gap: 14px; }
.auth-card .promise { margin-top: 4px; }
.btn.ghost { background: transparent; border-color: var(--ink); color: var(--ink); }
.btn.ghost:hover, .btn.ghost:active { background: var(--ink); color: var(--shop); border-color: var(--ink); }
.btn.wide { justify-content: center; width: 100%; gap: 10px; }
.btn .mark { width: 16px; height: 16px; flex: none; }

.account-card { max-width: 560px; }
.account-row {
  display: grid; grid-template-columns: minmax(0, 15ch) minmax(0, 1fr); gap: 20px;
  padding: 14px 0; border-top: 1px solid var(--rule);
}
.account-row:last-of-type { border-bottom: 1px solid var(--rule); }
.account-row dt { font: 500 11px var(--mono); letter-spacing: .06em; text-transform: uppercase; padding-top: 2px; }
.account-row dd { font-size: 14.5px; color: var(--muted); overflow-wrap: anywhere; }
.avatar { width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--edge); display: block; }
.account-actions { margin-top: 26px; display: flex; flex-wrap: wrap; gap: 16px; }
.danger-note { margin-top: 26px; padding-top: 16px; border-top: 1px solid var(--rule); }

/* ── 404 ──────────────────────────────────────────────────────────────── */

.err h1 { font-size: clamp(96px, 18vw, 210px); line-height: .9; }
.err-paths { margin-top: 30px; display: flex; flex-wrap: wrap; align-items: center; gap: 20px; }

@media (max-width: 560px) {
  .account-row { grid-template-columns: 1fr; gap: 4px; }
  .auth-card { padding: 22px; }
}

/* ── Centre alignment ─────────────────────────────────────────────────── */
/* The page used to hang everything off one left edge. It now runs down a
   centre line: the measure still caps the line length, and margin-inline:auto
   puts that measure in the middle of the band rather than against the gutter.
   Kept as one block at the end so the rule that centres a thing sits next to
   the rule that centres its neighbour, instead of scattered through the file. */

.band { text-align: center; }
.band > * { margin-inline: auto; }
.band-head h2, .page-head h1, .page-head .lede, .status-line, .why-body { margin-inline: auto; }
.actions { justify-content: center; }
.crumbs ol { justify-content: center; }

.well-caption, .shot .well-caption { margin-inline: auto; text-align: center; }
.wire-graph { margin-inline: auto; }

/* One column, centred. The index label sits above its heading rather than in
   a gutter beside it, which is the only way a three-column row survives being
   centred at all. */
.steps > li, .steps.verbs > li { grid-template-columns: minmax(0, 72ch); justify-content: center; gap: 6px; }
.steps h3, .steps p, .steps .note { grid-column: 1; }
.steps p { margin-inline: auto; }
/* An answer that is a list is centred as a block and read as a list: the
   bullets keep their own left edge, or the eye has no column to run down. */
.steps ul {
  display: inline-block; text-align: left; max-width: 64ch;
  list-style: disc; padding-left: 1.1em;
}
.steps ul li { font-size: 15px; line-height: 1.55; color: var(--ink); }
.steps ul li + li { margin-top: 6px; }
.steps .note { margin-inline: auto; }
.idx { padding-top: 0; }

.plan { align-items: center; text-align: center; }
.plan .btn { align-self: center; }

.access-inner { margin-inline: auto; }
.access h2 { margin-left: 0; }

.study { margin-inline: auto; text-align: center; }
.study .proof { text-align: center; }

/* ── Comparison ───────────────────────────────────────────────────────── */
/* A real table: the reader is comparing four tools on the same four questions,
   and that is what a table is for. It takes back the band's measure, because a
   five-column comparison capped at 72ch is unreadable. */

.band > .table-scroll { max-width: 1280px; }
.table-scroll { overflow-x: auto; }
.compare {
  width: 100%; min-width: 980px;
  border-collapse: collapse; text-align: left;
  background: var(--sheet); border: 1px solid var(--edge);
}
.compare th, .compare td {
  padding: 15px 18px; vertical-align: top;
  font-size: 13.5px; line-height: 1.55; color: var(--ink);
}
/* One rule per row, drawn once across the whole row rather than around every
   cell. A border on each cell turned four columns into a grid of boxes. */
.compare tbody tr + tr th, .compare tbody tr + tr td { border-top: 1px solid var(--rule); }
.compare thead th {
  border-top: 0; border-bottom: 1px solid var(--edge);
  font: 600 11px var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); white-space: normal;
}
/* Sentence case in the page's own sans, on a column wide enough to hold a
   line. Tracked uppercase mono at 15ch spent five lines on six words. */
.compare tbody th {
  width: 26ch;
  font: 600 13.5px var(--sans); letter-spacing: -.01em;
  color: var(--ink);
}
/* Our column is the argument, so it is the only one at full strength: the rest
   of the table is the same questions answered quietly. The verdict leads each
   of our cells in the accent, so the column can be read down on its own. */
.compare .ours { background: var(--sheet); color: var(--ink); }
.compare thead .ours { color: var(--accent-ink); }
.compare .ours b { color: var(--accent-ink); font-weight: 600; }
.compare td:not(.ours) { color: var(--muted); }

/* ── People ───────────────────────────────────────────────────────────── */

.people { margin-top: 30px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
.person { display: grid; gap: 10px; justify-items: center; text-align: center; }
.person img {
  width: 128px; height: 128px; object-fit: cover;
  border: 1px solid var(--edge); border-radius: 50%;
}
.person h3 { font-size: 16px; font-weight: 600; letter-spacing: -.015em; }
.person .role {
  font: 500 10px var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
}
.person p { font-size: 14px; line-height: 1.6; color: var(--muted); max-width: 34ch; }
.person a { font: 400 12px var(--mono); color: var(--ink); border-bottom: 1px solid var(--muted); text-decoration: none; }

/* The page head pads to zero at the bottom so its lede can meet the next band.
   With the team and the call to action inside it, it needs its own floor. */
.page-head .promise { margin-top: 16px; padding-bottom: clamp(22px, 3vw, 36px); }

/* Continues the page head rather than opening a section: no rule, no gap. */
.band.tight { border-top: 0; padding-top: 0; }

.map-embed { margin-inline: auto; }
.directions-links { justify-content: center; }
.address { text-align: center; }

@media (max-width: 860px) {
  .compare, .people { grid-template-columns: 1fr; }
}

/* ── Scroll reveals ───────────────────────────────────────────────────── */
/* Paired with site.js. Only elements the script has marked are hidden, so the
   no-JS page is the whole page. */

.reveal { opacity: 0; transform: translateY(16px); }
.reveal, .reveal.shown {
  transition: opacity .55s ease, transform .55s cubic-bezier(.22,.7,.28,1);
}
.reveal.shown { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .reveal, .reveal.shown { opacity: 1; transform: none; transition: none; }
}
