/*
  WieVer landing site — stylesheet.

  ZERO BUILD STEP (ADR-007): plain CSS, no preprocessor, no @import, no CDN
  font, no external request of any kind. The page ships
  `default-src 'self'; script-src 'self'; style-src 'self'` with no
  'unsafe-inline' and no nonce, which has two consequences that constrain
  every choice below:

    1. There are no inline <style> blocks and no style="" attributes anywhere
       in index.html. ALL styling lives here, addressed by selector. JS may
       only touch style through CSSOM (el.style.setProperty), never
       setAttribute('style', ...).
    2. Inlined SVG in index.html must be styled from this file. It must NOT
       carry a <style> child — that child is blocked and dies silently.
       (assets/aurora-core-animated.svg keeps its internal <style> only
       because it is loaded via <img src>, which renders it as a separate,
       independently-CSP'd document.)

  LAYERS
    1. Tokens          — surfaces, ink, hues, type scale, spacing, motion
    2. Base            — reset, typography, focus
    3. Layout          — container, section rhythm
    4. Components      — header, buttons, mock, cards, story, pricing, faq
    5. Motion          — reveals, thread shimmer, the loom canvas host
    7. Replay, engines
       & pause         — the hero replay's per-beat atmosphere, the motion
                         toggle, the three story diagrams' mote/glow motion,
                         the #engines section, and the user-pause freeze
    6. Adaptations     — reduced motion, forced colors, responsive

  WHY 7 SITS BEFORE 6 IN THE FILE: the adaptations layer (reduced motion,
  forced-colors) must stay LAST so it wins on source order against equal
  specificity. The numbering follows the conceptual layer, the position follows
  the cascade. Do not "tidy" 7 to the bottom.

  TYPOGRAPHY NOTE
  Headline tracking is negative and scales with size (-0.037em at display
  size, easing to 0 at caption size). Display line-height is 1.02-1.1. These
  two properties do more for perceived quality than anything else on the
  page; do not "tidy" them back to defaults.

  DEPTH NOTE
  Depth comes from a surface ladder plus 1px hairlines plus a top edge
  highlight — not from drop shadows. Large blurred black shadows on a
  near-black canvas read as smudge, not elevation.
*/

/* ═══════════════════════════════════════════════════════ 1. TOKENS ═════ */

:root {
  /* Surfaces — ~6% luminance step per level, faintly blue-tinted.
     Never pure #000: it haloes against off-white text. */
  --bg-deep:   #08090C;
  --bg:        #0B0D10;
  --surface-1: #101317;
  --surface-2: #15191F;
  --surface-3: #191E25;

  /* Hairlines carry the elevation work that shadows do in light themes. */
  --hairline:        rgba(230, 232, 235, 0.08);
  --hairline-strong: rgba(230, 232, 235, 0.14);
  --hairline-solid:  #2A2F35;
  --edge-highlight:  inset 0 1px 0 rgba(255, 255, 255, 0.05);

  /* Ink — off-white, never #FFFFFF. */
  --fg:           #F4F5F6;
  --fg-muted:     #A4ABB3;
  --fg-dim:       #808890;  /* 4.91:1 on --surface-2 — smallest allowed */
  --fg-on-accent: #08090C;
  --fg-on-aurora: #FFFFFF;

  /* Aurora hues — derived from the product logo mark; this palette predates
     the current site revision and is shared verbatim with the desktop
     wizard, so the two surfaces read as one product. */
  --aurora-violet:   #6E50F8;
  --aurora-cyan:     #2BC8E0;
  --aurora-green:    #5BD3A4;
  --aurora-violet-2: #B9A8FF;
  --aurora-cyan-2:   #7DE6F5;
  --aurora-green-2:  #7EE0B8;

  /* Ember — the single warm accent. Used ONLY where threads merge: the
     hero knot, the merge-train card, the "landed" state. Its scarcity is
     the point; spreading it across the page destroys the effect. */
  --ember:    #FF9A6C;
  --ember-hi: #FFCDAA;

  /* Roles */
  --accent: #5BD3A4;
  --focus:  #4080FF;
  --ok:     #5BD3A4;
  --warn:   #F4CC85;
  --danger: #E07260;

  /* Gradients — reserved for the hero atmosphere, hairline rules and the
     wordmark. NOT a per-section background treatment. */
  --aurora-gradient:        linear-gradient(135deg, #6E50F8 0%, #2BC8E0 52%, #5BD3A4 100%);
  --aurora-gradient-bright: linear-gradient(135deg, #B9A8FF 0%, #7DE6F5 50%, #7EE0B8 100%);
  --wordmark-gradient:      linear-gradient(90deg, #B9A8FF, #7DE6F5, #7EE0B8);

  /* Type scale — fluid, clamped. Display steps carry negative tracking. */
  --text-display: clamp(2.75rem, 6.5vw, 4.75rem);  /* 44 -> 76px */
  --text-h2:      clamp(1.75rem, 3.4vw, 2.75rem);  /* 28 -> 44px */
  --text-h3:      1.125rem;
  --text-lead:    clamp(1.0625rem, 1.5vw, 1.25rem);
  --text-body:    1rem;
  --text-sm:      0.875rem;
  --text-xs:      0.8125rem;

  --track-display: -0.037em;
  --track-h2:      -0.025em;
  --track-h3:      -0.015em;
  --track-caps:     0.08em;

  --lead-display: 1.02;
  --lead-h2:      1.1;
  --lead-body:    1.6;

  --font-sans: "Inter", -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --font-mono: "Cascadia Mono", "Consolas", ui-monospace, monospace;

  /* Spacing — 8pt grid + 4pt micro. */
  --space-1: 4px;   --space-2: 8px;   --space-3: 12px;  --space-4: 16px;
  --space-5: 24px;  --space-6: 32px;  --space-8: 48px;  --space-10: 64px;
  --space-12: 96px; --space-16: 128px;

  /* Radii — 3 / 6 / 10 / 16 / 22 / pill. --radius-xs exists so terminal-adjacent
     surfaces (the demo engine chips) can read tighter than a marketing card;
     uniform 16px across four different object classes is the tell we are
     avoiding. */
  --radius-xs:   3px;
  --radius-sm:   6px;
  --radius:      10px;
  --radius-lg:   16px;
  --radius-xl:   22px;
  --radius-pill: 999px;

  /* Motion — two duration bands plus one acknowledgement step, two easings
     plus one overshoot.
     --dur-fast is for *acknowledgements* (a caption cross-fade, a ring
     settling): action-tied feedback must begin under 100ms, and 140ms is the
     smallest value that still reads as a transition rather than a jump.
     --ease-spring OVERSHOOTS, so it is legal on the transform channel only —
     never on colour or shadow, where an overshoot has nowhere to go. */
  --dur:      240ms;
  --dur-fast: 140ms;
  --dur-slow: 900ms;
  --ease:        cubic-bezier(0.2, 0.7, 0.2, 1);
  --ease-out:    cubic-bezier(0.15, 0.9, 0.25, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);

  --shadow-ambient: 0 28px 70px rgba(0, 0, 0, 0.42), 0 14px 32px rgba(0, 0, 0, 0.28);

  --container: 1200px;
  --measure:   68ch;
}

/* Registered so it can be interpolated — unregistered custom properties are
   not animatable, which is what makes the travelling thread highlight work. */
@property --thread-pos {
  syntax: '<percentage>';
  inherits: false;
  initial-value: -30%;
}

/* ═══════════════════════════════════════════════════════ 2. BASE ═══════ */

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }
html, body { margin: 0; }

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  line-height: var(--lead-body);
  -webkit-font-smoothing: antialiased;
  /* Belt and braces: the loom canvas is absolutely positioned and sized off
     the hero, so a sub-pixel overflow must never produce a horizontal bar. */
  overflow-x: hidden;
}

h1, h2, h3 { margin: 0 0 var(--space-4); }

h1 {
  font-size: var(--text-display);
  line-height: var(--lead-display);
  letter-spacing: var(--track-display);
  font-weight: 600;
}

h2 {
  font-size: var(--text-h2);
  line-height: var(--lead-h2);
  letter-spacing: var(--track-h2);
  font-weight: 600;
}

h3 {
  font-size: var(--text-h3);
  line-height: 1.3;
  letter-spacing: var(--track-h3);
  font-weight: 600;
}

p { margin: 0 0 var(--space-4); }

code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--surface-1);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  padding: 1px 5px;
}

a { color: var(--accent); }

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--accent);
  color: var(--fg-on-accent);
  padding: var(--space-2) var(--space-4);
  border-radius: 0 0 var(--radius) 0;
  z-index: 100;
}
.skip-link:focus { left: 0; }

a:focus-visible,
button:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ═════════════════════════════════════════════════════ 3. LAYOUT ══════ */

.wrap {
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--space-5);
}

.wrap-narrow {
  max-width: 760px;
  margin: 0 auto;
  padding: 0 var(--space-5);
}

section { padding: var(--space-10) 0; }

@media (min-width: 768px) {
  section { padding: var(--space-12) 0; }
}

/* Sticky header height + a little air. */
section[id] { scroll-margin-top: 80px; }

.section-head {
  max-width: var(--measure);
  margin: 0 auto var(--space-8);
  text-align: center;
}

.section-head p {
  color: var(--fg-muted);
  font-size: var(--text-lead);
  margin: 0;
}

.eyebrow {
  display: inline-block;
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--aurora-cyan-2);
  margin: 0 0 var(--space-3);
}

/* ═════════════════════════════════════════════════ 4. COMPONENTS ══════ */

/* ── Header ─────────────────────────────────────────────────────────── */

.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: rgba(11, 13, 16, 0.72);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--hairline);
}

.header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-top: var(--space-3);
  padding-bottom: var(--space-3);
}

.wordmark {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}

.wordmark-mark { display: block; flex: none; }

.wordmark-text {
  display: block;
  flex: none;
  height: 22px;
  width: auto;
}

.nav-list {
  list-style: none;
  display: none;
  gap: var(--space-6);
  margin: 0;
  padding: 0;
}

.nav-list a {
  color: var(--fg-muted);
  text-decoration: none;
  font-size: var(--text-sm);
  transition: color var(--dur) var(--ease);
}

.nav-list a:hover { color: var(--fg); }

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

/* ── Buttons ────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none;
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-5);
  min-height: 44px;
  border: 1px solid transparent;
  cursor: pointer;
  line-height: 1.4;
  transition: box-shadow var(--dur) var(--ease),
              background-color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              transform 100ms var(--ease);
}

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

/* A brand-dark scrim under the aurora fill. White text over the RAW gradient
   falls to 1.86:1 at the mint end — the scrim composites it to ~4.9:1
   worst-case while keeping the aurora look. Do not add filter:brightness()
   on hover; it erodes the contrast margin. Glow + lift carry the affordance. */
.btn-primary {
  background: linear-gradient(rgba(8, 9, 12, 0.45), rgba(8, 9, 12, 0.45)), var(--aurora-gradient);
  color: var(--fg-on-aurora);
  box-shadow: var(--edge-highlight);
}

.btn-primary:hover {
  box-shadow: var(--edge-highlight), 0 0 28px rgba(91, 211, 164, 0.32);
  transform: translateY(-1px);
}

.btn-secondary {
  color: var(--fg);
  background: transparent;
  /* 0.45 alpha resolves to ~3.9:1, clearing the 3:1 SC 1.4.11 minimum for a
     border-only control boundary. */
  border-color: rgba(230, 232, 235, 0.45);
}

.btn-secondary:hover {
  background: rgba(230, 232, 235, 0.08);
  border-color: rgba(230, 232, 235, 0.6);
}

/* Text-only tertiary — the low-commitment escape hatch. It must obviously
   lose the visual fight with .btn-primary. */
.btn-ghost {
  color: var(--fg-muted);
  background: none;
  border: none;
  padding: var(--space-3) var(--space-2);
}

.btn-ghost:hover { color: var(--fg); }

.btn-lg {
  font-size: 16px;
  padding: var(--space-4) var(--space-6);
  min-height: 52px;
}

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

.hero {
  position: relative;
  z-index: 0;
  padding: var(--space-12) 0 var(--space-10);
  text-align: center;
  overflow: hidden;
}

/* The atmospheric floor. Gradients live HERE and in the final CTA panel —
   nowhere else. A gradient mesh behind every section is the classic
   overuse that reads as tasteless. */
.hero::before {
  content: "";
  position: absolute;
  inset: -10% -10% auto;
  height: 90%;
  z-index: -2;
  pointer-events: none;
  background:
    radial-gradient(62% 60% at 50% 0%, rgba(110, 80, 248, 0.28), transparent 66%),
    radial-gradient(52% 52% at 78% 26%, rgba(43, 200, 224, 0.20), transparent 70%),
    radial-gradient(46% 46% at 20% 34%, rgba(91, 211, 164, 0.12), transparent 72%);
}

/* Host for the loom canvas — see loom.js. Sits above the gradient floor and
   below all hero text. Decorative only. */
.loom-host {
  position: absolute;
  inset: 0 0 auto;
  height: 100%;
  z-index: -1;
  pointer-events: none;
  /* Fade the filaments out behind the headline so type always wins. */
  mask-image: radial-gradient(80% 70% at 50% 45%, transparent 12%, #000 62%);
  -webkit-mask-image: radial-gradient(80% 70% at 50% 45%, transparent 12%, #000 62%);
}

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

/* NOTE: the .hero-pill that used to sit above the headline is deliberately
   gone, and nothing replaces it — the <h1> is now the first thing under the
   sticky header. Its two facts were objection-answering, not attention-getting,
   so they moved down to .hero-micro where they fire at the decision point. That
   pulled the mock ~110px up the fold; do not spend the reclaimed space back on
   padding.

   .pill-dot BELOW IS STILL LIVE — .mock-live reuses it for the mock's status
   dot. Deleting it silently breaks the mock chrome. */
.pill-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ok);
  box-shadow: 0 0 0 3px rgba(91, 211, 164, 0.16);
  flex: none;
}

.hero h1 {
  max-width: 15ch;
  margin-left: auto;
  margin-right: auto;
}

/* The gradient text-fill is decorative; forced-colors resets it below. */
.hero h1 .grad {
  background: var(--wordmark-gradient);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.hero-pitch {
  color: var(--fg-muted);
  font-size: var(--text-lead);
  max-width: 60ch;
  margin: 0 auto var(--space-6);
}

.hero-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  align-items: center;
  /* --space-2, not --space-4: the deleted .hero-note is what the larger gap was
     separating. .mock-stage's own margin supplies the real breathing room. */
  margin-bottom: var(--space-2);
}

@media (min-width: 480px) {
  .hero-actions { flex-direction: row; justify-content: center; }
}

/*
  One microcopy line under the CTA row (replaces the deleted .hero-note).

  --fg-dim IS FORBIDDEN HERE. The hero's ::before composites
  rgba(110,80,248,0.28) over --bg; at its worst point that is #27204F, against
  which --fg-dim measures 4.11:1 — an SC 1.4.3 failure. --fg-muted on the same
  composite is 6.43:1 and --fg for the price is 13.66:1. Rule of thumb: --fg-dim
  is legal on --surface-1/2/3 and --bg-deep, illegal inside .hero or .final-cta.
*/
.hero-micro {
  color: var(--fg-muted);
  font-size: var(--text-xs);
  max-width: 68ch;
  margin: 0 auto;
  text-wrap: pretty;   /* progressive; ignored where unsupported */
}

.hero-micro b { color: var(--fg); font-weight: 600; }

/* The trailing link is one phrase and must break as one — left to itself it
   wraps after "your engine," and reads as two unrelated links (QA BUG-2).
   ~26ch at --text-xs fits inside the narrowest supported viewport, so nowrap
   here cannot cause horizontal overflow. */
.hero-micro a { white-space: nowrap; }

/* ── Product mock ───────────────────────────────────────────────────── */
/*
  The hero "screenshot" is built from elements, not an image: crisp at every
  DPR, themeable, ~2KB, and no asset pipeline. Its data mirrors the real
  app's vocabulary (worker states idle/running/done/failed/merging, merge
  results clean/conflict) so it never shows a state the product can't reach.
  If real screenshots arrive, swap .mock-body's contents and keep the frame.
*/

.mock-stage {
  position: relative;
  max-width: 980px;
  margin: var(--space-10) auto 0;
  padding: 0 var(--space-4);
}

.mock {
  position: relative;
  background: var(--surface-1);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-ambient), var(--edge-highlight);
  overflow: hidden;
  text-align: left;
}

/* App chrome, not browser chrome — this is a desktop product. */
.mock-chrome {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-2);
  border-bottom: 1px solid var(--hairline);
}

.mock-dots { display: flex; gap: 6px; flex: none; }

.mock-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--hairline-solid);
}

.mock-title {
  font-size: var(--text-xs);
  color: var(--fg-dim);
  font-weight: 500;
  margin: 0;
}

.mock-live {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-xs);
  color: var(--fg-muted);
}

.mock-body { padding: var(--space-5); }

/* Worker board.
   Single column on the narrowest phones: at two columns each .worker gets
   ~130px, and after the pill and the gap the branch name ellipsises down to
   roughly one character ("f…"), which destroys the point of the board (QA
   BUG-3). One column is taller but every branch name stays readable. */
.board {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

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

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

.worker {
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
  box-shadow: var(--edge-highlight);
}

/* Three tracks — branch name (shrinkable, ellipsizing), worktree badge, pill.
   Grid rather than space-between so the badge appearing at >=700px cannot push
   the pill off the row. */
.worker-top {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.worker-name {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pill {
  flex: none;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  border-radius: var(--radius-pill);
  padding: 2px 8px;
  border: 1px solid transparent;
}

.pill-running { color: var(--aurora-cyan-2); background: rgba(43, 200, 224, 0.12); border-color: rgba(43, 200, 224, 0.28); }
.pill-done    { color: var(--aurora-green-2); background: rgba(91, 211, 164, 0.12); border-color: rgba(91, 211, 164, 0.28); }
.pill-merging { color: var(--ember-hi); background: rgba(255, 154, 108, 0.12); border-color: rgba(255, 154, 108, 0.30); }
.pill-idle    { color: var(--fg-dim); background: rgba(230, 232, 235, 0.06); border-color: var(--hairline-strong); }

.worker-bar {
  height: 4px;
  border-radius: var(--radius-pill);
  background: rgba(230, 232, 235, 0.08);
  overflow: hidden;
}

.worker-bar i {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--aurora-gradient-bright);
  /* scaleX, not width: the replay swaps ladder classes on six bars at once and
     transform animates on the compositor with no layout at all. The pill radius
     is squashed horizontally as a side effect — sub-pixel at 4px tall. */
  transform-origin: left center;
  transition: transform var(--dur-slow) var(--ease-out);
}

/*
  The fill ladder. Widths as CLASSES, because style="" attributes and
  setAttribute('style', ...) are CSP-blocked on this page and fail SILENTLY —
  demo.js writes `bar.className = 'worker-bar w-60'` and never touches style.
  Quantised to seven rungs on purpose; indistinguishable at 4px tall.
  w-0 exists because beat 0 needs six EMPTY tracks: six 25% stubs read as
  "already started", which is the opposite of what that beat says.
*/
.w-0   i { transform: scaleX(0); }
.w-25  i { transform: scaleX(0.25); }
.w-40  i { transform: scaleX(0.40); }
.w-60  i { transform: scaleX(0.60); }
.w-75  i { transform: scaleX(0.75); }
.w-90  i { transform: scaleX(0.90); }
.w-100 i { transform: scaleX(1); }

/* Merge train rail */
.train {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--edge-highlight);
}

.train-label {
  font-size: var(--text-xs);
  color: var(--fg-dim);
  flex: none;
  font-weight: 500;
}

.train-rail {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 0;
  min-width: 0;
}

.train-node {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--aurora-cyan);
  flex: none;
  box-shadow: 0 0 0 3px rgba(43, 200, 224, 0.14);
}

.train-line {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, rgba(43, 200, 224, 0.5), rgba(255, 154, 108, 0.5));
  min-width: 12px;
}

.train-knot {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--ember);
  flex: none;
  box-shadow: 0 0 0 4px rgba(255, 154, 108, 0.18), 0 0 18px rgba(255, 154, 108, 0.5);
}

/* Both words live in ONE grid area and cross-fade. A display toggle or a
   textContent swap would resize this cell, and the replay's CLS-is-exactly-0
   invariant is that geometry never changes. */
.train-out {
  display: grid;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ember-hi);
  flex: none;
  font-weight: 600;
}

.train-out > i {
  grid-area: 1 / 1;
  font-style: normal;
  transition: opacity var(--dur) var(--ease);
}

.to-wait { color: var(--fg-dim); opacity: 0; }
.to-done { color: var(--ember-hi); opacity: 1; }

/* ── Trust strip ────────────────────────────────────────────────────── */

.trust {
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  padding: var(--space-8) 0;
  background: var(--bg-deep);
}

.trust-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-6) var(--space-4);
  text-align: center;
}

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

.trust-num {
  display: block;
  font-size: clamp(1.5rem, 3vw, 2rem);
  font-weight: 600;
  letter-spacing: var(--track-h2);
  color: var(--fg);
  line-height: 1.1;
}

.trust-label {
  display: block;
  font-size: var(--text-xs);
  color: var(--fg-dim);
  margin-top: var(--space-1);
}

/* ── Bento feature grid ─────────────────────────────────────────────── */

.bento {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  list-style: none;
  margin: 0;
  padding: 0;
}

@media (min-width: 720px) {
  .bento { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Asymmetry is what makes a bento a bento rather than a table of cards.
     At 2-col the lead card spans the full width; cards 2-9 tile as four clean
     pairs below it, and the 10th spans again to close the block — ten cards is
     an even count, so without that second span the last row is a lone orphan
     beside a hole. */
  .bento > li:nth-child(1) { grid-column: span 2; }
  .bento > li:nth-child(10) { grid-column: span 2; }
}

@media (min-width: 1040px) {
  .bento { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .bento > li:nth-child(1) { grid-column: span 2; }
  .bento > li:nth-child(6) { grid-column: span 2; }
  /* Explicitly UNDO the 2-col span above: min-width queries cascade, so it is
     still in force here. At 3-col the two spanned cards already square the
     grid (2+1 / 3 / 2+1 / 3) and a third span would re-open the gap. */
  .bento > li:nth-child(10) { grid-column: auto; }
}

.card {
  position: relative;
  background: var(--surface-1);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--edge-highlight);
  overflow: hidden;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.card:hover,
.card:focus-within {
  border-color: var(--hairline-strong);
  transform: translateY(-2px);
}

/* The thread along the card's top edge. At rest it is the static aurora
   rule; on hover a brighter filament travels along it. */
.card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background:
    linear-gradient(90deg,
      transparent 0%,
      var(--aurora-cyan-2) calc(var(--thread-pos) + 8%),
      transparent calc(var(--thread-pos) + 22%)),
    var(--aurora-gradient);
}

.card:hover::before,
.card:focus-within::before {
  animation: thread-run 1100ms var(--ease);
}

@keyframes thread-run {
  to { --thread-pos: 100%; }
}

/*
  The merge card, and only the merge card, runs the filament TWICE with a
  250ms gap: once for the merge, once for the verify gate that re-runs the
  tests on the merged tree before anything is allowed to stay. The gap is
  deliberate — 100ms reads as one event, 500ms as two unrelated ones. Do not
  repeat this beat on other cards or it stops meaning anything.
*/
.card-merge:hover::before,
.card-merge:focus-within::before {
  animation: thread-run 700ms var(--ease), thread-run 700ms var(--ease) 250ms;
}

.card h3 { color: var(--fg); }

.card p {
  color: var(--fg-muted);
  font-size: var(--text-sm);
  margin-bottom: 0;
}

.card-icon {
  display: block;
  width: 26px;
  height: 26px;
  margin-bottom: var(--space-4);
  color: var(--aurora-cyan-2);
}

/* ── Story sections (the scroll-driven "slides") ────────────────────── */
/*
  Each story block is a two-column grid whose media column is sticky. As the
  text column scrolls past, the visual holds — the pinned-slide feel, with
  no scroll-jacking, no JS, and a graceful single-column stack on phones.
*/

.story {
  border-top: 1px solid var(--hairline);
}

.story-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-8);
  align-items: start;
}

@media (min-width: 900px) {
  .story-grid {
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr);
    gap: var(--space-12);
    min-height: 78vh;
  }
  .story-media {
    position: sticky;
    top: 18vh;
  }
  /* Alternate sides so consecutive blocks don't read as a list. */
  .story-alt .story-copy { order: 2; }
  .story-alt .story-media { order: 1; }
}

.story-copy h2 { margin-bottom: var(--space-4); }

.story-copy p {
  color: var(--fg-muted);
  font-size: var(--text-lead);
  max-width: 48ch;
}

.story-points {
  list-style: none;
  margin: var(--space-6) 0 0;
  padding: 0;
}

.story-points li {
  position: relative;
  padding-left: var(--space-6);
  margin-bottom: var(--space-3);
  color: var(--fg-muted);
  font-size: var(--text-sm);
}

.story-points li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--aurora-cyan);
  box-shadow: 0 0 0 3px rgba(43, 200, 224, 0.14);
}

.story-points b { color: var(--fg); font-weight: 600; }

/*
  Skip rendering the diagram column while it is off-screen. Applied to
  .story-media ONLY — never to .story or .story-grid, which carries
  min-height: 78vh and whose media child is position: sticky at >=900px, so
  containing the whole section is a layout hazard. The intrinsic size is the
  460x240 viewBox at the column's real width (~260px tall), and the grid's own
  min-height absorbs any residual difference, so this cannot shift the page.
  Back this pair of declarations out if sticky or find-in-page ever regresses —
  it is a nice-to-have, and nothing else depends on it.
*/
.story-media {
  content-visibility: auto;
  contain-intrinsic-size: auto 260px;
}

/* Inline SVG diagrams. NOTE: these carry no <style> child and no style=""
   attribute — both are CSP-blocked. Everything is styled from here. */
.diagram {
  display: block;
  width: 100%;
  height: auto;
  background: var(--surface-1);
  /* --radius, not --radius-lg: this is a technical schematic sitting beside
     prose, and it must not read as soft as a marketing card. Four different
     object classes at an identical 16px is the uniformity tell. */
  border-radius: var(--radius);
  border: 1px solid var(--hairline);
  box-shadow: var(--edge-highlight);
}

/*
  THE RESTING STATE IS THE DRAWN STATE (ADR-102). stroke-dashoffset: 0 with
  pathLength="1" and stroke-dasharray: 1 means one dash covering the whole
  path — i.e. fully drawn — with NO animation, NO class and NO JS. The draw-in
  keyframes only exist under .diagram.is-live, which motion.js adds from the
  shared IntersectionObserver.

  This is the inversion of the original authoring, where the default was
  stroke-dashoffset: 1 (blank) and the reduced-motion block had to patch it
  back. Do not reintroduce a blank default: it silently blanks all three
  diagrams for JS-blocked, reduced-motion, forced-colors, print and crawler
  renders at once.
*/
.diagram .d-thread {
  fill: none;
  stroke: var(--aurora-cyan);
  stroke-width: 1.5;
  stroke-linecap: round;
  /* pathLength="1" on the path normalises dash maths to a 0-1 scale, so the
     draw-on works without ever calling getTotalLength() in JS. */
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
}

.diagram .d-thread-2 { stroke: var(--aurora-violet-2); }
.diagram .d-thread-3 { stroke: var(--aurora-green-2); }
.diagram .d-thread-4 { stroke: var(--aurora-cyan-2); }

.diagram .d-merged {
  fill: none;
  stroke: var(--ember);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
}

/* ONE-SHOT per on-screen entry, never a loop: stroke-dashoffset repaints the
   whole path every frame, so it is the one property on this page that may not
   run continuously. The looping per-cycle motion is carried by the motes, the
   knot flash and the plate pulses, all of which are transform/opacity/paint on
   tiny elements. */
.diagram.is-live .d-thread {
  animation: weave-draw var(--dur-slow) var(--ease) both;
}

.diagram.is-live .d-thread-2 { animation-delay: 120ms; }
.diagram.is-live .d-thread-3 { animation-delay: 240ms; }
.diagram.is-live .d-thread-4 { animation-delay: 360ms; }

.diagram.is-live .d-merged {
  animation: weave-draw var(--dur-slow) var(--ease) 480ms both;
}

.diagram .d-knot { fill: var(--ember); }
.diagram .d-node { fill: var(--aurora-cyan); }
.diagram .d-label { fill: var(--fg-dim); font-size: 11px; font-family: var(--font-mono); }
.diagram .d-label-hi { fill: var(--ember-hi); font-size: 11px; font-family: var(--font-mono); font-weight: 600; }
.diagram .d-plate { fill: var(--surface-2); stroke: var(--hairline-solid); stroke-width: 1; }

/* Diagram 1's terminal glyph: hollow + dashed = unresolved. Diagrams 2 and 3
   end in a FILLED ember knot = landed. The pair is the page's terminal-glyph
   grammar and the dash pattern is preserved even under forced-colors, because
   the open-vs-filled distinction is the meaning. */
.diagram .d-open {
  fill: none;
  stroke: var(--fg-dim);
  stroke-width: 1.2;
  stroke-dasharray: 2.6 2.6;
}

@keyframes weave-draw {
  from { stroke-dashoffset: 1; }
  to   { stroke-dashoffset: 0; }
}

/* Retime the draw-on to scroll position where supported — an ENHANCEMENT of
   the .is-live animation, never a replacement for it: without it the diagram is
   already drawn (the resting state), and with .is-live absent there is no
   animation to retime. Scoped to .is-live so a paused or off-screen diagram
   cannot be re-driven by the scroll timeline. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .diagram.is-live .d-thread,
    .diagram.is-live .d-merged {
      animation-timeline: view();
      animation-range: entry 12% cover 42%;
      animation-duration: auto;
      animation-fill-mode: both;
    }
  }
}

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

.pricing-card {
  max-width: 420px;
  margin: 0 auto;
  background: linear-gradient(var(--surface-1), var(--surface-1)) padding-box,
              var(--aurora-gradient) border-box;
  border: 1px solid transparent;
  border-radius: var(--radius-xl);
  padding: var(--space-8);
  text-align: center;
  box-shadow: var(--shadow-ambient);
}

.pricing-plan {
  color: var(--fg-muted);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  margin-bottom: var(--space-2);
}

.pricing-amount { margin-bottom: var(--space-6); }

.pricing-number {
  color: var(--fg);
  font-size: 56px;
  font-weight: 600;
  letter-spacing: -0.03em;
}

.pricing-period { color: var(--fg-muted); font-size: var(--text-body); }

.pricing-features {
  list-style: none;
  text-align: left;
  margin: 0 0 var(--space-6);
  padding: 0;
}

.pricing-features li {
  position: relative;
  color: var(--fg-muted);
  padding: var(--space-3) 0 var(--space-3) var(--space-6);
  border-bottom: 1px solid var(--hairline);
  font-size: var(--text-sm);
}

.pricing-features li:last-child { border-bottom: none; }

.pricing-features li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 1.25em;
  width: 10px;
  height: 5px;
  border-left: 1.5px solid var(--accent);
  border-bottom: 1.5px solid var(--accent);
  transform: rotate(-45deg);
}

.pricing-cta { width: 100%; }

/* ── BYO Claude ─────────────────────────────────────────────────────── */

.byo-claude .wrap-narrow { text-align: center; }
.byo-claude p { color: var(--fg-muted); font-size: var(--text-lead); }

/* ── FAQ ────────────────────────────────────────────────────────────── */

.faq-item {
  position: relative;
  background: var(--surface-1);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-3);
  box-shadow: var(--edge-highlight);
}

.faq-item summary {
  cursor: pointer;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.faq-item summary::marker { color: var(--accent); }

.faq-item p {
  color: var(--fg-muted);
  font-size: var(--text-sm);
  margin: var(--space-3) 0 0;
}

.faq-item[open]::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 3px;
  border-radius: var(--radius) 0 0 var(--radius);
  background: var(--aurora-gradient);
  opacity: 0.8;
}

/* ── Final CTA ──────────────────────────────────────────────────────── */

.final-cta {
  position: relative;
  overflow: hidden;
  border-top: 1px solid var(--hairline);
  background: var(--bg-deep);
  text-align: center;
}

.final-cta::before {
  content: "";
  position: absolute;
  inset: auto -10% -60%;
  height: 120%;
  pointer-events: none;
  background: radial-gradient(50% 50% at 50% 100%, rgba(110, 80, 248, 0.22), transparent 70%);
}

.final-cta .wrap-narrow { position: relative; }

.final-cta p {
  color: var(--fg-muted);
  font-size: var(--text-lead);
  margin-bottom: var(--space-6);
}

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

.site-footer {
  border-top: 1px solid var(--hairline);
  padding: var(--space-10) 0 var(--space-6);
  color: var(--fg-muted);
  font-size: var(--text-sm);
}

.footer-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-8);
  margin-bottom: var(--space-8);
}

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

.footer-brand p {
  color: var(--fg-dim);
  font-size: var(--text-xs);
  max-width: 34ch;
  margin: var(--space-3) 0 0;
}

.footer-col h3 {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--fg-dim);
  margin: 0 0 var(--space-3);
  font-weight: 600;
}

.footer-col ul { list-style: none; margin: 0; padding: 0; }
.footer-col li { margin-bottom: var(--space-2); }

.footer-col a {
  color: var(--fg-muted);
  text-decoration: none;
  transition: color var(--dur) var(--ease);
}

.footer-col a:hover { color: var(--fg); }

.footer-bottom {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border-top: 1px solid var(--hairline);
  padding-top: var(--space-5);
  color: var(--fg-dim);
  font-size: var(--text-xs);
}

@media (min-width: 640px) {
  .footer-bottom { flex-direction: row; justify-content: space-between; }
}

.footer-bottom p { margin: 0; }

/* ═══════════════════════════════════════════════════════ 5. MOTION ════ */

/*
  Scroll reveals.

  CRITICAL: the default state is VISIBLE. The hidden start state exists only
  inside @supports, because scroll-driven animations are not Baseline —
  Firefox still has them behind a flag as of mid-2026 (~83% global support).
  Authoring `opacity: 0` as the resting state outside this guard is exactly
  how ~17% of visitors, every crawler and every screenshot service get a
  blank page.
*/
.reveal { opacity: 1; }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .reveal {
      animation: reveal both;
      animation-timeline: view();
      animation-range: entry 8% cover 34%;
    }
    /* Stagger by shifting each item's range rather than by animation-delay,
       which a view() timeline ignores. */
    .reveal-2 { animation-range: entry 12% cover 40%; }
    .reveal-3 { animation-range: entry 16% cover 46%; }
  }
}

@keyframes reveal {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: none; }
}

/* ═══════════════════════════════ 7. REPLAY, ENGINES & PAUSE ═══════════ */

/*
  The hero board replay's chrome and per-beat atmosphere, plus the global motion
  pause. TWO INVARIANTS GOVERN THIS WHOLE LAYER:

  (1) RESTING STATE = FINISHED STATE. Every rule below is authored so that the
      state with NO class, NO attribute and NO JS is the LAST beat: pills done,
      bars full, knot lit, "merged" showing, all six dots on. demo.js writes
      [data-beat="…"] to REWIND. Authoring an empty resting state here silently
      breaks five fallbacks at once (JS-blocked, reduced-motion, forced-colors,
      print, crawlers) — the same class of bug already scarred the .reveal note
      in layer 5.

  (2) THE PAUSE LIST AT THE BOTTOM OF THIS LAYER IS ENUMERATED, NOT `*`. Any new
      LOOPING animation anywhere on the page MUST be added to it, or the header
      toggle silently fails to stop it. A user pause is a FREEZE
      (animation-play-state), which is a different intent from reduced motion's
      duration-zeroing in layer 6 — that one wants the finished state. Two
      intents, two mechanisms; keep both in step, and keep the JS half
      (motion.js) in step with both.

  THE BEAT TABLE IS NOT HERE. It is data in demo.js — six objects naming the
  caption, the six pill labels, the six ladder classes and the picked chips.
  This layer only carries atmosphere keyed off [data-beat].
*/

/* ── Motion toggle (header) ──────────────────────────────────────────── */

/* One flex child holding toggle + Download, so .header-row keeps the three-item
   space-between rhythm it had before the toggle existed. */
.header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: none;
}

/* ── Demo: engine chips ─────────────────────────────────────────────── */

.demo-engines {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: var(--space-4);
}

.demo-chip {
  font-family: var(--font-mono);
  font-size: 11px;                  /* matches .pill */
  color: var(--fg-dim);             /* 4.91:1 on --surface-2 — the floor */
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-xs);  /* terminal-tight, not card-round */
  padding: 2px 7px;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}

.demo-chip.is-picked {
  color: var(--aurora-cyan-2);
  border-color: rgba(43, 200, 224, 0.28);   /* = .pill-running's border */
  box-shadow: 0 0 12px rgba(43, 200, 224, 0.16);
}

/* The pick RHYTHM is CSS, not JS: demo.js adds .is-picked to all three chips in
   one write and these delays fan them out 0 / 240 / 480ms. */
[data-beat="engines"] .demo-chip:nth-child(2) { transition-delay: 240ms; }
[data-beat="engines"] .demo-chip:nth-child(3) { transition-delay: 480ms; }

/* ── Demo: worktree badge + row state ───────────────────────────────── */

.worker-wt {
  display: none;                    /* <700px there is no room in a 2-col board */
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--aurora-green-2);     /* resting = LIT (finished state) */
  opacity: 1;
  transition: opacity var(--dur) var(--ease), color var(--dur) var(--ease);
}

@media (min-width: 700px) {
  .worker-wt { display: block; }
}

.worker-name { transition: color var(--dur) var(--ease); }

.pill {
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}

/* Beats 0-1: nothing has started. Expressed with COLOUR, never opacity or
   display — dimming a text node could change which element the browser picks as
   the LCP element. */
[data-beat="project"] .worker-name,
[data-beat="engines"] .worker-name { color: var(--fg-dim); }

[data-beat="project"] .worker-wt,
[data-beat="engines"] .worker-wt { color: var(--fg-dim); opacity: 0.4; }

/* Beat 2 lights the six rows left-to-right in reading order, 6 x 180ms. */
[data-beat="isolate"] .worker:nth-child(1) .worker-wt,
[data-beat="isolate"] .worker:nth-child(1) .pill { transition-delay: 0ms; }
[data-beat="isolate"] .worker:nth-child(2) .worker-wt,
[data-beat="isolate"] .worker:nth-child(2) .pill { transition-delay: 180ms; }
[data-beat="isolate"] .worker:nth-child(3) .worker-wt,
[data-beat="isolate"] .worker:nth-child(3) .pill { transition-delay: 360ms; }
[data-beat="isolate"] .worker:nth-child(4) .worker-wt,
[data-beat="isolate"] .worker:nth-child(4) .pill { transition-delay: 540ms; }
[data-beat="isolate"] .worker:nth-child(5) .worker-wt,
[data-beat="isolate"] .worker:nth-child(5) .pill { transition-delay: 720ms; }
[data-beat="isolate"] .worker:nth-child(6) .worker-wt,
[data-beat="isolate"] .worker:nth-child(6) .pill { transition-delay: 900ms; }

/* ── Demo: the merge train ──────────────────────────────────────────── */

.train-node,
.train-line,
.train-knot {
  transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

/* The train must not light before there is anything to merge, so beats 0-3 run
   it dark. Beat 4 simply stops matching these rules and the authored (lit)
   values return. */
[data-beat="project"] .train-node,
[data-beat="engines"] .train-node,
[data-beat="isolate"] .train-node,
[data-beat="run"] .train-node,
[data-beat="project"] .train-knot,
[data-beat="engines"] .train-knot,
[data-beat="isolate"] .train-knot,
[data-beat="run"] .train-knot {
  background: var(--hairline-solid);
  box-shadow: none;
}

[data-beat="project"] .train-line,
[data-beat="engines"] .train-line,
[data-beat="isolate"] .train-line,
[data-beat="run"] .train-line {
  background: var(--hairline-strong);
}

/* Beat 4 relights the three nodes left->right. :nth-of-type is WRONG here —
   every child of .train-rail is an <i>, so type counting cannot separate nodes
   from lines. The nodes are children 1, 3 and 5. */
[data-beat="verify"] .train-rail > :nth-child(1) { transition-delay: 0ms; }
[data-beat="verify"] .train-rail > :nth-child(3) { transition-delay: 220ms; }
[data-beat="verify"] .train-rail > :nth-child(5) { transition-delay: 440ms; }

.train-rail { position: relative; }

/*
  A mote crosses the rail on the verify beat. The pseudo-element spans the WHOLE
  rail and paints the dot with a radial gradient at its left edge, so
  translateX(100%) is a percentage of the RAIL's width and the whole thing stays
  a compositor-only opacity+transform animation. The obvious implementation —
  a 6px dot with animated `left` inside `overflow: hidden` — was rejected twice
  over: it animates layout, and the clip would crop the nodes' 3px glow rings
  (9px dot + 6px ring > the 13px rail height), changing the RESTING picture.
*/
[data-beat="verify"] .train-rail::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(6px 6px at 3px 50%, var(--ember) 0 45%, rgba(255, 154, 108, 0) 70%);
  animation: rail-mote 1100ms var(--ease) 300ms both;
}

@keyframes rail-mote {
  0%   { opacity: 0; transform: translateX(0); }
  18%  { opacity: 1; }
  82%  { opacity: 1; }
  100% { opacity: 0; transform: translateX(100%); }
}

/*
  The verify DOUBLE-PULSE. This is the page's existing idiom, reused byte for
  byte: 700ms + a 250ms gap + 700ms, exactly as .card-merge's filament runs —
  once for the merge, once for the verify gate that re-runs the tests on the
  merged tree. Do NOT invent a second rhythm here; the whole point is that
  "merge, then verify" is ONE motion idiom page-wide.
*/
[data-beat="verify"] .train-knot {
  animation: knot-pulse 700ms var(--ease), knot-pulse 700ms var(--ease) 950ms;
}

@keyframes knot-pulse {
  0%   { transform: scale(1); box-shadow: 0 0 0 4px rgba(255, 154, 108, 0.18), 0 0 18px rgba(255, 154, 108, 0.5); }
  50%  { transform: scale(1.18); box-shadow: 0 0 0 6px rgba(255, 154, 108, 0.22), 0 0 26px rgba(255, 154, 108, 0.6); }
  100% { transform: scale(1); box-shadow: 0 0 0 4px rgba(255, 154, 108, 0.18), 0 0 18px rgba(255, 154, 108, 0.5); }
}

/* Beat 5: the payoff flash. */
[data-beat="merged"] .train-knot {
  animation: knot-flash 620ms var(--ease-out) both;
}

@keyframes knot-flash {
  0%   { transform: scale(1); box-shadow: 0 0 0 4px rgba(255, 154, 108, 0.18), 0 0 18px rgba(255, 154, 108, 0.5); }
  45%  { transform: scale(1.35); box-shadow: 0 0 0 6px rgba(255, 154, 108, 0.24), 0 0 30px rgba(255, 154, 108, 0.62); }
  100% { transform: scale(1); box-shadow: 0 0 0 4px rgba(255, 154, 108, 0.18), 0 0 18px rgba(255, 154, 108, 0.5); }
}

/* "queued" -> "merged" is an opacity cross-fade between two always-present
   words. The 320ms delay makes the word arrive AFTER the flash, not with it. */
[data-beat="project"] .to-wait,
[data-beat="engines"] .to-wait,
[data-beat="isolate"] .to-wait,
[data-beat="run"] .to-wait,
[data-beat="verify"] .to-wait { opacity: 1; }

[data-beat="project"] .to-done,
[data-beat="engines"] .to-done,
[data-beat="isolate"] .to-done,
[data-beat="run"] .to-done,
[data-beat="verify"] .to-done { opacity: 0; }

[data-beat="merged"] .to-done { transition-delay: 320ms; }

/* ── Demo: progress dots + caption ──────────────────────────────────── */

.demo-meta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

.demo-dots { display: flex; gap: var(--space-1); }

.demo-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--aurora-cyan-2);   /* resting = all six lit */
  transition: background-color var(--dur) var(--ease);
}

/* Order matters: the unlit rule is scoped to [data-beat], so the attribute-free
   resting markup lights all six with no extra selector, and the per-beat rules
   below (one class heavier) light 1..N. Read-only indicator — six dots tell the
   visitor this is a finite loop rather than a hung animation. NOT interactive,
   so SC 2.5.8's 24px floor does not apply. */
[data-beat] .demo-dot { background: var(--hairline-solid); }

[data-beat="project"] .demo-dot:nth-child(-n+1),
[data-beat="engines"] .demo-dot:nth-child(-n+2),
[data-beat="isolate"] .demo-dot:nth-child(-n+3),
[data-beat="run"]     .demo-dot:nth-child(-n+4),
[data-beat="verify"]  .demo-dot:nth-child(-n+5) { background: var(--aurora-cyan-2); }

[data-beat="merged"] .demo-dot { background: var(--aurora-cyan-2); }

.demo-caption {
  /* The single credible CLS source on this page: a two-line beat name must not
     shift anything. Reserve the second line permanently. */
  min-height: 2.6em;
  margin: 0;
  font-size: var(--text-xs);
  color: var(--fg-muted);
  text-align: center;
}

/*
  The caption cross-fade is zero-JS: changing [data-beat] changes which rule
  matches, and a DIFFERENT animation-name is what makes the browser restart it.
  Two identical keyframes alternating a/b is therefore load-bearing, not
  sloppiness — six rules naming ONE keyframe would compute to the same
  animation-name and never re-run. Consecutive beats (including merged -> the
  rewind to project) always differ.
*/
[data-beat="project"] .demo-caption,
[data-beat="isolate"] .demo-caption,
[data-beat="verify"]  .demo-caption { animation: cap-in-a var(--dur-fast) var(--ease-out); }

[data-beat="engines"] .demo-caption,
[data-beat="run"]     .demo-caption,
[data-beat="merged"]  .demo-caption { animation: cap-in-b var(--dur-fast) var(--ease-out); }

@keyframes cap-in-a { from { opacity: 0; transform: translateY(3px); } }
@keyframes cap-in-b { from { opacity: 0; transform: translateY(3px); } }

/* ── Story diagrams: motes, glow, stall (the wiever effect) ─────────── */

/*
  The loom's grammar, propagated to the three inline SVGs. Read this before
  touching anything below:

  * RESTING = FINISHED, again. Every diagram ships fully drawn (layer 4), with
    its motes at opacity 0 and — for diagrams 2 and 3 — the ember knot already
    lit. Reduced motion, a user pause, an off-screen diagram, JS blocked,
    unsupported offset-path and print all land on that same frame, which already
    says "four branches became one verified branch" / "it resumed".
  * TWO gates reveal a mote, never one: .motes-ready (diagrams.js seeded
    offset-path from the paired path's `d`) AND .is-live (motion.js says this
    diagram is on screen and no gate is off). Opacity lives ONLY inside the
    keyframes, so a seeded-but-not-live diagram shows no stationary dots parked
    at path start.
  * offset-path is NOT set here. It is seeded per mote from the `d` attribute in
    index.html — one source of truth per curve. Never paste a path string into
    this file.
  * Every animation below is transform / opacity / fill / stroke on a small
    element. stroke-dashoffset appears exactly once, in layer 4's ONE-SHOT
    draw-in, because it repaints the whole path every frame.
  * Each diagram's animations share ONE cycle duration (d1 3200ms, d2 3600ms,
    d3 4200ms) and encode their timing as percentages of it. That is what keeps
    the mote arrival, the knot flash and the plate pulse permanently in phase;
    animation-delay is then safe, because an equal-duration animation delayed by
    N ms keeps its phase offset forever.
  * LONGHANDS, NEVER the `animation` shorthand, for anything that LOOPS here.
    The shorthand resets animation-play-state to `running`, and these selectors
    are three classes deep — a specificity note kept from when a pause list existed at
    the bottom of this layer — so a shorthand would silently win and defeat the
    pause toggle. Do not "tidy" these longhands back into one line. (Layer 4's
    one-shot weave-draw keeps the shorthand on purpose: a one-shot draw-in must
    NOT be freezable half-drawn, and it is not in the pause list.)
*/

.diagram .d-mote {
  fill: var(--aurora-cyan-2);
  opacity: 0;
  /* The mote is a dot: rotating it along the path would be invisible work. */
  offset-rotate: 0deg;
}

.diagram .d-glow {
  opacity: 0;
  mix-blend-mode: screen;
  transform-box: fill-box;
  transform-origin: center;
}

/* transform-box: fill-box is REQUIRED — without it an SVG transform-origin
   resolves against the whole viewBox and the knot flies off when it scales. */
.diagram .d-knot {
  transform-box: fill-box;
  transform-origin: center;
}

/* Both gates. Iteration count here, animation-name per diagram below. */
.diagram.motes-ready.is-live .d-mote {
  animation-iteration-count: infinite;
}

/* Low tier (small viewport or <=4 cores) halves every mote group. Hidden by
   opacity and a cancelled animation — never display, never node removal — so
   the geometry is identical on both tiers. Specificity has to clear the
   three-class gate rule above, hence html.tier-low + the same three classes. */
html.tier-low .diagram.motes-ready.is-live .d-m2,
html.tier-low .diagram.motes-ready.is-live .d-m4,
html.tier-low .diagram.motes-ready.is-live .d-e2 {
  animation-name: none;
  opacity: 0;
}

/* Diagram 1 — DIVERGENCE, grammar inverted. 3200ms, var(--ease): decelerating,
   because this is energy draining away rather than arriving. The motes cool
   from cyan to --fg-dim and fade out BEFORE the terminus. There is no knot,
   no flash and no .d-glow element in this SVG at all; the four dashed open
   rings say "unresolved" instead. That withholding is the diagram's argument —
   do not add a payoff here. */
.diagram-1.motes-ready.is-live .d-mote {
  animation-name: d1-mote;
  animation-duration: 3200ms;
  animation-timing-function: var(--ease);
}

.diagram-1 .d-m2 { animation-delay: 300ms; }
.diagram-1 .d-m3 { animation-delay: 600ms; }
.diagram-1 .d-m4 { animation-delay: 900ms; }

@keyframes d1-mote {
  0%   { offset-distance: 0%; opacity: 0; fill: var(--aurora-cyan-2); }
  8%   { opacity: 0.95; }
  65%  { opacity: 0.55; }
  100% { offset-distance: 100%; opacity: 0; fill: var(--fg-dim); }
}

/* Diagram 2 — the canonical grammar. One 3600ms cycle:
   0-1600 four motes converge on the knot · 1600-2120 ember flash ·
   1700-2400 verify pulse A · 250ms gap · 2650-3350 verify pulse B ·
   2900-3600 two ember motes leave along the merged branch. */
.diagram-2.motes-ready.is-live .d-mote {
  animation-duration: 3600ms;
  /* Accelerating INTO the junction — the opposite of diagram 1's easing. */
  animation-timing-function: var(--ease-out);
}

.diagram-2.motes-ready.is-live .d-m1,
.diagram-2.motes-ready.is-live .d-m2,
.diagram-2.motes-ready.is-live .d-m3,
.diagram-2.motes-ready.is-live .d-m4 { animation-name: d2-mote; }

.diagram-2 .d-m2 { animation-delay: 180ms; }
.diagram-2 .d-m3 { animation-delay: 360ms; }
.diagram-2 .d-m4 { animation-delay: 540ms; }

/* Each mote keeps its own thread's hue, so four sources read as four sources. */
.diagram-2 .d-m1 { fill: var(--aurora-cyan); }
.diagram-2 .d-m2 { fill: var(--aurora-violet-2); }
.diagram-2 .d-m3 { fill: var(--aurora-green-2); }
.diagram-2 .d-m4 { fill: var(--aurora-cyan-2); }

.diagram-2.motes-ready.is-live .d-ember { animation-name: d2-ember; }
.diagram-2 .d-e2 { animation-delay: 200ms; }

.diagram .d-ember { fill: var(--ember-hi); }

@keyframes d2-mote {
  0%        { offset-distance: 0%; opacity: 0; }
  3%        { opacity: 0.95; }
  44%       { offset-distance: 100%; opacity: 0.95; }
  47%       { opacity: 0; }
  100%      { offset-distance: 100%; opacity: 0; }
}

@keyframes d2-ember {
  0%, 80%   { offset-distance: 0%; opacity: 0; }
  82%       { opacity: 1; }
  99%       { offset-distance: 100%; opacity: 1; }
  100%      { offset-distance: 100%; opacity: 0; }
}

.diagram-2.is-live .d-knot {
  animation-name: d2-knot;
  animation-duration: 3600ms;
  animation-timing-function: var(--ease-out);
  animation-iteration-count: infinite;
}

.diagram-2.is-live .d-glow {
  animation-name: d2-glow;
  animation-duration: 3600ms;
  animation-timing-function: var(--ease-out);
  animation-iteration-count: infinite;
}

@keyframes d2-knot {
  0%, 44%    { transform: scale(1); }
  50%        { transform: scale(1.4); }
  59%, 100%  { transform: scale(1); }
}

@keyframes d2-glow {
  0%, 44%    { opacity: 0; transform: scale(0.4); }
  52%        { opacity: 0.9; transform: scale(1); }
  59%, 100%  { opacity: 0; transform: scale(1); }
}

/*
  The verify DOUBLE-PULSE: 700ms, a 250ms gap, 700ms — the page's existing
  idiom, byte for byte the rhythm of .card-merge and the hero replay's verify
  beat. Once for the merge, once for the test run on the merged tree. Three
  surfaces, ONE rhythm. Do not invent a second one.
  At 3600ms: 47.2% -> 66.7%, gap to 73.6%, -> 93.1%.
*/
.diagram-2.is-live .d-plate {
  animation-name: d2-plate;
  animation-duration: 3600ms;
  animation-timing-function: var(--ease);
  animation-iteration-count: infinite;
}

@keyframes d2-plate {
  0%, 47%    { stroke: var(--hairline-solid); }
  57%        { stroke: var(--aurora-cyan-2); }
  67%, 74%   { stroke: var(--hairline-solid); }
  84%        { stroke: var(--aurora-cyan-2); }
  93%, 100%  { stroke: var(--hairline-solid); }
}

/* Diagram 3 — LIMIT -> STALL -> RESUME. One 4200ms cycle. The stall must read
   as WAITING, never as broken: the motes hold their position (26% -> 62% of the
   cycle) while their opacity breathes through two slow 750ms blinks. A held
   position with a live pulse is waiting; a frozen opaque dot is a bug. */
.diagram-3.motes-ready.is-live .d-mote {
  animation-duration: 4200ms;
  animation-timing-function: var(--ease-out);
}

.diagram-3.motes-ready.is-live .d-stall  { animation-name: d3-stall; }
.diagram-3.motes-ready.is-live .d-resume { animation-name: d3-resume; }
.diagram-3.motes-ready.is-live .d-ember  { animation-name: d3-ember; }

.diagram-3 .d-m2 { animation-delay: 220ms; }
.diagram-3 .d-e2 { animation-delay: 200ms; }

.diagram-3 .d-resume { fill: var(--aurora-green-2); }

@keyframes d3-stall {
  0%        { offset-distance: 0%; opacity: 0; }
  3%        { opacity: 0.95; }
  26%       { offset-distance: 100%; opacity: 0.95; }
  35%       { opacity: 0.35; }
  44%       { opacity: 0.95; }
  53%       { opacity: 0.35; }
  62%       { offset-distance: 100%; opacity: 0.95; }
  65%       { opacity: 0; }
  100%      { offset-distance: 100%; opacity: 0; }
}

@keyframes d3-resume {
  0%, 62%   { offset-distance: 0%; opacity: 0; }
  64%       { opacity: 0.95; }
  76%       { offset-distance: 100%; opacity: 0.95; }
  79%       { opacity: 0; }
  100%      { offset-distance: 100%; opacity: 0; }
}

@keyframes d3-ember {
  0%, 76%   { offset-distance: 0%; opacity: 0; }
  78%       { opacity: 1; }
  93%       { offset-distance: 100%; opacity: 1; }
  96%       { opacity: 0; }
  100%      { offset-distance: 100%; opacity: 0; }
}

/* --warn appears NOWHERE else on the page, which is what makes the stall
   legible in one glance. Keyed off .d3-plate so it cannot leak into diagram 2's
   `verify` plate. Both rules live OUTSIDE the forced-colors block on purpose,
   so CanvasText wins there. */
.diagram-3.is-live .d3-plate {
  animation-name: d3-plate;
  animation-duration: 4200ms;
  animation-timing-function: var(--ease);
  animation-iteration-count: infinite;
}

.diagram-3.is-live .d3-limit {
  animation-name: d3-limit;
  animation-duration: 4200ms;
  animation-timing-function: var(--ease);
  animation-iteration-count: infinite;
}

@keyframes d3-plate {
  0%, 26%   { stroke: var(--hairline-solid); }
  33%, 62%  { stroke: var(--warn); }
  69%, 100% { stroke: var(--hairline-solid); }
}

@keyframes d3-limit {
  0%, 26%   { fill: var(--fg-dim); }
  33%, 62%  { fill: var(--warn); }
  69%, 100% { fill: var(--fg-dim); }
}

.diagram-3.is-live .d-knot {
  animation-name: d3-knot;
  animation-duration: 4200ms;
  animation-timing-function: var(--ease-out);
  animation-iteration-count: infinite;
}

.diagram-3.is-live .d-glow {
  animation-name: d3-glow;
  animation-duration: 4200ms;
  animation-timing-function: var(--ease-out);
  animation-iteration-count: infinite;
}

@keyframes d3-knot {
  0%, 90%   { transform: scale(1); }
  95%       { transform: scale(1.3); }
  100%      { transform: scale(1); }
}

@keyframes d3-glow {
  0%, 90%   { opacity: 0; transform: scale(0.4); }
  95%       { opacity: 0.85; transform: scale(1); }
  100%      { opacity: 0; transform: scale(1); }
}

/* ── Engines section ────────────────────────────────────────────────── */

/*
  #engines sits on --bg-deep (the trust strip's surface) so it reads as its own
  band without inventing a gradient; the cards stay --surface-1, so the
  card-vs-band separation survives.

  NO per-engine accent hue, deliberately. The aurora hues are semantically bound
  on this page — cyan = running, green = done, ember = merged — and --ember's
  scarcity is a stylesheet invariant. There are also not six aurora hues that
  clear 4.5:1 (--aurora-violet measures 3.5:1 on --surface-2). Engines are
  differentiated by a WieVer-drawn monogram plus hierarchy instead.

  NO content-visibility here: this text must stay find-in-page friendly.
*/
#engines {
  padding: var(--space-10) 0;
  border-top: 1px solid var(--hairline);
  background: var(--bg-deep);
}

/* Flex GAPS are the separators — no middot characters, so a wrapped row can
   never orphan one at a line edge. */
.engine-names {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2) var(--space-4);
  max-width: 60ch;
  margin: 0 auto var(--space-6);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--fg);
  text-align: center;
}

/* Centred to match both of its siblings in this column — .section-head p and
   .engine-names are centred, so a left-aligned block sandwiched between them
   read as a layout bug rather than a choice (QA BUG-1). */
.engine-arg {
  max-width: var(--measure);
  margin: 0 auto var(--space-4);
  color: var(--fg-muted);
  font-size: var(--text-sm);
  text-align: center;
  text-wrap: pretty;   /* progressive; ignored where unsupported */
}

.engine-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  list-style: none;
  margin: var(--space-8) 0 0;
  padding: 0;
}

/* 720 / 1040 are the .bento breakpoints. Reused on purpose: a 768 here would
   give the page two competing rhythms. The Claude Code card spans two columns
   so "this is the default engine" is encoded as hierarchy, not colour. */
@media (min-width: 720px) {
  .engine-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .engine-grid > li:nth-child(1) { grid-column: span 2; }
}

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

/* .engine-card IS a .card variant, so it inherits the top-edge aurora thread,
   the 1100ms thread-run hover filament, the lift and :focus-within parity for
   free. Do NOT author a second hover treatment — the whole point is that the
   new section speaks the page's existing idiom. */
.engine-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.engine-card:nth-child(1) { background: var(--surface-2); }

.engine-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.engine-glyph {
  width: 20px;
  height: 20px;
  flex: none;
  color: var(--fg-muted);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: color var(--dur) var(--ease);
}

.engine-glyph-thin { stroke-width: 1.2; }

.engine-card:hover .engine-glyph,
.engine-card:focus-within .engine-glyph { color: var(--aurora-cyan-2); }

/*
  The monogram weaves itself on hover — the loom's own grammar at 20px, one-shot
  and well under 5s, so it needs no pause control of its own. It is animated on
  the <svg>, not the <path>, because stroke-dasharray/dashoffset INHERIT: that
  keeps the single selector in the pause list at the bottom of this layer
  effective. No dasharray at rest, so the glyph is solid when nothing is
  hovering it.
*/
.engine-card:hover .engine-glyph,
.engine-card:focus-within .engine-glyph {
  animation-name: glyph-weave;
  animation-duration: 400ms;
  animation-timing-function: var(--ease-out);
}

@keyframes glyph-weave {
  from { stroke-dasharray: 40; stroke-dashoffset: 40; }
  to   { stroke-dasharray: 40; stroke-dashoffset: 0; }
}

/* These six rules are scoped under .engine-card on purpose: `.card p` is
   (0,1,1) and would otherwise beat a bare (0,1,0) class on colour and
   font-size, flattening every card's typography. */
.engine-card .engine-eyebrow {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--aurora-cyan-2);
}

/* Mono, and letter-spacing: 0 explicitly overrides --track-h3 — mono type must
   not be tracked in. Never italic, never uppercase: both count as altering
   someone else's mark. */
.engine-name {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-h3);
  letter-spacing: 0;
  color: var(--fg);
}

.engine-card .engine-note {
  margin: 0;
  color: var(--fg-muted);
  font-size: var(--text-sm);
}

.engine-card .engine-models-label {
  margin: 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--hairline);
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--fg-dim);
}

.engine-models {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* tabular-nums so "GPT-5.6" and "K3 256k" stop wobbling between cards. */
.engine-card .engine-models li,
.engine-card .engine-models-note {
  margin: 0;
  padding: 2px 0;
  font-size: var(--text-xs);
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}

.engine-card .engine-models li { font-family: var(--font-mono); }

.engine-card .engine-caveat {
  display: flex;
  gap: var(--space-2);
  margin: 0;
  color: var(--fg-muted);
  font-size: var(--text-xs);
}

/* A typographic glyph, not an icon font: no request, and a pseudo-element
   never enters the accessibility tree as content in any current AT. */
.engine-card .engine-caveat::before {
  content: "\25B8";
  color: var(--warn);
  flex: none;
}

.engine-card .engine-install {
  margin: 0;
  color: var(--aurora-green-2);
  font-size: var(--text-xs);
}

.engine-disclaimer {
  max-width: var(--measure);
  margin: var(--space-6) auto 0;
  color: var(--fg-dim);
  font-size: var(--text-xs);
  text-align: center;
}

/* Bento card 6's teaser link into #engines. */
.card-link {
  color: var(--aurora-cyan-2);
  font-size: var(--text-sm);
  text-decoration: none;
}

.card-link:hover { text-decoration: underline; }

/* The final CTA's one muted line. --fg-dim is ILLEGAL here: over the
   .final-cta gradient composite it measures 4.11:1. --fg-muted is 6.4:1. */
.cta-micro {
  margin: var(--space-4) 0 0;
  color: var(--fg-muted);
  font-size: var(--text-xs);
}

/*
  `.reveal` must never be given `animation-play-state: paused`.
  It is a ONE-SHOT entrance animation on a `view()` timeline, so pausing it
  strands every not-yet-revealed card at `opacity: 0` — permanently, because
  un-pausing alone does not resume a scroll-timeline animation that never
  advanced. Pressing pause before scrolling used to blank the whole of
  #features and #pricing (a11y BLOCKING finding, 10-accessibility.md).
  This is exactly the failure mode the "ENUMERATED, NEVER `*`" note above
  warns about — the list is for LOOPING animations only.
*/

/* ═════════════════════════════════════════════ 6. ADAPTATIONS ═════════ */

/*
  Reduced motion.

  This block governs CSS only. The loom canvas runs on requestAnimationFrame,
  which no CSS rule can stop — loom.js gates itself on the same media query
  and paints a single static frame instead. Keep the two in step.
*/
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  /* NOTE: there is deliberately no `.d-thread { stroke-dashoffset: 0 }` patch
     here any more. The diagrams' AUTHORED default is now the drawn state
     (layer 4) and the draw-in only exists under .diagram.is-live, so the
     finished frame is what a reduced-motion visitor gets with no help from this
     block. Every new element in layers 4 and 7 is authored the same way — if
     you find yourself needing to patch a resting state in here, the resting
     state is wrong. */

  .reveal { opacity: 1; transform: none; }
}

/* Forced colors — drop translucency, blur and gradient fills; let the OS
   palette drive. Decorative layers are removed outright. */
@media (forced-colors: active) {
  /* The countdown is entirely gradient-and-glow; none of it survives a forced
     palette, so reduce it to a plain bordered box with system text. */
  .countdown-clock::before { display: none; }
  .cd-cell {
    background: Canvas;
    border: 1px solid CanvasText;
    box-shadow: none;
    animation: none;
  }
  .cd-cell::after { display: none; }
  .cd-cell b { background: none; -webkit-text-fill-color: unset; color: CanvasText; }
  .cd-sep { color: CanvasText; opacity: 1; }

  .hero::before,
  .final-cta::before,
  .loom-host { display: none; }

  .site-header {
    background: Canvas;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-color: CanvasText;
  }

  .hero h1 .grad {
    background: none;
    -webkit-text-fill-color: unset;
    color: CanvasText;
  }

  .card,
  .pricing-card,
  .faq-item,
  .mock,
  .worker,
  .train,
  .diagram {
    background: Canvas;
    border-color: CanvasText;
    box-shadow: none;
  }

  .card::before,
  .faq-item[open]::before { background: CanvasText; }

  .card:hover,
  .card:focus-within { border-color: Highlight; transform: none; }

  .btn-primary { background: Highlight; color: HighlightText; box-shadow: none; }
  .btn-primary:hover { box-shadow: none; }
  .btn-secondary { border-color: ButtonBorder; }

  .worker-bar i,
  .train-line { background: CanvasText; }

  .train-node,
  .train-knot { background: CanvasText; box-shadow: none; }

  .diagram .d-thread,
  .diagram .d-merged { stroke: CanvasText; }

  .diagram .d-knot,
  .diagram .d-node,
  .diagram .d-mote { fill: CanvasText; }

  /* A radial gradient carries no meaning in a forced palette, so it goes
     entirely rather than being approximated. */
  .diagram .d-glow { display: none; }

  /* Dash pattern RETAINED: hollow-and-dashed vs filled is the only thing
     distinguishing "unresolved" from "landed" once the palette collapses. */
  .diagram .d-open { stroke: CanvasText; fill: Canvas; }

  /* Both the verify plate and the limit plate. The amber .d3-plate /.d3-limit
     rules live outside this block, so these win. */
  .diagram .d-plate { fill: Canvas; stroke: CanvasText; }
  .diagram .d-label,
  .diagram .d-label-hi { fill: CanvasText; }

  .pill { border-color: CanvasText; color: CanvasText; background: Canvas; }

  /* Layer-7 additions. A gradient and a glow have no meaning in a forced
     palette, so they are removed outright rather than approximated. */
  .demo-chip { background: Canvas; border-color: CanvasText; color: CanvasText; }
  .demo-chip.is-picked { border-color: Highlight; color: Highlight; box-shadow: none; }

  .demo-dot { background: CanvasText; }
  [data-beat] .demo-dot { background: GrayText; }

  .worker-wt { color: CanvasText; opacity: 1; }
  .train-out > i { color: CanvasText; }
  .train-rail::after { display: none; }

  /* Engines section. .engine-card inherits .card's entry above for background /
     border / shadow; these are the pieces .card does not cover. The selectors
     are doubled up to clear layer 7's (0,2,0) rules — forced-colors forces
     colour at used-value time anyway, so this is belt and braces, not the
     mechanism. */
  .engine-grid .engine-card { background: Canvas; }
  .engine-card .engine-glyph { color: CanvasText; }
  .engine-card .engine-caveat::before { color: CanvasText; }
  .engine-card .engine-install,
  .engine-card .engine-eyebrow { color: CanvasText; }
}


/* ═════════════════════════════════════════════════ 8. THE TOUR ════════ */

/*
  The 46s product film. Sits between the trust numbers and the story beats:
  proof before explanation.

  The stage is a fixed 16:9 box so the poster, the video and the empty state
  all occupy identical space — a video that pops the page taller when it loads
  is the single biggest CLS source a marketing page can ship, and this section
  sits above three more screens of content that would all jump with it.
*/
.demo {
  padding-top: var(--space-8);
  padding-bottom: var(--space-8);
}

.demo-stage {
  position: relative;
  max-width: 1160px;
  margin: 0 auto;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg-deep);
  border: 1px solid var(--hairline);
  box-shadow: 0 30px 90px -30px rgba(0, 0, 0, 0.85), var(--edge-highlight);
}

/* aspect-ratio is Baseline, but a fallback keeps the box from collapsing to
   zero height on anything that misses it — an invisible video is worse than a
   slightly wrong one. */
@supports not (aspect-ratio: 16 / 9) {
  .demo-stage { height: 0; padding-bottom: 56.25%; }
}

.demo-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  background: var(--bg-deep);
}

/* A hairline sheen along the top edge so the stage reads as a screen rather
   than a hole punched in the page. Purely decorative, never interactive. */
.demo-stage::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.10);
}

@media (max-width: 767px) {
  .demo-stage { border-radius: var(--radius); }
}

/* ═══════════════════════════════════════════ 9. RELEASE COUNTDOWN ═════ */

/*
  Two mutually exclusive states, driven by a class on <html> that countdown.js
  sets: `.is-prerelease` (authored default, so a JS-blocked visitor sees the
  honest "not yet") and `.is-released`.

  The HTML ships pre-release ON PURPOSE. Authoring the released state and
  hiding it with JS would show a working Download button for a beat before the
  script ran, and would leave it working forever if the script failed.
*/

.countdown {
  margin: 0 0 var(--space-6);
  text-align: center;
}
html.is-released .countdown { display: none; }

.countdown-label {
  margin: 0 0 var(--space-3);
  font-size: var(--text-xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-dim);
}

.countdown-clock {
  position: relative;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: var(--space-2);
}

/*
  Ambient aurora bloom behind the digits — the same light the hero loom throws,
  so the clock reads as part of the product rather than a bolted-on widget.
  Sits BEHIND the cells (negative z-index on a positioned parent) and is
  pointer-transparent so it can never eat a click.
*/
.countdown-clock::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 460px;
  height: 190px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  pointer-events: none;
  z-index: -1;
  background:
    radial-gradient(50% 60% at 30% 50%, rgba(110, 80, 248, 0.34), transparent 70%),
    radial-gradient(48% 58% at 68% 50%, rgba(43, 200, 224, 0.28), transparent 72%);
  filter: blur(26px);
  animation: cd-bloom 4200ms ease-in-out infinite;
}

@keyframes cd-bloom {
  0%, 100% { opacity: 0.75; transform: translate(-50%, -50%) scale(1); }
  50%      { opacity: 1;    transform: translate(-50%, -50%) scale(1.06); }
}

.cd-cell {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 74px;
  padding: var(--space-3) var(--space-2);
  border-radius: var(--radius);
  /* Gradient hairline: the padding-box layer paints the surface, the border-box
     layer paints the aurora underneath it, so the 1px border becomes the
     gradient. Cheaper and better-supported than border-image, and unlike a
     pseudo-element ring it cannot mis-round at the corners. */
  border: 1px solid transparent;
  background:
    linear-gradient(var(--surface-1), var(--surface-1)) padding-box,
    var(--aurora-gradient) border-box;
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.35),
    0 10px 34px -14px rgba(110, 80, 248, 0.75),
    var(--edge-highlight);
}

/* Inner top sheen — reads as glass catching the bloom below. */
.cd-cell::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.07), transparent 55%);
}

.cd-cell b {
  position: relative;   /* above ::after's sheen */
  font-family: var(--font-mono);
  /* tabular-nums stops the whole row shuffling every second as digit widths
     change — the single most important property in this block. */
  font-variant-numeric: tabular-nums;
  font-size: 34px;
  line-height: 1;
  font-weight: 700;
  /* Same gradient treatment as the hero headline's .grad, so the digits are
     lit by the brand rather than merely coloured. */
  background: var(--aurora-gradient-bright);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.cd-cell i {
  position: relative;   /* above ::after's sheen */
  margin-top: 6px;
  font-style: normal;   /* <i> is a semantic hook here, not italics */
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-dim);
}

/* The seconds cell is the only thing on screen that changes every tick, so it
   carries the pulse. Pulsing all four would read as a warning, not a heartbeat. */
.cd-cell:last-of-type {
  animation: cd-tick 1000ms ease-out infinite;
}

@keyframes cd-tick {
  0%       { box-shadow: 0 0 0 1px rgba(0,0,0,0.35), 0 10px 34px -14px rgba(43,200,224,0.95), var(--edge-highlight); }
  55%,100% { box-shadow: 0 0 0 1px rgba(0,0,0,0.35), 0 10px 34px -14px rgba(110,80,248,0.55), var(--edge-highlight); }
}

.cd-sep {
  font-family: var(--font-mono);
  font-size: 28px;
  line-height: 1;
  padding-top: 14px;
  color: var(--aurora-cyan-2);
  opacity: 0.55;
}

.countdown-when {
  margin: var(--space-3) 0 0;
  font-size: var(--text-xs);
  color: var(--fg-muted);
}
.countdown-when time { color: var(--fg); font-weight: 600; }

/* The gated Download button: same footprint, different label + affordance. */
.dl-pre, .dl-post { display: none; }
html.is-prerelease .dl-pre { display: inline; }
html.is-released .dl-post { display: inline; }

[data-download-link][aria-disabled="true"] {
  cursor: not-allowed;
  filter: grayscale(0.65);
  opacity: 0.72;
}
[data-download-link][aria-disabled="true"]:hover {
  transform: none;
  filter: grayscale(0.65);
}

@media (max-width: 520px) {
  .cd-cell { min-width: 60px; padding: var(--space-2) 6px; }
  .cd-cell b { font-size: 26px; }
  .cd-sep { font-size: 20px; padding-top: 10px; }
}
