/* Tether landing — page layout on top of /shared.css tokens. */

/* Registered custom property so the mesh gradient can be animated by the
   browser's own interpolation (same technique web/visual-lab/tilt-proof.css
   proved under this exact CSP: @property + radial-gradient layers, no
   canvas, no WebGL, no script). Tuned to the landing page's own --pass/
   --info/--warn tokens rather than the lab's fixed rgba values, so the hero
   reads as this page's palette drifting, not a copy of the sandbox proof. */
@property --mesh-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

/* ---- surface ladder ---------------------------------------------------------------
   Five landing-local surface levels, each DERIVED from the shared tokens rather than
   restated: `--surface-N` stays the single source of truth (pinned to
   design/tokens/tether-tokens.json by SharedPaletteTests), and each level here is that
   token nudged toward the page's subject — data/speed, i.e. the --info cyan and the
   --pass emerald — by a few percent. That is what keeps the page from reading as flat
   black boxes: every raised plane carries the same faint cool cast instead of pure grey.

   The percentages are deliberately small and were measured, not guessed: at 4% the
   lowest-contrast pair the page uses anywhere (`--text-3` on --surface-3) still clears
   AA at 4.52:1 dark / 4.63:1 light. An 8% mixed plane looked better and measured 4.18
   dark / 4.40 light — under the floor — so the ladder stops at 4%. This is a tint, not
   a new palette.

   Named `--plane-*`, not `--surface-*`, so nothing here can shadow the shared token of
   the same name for another stylesheet loaded on the same page (split-run.css). */
:root {
  --plane-0: var(--bg);
  --plane-1: color-mix(in srgb, var(--surface-1) 97%, var(--info) 3%);
  --plane-2: color-mix(in srgb, var(--surface-2) 96%, var(--info) 4%);
  --plane-3: color-mix(in srgb, var(--surface-3) 96%, var(--info) 4%);
  --plane-4: color-mix(in srgb, var(--surface-3) 96%, var(--pass) 4%);
}

/* Browsers without color-mix() (or with it disabled) fall back to the plain ladder,
   so the page degrades to the shipped palette instead of to transparent planes. */
@supports not (background: color-mix(in srgb, red 50%, blue)) {
  :root {
    --plane-0: var(--bg);
    --plane-1: var(--surface-1);
    --plane-2: var(--surface-2);
    --plane-3: var(--surface-3);
    --plane-4: var(--surface-3);
  }
}

.hero {
  --mesh-angle: 0deg;
  max-width: var(--page);
  margin: 0 auto;
  padding: var(--s-8) var(--gutter) var(--s-7);
  text-align: center;
  min-width: 0;
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

/* Atmosphere behind the centered text, not a poster fighting it: three
   low-opacity radial layers drifting via one animated angle, the same
   @property technique web/visual-lab/tilt-proof.css proved under this exact
   CSP, tuned to this page's own --pass/--info/--warn tint tokens instead of
   the lab's fixed rgba values. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(38% 46% at calc(20% + 6% * cos(var(--mesh-angle))) 18%, var(--pass-dim) 0%, transparent 68%),
    radial-gradient(42% 50% at calc(82% + 6% * sin(var(--mesh-angle))) 28%, var(--info-dim) 0%, transparent 70%),
    radial-gradient(46% 40% at calc(48% - 5% * sin(var(--mesh-angle))) 92%, var(--warn-dim) 0%, transparent 72%);
  animation: hero-mesh-drift 26s linear infinite;
  will-change: --mesh-angle;
}

@keyframes hero-mesh-drift {
  to { --mesh-angle: 360deg; }
}

@media (prefers-reduced-motion: reduce) {
  .hero::before { animation: none; }
}

/* Light mode as its own considered scheme, not dark-mode-inverted: the same
   three-layer mesh reads as a muddy wash on a white surface at dark mode's
   opacity, so it turns down and the panel gets a crisper, more defined edge
   instead of the soft ambient bloom that works on a near-black surface. No
   named token values change here (SharedPaletteTests only pins shared.css) —
   this is layout-file-local restyling using the tokens as they already are. */
@media (prefers-color-scheme: light) {
  .hero::before { opacity: 0.6; }
  .fabric__panel {
    background:
      radial-gradient(680px 320px at 18% 14%, var(--overlay-up) 0%, transparent 68%),
      var(--plane-1);
  }
  .metric {
    background:
      radial-gradient(140% 100% at 50% -20%, var(--pass-dim) 0%, transparent 55%),
      var(--plane-1);
  }
  .cta__card {
    background:
      radial-gradient(140% 120% at 50% -10%, var(--info-dim) 0%, transparent 58%),
      var(--plane-1);
  }
  /* Dark mode's shadows carry real ambient bloom against a near-black page;
     the same spread on white looks like dirt under the card. Light mode
     tightens the blur radius and lowers the elevation on hover/tilt so a
     "lifted" card still reads as paper on paper, not a grey smear. */
  [data-tilt]:hover, .card:hover:not([data-tilt]) {
    box-shadow: 0 1px 2px rgba(16, 24, 40, 0.08), 0 8px 20px rgba(16, 24, 40, 0.10);
  }
}

.hero__chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s-2);
  margin: 0 0 var(--s-5);
}

/* Display type: tighter tracking as size grows (optical, like SF Display),
   and a top-lit tone on the headline so it reads as set, not typed. The
   gradient runs --text-1 -> --text-2, both AA on the page in both schemes.
   Fluid sizing via clamp() scales smoothly from 390px to 1440px. */
.hero h1 {
  font-size: clamp(2rem, 1.2rem + 4.2vw, 4rem);
  line-height: 1.04;
  font-weight: 600;
  letter-spacing: -0.032em;
  max-width: 16ch;
  margin: 0 auto var(--s-5);
  text-wrap: balance;
}
.hero h1 > span {
  background: linear-gradient(180deg, var(--text-1) 30%, var(--text-2) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
@media (forced-colors: active) {
  .hero h1 > span { color: CanvasText; background: none; }
}

.hero h1 em {
  font-style: normal;
  color: var(--pass);
}

.hero__scope {
  max-width: var(--measure);
  margin: 0 auto var(--s-4);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--warn-line);
  border-radius: var(--r-md);
  background: var(--tint-warn);
  color: var(--text-1);
  font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem);
  line-height: 1.5;
}

.hero__lead {
  max-width: var(--measure);
  margin: 0 auto var(--s-6);
  color: var(--text-2);
  font-size: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
  line-height: 1.55;
}

.hero__actions {
  display: flex;
  gap: var(--s-3);
  justify-content: center;
  flex-wrap: wrap;
}

.hero__footnote {
  margin-top: var(--s-4);
  font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem);
  color: var(--text-3);
}

.status-now,
.section,
.cta {
  max-width: var(--page);
  margin: 0 auto;
  padding: var(--s-7) var(--gutter);
  min-width: 0;
}

.section__label {
  font-size: clamp(0.625rem, 0.6rem + 0.12vw, 0.6875rem);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-3);
  font-weight: 560;
  margin-bottom: var(--s-2);
}

/* Section rhythm: headings tighten with size, and every informational card
   gets the same top-lit hairline the map nodes carry, so elevation reads as
   one light source across the page instead of flat stacked boxes.
   Fluid typography scales all headings and body text smoothly 390-1440px. */
.status-now h2, .section h2 {
  font-size: clamp(1.5rem, 1.2rem + 1.6vw, 2.125rem);
  letter-spacing: -0.026em;
  max-width: 28ch;
  text-wrap: balance;
  line-height: 1.1;
}
.gate, .step, .compat__col, .grammar, .feed-snapshot {
  box-shadow: var(--e-2), inset 0 1px 0 var(--overlay-up);
}
.fabric-roster__item :focus-visible,
.fabric__panel :focus-visible { outline-offset: 3px; }

.section__lead {
  color: var(--text-2);
  font-size: clamp(0.875rem, 0.83rem + 0.18vw, 0.9375rem);
  line-height: 1.6;
  max-width: var(--measure);
  margin: var(--s-3) 0 var(--s-5);
}

.gate-grid,
.steps,
.compat {
  display: grid;
  gap: var(--s-4);
  min-width: 0;
}

.gate-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.steps { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.compat { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* Pointer-driven tilt on the proof metric cards:
   ~15 lines of hand-written JS in landing.js sets rotateX/rotateY on
   pointermove (never hover — a hover-only trigger reads identical to a dead
   feature under the pointermove-driven math this reuses from
   web/visual-lab/tilt-proof.js) and resets on pointerleave/blur. The
   transition below only smooths the RESET; the live tilt itself is set as an
   inline style per frame, same division of labour as the tilt-proof. */
[data-tilt] {
  transform-style: preserve-3d;
  transition: transform var(--dur) var(--ease), border-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}


.gate h3 {
  margin: var(--s-3) 0 var(--s-2);
  font-size: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
}
.gate p {
  color: var(--text-2);
  font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem);
  line-height: 1.55;
}

/* Hierarchy of importance, one system applied consistently: the verified
   evidence metrics and the closing CTA are the primary artifacts on this
   page (a receipt-backed number, and the one action the page wants taken),
   so they carry a --pass-tinted accent border and a heavier elevation than
   the informational cards (gate/step/compat) around them, which
   keep the plain --e-2 card. This is restyling, not new claims: the metric
   values and their source captions are untouched. */
.metric {
  text-align: center;
  border-color: var(--pass-line);
  box-shadow: var(--e-3);
  /* The colour layer stays the pinned --surface-1 token (the identity suite reads the
     first .card's computed backgroundColor and requires the exact token). The landing
     tint rides in the gradient stack instead, which is paint on top of that plane. */
  background:
    radial-gradient(120% 90% at 82% -10%, var(--info-dim) 0%, transparent 58%),
    radial-gradient(140% 100% at 50% -20%, var(--pass-dim) 0%, transparent 60%),
    var(--surface-1);
}
.metric__value {
  font-size: clamp(1.5rem, 1rem + 2.6vw, 2.125rem);
  line-height: 1.05;
  font-weight: 500;
  color: var(--pass);
}
.metric__unit {
  font-size: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
  font-weight: 400;
  color: var(--text-3);
}
.metric__label {
  margin: var(--s-2) 0 0;
  font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem);
  color: var(--text-2);
}
.metric__source {
  margin: var(--s-2) 0 0;
  font-size: clamp(0.625rem, 0.6rem + 0.12vw, 0.6875rem);
  color: var(--text-3);
  font-family: var(--mono);
}

.step__num {
  font-family: var(--mono);
  font-size: clamp(0.625rem, 0.6rem + 0.12vw, 0.6875rem);
  font-weight: 600;
  letter-spacing: 0.06em;
  display: inline-block;
  padding: 2px var(--s-2);
  border-radius: var(--r-sm);
  border: 1px solid var(--hairline-strong);
  color: var(--info);
  /* Opaque tint: --info text on the translucent tint is ~4.2:1 in light mode. */
  background: var(--tint-info);
  margin-bottom: var(--s-3);
}
.step h3 {
  font-size: clamp(0.875rem, 0.83rem + 0.18vw, 0.9375rem);
  margin-bottom: var(--s-2);
}
.step p {
  font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem);
  color: var(--text-2);
  line-height: 1.55;
}
.step code {
  font-size: clamp(0.625rem, 0.6rem + 0.12vw, 0.6875rem);
  color: var(--text-1);
  background: var(--surface-3);
  padding: 2px 6px;
  border-radius: var(--r-sm);
  display: block;
  margin-top: var(--s-3);
  max-width: 100%;
}

/* Measured-today: a three-step strip instead of the four checkout steps, and a
   single left-aligned metric card carrying the product-path figures. All type
   and surfaces reuse the tokens the proof cards already measure for axe. */
.steps--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.mt-fig { max-width: 44rem; margin: var(--s-5) 0 var(--s-4); }
.mt-fig .metric { text-align: left; }
.mt-note {
  color: var(--text-2);
  font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem);
  line-height: 1.6;
  max-width: var(--measure);
  margin: 0 0 var(--s-5);
}

.compat__col h3 { font-size: clamp(0.875rem, 0.83rem + 0.18vw, 0.9375rem); margin-bottom: var(--s-3); }
.compat__col ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-2);
}
.compat__col li {
  font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem);
  color: var(--text-2);
  padding-left: 14px;
  position: relative;
}
.compat__col li::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-3);
  position: absolute;
  left: 0;
  top: 0.45em;
}
/* Bullet colour states what the column is, not where it sits in the row. */
.compat__col--pass li::before { background: var(--pass); }
.compat__col--open li::before { background: var(--warn); }

.grammar {
  margin-top: var(--s-5);
  padding: var(--s-5);
}
.grammar h3 { margin-bottom: var(--s-2); }
.grammar p {
  color: var(--text-2);
  font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem);
  line-height: 1.55;
  margin-bottom: var(--s-4);
}
.grammar__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-2);
}
.grammar__list code {
  display: block;
  font-size: clamp(0.625rem, 0.6rem + 0.12vw, 0.6875rem);
  color: var(--text-1);
  background: var(--surface-3);
  padding: 6px 8px;
  border-radius: var(--r-sm);
}

.feed-snapshot { padding: var(--s-5); margin-bottom: var(--s-5); }
.feed-meta {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3) var(--s-4);
  margin: var(--s-4) 0;
}
.feed-meta dt {
  font-size: clamp(0.625rem, 0.6rem + 0.12vw, 0.6875rem);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-3);
  margin-bottom: 2px;
}
.feed-meta dd { margin: 0; }
.feed-note {
  font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem);
  color: var(--text-2);
  line-height: 1.55;
}
.feed-matrix p { font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); color: var(--text-2); line-height: 1.5; }
.feed-asset {
  margin-top: var(--s-2);
  font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem);
  color: var(--text-3);
  font-family: var(--mono);
}

.cta {
  padding-bottom: var(--s-9);
  text-align: center;
}
.cta__card {
  width: 100%;
  max-width: 40rem;
  margin: 0 auto;
  padding: var(--s-6);
  border-color: var(--info-line);
  box-shadow: var(--e-3);
  background:
    radial-gradient(140% 120% at 50% -10%, var(--info-dim) 0%, transparent 62%),
    var(--plane-1);
}
.cta__card h2 {
  font-size: clamp(1.5rem, 1rem + 2.6vw, 2.125rem);
  margin-bottom: var(--s-3);
}
.cta__card p {
  color: var(--text-2);
  font-size: clamp(0.875rem, 0.83rem + 0.18vw, 0.9375rem);
  line-height: 1.55;
  margin-bottom: var(--s-5);
}
.cta__actions {
  display: flex;
  gap: var(--s-3);
  justify-content: center;
  flex-wrap: wrap;
}

/* ---- Fabric map -------------------------------------------------------- */

.fabric__panel {
  padding: var(--s-6);
  border-radius: var(--r-lg);
  background:
    radial-gradient(680px 320px at 18% 14%, var(--bg-glow) 0%, transparent 68%),
    radial-gradient(520px 300px at 86% 92%, var(--overlay-up) 0%, transparent 62%),
    var(--plane-1);
  box-shadow: var(--e-3), inset 0 1px 0 var(--overlay-up);
  position: relative;
  overflow: hidden;
}
/* An instrument-panel edge, not a bare card: a hairline vignette that frames
   the illustration the way the surrounding chrome frames photography, so the
   map reads as mounted inside a designed bezel rather than floating on a
   plain background. */
.fabric__panel::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 1px solid var(--hairline-strong);
  box-shadow: inset 0 0 0 1px var(--overlay-down);
  pointer-events: none;
}

.fabric-map {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  position: relative;
  /* Map-local depth tokens, derived from the shared palette so both schemes
     repaint every gradient stop and filter flood below. */
  --lf-shadow: rgba(0, 0, 0, 0.42);
  --lf-sheen: rgba(255, 255, 255, 0.10);
  --lf-spring: cubic-bezier(0.34, 1.36, 0.64, 1);
}
@media (prefers-color-scheme: light) {
  .fabric-map {
    --lf-shadow: rgba(16, 24, 40, 0.16);
    --lf-sheen: rgba(255, 255, 255, 0.85);
  }
}

/* Gradient stops and pattern paint, from tokens (SVG stop-color accepts var()). */
.lf-disc__hi { stop-color: var(--surface-3); }
.lf-disc__lo { stop-color: var(--surface-1); }
.lf-sheen__hi { stop-color: var(--lf-sheen); }
.lf-sheen__lo { stop-color: var(--lf-sheen); stop-opacity: 0; }
.lf-halo__in { stop-color: var(--pass); stop-opacity: 0.16; }
.lf-halo__out { stop-color: var(--pass); stop-opacity: 0; }
.lf-hatch__bg { fill: var(--surface-2); }
.lf-hatch__line { stroke: var(--text-3); stroke-width: 1.4; opacity: 0.55; }

/* Parallax layers: landing.js writes a translate on each [data-depth] group
   while the pointer moves over the panel, and nothing when it does not. */
.fabric-map [data-depth] { will-change: auto; }
.fabric-map.is-parallax [data-depth] { will-change: transform; }

/* Two dot-grid layers at different opacity read as an instrument backdrop. */
.fabric-map__grid { opacity: 0.5; }
.fabric-map__grid line {
  stroke: var(--hairline);
  stroke-width: 1;
  stroke-dasharray: 1 11;
  stroke-linecap: round;
}

.flink {
  fill: none;
  stroke-linecap: round;
  transition: opacity var(--dur-slow) var(--lf-spring), stroke-width var(--dur-slow) var(--lf-spring);
}
/* A receipt-backed tether: solid, in --pass, and the only link that moves. */
.flink--verified {
  stroke: var(--pass);
  stroke-width: 2;
}
.flink--flow {
  stroke: var(--pass);
  stroke-width: 4;
  stroke-dasharray: 10 26;
  animation: fabric-flow 2.4s linear infinite;
  filter: url(#lf-glow);
  opacity: 0.55;
}
/* No authorized host: dashed, muted, and deliberately motionless. */
.flink--absent {
  stroke: var(--text-3);
  stroke-width: 1.5;
  stroke-dasharray: 3 7;
  opacity: 0.7;
}
.flink.is-selected.flink--verified:not(.flink--flow) { stroke-width: 3; }
.flink.is-muted { opacity: 0.14; }

@keyframes fabric-flow {
  from { stroke-dashoffset: 36; }
  to { stroke-dashoffset: 0; }
}

/* The measured packet: one bright dot riding the verified link, created by
   landing.js ONLY when the live verdict is "pass" (WAAPI, transform only). */
.fpacket {
  fill: var(--pass);
  filter: url(#lf-glow);
  pointer-events: none;
}
.fpacket__core { fill: var(--surface-1); }

/* Link-state badges. Absent host: a struck circle. Peer: a lock on fail,
   a question mark on unknown, nothing on pass (data-state unset). */
.fglyph__plate {
  fill: var(--surface-1);
  stroke: var(--hairline-strong);
  stroke-width: 1;
}
.fglyph path, .fglyph rect, .fglyph circle:not(.fglyph__plate) {
  fill: none;
  stroke: var(--text-3);
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.fglyph__unknown circle { fill: var(--text-3); stroke: none; }
.fglyph[data-glyph="peer-mac"] { opacity: 0; transition: opacity var(--dur-slow) var(--ease); }
.fglyph[data-glyph="peer-mac"] .fglyph__lock,
.fglyph[data-glyph="peer-mac"] .fglyph__unknown { display: none; }
.fglyph[data-state="fail"],
.fglyph[data-state="unknown"] { opacity: 1; }
.fglyph[data-state="fail"] .fglyph__lock { display: inline; }
.fglyph[data-state="fail"] .fglyph__lock rect,
.fglyph[data-state="fail"] .fglyph__lock path { stroke: var(--fail); }
.fglyph[data-state="fail"] .fglyph__plate { stroke: var(--fail-line); }
.fglyph[data-state="unknown"] .fglyph__unknown { display: inline; }
.fglyph--absent .fglyph__plate { stroke: var(--fail-line); }
.fglyph { transition: opacity var(--dur-slow) var(--ease); }
.fglyph.is-muted { opacity: 0.2; }
.fglyph[data-glyph="peer-mac"]:not([data-state]).is-muted { opacity: 0; }

/* ---- Nodes: halo, pulse ring, gradient disc, sheen, core ------------- */
.fnode__halo { fill: url(#lf-halo); opacity: 0.9; }
.fnode__ring {
  fill: none;
  stroke: var(--hairline-strong);
  stroke-width: 1;
  transition: stroke-width var(--dur-slow) var(--lf-spring);
}
.fnode__disc {
  fill: url(#lf-disc);
  stroke: var(--hairline-strong);
  stroke-width: 1;
  filter:
    drop-shadow(0 1px 1.5px var(--lf-shadow))
    drop-shadow(0 8px 14px var(--lf-shadow));
}
/* Inner top highlight: a soft specular edge so the disc reads as a lens. */
.fnode__sheen {
  fill: url(#lf-sheen);
  pointer-events: none;
}
.fnode__core { fill: var(--text-3); }
.fnode__label {
  fill: var(--text-2);
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 520;
  letter-spacing: -0.005em;
}

.fnode--pass .fnode__ring { stroke: var(--pass-line); }
.fnode--pass .fnode__core { fill: var(--pass); filter: url(#lf-glow); }
.fnode--pass .fnode__disc { stroke: var(--pass-line); }
.fnode--blocked .fnode__ring { stroke: var(--fail-line); stroke-dasharray: 2 4; }
.fnode--blocked .fnode__core { fill: var(--fail); }
.fnode--blocked .fnode__disc { stroke: var(--fail-line); }
.fnode--blocked .fnode__halo { display: none; }
.fnode--hub .fnode__label { fill: var(--text-1); }

/* Qualified-hub pulse: the ONE breathing element, and only because it
   encodes a state (the controller is qualified and holding the store).
   Transform + opacity only; frozen by reduced motion below. */
.fnode__pulse {
  fill: none;
  stroke: var(--pass);
  stroke-width: 1.5;
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}
.fnode--hub.fnode--pass .fnode__pulse {
  animation: fabric-hub-pulse 3.2s cubic-bezier(0.2, 0.6, 0.3, 1) infinite;
}
@keyframes fabric-hub-pulse {
  0% { transform: scale(1); opacity: 0.55; }
  70% { transform: scale(1.42); opacity: 0; }
  100% { transform: scale(1.42); opacity: 0; }
}

/* Live reading came back "unknown": hatched, desaturated, no halo, no glow,
   never the pass palette. Same hatch meaning as status-dashboard/fabric-map.js. */
.fnode.is-unknown .fnode__ring,
.fnode.is-unknown .fnode__core { stroke: var(--text-3); fill: var(--text-3); filter: none; }
.fnode.is-unknown .fnode__ring { fill: none; stroke-dasharray: 2 4; }
.fnode.is-unknown .fnode__disc { fill: url(#lf-hatch); stroke: var(--text-3); opacity: 0.8; filter: none; }
.fnode.is-unknown .fnode__halo { display: none; }
.flink.is-unknown {
  stroke: var(--text-3);
  stroke-dasharray: 2 5;
  opacity: 0.5;
}
/* A live "unknown" link must never carry the animated-flow treatment. */
.flink.is-unknown.flink--flow { animation: none; filter: none; }

/* Highlight: a spring on transform + opacity only. */
.fnode {
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity var(--dur-slow) var(--ease), transform 420ms var(--lf-spring);
}
.fnode.is-muted { opacity: 0.26; transform: scale(0.96); }
.fnode.is-selected { transform: scale(1.045); }
.fnode.is-selected .fnode__ring { stroke-width: 2.5; }

/* A map scrolled out of view pauses its state-encoding loops. */
.fabric-map.is-offscreen .flink--flow,
.fabric-map.is-offscreen .fnode__pulse { animation-play-state: paused; }

/* Reduced motion: no pulse ring at all, no packets (landing.js never makes
   them), no scale spring. The static classes above still carry every state. */
@media (prefers-reduced-motion: reduce) {
  .fnode__pulse { display: none; }
  .fnode.is-selected, .fnode.is-muted { transform: none; }
}

/* SVG text scales with the viewBox, so below this width the in-map labels
   would render under 12px however large the font-size is set. Drop them and
   let the roster carry the names: it repeats all four at full body size, and
   the map keeps its role="img" title and desc. */
@media (max-width: 720px) {
  .fnode__label { display: none; }
}

.fabric__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-5);
  margin-top: var(--s-4);
  font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem);
  color: var(--text-2);
}
.legend {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
}
.legend::before {
  content: "";
  width: 22px;
  height: 0;
  flex: none;
  border-top-width: 2px;
  border-top-style: solid;
}
.legend--pass::before { border-top-color: var(--pass); }
.legend--absent::before {
  border-top-style: dashed;
  border-top-color: var(--text-3);
}

.fabric-roster {
  list-style: none;
  margin: var(--s-5) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-4);
}
.fabric-roster__item {
  padding: var(--s-4) var(--s-5);
  border-radius: var(--r-md);
  border: 1px solid var(--hairline);
  border-left: 3px solid var(--hairline-strong);
  background: var(--plane-1);
  min-width: 0;
  box-shadow: var(--e-1), inset 0 1px 0 var(--overlay-up);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              transform 420ms cubic-bezier(0.34, 1.36, 0.64, 1);
}
.fabric-roster__item[data-roster="controller"],
.fabric-roster__item[data-roster="peer-mac"] { border-left-color: var(--pass); }
.fabric-roster__item[data-roster="windows-host"],
.fabric-roster__item[data-roster="linux-host"] { border-left-color: var(--fail); }
.fabric-roster__item.is-selected { border-color: var(--info); }
/* Preview (hover/focus) shares the selection's hue at half weight, so the
   roster item and the lit map subset read as one object. */
.fabric-roster__item.is-previewed:not(.is-selected) {
  border-color: var(--info-line);
  box-shadow: var(--e-2);
  transform: translateY(-1px);
}
.fabric-roster__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  margin-bottom: var(--s-2);
  flex-wrap: wrap;
}
.fabric-roster__head h3 { font-size: clamp(0.875rem, 0.83rem + 0.18vw, 0.9375rem); }
.fabric-roster__item p {
  font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem);
  color: var(--text-2);
  line-height: 1.55;
}
.fabric-roster__ctl { margin-top: var(--s-3); }
.fabric-roster__ctl[hidden] { display: none; }

.fabric-roster__live {
  display: block;
  margin-top: var(--s-2);
  font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem);
  font-family: var(--mono);
  color: var(--text-3);
}
.fabric-roster__live[hidden] { display: none; }

/* Pending reading: a quiet shimmer bar while fabric-snapshot.json is in flight.
   Script-inserted (landing.js), removed on the first answer. Transform-free
   background-position sweep; under reduced motion it holds still as a flat bar. */
.fabric-roster__pending {
  display: block;
  width: 11rem;
  max-width: 60%;
  height: 0.75rem;
  margin-top: var(--s-2);
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--surface-3) 0%, var(--hairline-strong) 50%, var(--surface-3) 100%);
  background-size: 220% 100%;
  animation: reading-pending 1.4s var(--ease) infinite;
}
@keyframes reading-pending {
  from { background-position: 120% 0; }
  to { background-position: -120% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .fabric-roster__pending { animation: none; background: var(--surface-3); }
}

/* Current in-page section in the nav (landing.js sets aria-current="location"). */
.site-nav__links a[aria-current="location"] {
  color: var(--text-1);
  font-weight: 580;
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 8px;
  text-decoration-color: var(--info);
}

/* Anchored sections land below the sticky nav instead of under it. */
#how-it-works, #status-now, #get-the-bits { scroll-margin-top: calc(var(--tap) + var(--s-5)); }

.btn--ghost {
  background: transparent;
  border-color: var(--hairline-strong);
  color: var(--text-2);
  min-height: var(--tap);
  padding: var(--s-2) var(--s-4);
}
.btn--ghost:hover { background: var(--surface-2); color: var(--text-1); }
.btn--ghost[aria-pressed="true"] {
  background: var(--tint-info);
  border-color: var(--info);
  color: var(--info);
}

.fabric__live {
  margin-top: var(--s-4);
  font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem);
  color: var(--text-2);
  min-height: 1.4em;
}

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

.card {
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
@media (hover: hover) {
  /* :not([data-tilt]) because a tilt card's hover personality is the pointer
     tilt itself (rotateX/rotateY set inline by landing.js) — stacking a
     translateY lift on top would fight the inline transform landing.js
     writes on the same property. */
  .card:hover:not([data-tilt]) {
    border-color: var(--hairline-strong);
    transform: translateY(-2px);
    box-shadow: var(--e-3);
  }
  [data-tilt]:hover {
    border-color: var(--hairline-strong);
    box-shadow: var(--e-3);
  }
}

@media (max-width: 960px) {
  .gate-grid,
    .steps,
  .compat,
  .feed-meta { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 720px) {
  .site-nav__inner { align-items: flex-start; }
  .site-nav__links { width: 100%; }
}

@media (max-width: 600px) {
  .gate-grid,
    .steps,
  .compat,
  .fabric-roster,
  .feed-meta { grid-template-columns: minmax(0, 1fr); }
  .fabric__panel { padding: var(--s-4); }
  .hero { padding: var(--s-6) var(--gutter) var(--s-5); }
  .status-now, .section { padding: var(--s-6) var(--gutter); }
  .cta { padding: var(--s-6) var(--gutter) var(--s-8); }

  /* A 28-screen-long undifferentiated scroll was the single worst mobile
     defect: every section stacked with identical padding and no boundary
     marking. A hairline rule plus a raised, tinted section__label turns each
     section into a legible "you have moved to a new topic" beat instead of
     one continuous wall of cards. .hero and .cta keep their own distinct
     treatment (mesh backdrop / accent card) so they are not double-marked. */
  .status-now, .section {
    border-top: 1px solid var(--hairline);
  }
  .status-now:first-of-type { border-top: none; }
  .section__label {
    display: inline-flex;
    padding: 3px var(--s-2);
    margin-bottom: var(--s-3);
    border-radius: var(--r-sm);
    background: var(--tint-info);
    color: var(--info);
  }
  /* Tighter, more paced type scale: the desktop clamp still reads slightly
     loose at 390px, and section leads at t-15 crowd the tightened headings. */
  h2 { font-size: var(--t-24); }
  .section__lead { font-size: var(--t-13); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
  }
  .card:hover { transform: none; }
}

/* Mobile density: the landing is a reading page, so on a phone the cards give back some air
   (padding, gaps) without touching type size or the 44px tap targets. */
@media (max-width: 600px) {
  .gate-grid, .steps, .compat, .fabric-roster { gap: var(--s-3); }
  .gate, .step, .compat__col, .grammar, .feed-snapshot { padding: var(--s-4); }
  .status-now, .section { padding-top: var(--s-5); padding-bottom: var(--s-5); }
  .section__lead { margin-bottom: var(--s-4); }
}

/* ---- polish: keyboard focus, 150-250ms easing, no layout shift ------------------------
   Focus rings reuse the shared 3px outline; these add the halo on custom surfaces and
   make sure nothing is clipped by an overflow:hidden ancestor. Hover/press only change
   transform, colour, shadow and opacity, never size, so nothing reflows. */
/* Primary buttons are excluded: shared.css draws their ring INSIDE (offset -3px, in
   --on-info) because an outside ring would sit on the page at contrast 1. */
.hero__actions :focus-visible:not(.btn--primary) { outline-offset: 3px; }
.hero { overflow: clip; overflow-clip-margin: 6px; }
.site-nav__links a { transition: color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease); }

/* ---- micro-interactions: the page's own button feel --------------------------------
   shared.css owns the button's base feel (a -1px lift plus an --e-2 shadow on hover,
   and the `--press-scale` press). The landing adds the one motion it was asked for --
   a 1.02 hover growth -- WITHOUT dropping that lift: `translateY(-1px) scale(1.02)`
   keeps the shared language and adds the lift of scale on top. Transform is paint,
   never layout, so no control reflows and nothing below it moves.

   Two-sided by construction: `(hover: hover)` keeps this off touch screens, where a
   hover state sticks after a tap, and `(prefers-reduced-motion: no-preference)` keeps
   the scale off for anyone who asked for stillness. shared.css already disables every
   transition under reduce, but a transform in a :hover rule still applies instantly --
   an unanimated size jump is exactly what that preference asks us not to do, so the
   guard has to be on the rule itself, not only on the transition.

   The press on the two heaviest controls goes slightly deeper (0.965 vs the shared
   0.97) because at their size a 3.5% squash reads as key travel rather than a glitch.
   Anything already disabled stays inert: both rules exclude :disabled. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .hero__actions .btn:hover:not(:disabled),
  .get-the-bits .cta__actions .btn:hover:not(:disabled) {
    transform: translateY(-1px) scale(1.02);
  }
}
.hero__actions .btn:active:not(:disabled),
.get-the-bits .cta__actions .btn:active:not(:disabled) {
  transform: scale(0.965);
}

/* Inline text links inside prose: the underline thickens to 2px on hover. Thickness is
   the one affordance that does not move the line box or the baseline -- the links sit
   mid-sentence, so anything that changes `display` would reflow the paragraph, and
   anything that grows the box would shift the text after it. Colour is left to the
   shared rule so the accessible state colour survives. */
.section__lead a,
.proof__why a,
.feed-note a {
  transition: text-decoration-color var(--dur-fast) var(--ease),
              text-decoration-thickness var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .section__lead a:hover,
  .proof__why a:hover,
  .feed-note a:hover { text-decoration-thickness: 2px; }
}

/* Hero arrival: opacity only (cannot move layout), staggered 40ms, 220ms each. */
@keyframes hero-in { from { opacity: 0; } to { opacity: 1; } }
.hero > * { animation: hero-in 220ms var(--ease) both; }
.hero > :nth-child(2) { animation-delay: 40ms; }
.hero > :nth-child(3) { animation-delay: 80ms; }
.hero > :nth-child(4) { animation-delay: 120ms; }
.hero > :nth-child(5) { animation-delay: 160ms; }
.hero > :nth-child(6) { animation-delay: 200ms; }

/* Hero rhythm: a calmer lead, a tighter scope note, and optical balance on the lead. */
.hero__lead { text-wrap: pretty; }
.hero__scope { text-wrap: pretty; }
.hero__actions .btn { min-width: 11rem; justify-content: center; }
@media (max-width: 640px) { .hero__actions .btn { width: 100%; } }

@media (prefers-reduced-motion: reduce) {
  .hero > * { animation: none !important; }
}

/* ---- measured fabric: real paired samples (see fabric-measured.js) ---------------------
   Everything here is drawn from fabric-measured.json. Colours are the shared tokens, so both
   schemes repaint it. Mac = filled circle in --info, peer = hollow diamond in --text-1, so the two are told
   apart by SHAPE and by row label as well as by colour.
   Motion: a one-time staggered entrance and a hover/focus ring, both gone under
   prefers-reduced-motion. */
.fm-wrap { max-width: var(--page); margin: 0 auto; padding: 0 var(--gutter) var(--s-5); min-width: 0; }
.fm {
  padding: var(--s-5);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-lg);
  background: var(--plane-1);
  box-shadow: var(--e-2), var(--lit);
  min-width: 0;
}
.fm__head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s-3); margin-bottom: var(--s-2); }
.fm__title { font-size: clamp(1.25rem, 1.1rem + 0.7vw, 1.5rem); margin: 0; letter-spacing: -0.015em; }
.fm__lead { margin: 0 0 var(--s-4); color: var(--text-2); max-width: 62ch; line-height: 1.55; }
.fm__status { margin: 0; padding: var(--s-4); color: var(--text-2); border: 1px dashed var(--hairline-strong); border-radius: var(--r-md); }
.fm__status[hidden] { display: none; }
.fm__body { min-width: 0; }
.fm-summary {
  margin: 0 0 var(--s-3); padding: var(--s-3) var(--s-4);
  font-size: clamp(0.875rem, 0.83rem + 0.18vw, 0.9375rem); line-height: 1.55; color: var(--text-1);
  border-left: 3px solid var(--info); background: var(--plane-2); border-radius: 0 var(--r-md) var(--r-md) 0;
}
.fm-summary[data-verdict="local"] { border-left-color: var(--warn); }
.fm-caveat { margin: 0 0 var(--s-4); font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); line-height: 1.55; color: var(--text-2); }
.fm-grid-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr)); gap: var(--s-4); min-width: 0; }
.fm-card {
  min-width: 0; padding: var(--s-4);
  border: 1px solid var(--hairline-strong); border-radius: var(--r-md); background: var(--plane-2);
}
.fm-card--local { border-top: 3px solid var(--warn); }
.fm-card--qualified { border-top: 3px solid var(--info); }
.fm-card__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s-2) var(--s-3); }
.fm-card__title { margin: 0; font-size: clamp(0.875rem, 0.83rem + 0.18vw, 0.9375rem); font-variant-numeric: tabular-nums; }
.fm-card__chips { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.fm-card__note { margin: var(--s-3) 0; font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); line-height: 1.55; color: var(--text-2); }

.fm-nodes { margin: var(--s-3) 0; padding: var(--s-3); border: 1px solid var(--hairline); border-radius: var(--r-md); background: var(--surface-1); }
.fm-nodes__title { margin: 0 0 var(--s-2); font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem); letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-3); font-weight: 600; }
.fm-nodes__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-2); }
.fm-node { display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "head text" "cores cores"; align-items: center; gap: var(--s-1) var(--s-3); font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem); }
.fm-node > .fm-node__head { grid-area: head; }
.fm-node > .fm-cores { grid-area: cores; }
.fm-node > .fm-node__text { grid-area: text; text-align: right; }
.fm-node:not(:has(.fm-core)) { grid-template-areas: "head text"; }
.fm-node__head { display: flex; align-items: center; gap: var(--s-2); min-width: 0; flex-wrap: wrap; }
.fm-node__name { font-weight: 600; color: var(--text-1); font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); }
.fm-cores { display: flex; flex-wrap: wrap; gap: 3px; }
.fm-core { width: 8px; height: 8px; border-radius: 2px; border: 1px solid var(--hairline-strong); background: transparent; }
.fm-core--on { background: var(--pass); border-color: var(--pass); }
.fm-node__text { color: var(--text-2); font-variant-numeric: tabular-nums; }
.fm-nodes__total { margin: var(--s-2) 0 0; font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); font-weight: 600; font-variant-numeric: tabular-nums; }

.fm-chart { margin-top: var(--s-3); min-width: 0; }
.fm-svg { display: block; width: 100%; height: auto; overflow: visible; }
.fm-grid { stroke: var(--hairline); stroke-width: 1; }
.fm-tick, .fm-axis { fill: var(--text-3); font: 500 11px var(--sans); font-variant-numeric: tabular-nums; }
.fm-rowlabel { fill: var(--text-1); font: 600 13px var(--sans); }
.fm-p95 { stroke-width: 2; stroke-dasharray: 4 3; }
.fm-p95--local { stroke: var(--info); }
.fm-p95--remote { stroke: var(--text-2); }
.fm-p95label { fill: var(--text-2); font: 600 12px var(--sans); font-variant-numeric: tabular-nums; }
/* No outline override: the global :focus-visible ring (shared.css) is the keyboard indicator;
   .fm-ring adds the in-chart halo on top. */
.fm-dot { cursor: default; }
.fm-hit { fill: transparent; }
.fm-ring { fill: none; stroke: var(--focus-ring); stroke-width: 2; opacity: 0; transition: opacity var(--dur-fast) var(--ease); }
.fm-dot--local .fm-mark { fill: var(--info); stroke: var(--surface-2); stroke-width: 2; }
.fm-dot--remote .fm-mark { fill: var(--surface-2); stroke: var(--text-1); stroke-width: 2; }
.fm-mark { transition: transform var(--dur) var(--ease); transform-box: fill-box; transform-origin: center; }
.fm-dot.is-on .fm-mark, .fm-dot:hover .fm-mark { transform: scale(1.3); }
.fm-dot.is-on .fm-ring, .fm-dot:focus .fm-ring, .fm-dot:focus-visible .fm-ring { opacity: 1; }
.fm-svg--active .fm-dot:not(.is-on) { opacity: 0.42; }
.fm-dot { transition: opacity var(--dur) var(--ease); }
.fm-pair { stroke: var(--text-2); stroke-width: 1.5; stroke-dasharray: 2 3; opacity: 0; transition: opacity var(--dur-fast) var(--ease); pointer-events: none; }
.fm-val { fill: var(--text-1); font: 700 13px var(--sans); font-variant-numeric: tabular-nums; paint-order: stroke; stroke: var(--surface-2); stroke-width: 4px; opacity: 0; pointer-events: none; transition: opacity var(--dur-fast) var(--ease); }
.fm-readout { margin: var(--s-2) 0 0; min-height: 2.8em; font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); line-height: 1.5; color: var(--text-2); font-variant-numeric: tabular-nums; }
.fm-readout[data-active] { color: var(--text-1); }
.fm-legend { margin: var(--s-2) 0 0; font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem); color: var(--text-3); }
.fm-details { margin-top: var(--s-3); font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); }
.fm-details summary { cursor: pointer; color: var(--text-2); padding: var(--s-1) 0; }
.fm-table { width: 100%; border-collapse: collapse; margin-top: var(--s-2); font-variant-numeric: tabular-nums; }
.fm-table caption { text-align: left; color: var(--text-3); padding-bottom: var(--s-2); font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem); }
.fm-table th, .fm-table td { text-align: right; padding: 4px var(--s-2); border-bottom: 1px solid var(--hairline); }
.fm-table th:first-child { text-align: left; }
.fm-table__p95 th, .fm-table__p95 td { font-weight: 700; border-top: 1px solid var(--hairline-strong); }
.fm-source { margin: var(--s-3) 0 0; font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem); color: var(--text-3); overflow-wrap: anywhere; }
.fm-prov { margin: var(--s-4) 0 0; font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem); color: var(--text-3); line-height: 1.5; max-width: 70ch; }

/* Entrance: dots rise in once, staggered, only when the script armed it (motion allowed + in view API). */
@media (prefers-reduced-motion: no-preference) {
  .fm-wrap[data-fm-armed]:not([data-fm-inview]) .fm-dot { opacity: 0; }
  .fm-wrap[data-fm-armed][data-fm-inview] .fm-dot { animation: fm-in 420ms var(--ease) backwards; animation-delay: calc(var(--i, 0) * 45ms); }
}
@keyframes fm-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .fm-mark, .fm-ring, .fm-dot, .fm-pair, .fm-val { transition: none !important; }
  .fm-dot { animation: none !important; }
}
@media (max-width: 640px) {
  .fm { padding: var(--s-4); }
  .fm-node, .fm-node:not(:has(.fm-core)) { grid-template-columns: minmax(0, 1fr); grid-template-areas: "head" "text" "cores"; }
  .fm-node > .fm-node__text { text-align: left; }
}
@media (forced-colors: active) {
  .fm-dot--local .fm-mark { fill: Highlight; }
  .fm-dot--remote .fm-mark { fill: Canvas; stroke: CanvasText; }
  .fm-p95, .fm-grid, .fm-pair { stroke: CanvasText; }
  .fm-tick, .fm-axis, .fm-rowlabel, .fm-p95label, .fm-val { fill: CanvasText; }
}

/* ---- fabric time series: local against with-peer wall time (uPlot, see fabric-timeseries.js) --
   uPlot draws a canvas, so everything that makes it readable lives here and in the script:
   the focus ring, the text readout, the legend and the visually hidden data table. Series are
   told apart by line style and marker fill as well as colour (Mac solid + filled, peer dashed +
   hollow). The cursor is not animated and nothing here transitions, so reduced motion needs
   nothing removed; the guard below pins that. */
.ft-wrap { max-width: var(--page); margin: 0 auto; padding: 0 var(--gutter) var(--s-5); min-width: 0; }
.ft {
  padding: var(--s-5);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-lg);
  background: var(--plane-1);
  box-shadow: var(--e-2), var(--lit);
  min-width: 0;
}
.ft__head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s-3); margin-bottom: var(--s-2); }
.ft__title { font-size: clamp(1.25rem, 1.1rem + 0.7vw, 1.5rem); margin: 0; letter-spacing: -0.015em; }
.ft__lead { margin: 0 0 var(--s-4); color: var(--text-2); max-width: 62ch; line-height: 1.55; }
.ft__status { margin: 0; padding: var(--s-4); color: var(--text-2); border: 1px dashed var(--hairline-strong); border-radius: var(--r-md); min-height: 4.5rem; }
.ft__status[hidden], .ft__body[hidden] { display: none; }
.ft-wrap[data-ft-state="error"] .ft__status { border-style: solid; border-color: var(--warn-line); background: var(--warn-dim); color: var(--text-1); }
.ft__body { min-width: 0; }
.ft__basis { margin: 0 0 var(--s-2); font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); line-height: 1.55; color: var(--text-2); max-width: 74ch; }
.ft__summary { margin: 0 0 var(--s-3); font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); line-height: 1.55; color: var(--text-1); max-width: 74ch; }
.ft-legend { list-style: none; margin: 0 0 var(--s-3); padding: 0; display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-4); font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); color: var(--text-2); }
.ft-legend__item { display: inline-flex; align-items: center; gap: var(--s-2); }
.ft-swatch { display: inline-block; width: 28px; height: 10px; box-sizing: border-box; position: relative; }
.ft-swatch::before { content: ""; position: absolute; left: 0; right: 0; top: 4px; border-top: 2px solid var(--info); }
.ft-swatch::after { content: ""; position: absolute; left: 9px; top: 0; width: 10px; height: 10px; box-sizing: border-box; border-radius: 50%; border: 2px solid var(--info); background: var(--info); }
.ft-swatch--peer::before { border-top: 2px dashed var(--text-1); }
.ft-swatch--peer::after { border-color: var(--text-1); background: var(--surface-1); }
/* No padding / negative-margin bleed: the bleed pushed the host past .ft__body and overflowed it.
   The focus ring is an outline, which never adds to layout or scroll overflow. */
.ft__plot { min-width: 0; border-radius: var(--r-md); }
.ft__plot:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
.ft__canvas { min-width: 0; }
.ft__canvas .uplot { width: 100%; font-family: var(--sans); }
.ft__readout { margin: var(--s-2) 0 0; min-height: 2.8em; font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); line-height: 1.5; color: var(--text-2); font-variant-numeric: tabular-nums; }
.ft__readout[data-active] { color: var(--text-1); }
.ft__note { margin: var(--s-3) 0 0; font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); color: var(--text-2); }
.ft__table--shown { margin-top: var(--s-3); overflow-x: auto; }
.ft-table { width: 100%; border-collapse: collapse; font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); font-variant-numeric: tabular-nums; }
.ft-table caption { text-align: left; color: var(--text-3); padding-bottom: var(--s-2); font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem); }
.ft-table th, .ft-table td { text-align: right; padding: 4px var(--s-2); border-bottom: 1px solid var(--hairline); }
.ft-table th:first-child { text-align: left; }
@media (prefers-reduced-motion: reduce) {
  .ft *, .ft *::before, .ft *::after { animation: none !important; transition: none !important; }
}
@media (max-width: 640px) {
  .ft { padding: var(--s-4); }
}
@media (forced-colors: active) {
  .ft-swatch::before { border-top-color: CanvasText; }
  .ft-swatch::after { border-color: CanvasText; background: Canvas; }
}

/* ---- compact layout: map beside its roster, gates in one row, feed beside platforms ---------
   Wide screens only (the map keeps its labels at 12px or more: the column is 1.5fr, and below
   1200px every block stacks exactly as before). */
@media (min-width: 1200px) {
  .section.fabric { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); column-gap: var(--s-5); align-items: start; }
  .section.fabric > .section__label, .section.fabric > h2, .section.fabric > .section__lead, .section.fabric > .fabric__live { grid-column: 1 / -1; }
  .section.fabric .fabric__panel { padding: var(--s-4); }
  .section.fabric .fabric-roster { grid-template-columns: minmax(0, 1fr); margin-top: 0; gap: var(--s-3); }
  .section.fabric .fabric-roster__item { padding: var(--s-3) var(--s-4); }
}
@media (min-width: 961px) {
  .gate-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .get-the-bits { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); column-gap: var(--s-5); align-items: start; }
  .get-the-bits > .section__label, .get-the-bits > h2, .get-the-bits > .section__lead, .get-the-bits > .cta__actions { grid-column: 1 / -1; }
  .get-the-bits .feed-snapshot { margin-bottom: 0; }
  .get-the-bits .feed-matrix { grid-template-columns: minmax(0, 1fr); gap: var(--s-3); }
}

@media (min-width: 961px) {
  .status-now, .section { padding-top: var(--s-5); padding-bottom: var(--s-5); }
  .hero { padding-top: var(--s-7); padding-bottom: var(--s-6); }
  .hero__lead { margin-bottom: var(--s-4); }
  .section__lead { margin-bottom: var(--s-4); }
  .steps .step p { font-size: var(--t-13); }
}

/* ---- proof: the two measured results, then the evidence they come from ------------------
   Numbers are filled from fabric-measured.json by landing.js; the HTML carries the same
   values so the page is complete without a script. The win leads, the honest loss follows. */
.proof { max-width: var(--page); margin: 0 auto; padding: var(--s-6) var(--gutter) var(--s-5); min-width: 0; }

/* "When Tether helps": same card grid as the proof above it, one row of four on a
   wide screen and a single column on a phone. The figures are filled by script, so
   the status line is what a visitor sees until (and unless) they arrive. */
.when { max-width: var(--page); margin: 0 auto; padding: var(--s-5) var(--gutter) var(--s-6); min-width: 0; }
.when__head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--s-3); }
.when__title { font-size: clamp(1.25rem, 1.1rem + 0.7vw, 1.5rem); letter-spacing: -0.02em; max-width: 32ch; text-wrap: balance; margin: 0; }
.when__source { margin: 0; color: var(--text-3); }
.when__lead { max-width: 68ch; color: var(--text-2); margin: var(--s-4) 0; }
.when__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s-4); min-width: 0; }
.when .metric { text-align: left; display: flex; flex-direction: column; gap: var(--s-2); }
.when__status { margin: 0 0 var(--s-4); color: var(--text-3); }
.when[data-when-state="live"] .when__status { display: none; }
@media (max-width: 900px) {
  .when__grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 640px) {
  .when__grid { grid-template-columns: 1fr; }
}

/* "How Tether decides" plus the low-load benchmark line. Number slots have a
   reserved width (the longest shipped figure) so filling them does not reflow
   the sentence. At 360px the three checks stack; nothing in the strip has a
   fixed width wider than the gutter allows. */
.decide { max-width: var(--page); margin: 0 auto; padding: 0 var(--gutter) var(--s-6); min-width: 0; }
.decide__head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--s-3); }
.decide__title { font-size: clamp(1.25rem, 1.1rem + 0.7vw, 1.5rem); letter-spacing: -0.02em; max-width: 22ch; text-wrap: balance; margin: 0; }
.decide__source { margin: 0; color: var(--text-3); }
.decide__source a { color: var(--text-2); text-underline-offset: 3px; display: inline-flex; align-items: center; min-height: var(--tap); }
.decide__lead { max-width: 68ch; color: var(--text-2); margin: var(--s-4) 0; }
.decide__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-4); list-style: none; margin: 0; padding: 0; min-width: 0; }
.decide__grid > li { min-width: 0; }
.decide h3 { font-size: 1rem; margin: 0 0 var(--s-2); }
.decide__sentence { margin: 0; color: var(--text-1); line-height: 1.5; overflow-wrap: anywhere; }
.decide__num, .stay__num {
  display: inline-block; min-width: 3ch; text-align: center;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.stay__num[data-stay="units"],
.stay__num[data-stay="identical"],
.stay__num[data-stay="pairs"] { min-width: 2ch; }
.stay__num[data-stay="speedup-low"],
.stay__num[data-stay="speedup-high"] { min-width: 4ch; }
.decide__num:empty::before, .stay__num:empty::before { content: "\2013"; color: var(--text-2); }
.decide__docs { margin: var(--s-4) 0 0; }
.decide__docs a {
  color: var(--text-1); text-underline-offset: 3px;
  display: inline-flex; align-items: center; min-height: var(--tap);
}
.stay {
  margin: var(--s-4) 0 0; padding: var(--s-4); min-width: 0;
  border-left: 3px solid var(--info); color: var(--text-1);
  line-height: 1.5; text-wrap: pretty; overflow-wrap: anywhere;
}
.stay a { color: var(--text-2); text-underline-offset: 3px; }
.stay__range { white-space: nowrap; }
@media (max-width: 800px) {
  .decide__grid { grid-template-columns: minmax(0, 1fr); }
}
.proof__title { font-size: clamp(1.25rem, 1.1rem + 0.7vw, 1.5rem); letter-spacing: -0.02em; max-width: 32ch; text-wrap: balance; margin: 0 0 var(--s-4); }
.proof__grid { display: grid; grid-template-columns: 1.4fr 1fr 1.4fr; gap: var(--s-4); perspective: 1000px; min-width: 0; }
.proof .metric { text-align: left; display: flex; flex-direction: column; gap: var(--s-2); }
/* "Measured, not claimed": three bands, never one headline number. The numbers are filled
   from measured-not-claimed.json; this only lays the bands out. The loss band reuses the
   existing loss treatment so a slower result is visibly a loss. */
.measured { max-width: var(--page); margin: 0 auto; padding: var(--s-4) var(--gutter) var(--s-4); min-width: 0; }
.measured__head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--s-3); }
.measured__title { font-size: clamp(1.25rem, 1.1rem + 0.7vw, 1.5rem); letter-spacing: -0.02em; max-width: 32ch; text-wrap: balance; margin: 0; }
.measured__source { margin: 0; color: var(--text-2); }
.measured__lead { margin: var(--s-3) 0; max-width: var(--measure); color: var(--text-1); line-height: 1.5; text-wrap: pretty; }
.measured__status { margin: 0 0 var(--s-3); color: var(--text-2); }
.measured__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-4); min-width: 0; }
.measured .metric { text-align: left; display: flex; flex-direction: column; gap: var(--s-2); }
.measured .metric__value { font-size: clamp(1.5rem, 1.35rem + 1vw, 2.125rem); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.measured .metric__label { margin: 0; line-height: 1.5; }
.measured .metric__source { margin-top: auto; padding-top: var(--s-2); }
.measured__band { margin: 0; color: var(--text-2); font-size: var(--t-12); letter-spacing: 0.04em; text-transform: uppercase; }
.measured__note { margin: var(--s-4) 0 0; color: var(--text-2); }
.measured[data-mnc-state="unavailable"] .metric__value { color: var(--text-3); }
@media (max-width: 960px) { .measured__grid { grid-template-columns: minmax(0, 1fr); } }
.proof .metric__value { font-size: clamp(1.5rem, 1.35rem + 1vw, 2.125rem); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.proof .metric__label { margin: 0; line-height: 1.5; }
.proof .metric__source { margin-top: auto; padding-top: var(--s-2); }
.metric__arrow { font-size: clamp(1rem, 0.9rem + 0.5vw, 1.125rem); color: var(--text-3); font-weight: 400; }
.metric--loss { border-color: var(--warn-line); background: radial-gradient(140% 100% at 50% -20%, var(--warn-dim) 0%, transparent 60%), var(--plane-1); }
.metric--loss .metric__value { color: var(--warn); }
.proof__why { margin: var(--s-4) 0 0; max-width: var(--measure); color: var(--text-1); font-size: clamp(0.875rem, 0.83rem + 0.18vw, 0.9375rem); line-height: 1.6; padding-left: var(--s-4); border-left: 3px solid var(--pass); text-wrap: pretty; }
.evidence { max-width: var(--page); margin: 0 auto var(--s-5); padding: 0 var(--gutter); min-width: 0; }
.evidence__sum {
  display: flex; align-items: center; min-height: var(--tap); padding: 0 var(--s-4); cursor: pointer;
  border: 1px solid var(--hairline-strong); border-radius: var(--r-md); background: var(--plane-1);
  color: var(--text-1); font-weight: 560; list-style: none;
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
.evidence__sum::-webkit-details-marker, .grammar__sum::-webkit-details-marker { display: none; }
.evidence__sum::after, .grammar__sum::after { content: "+"; margin-left: auto; color: var(--text-3); font-size: clamp(1rem, 0.9rem + 0.5vw, 1.125rem); }
.evidence[open] > .evidence__sum::after, .grammar[open] > .grammar__sum::after { content: "\2212"; }
.evidence__sum:hover { border-color: var(--info-line); background: var(--plane-2); }
.evidence[open] > .evidence__sum { margin-bottom: var(--s-4); }
.evidence .fm-wrap, .evidence .ft-wrap { padding-left: 0; padding-right: 0; }
.grammar__sum { display: flex; align-items: center; min-height: var(--tap); cursor: pointer; list-style: none; font-weight: 600; }
.grammar { margin-top: var(--s-4); }
.grammar[open] > .grammar__sum { margin-bottom: var(--s-3); }
.evidence__sum:focus-visible, .grammar__sum:focus-visible { outline-offset: 3px; }
@media (max-width: 960px) { .proof__grid { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 600px) { .proof { padding-top: var(--s-5); } }

/* Feed measurement: reference detail, collapsed by default so the first look stays short. */
.feed-detail { margin-top: var(--s-5); }
.feed-detail__sum {
  cursor: pointer;
  min-height: 44px;
  display: flex;
  align-items: center;
  font-weight: 600;
  color: var(--text-1);
}
.feed-detail__sum:focus-visible { outline: 2px solid var(--info); outline-offset: 3px; }
.feed-detail[open] > .feed-detail__sum { margin-bottom: var(--s-4); }

/* Three status cards sit in one row on wide screens; they stack below this. */
@media (min-width: 960px) {
  .status-now .gate-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Roster items: heading and "Show on map" share one row, copy sits below. Grid areas place the
   control by name, so the DOM order (head, copy, live reading, control) is unchanged for
   keyboard and screen readers. Without this the roster column outgrew the map beside it. */
.fabric-roster__item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "head ctl" "copy copy" "live live";
  align-items: center;
  column-gap: var(--s-3);
}
.fabric-roster__item > .fabric-roster__head { grid-area: head; margin-bottom: 0; }
.fabric-roster__item > p { grid-area: copy; margin-top: var(--s-2); }
.fabric-roster__item > .fabric-roster__live { grid-area: live; }
.fabric-roster__item > .fabric-roster__ctl { grid-area: ctl; margin-top: 0; }
@media (min-width: 1200px) {
  .section.fabric .fabric-roster { gap: var(--s-2); }
}

/* Footer on a phone: Product beside Reference and Machines, instead of three stacked lists.
   Links keep their 44px targets; only the stacking changes. */
@media (max-width: 520px) {
  .pagefoot { padding-top: var(--s-6); padding-bottom: var(--s-6); }
  .pagefoot__top {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-areas: "ident ident" "prod ref" "prod mach";
    gap: var(--s-4);
  }
  .pagefoot__ident { grid-area: ident; }
  .pagefoot__group:nth-child(1) { grid-area: prod; }
  .pagefoot__group:nth-child(2) { grid-area: ref; }
  .pagefoot__group:nth-child(3) { grid-area: mach; }
}

/* Final rhythm pass: preserve the hierarchy while keeping the page within the tested reading
   length. These are token-aligned reductions, not fixed-pixel hacks. */
@media (min-width: 961px) {
  .status-now, .section { padding-top: var(--s-4); padding-bottom: var(--s-4); }
  .hero { padding-top: var(--s-6); padding-bottom: var(--s-5); }
}
@media (max-width: 600px) {
  .status-now, .section { padding-top: var(--s-4); padding-bottom: var(--s-4); }
  .proof { padding-top: var(--s-4); padding-bottom: var(--s-4); }
}
@media (max-width: 520px) {
  .pagefoot { padding-top: var(--s-5); padding-bottom: var(--s-5); }
}
@media (min-width: 961px) {
  .pagefoot { padding-top: var(--s-6); padding-bottom: var(--s-7); }
}

/* ---- landingux: hierarchy, rhythm, scroll-linked reveal ----------------------------------
   Reveal is native CSS (animation-timeline: view()), no script and no dependency. It only
   animates opacity and the independent `translate` property, so it never shifts layout and
   never fights the tilt cards' `transform`. Browsers without scroll timelines, and anyone who
   asks for reduced motion, get the finished page with no animation at all. */
.hero__actions { align-items: center; }
.hero__evidence-link {
  display: inline-flex; align-items: center; min-height: var(--tap); padding: 0 var(--s-3);
  color: var(--text-2); font-size: clamp(0.875rem, 0.83rem + 0.18vw, 0.9375rem); text-decoration: underline; text-underline-offset: 4px;
  text-decoration-color: var(--hairline-strong);
  transition: color var(--dur-fast) var(--ease);
}
.hero__evidence-link:hover { color: var(--text-1); text-decoration-color: currentColor; }
.hero__evidence-link::after { content: "\2193"; margin-left: var(--s-2); }
.proof__head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0 var(--s-4); margin-bottom: var(--s-4); }
.proof__head .proof__title { margin: 0; }
.proof__source { margin: 0; font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem); color: var(--text-3); }
.proof__source a { color: var(--text-2); text-underline-offset: 3px; display: inline-flex; align-items: center; min-height: var(--tap); margin-block: calc((var(--s-6) - var(--tap)) / 2); }
.get-the-bits .cta__actions { justify-content: flex-start; }
.get-the-bits .cta__actions .btn { min-height: var(--tap); }
@media (min-width: 961px) { .get-the-bits { padding-top: var(--s-3); padding-bottom: var(--s-3); } }
.get-the-bits .feed-detail { margin-top: var(--s-3); }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* Motion only: text stays at full opacity the whole time, so it never drops below the WCAG
       contrast ratio while it is partly scrolled in (axe flagged the 0.45 fade on .gate). */
    @keyframes lux-reveal { from { translate: 0 14px; } to { translate: 0 0; } }
    [data-reveal] {
      animation: lux-reveal linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 40%;
    }
    /* Grid stagger: each row crosses its viewport band slightly later than the one
       before it, so a row of cards arrives as one system travelling left to right
       rather than as a single slab fading. Ranges are offsets of the same 40%
       band, not new keyframes, so every section shares one motion signature. */
    .proof__grid > [data-reveal]:nth-child(2) { animation-range: entry 6% entry 46%; }
    .proof__grid > [data-reveal]:nth-child(3) { animation-range: entry 12% entry 52%; }
    .gate-grid > [data-reveal]:nth-child(2),
    .steps > [data-reveal]:nth-child(2),
    .compat > [data-reveal]:nth-child(2) { animation-range: entry 4% entry 44%; }
    .gate-grid > [data-reveal]:nth-child(3),
    .steps > [data-reveal]:nth-child(3),
    .compat > [data-reveal]:nth-child(3) { animation-range: entry 8% entry 48%; }
    .steps > [data-reveal]:nth-child(4) { animation-range: entry 12% entry 52%; }

    /* Section shells carry the broad 14px arrival; their cards use a shorter
       8px travel so nested animation reads as depth, not double movement. */
    .proof > [data-reveal],
    .fm-wrap > [data-reveal],
    .ft-wrap > [data-reveal],
    .sr-wrap > [data-reveal],
    .fabric > [data-reveal],
    .status-now > [data-reveal],
    .section > [data-reveal] {
      --reveal-distance: 8px;
      animation-name: lux-reveal-card;
    }
    @keyframes lux-reveal-card { from { translate: 0 var(--reveal-distance); } to { translate: 0 0; } }
  }
}
/* Content is never hidden outright (start state is a dim, not zero) and print gets the end state. */
@media print { [data-reveal] { animation: none !important; } }

/* CLS: below 960px shared.css collapses the nav links only after site-nav.js runs, so the open
   list (100px) snapped shut on load. When scripting is on, start collapsed: same declarations
   the script's .is-collapsible state uses. No-JS visitors keep the documented open list. */
@media (max-width: 959.98px) and (scripting: enabled) {
  .site-nav:not(.is-collapsible) .site-nav__links {
    position: absolute; left: 0; right: 0; top: 100%; overflow: hidden; max-height: 0; opacity: 0; visibility: hidden;
  }
  .site-nav:not(.is-collapsible) .site-nav__toggle { display: inline-flex; visibility: hidden; }
}

/* CLS: the scene status line is 2 lines before its text arrives and 3 after, which pushed the
   canvas down 24px. Reserve the 3-line box on wide screens (aspect-ratio already holds the canvas). */
@media (min-width: 641px) { .fs .fs__status { min-height: 4.7em; } }

/* CLS: the run selector is revealed after the data loads; keep the pause button where it will
   end up so the control does not resize. */
.fs__ctl > .btn[data-fs-pause] { margin-left: auto; }
@media (min-width: 641px) { .fs__ctl { min-width: 21.5rem; } }

/* ---- split-run visual: page chrome only; the figure itself is status-dashboard/split-run.css ---- */
.sr-wrap { max-width: var(--page); margin: 0 auto; padding: 0 var(--gutter) var(--s-5); min-width: 0; }
.sr-landing {
  padding: var(--s-5);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-lg);
  background: var(--plane-1);
  box-shadow: var(--e-2), var(--lit);
  min-width: 0;
}
.sr-landing__head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s-3); margin-bottom: var(--s-2); }
.sr-landing__title { font-size: clamp(1.25rem, 1.1rem + 0.7vw, 1.5rem); margin: 0; letter-spacing: -0.015em; }
.sr-landing__lead { margin: 0 0 var(--s-4); color: var(--text-2); max-width: 68ch; line-height: 1.55; }
.sr-landing__note { margin: var(--s-3) 0 0; color: var(--text-2); max-width: 68ch; line-height: 1.55; }
/* The measured table has six columns, so it scrolls inside its own region
   (keyboard reachable via tabindex) instead of widening the page. */
.sr-landing .tablewrap {
  max-width: 100%;
  margin: 0 0 var(--s-3);
  overflow-x: auto;
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
}
.sr-landing .tablewrap:focus-visible { box-shadow: var(--focus-halo); }
.sr-landing .bench {
  width: 100%;
  min-width: 46rem;
  border-collapse: collapse;
  font-size: var(--t-13);
}
.sr-landing .bench caption { caption-side: bottom; padding: var(--s-2) var(--s-3) var(--s-3); text-align: left; color: var(--text-2); font-size: var(--t-12); line-height: 1.5; }
.sr-landing .bench th, .sr-landing .bench td {
  padding: var(--s-2) var(--s-3);
  text-align: left;
  border-bottom: 1px solid var(--hairline);
  color: var(--text-2);
}
.sr-landing .bench thead th { color: var(--text-1); font-weight: 560; font-size: var(--t-12); }
.sr-landing .bench tbody th { color: var(--text-1); font-weight: 560; }
.sr-landing .bench tbody tr:last-child th, .sr-landing .bench tbody tr:last-child td { border-bottom: 0; }
@media (max-width: 640px) {
  .sr-landing { padding: var(--s-4); }
}

/* Measured-proof strip under the hero. Every value is painted from
   feed-snapshot.json by feed-proof.js; a missing field reads "not measured".
   Sized as a band, not a section, so the page's first screen still shows the
   promise and one checkable fact rather than a wall of cards. */
.feed-proof {
  max-width: var(--page);
  margin: 0 auto;
  padding: var(--s-3) var(--gutter);
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  background: var(--surface-1);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-2) var(--s-5);
}
.feed-proof__title { margin: 0; font-size: clamp(0.875rem, 0.84rem + 0.16vw, 0.9375rem); letter-spacing: -0.01em; }
.feed-proof__source { margin: 0; font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem); color: var(--text-3); }
.feed-proof__source a { color: var(--text-2); text-underline-offset: 3px; }
.feed-proof__grid { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-5); margin: 0; flex: 1 1 100%; }
.feed-proof__cell { margin: 0; min-width: 0; flex: 0 1 auto; }
.feed-proof__cell { display: flex; align-items: baseline; gap: var(--s-2); }
.feed-proof__cell dt {
  font-size: clamp(0.625rem, 0.6rem + 0.12vw, 0.6875rem);
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-3);
}
.feed-proof__cell dd { margin: 0; color: var(--text-1); font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); }
@media (forced-colors: active) { .feed-proof { border-color: CanvasText; } }

/* ---- Recorded replay (web/landing/replay.js) -------------------------------------------
   One measured split run, played back. The STATIC render below is the record: two lanes
   whose bar widths are the recorded legs on ONE shared scale (set inline by the module's
   own numbers, never by the script). Motion is a second layer: `[data-replay-state=
   "playing"]` runs each fill from zero to its own width with a duration taken from that
   same record, so the bar that ends first is the machine that finished first.

   Nothing here is a fixed number in seconds: the durations arrive as the custom
   properties --replay-*-ms, written by replay.js from the record through ONE scale
   factor (MAX_WINDOW_MS / the longer leg). Under prefers-reduced-motion the layer is
   absent entirely, and the static bar widths are already correct, so the section still
   says everything it says with motion. */
.replay {
  max-width: var(--page);
  margin: 0 auto;
  padding: var(--s-7) var(--gutter) var(--s-8);
  min-width: 0;
}
.replay__kicker {
  font-size: clamp(0.625rem, 0.6rem + 0.12vw, 0.6875rem);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-3);
  font-weight: 560;
  margin: 0 0 var(--s-2);
}
.replay__title {
  font-size: clamp(1.25rem, 1.1rem + 0.7vw, 1.75rem);
  color: var(--text-1);
  margin: 0;
}
.replay__lead {
  color: var(--text-2);
  font-size: clamp(0.875rem, 0.83rem + 0.18vw, 0.9375rem);
  line-height: 1.6;
  max-width: var(--measure);
  margin: var(--s-3) 0 var(--s-5);
}
.replay__stage {
  display: grid;
  gap: var(--s-3);
  padding: var(--s-4);
  background: var(--plane-2);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
}
/* The job card: what the run was, above the two lanes that carried it. Its figures are
   the record's (workload class, unit count) like every other number in the section. */
.replay__job {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-2) var(--s-3);
  padding-block-end: var(--s-3);
  border-block-end: 1px solid var(--hairline);
  min-width: 0;
}
.replay__jobClass {
  font-family: var(--mono);
  font-size: clamp(0.75rem, 0.72rem + 0.14vw, 0.8125rem);
  color: var(--text-1);
  font-weight: 600;
  overflow-wrap: anywhere;
}
.replay__jobUnits {
  color: var(--text-3);
  font-size: clamp(0.75rem, 0.72rem + 0.14vw, 0.8125rem);
}
.replay__lane {
  display: grid;
  grid-template-columns: minmax(6rem, 9rem) minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-3);
  min-width: 0;
}
.replay__machine {
  font-weight: 600;
  font-size: clamp(0.8125rem, 0.78rem + 0.16vw, 0.875rem);
  color: var(--text-1);
  overflow-wrap: anywhere;
}
.replay__bar {
  position: relative;
  height: 18px;
  border-radius: var(--r-sm);
  background: var(--surface-3);
  overflow: hidden;
  min-width: 0;
}
/* Static width: the lane's own measured leg as a share of the LONGEST leg of the two,
   expressed with the module's rounded seconds. These two values are replaced at paint
   time from the record; the numbers here are the record's own, so a page with no
   JavaScript still shows the measured proportions. */
.replay__lane--local .replay__fill {
  background: var(--info);
  inline-size: var(--replay-local-share, 100%);
}
.replay__lane--peer .replay__fill {
  background: var(--pass);
  inline-size: var(--replay-peer-share, 86.42%);
}
.replay__fill {
  display: block;
  block-size: 100%;
  border-radius: inherit;
}
/* The transfer phase of the peer's leg: dispatch + transfer happen BEFORE the peer
   computes, so the window sits at the START of the peer's own recorded leg. */
.replay__fly {
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: var(--replay-fly-share, 8.13%);
  background: var(--warn-dim);
  border-inline-end: 1px dashed var(--warn-line);
}
.replay__count {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem);
  color: var(--text-2);
  white-space: nowrap;
}
/* Donor-stats strip: the same record, read as a table rather than as bars. Every value
   is a slot replay.js repaints and a test pins to the record. */
.replay__stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--s-3);
  margin: var(--s-2) 0 0;
  padding-block-start: var(--s-3);
  border-block-start: 1px solid var(--hairline);
}
.replay__stat {
  display: grid;
  gap: 2px;
  min-width: 0;
}
.replay__stat dt {
  font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem);
  color: var(--text-3);
  line-height: 1.4;
}
.replay__stat dd {
  margin: 0;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(0.8125rem, 0.78rem + 0.16vw, 0.875rem);
  color: var(--text-1);
  font-weight: 600;
  overflow-wrap: anywhere;
}
/* The replay layer. Only present when the page is playing AND the user has not asked
   for reduced motion: the static render is the default, the motion is the extra. */
@media (prefers-reduced-motion: no-preference) {
  [data-replay-state="playing"] .replay__fill {
    animation-duration: var(--replay-local-ms, 0ms);
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-name: replay-grow;
  }
  [data-replay-state="playing"] .replay__lane--peer .replay__fill {
    animation-duration: var(--replay-peer-ms, 0ms);
  }
  [data-replay-state="playing"] .replay__fly {
    animation-duration: var(--replay-fly-ms, 0ms);
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-name: replay-fly;
  }
  [data-replay-paused="true"] .replay__fill,
  [data-replay-paused="true"] .replay__fly { animation-play-state: paused; }
}
@keyframes replay-grow {
  from { inline-size: 0; }
}
@keyframes replay-fly {
  from { inline-size: 0; opacity: 0; }
  to { opacity: 1; }
}
.replay__ctl {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-3);
}
.replay__status {
  margin: 0;
  font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem);
  color: var(--text-3);
  line-height: 1.5;
  max-width: 80ch;
}
.replay__end {
  display: grid;
  gap: var(--s-2);
  margin-top: var(--s-4);
}
.replay__result, .replay__same, .replay__note {
  margin: 0;
  font-size: clamp(0.75rem, 0.72rem + 0.14vw, 0.8125rem);
  line-height: 1.55;
  color: var(--text-2);
  max-width: var(--measure);
}
.replay__figures {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-1);
  font-weight: 600;
  margin-inline-end: var(--s-2);
}
.replay__arrow { color: var(--text-3); }
.replay__digest {
  font-family: var(--mono);
  color: var(--pass);
  margin-inline-end: var(--s-2);
}
.replay__note { color: var(--text-3); }
@media (max-width: 640px) {
  .replay__lane { grid-template-columns: 1fr; gap: var(--s-1); }
  .replay__count { justify-self: start; }
}

/* ---- fabric scene: jobs out, receipts back (see fabric-scene.js) -------------------------
   A Canvas2D 3D scene drawn from fabric-measured.json. The canvas and the SVG fallback are
   aria-hidden; the status line and the machine list are the visible text equivalent. Colours
   come from the shared tokens (the script reads them at paint time, so both schemes repaint).
   The box is reserved by aspect-ratio so the script causes no layout shift. */
.fs-wrap { max-width: var(--page); margin: 0 auto; padding: 0 var(--gutter) var(--s-5); min-width: 0; }
.fs {
  margin: 0;
  padding: var(--s-5);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-lg);
  background:
    radial-gradient(80% 90% at 50% 100%, var(--pass-dim) 0%, transparent 60%),
    radial-gradient(90% 110% at 50% 0%, var(--info-dim) 0%, transparent 62%),
    var(--surface-1);
  box-shadow: var(--e-2), var(--lit);
}
.fs__head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--s-3); margin-bottom: var(--s-2); }
.fs__title { font-size: clamp(1.25rem, 1.1rem + 0.7vw, 1.5rem); margin: 0; letter-spacing: -0.015em; }
/* Segmented control: the run choices and the pause toggle share one bordered track, so they
   read as one control (not three stacked buttons) at every width. */
.fs__ctl { display: flex; align-items: stretch; min-width: 0; max-width: 100%; border: 1px solid var(--hairline-strong); border-radius: var(--r-pill); background: var(--surface-2); overflow: hidden; }
.fs__runs { display: flex; min-width: 0; flex: 1 1 auto; }
.fs__runs[hidden], .fs__ctl .btn[hidden] { display: none; }
.fs__ctl .btn { border: 0; border-radius: 0; min-height: var(--tap); padding: 0 var(--s-4); background: transparent; box-shadow: none; font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); white-space: nowrap; }
.fs__ctl .btn + .btn, .fs__runs + .btn, .fs__runs .btn + .btn { border-left: 1px solid var(--hairline); }
.fs__ctl .btn:hover { background: var(--surface-3); }
.fs__ctl .btn[aria-current="true"] { background: var(--info-dim); color: var(--text-1); box-shadow: inset 0 -2px 0 var(--info); }
.fs__ctl .btn[aria-pressed="true"] { color: var(--text-2); }
.fs-run { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px; line-height: 1.15; }
.fs-run__verdict { font-weight: 600; text-transform: capitalize; }
.fs-run__when { font-size: clamp(0.625rem, 0.6rem + 0.12vw, 0.6875rem); color: var(--text-2); font-variant-numeric: tabular-nums; }
.fs__ctl .btn[aria-current="true"] .fs-run__when { color: var(--text-1); }
.fs__ctl :focus-visible { outline-offset: -3px; }
@media (max-width: 640px) { .fs__ctl { width: 100%; } .fs__runs .btn { flex: 1 1 0; min-width: 0; padding: 0 var(--s-2); } .fs__ctl .btn { font-size: var(--t-12); } .fs__ctl > .btn { flex: 0 0 auto; padding: 0 var(--s-3); } }
.fs__status { margin: 0 0 var(--s-3); color: var(--text-1); line-height: 1.55; max-width: 74ch; min-height: 3.1em; }
.fs__stage { position: relative; aspect-ratio: 960 / 440; width: 100%; touch-action: pan-y; border-radius: var(--r-md); }
.fs__stage[hidden] { display: none; }
.fs__canvas, .fs__svg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.fs__canvas[hidden], .fs__svg[hidden] { display: none; }
.fs-stage--hit { cursor: pointer; }
@media (max-width: 640px) { .fs { padding: var(--s-3); } .fs__stage { aspect-ratio: 390 / 340; } }

.fs__nodes { list-style: none; margin: var(--s-3) 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--s-2); }
.fs-node {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px; text-align: left;
  min-height: 44px; padding: var(--s-2) var(--s-3); font: inherit; color: var(--text-1);
  background: var(--surface-2); border: 1px solid var(--hairline-strong); border-radius: var(--r-md); cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
.fs-node:hover, .fs-node[aria-pressed="true"] { border-color: var(--info-line); background: var(--surface-3); }
.fs-node__name { font-weight: 600; font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem); }
.fs-node__sub { font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem); color: var(--text-2); font-variant-numeric: tabular-nums; }
.fs__note { margin: var(--s-3) 0 0; font-size: clamp(0.6875rem, 0.66rem + 0.12vw, 0.75rem); color: var(--text-3); line-height: 1.5; max-width: 80ch; }
.fs :focus-visible:not(.btn--primary) { outline-offset: 3px; }

.fs-fill-pass { fill: var(--pass); } .fs-fill-info { fill: var(--info); } .fs-fill-warn { fill: var(--warn); }
.fs-fill-surface { fill: var(--surface-3); } .fs-fill-hair { fill: var(--hairline-strong); }
.fs-fill-text1 { fill: var(--text-1); } .fs-fill-text2 { fill: var(--text-2); } .fs-fill-text3 { fill: var(--text-3); }
.fs-stroke-pass { stroke: var(--pass); } .fs-stroke-info { stroke: var(--info); } .fs-stroke-warn { stroke: var(--warn); }
.fs-stroke-hair { stroke: var(--hairline-strong); } .fs-stroke-text1 { stroke: var(--text-1); }

@media (forced-colors: active) {
  .fs-fill-surface, .fs-fill-hair { fill: Canvas; }
  .fs-fill-pass, .fs-fill-info, .fs-fill-warn, .fs-fill-text1, .fs-fill-text2, .fs-fill-text3 { fill: CanvasText; }
  .fs-stroke-pass, .fs-stroke-info, .fs-stroke-warn, .fs-stroke-hair, .fs-stroke-text1 { stroke: CanvasText; }
}
