/* Koinessa public site — the split-register design system (KWR-001 W2, D-2).
 *
 * This file REPLACES `assets/conexus.css` for every App Router page. That file was the
 * dashboard's skin: its own header admits the tokens were "copied name-for-name and
 * value-for-value from the dashboard", and those values are the Claude/Tailwind default
 * palette — indigo-500 `#6366f1`, violet-400 `#a78bfa`, cyan-400 `#22d3ee`, slate-900
 * `#0f172a` — with a 135deg indigo->violet->cyan gradient reused as hero text, every panel
 * rail and every primary button. `conexus.css` stays on disk only because `whitepaper.html`
 * still links it; retiring it is W6 (US-605).
 *
 * THE PALETTE IS THE BRAND, SAMPLED. `#2B337F` is the dominant ground of both shipped logo
 * files (`assets/koinessa-wordmark.png`, `assets/koinessa-mark.png`); every other pixel in
 * them is white or antialiasing between the two. It is `--k-navy`, it is exact, and it is not
 * to be adjusted. The mark is a CARYATID — the Greek column carved as a robed woman, holding a
 * structure up — and the wordmark is a high-contrast Didone. The identity is classical and
 * architectural, so the palette is the one that has always gone with that: NAVY, CREAM and
 * BRASS. Marble, bronze, patina, a Penguin spine.
 *
 * THE HUE TRAP, AND WHY KEEPING NAVY IS THE ANSWER RATHER THAN THE PROBLEM. `#2B337F` is a
 * blue-purple, and the complaint this epic exists to answer is "the purple bluish AI slop
 * look". Those are not the same thing and the difference is measurable:
 *
 *   slop      #6366f1 = hsl(239, 84%, 67%) — bright, saturated, on Tailwind slate #0f172a,
 *             under a geometric sans, with 135deg gradients and glow.
 *   Koinessa  #2B337F = hsl(234, 49%, 33%) — HALF the saturation and HALF the lightness,
 *             under a Didone serif and a classical figure, flat, no gradient, no glow.
 *
 * Same hue family, opposite register. The differentiator is treatment, not hue, so the ban
 * stands exactly as written and is enforced by `tests/design-system.test.mjs`: no `#6366f1`,
 * no `#a78bfa`, no cyan, no gradient text, no multi-stop accent gradients, and never the
 * blue-slate `#0f172a` cast. This file contains none of them.
 *
 * THE IDEA. One page, two registers, one continuous scroll. Ad traffic lands in the WARM
 * tier: cream ground, Didone headlines, one idea per screen. As the visitor scrolls, the page
 * COOLS — the ground darkens from cream to navy-ink, the serif gives way to sans and mono,
 * density rises, and the evidence layer appears with claim labels and sourced numbers. The
 * visitor who bounces at 30% got the message; the reader who reaches the bottom got the proof.
 * Cream-to-navy ARRIVES at the brand rather than away from it, which a neutral black would not.
 *
 * THREE COMMITTED SURFACES, not two. The register mechanism has two — warm and instrument —
 * and a third sits outside it: `--k-navy-*`, the BRAND PLATE, which is the header, the footer
 * and the crossover plate. It is `#2B337F` exactly, so the wordmark's own field dissolves into
 * the masthead instead of sitting on it as a visible rectangle.
 *
 * HOW CONTRAST IS GUARANTEED (AC-3). Every end is a COMMITTED value, never a blend. Every
 * text-bearing surface pins its own opaque ground — `[data-register="warm"]` paints
 * `--k-warm-ground`, `[data-register="instrument"]` paints `--k-inst-ground`, the chrome paints
 * `--k-navy` — so no character is ever rendered against an interpolated colour. The
 * scroll-linked interpolation (`--k-t`, written on `<html>` by
 * `components/site/RegisterScroll.tsx`) drives ONE thing: the midpoint of `.k-crossover`'s
 * gradient, a band that carries no text except on an opaque plate.
 * `tests/contrast.test.mjs` computes WCAG 2.1 ratios for every pair in all three surfaces and
 * fails under 4.5:1 (text) or 3:1 (borders and large type). SWI-001 shipped a contrast bug in
 * a brand CTA precisely because nobody measured.
 *
 * THEME. `data-theme` on `<html>` is preserved with both `localStorage` keys, but its meaning
 * is re-stated: `light` (the new default) is CREAM — the full split-register — and `dark` is
 * INSTRUMENT, which pins the warm tier to the navy-ink end so the whole page reads as one cold
 * surface. The toggle is therefore "paper / instrument", which is honest about what it does.
 * The default flipped because the warm tier must be cream for the design to exist at all.
 *
 * TOKEN NAMES ARE A CONTRACT. `components/scroll/` reads `--k-accent`, `--k-on-accent`,
 * `--k-ground`, `--k-ink`, `--k-ink-soft`, `--k-warm-ink`, `--k-inst-ink`, `--k-font-display`,
 * `--k-font-sans` and `--k-font-mono` off the computed style to pick legible ink over video.
 * Do not rename them, and KEEP EVERY COLOUR TOKEN A PLAIN 6-DIGIT HEX: that engine's parser
 * understands 3- and 6-digit hex only, and a value it cannot parse makes it silently assume
 * "legible" and skip its own contrast check. The alias chain below resolves to plain hex on
 * purpose. `--k-page-ground` is a live `color-mix()` and is deliberately outside that set.
 */

/* ----------------------------------------------------------------- typefaces */

/* Self-hosted, latin subset, `font-display: swap`. Both families are SIL OFL 1.1 — see
 * `assets/fonts/LICENSE.md`. Nothing loaded a webfont on this site before (Template A declared
 * system stacks; Template B named `Inter` and never shipped the file), so this is additive.
 *
 * Six files, 117,860 bytes total. The `latin` subset covers Latin-1 Supplement, so every
 * Spanish diacritic plus the inverted marks are present and the `/es/` pages need no second
 * download.
 *
 * THE DISPLAY FACE ANSWERS THE WORDMARK. The Koinessa wordmark is a high-contrast Didone
 * (Bodoni/Didot family), so the headline face has to belong to that world or the logo reads as
 * borrowed from somewhere else. Playfair Display is the closest robust OFL face: high stroke
 * contrast, vertical stress, ball terminals, and a large x-height that survives at the 1.25rem
 * `h3` size where a true Bodoni's hairlines thin out to nothing on a low-DPI screen. Bodoni
 * Moda was fetched and measured as the literal-match alternative (29,480 bytes for the same
 * two weights, so cheaper) and rejected on that robustness point, and because a headline that
 * IS the wordmark's face reads flat rather than composed.
 *
 * THE MONO IS NOT A CODE FONT, AND IS CHOSEN AS IF IT WERE NOT ONE (W6). Its dominant job on
 * this site is figures: `17,903`, `2026-08-23 21:33 ET`, `project_id`, every claim chip, every
 * provenance stamp, every `.num` table cell. Code snippets are a minor use. IBM Plex Mono held
 * the slot through W2 and was replaced because it is a recognisable developer-tool default —
 * the same tell in type that `#6366f1` is in colour — and this epic exists to stop looking like
 * a default. Its replacement is SPLINE SANS MONO, the monospace companion to Spline Sans, so it
 * is an editorial grotesque derived from a neutral sans rather than a terminal face.
 *
 * Measured against the alternatives at the `latin` 400 subset, because "denser and clearer at
 * small sizes" is computable and was computed:
 *
 *   family            adv/em   x-height/em   cap/em   zero        verdict
 *   Plex Mono         0.600    0.516         0.698    slashed     the default being escaped
 *   Spline Sans Mono  0.600    0.545         0.727    dotted      CHOSEN
 *   Chivo Mono        0.600    0.511         0.686    UNMARKED    0 and O are the same shape
 *   Martian Mono      0.700    0.600         0.800    slashed     17% wider; tables pay for it
 *
 * Spline matches Plex Mono's 0.600 advance exactly, so a restyle costs nothing in table width,
 * and carries the tallest x-height and cap height of the three at that width — 5.6% more figure
 * height than Plex Mono at the same `font-size`, which is the whole argument at 0.655rem chip
 * text. Chivo Mono was rejected on the zero: `0` and `O` are both two contours, identical
 * shapes, in a face whose main job is reading `project_id` and hash-shaped identifiers. Martian
 * Mono was rejected on density — 17% wider on every glyph, paid on every readout table.
 * Commit Mono was not measured: it describes itself as a neutral PROGRAMMING typeface, which is
 * the category being left. Spline also ships lighter: 25,936 bytes for both weights against
 * Plex Mono's 30,328.
 *
 * The three families are the register, in type:
 *   Playfair Display — warm tier only. The Didone voice; cream paper's own.
 *   Plex Sans        — body everywhere; the connective tissue between the two tiers.
 *   Spline Mono      — evidence, numbers, labels, provenance stamps; the instrument's voice.
 */
@font-face {
  font-family: "Playfair Display";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/playfair-400.woff2") format("woff2");
}
@font-face {
  font-family: "Playfair Display";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/playfair-600.woff2") format("woff2");
}
@font-face {
  font-family: "Plex Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/plex-sans-400.woff2") format("woff2");
}
@font-face {
  font-family: "Plex Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/plex-sans-600.woff2") format("woff2");
}
@font-face {
  font-family: "Spline Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/spline-mono-400.woff2") format("woff2");
}
@font-face {
  font-family: "Spline Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/spline-mono-600.woff2") format("woff2");
}

/* -------------------------------------------------------------------- tokens */

:root {
  /* --- THE BRAND ANCHOR. Sampled from both shipped logo files. Do not alter. --- */
  --k-navy: #2b337f;

  /* --- the brand plate: header, footer, crossover plate. A third committed surface, outside
   * the register mechanism, so the wordmark's own field dissolves into the masthead. --- */
  --k-navy-on: #e8e6f0;
  --k-navy-on-soft: #b9bdd6;
  --k-navy-rule: #47509b;
  --k-navy-rule-strong: #8a91c4;

  /* --- the warm end: cream. A book page, not a UI grey. --- */
  --k-warm-ground: #f7f4ec;
  --k-warm-surface: #fffdf7;
  --k-warm-sunk: #ede8da;
  --k-warm-ink: #14162e;
  --k-warm-ink-soft: #4a4e6b;
  --k-warm-rule: #d6cfc0;
  --k-warm-rule-strong: #83889f;
  --k-warm-accent: #836125;
  --k-warm-on-accent: #fffdf8;
  --k-warm-pass: #3f6b4a;
  --k-warm-warn: #8f3f12;
  --k-warm-fail: #8e1f32;
  --k-warm-grid: rgba(20, 22, 46, 0.05);

  /* --- the instrument end: navy-ink. The brand hue taken down to 15% lightness, NEVER the
   * blue-slate #0f172a cast and never a neutral black -- a neutral would land the register
   * journey away from the brand instead of on it. --- */
  --k-inst-ground: #11143a;
  --k-inst-surface: #1b2154;
  --k-inst-sunk: #0b0d26;
  --k-inst-ink: #e8e6f0;
  --k-inst-ink-soft: #9a9cb8;
  --k-inst-rule: #262a55;
  --k-inst-rule-strong: #69709a;
  --k-inst-accent: #d6b265;
  --k-inst-on-accent: #11143a;
  --k-inst-pass: #6faf85;
  --k-inst-warn: #d9783a;
  --k-inst-fail: #e8899a;
  --k-inst-grid: rgba(232, 230, 240, 0.045);

  /* --- WHY THE ACCENT IS BRASS. It is navy's classical companion and the caryatid's own
   * material world: marble, bronze, patina. It is also, usefully, the exact opposite of cyan
   * on the wheel, so the one colour the eye is pulled to is the furthest thing from the hue
   * this epic is escaping. A first pass used a burnt vermilion, chosen against a neutral
   * ground; beside `#2B337F` it reads as an unrelated second brand, where brass reads as the
   * navy's own metal. The two values are ONE hue (38deg / 41deg) at two lightnesses, because a
   * brass light enough to clear 4.5:1 on navy-ink cannot clear it on cream.
   *
   * THE WARM VALUE IS 4% DARKER THAN THE ONE PROPOSED, AND HERE IS WHY. `#8C6A2F` measures
   * 4.53:1 on `--k-warm-ground` -- fine -- but 4.07:1 on `--k-warm-sunk`, which is the ground
   * of `.cx-evidence`, and `.cx-evidence .str` paints SQL string literals in the accent. That
   * is real body text under the floor, so the value moved to `#836125`: same hue to within
   * 0.2deg, 4.63:1 on the sunk ground, 5.16:1 on the page.
   *
   * `--k-warn` was pushed from 26deg to 22deg (warm) and 28deg to 23deg (instrument) to open
   * the gap to brass from 12deg to 17deg. States are carried by glyph AND word, never colour
   * alone, so this was never a correctness problem -- it just makes an amber chip beside a
   * brass button easier to tell apart at a glance. --- */

  /* --- register position, 0 = paper, 1 = instrument ---
   * Written on <html> by RegisterScroll. `0.5` is the no-JS / reduced-motion resting value:
   * it makes the crossover band read as an even paper-to-black sweep with no script at all. */
  --k-t: 0.5;

  /* --- the interpolated ground. It is the MIDPOINT of `.k-crossover`'s gradient and nothing
   * else: every text-bearing surface paints a committed ground over it. `oklab` keeps the
   * sweep perceptually even instead of dipping through mud in the middle. --- */
  --k-page-ground: color-mix(
    in oklab,
    var(--k-warm-ground) calc((1 - var(--k-t)) * 100%),
    var(--k-inst-ground)
  );

  /* --- active set. Redeclared by the two [data-register] blocks below; these defaults are
   * the instrument end, because the chrome (header, footer) is pinned there. --- */
  --k-ground: var(--k-inst-ground);
  --k-surface: var(--k-inst-surface);
  --k-sunk: var(--k-inst-sunk);
  --k-ink: var(--k-inst-ink);
  --k-ink-soft: var(--k-inst-ink-soft);
  --k-rule: var(--k-inst-rule);
  --k-rule-strong: var(--k-inst-rule-strong);
  --k-accent: var(--k-inst-accent);
  --k-on-accent: var(--k-inst-on-accent);
  --k-pass: var(--k-inst-pass);
  --k-warn: var(--k-inst-warn);
  --k-fail: var(--k-inst-fail);

  /* --- type --- */
  --k-font-display:
    "Playfair Display", "Iowan Old Style", "Palatino Linotype", Palatino,
    Georgia, "Times New Roman", serif;
  --k-font-sans:
    "Plex Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  /* `ui-monospace` is deliberately NOT in this stack. It resolves to SF Mono / Cascadia Mono /
   * Menlo, every one of which is already named explicitly below it, so it adds no coverage --
   * and as the FIRST entry it is precisely the declaration this wave removed from the blog
   * generator. Leaving the keyword here would make the grep gate that bans it true on one
   * surface and false on the other, which is the shape of a gate nobody trusts. */
  --k-font-mono:
    "Spline Mono", "SFMono-Regular", "Cascadia Mono", Menlo, Consolas, monospace;

  /* --- metrics --- */
  --k-max: 1180px;
  --k-gutter: clamp(20px, 5vw, 52px);
  --k-measure: 66ch;
  --k-header-h: 64px;
  --k-section-y: clamp(56px, 8vw, 112px);
  --k-radius: 2px;
}

/* INSTRUMENT MODE. `data-theme="dark"` pins the warm end to the instrument end, so the page
 * is one cold surface top to bottom and `--k-t` becomes visually inert (both ends are equal,
 * so the interpolation resolves to the same colour at every t). Nothing else changes: the
 * toggle, both `localStorage` keys, and every glyph behave exactly as they did. */
:root[data-theme="dark"] {
  --k-warm-ground: var(--k-inst-ground);
  --k-warm-surface: var(--k-inst-surface);
  --k-warm-sunk: var(--k-inst-sunk);
  --k-warm-ink: var(--k-inst-ink);
  --k-warm-ink-soft: var(--k-inst-ink-soft);
  --k-warm-rule: var(--k-inst-rule);
  --k-warm-rule-strong: var(--k-inst-rule-strong);
  --k-warm-accent: var(--k-inst-accent);
  --k-warm-on-accent: var(--k-inst-on-accent);
  --k-warm-pass: var(--k-inst-pass);
  --k-warm-warn: var(--k-inst-warn);
  --k-warm-fail: var(--k-inst-fail);
  --k-warm-grid: var(--k-inst-grid);
}

/* The two committed palettes. A section carrying one of these attributes paints an OPAQUE
 * ground, so its text is never on an interpolated colour — this is what makes AC-3 provable
 * by measuring two palettes rather than a continuum. */
[data-register="warm"] {
  --k-ground: var(--k-warm-ground);
  --k-surface: var(--k-warm-surface);
  --k-sunk: var(--k-warm-sunk);
  --k-ink: var(--k-warm-ink);
  --k-ink-soft: var(--k-warm-ink-soft);
  --k-rule: var(--k-warm-rule);
  --k-rule-strong: var(--k-warm-rule-strong);
  --k-accent: var(--k-warm-accent);
  --k-on-accent: var(--k-warm-on-accent);
  --k-pass: var(--k-warm-pass);
  --k-warn: var(--k-warm-warn);
  --k-fail: var(--k-warm-fail);
}

[data-register="instrument"] {
  --k-ground: var(--k-inst-ground);
  --k-surface: var(--k-inst-surface);
  --k-sunk: var(--k-inst-sunk);
  --k-ink: var(--k-inst-ink);
  --k-ink-soft: var(--k-inst-ink-soft);
  --k-rule: var(--k-inst-rule);
  --k-rule-strong: var(--k-inst-rule-strong);
  --k-accent: var(--k-inst-accent);
  --k-on-accent: var(--k-inst-on-accent);
  --k-pass: var(--k-inst-pass);
  --k-warn: var(--k-inst-warn);
  --k-fail: var(--k-inst-fail);
}

/* ---------------------------------------------------------------------- base */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--k-header-h) + 20px);
}

/* A COMMITTED colour, never the interpolated one. Nothing text-bearing sits directly on the
 * body, and keeping it static means a browser without `color-mix()` still paints a correct
 * page rather than a transparent one. */
body {
  margin: 0;
  background-color: var(--k-warm-ground);
  color: var(--k-ink);
  font-family: var(--k-font-sans);
  /* RSP-001 — FLUID BODY SIZE. A locked 16px rendered the same physical text on a
     360px phone and a 2560px display, so a wide screen bought margin rather than
     legibility, and the `@media (max-width: 640px)` rule below used to make it
     worse by SHRINKING to 15.5px on the screens least able to afford it (that
     declaration is gone; the rest of the block stays).
     16px at 360px, ~19px at 1440px, capped at 19.04px. Because --k-measure is in
     ch it tracks this curve, so the reading column grows with the type instead of
     needing its own breakpoints. The floor is exactly 1rem, so a reader who has
     raised their browser's default text size never gets less than it. */
  font-size: clamp(1rem, 0.94rem + 0.28vw, 1.19rem);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

a {
  color: var(--k-ink);
  text-decoration-color: var(--k-rule-strong);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
a:hover {
  text-decoration-color: var(--k-ink);
}

:focus-visible {
  outline: 2px solid var(--k-accent);
  outline-offset: 2px;
  border-radius: var(--k-radius);
}

::selection {
  background: var(--k-ink);
  color: var(--k-ground);
}

button {
  font: inherit;
  cursor: pointer;
}

.cx-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.cx-skip-link {
  position: absolute;
  left: 12px;
  top: -48px;
  z-index: 100;
  padding: 11px 16px;
  background: var(--k-accent);
  color: var(--k-on-accent);
  border-radius: var(--k-radius);
  font-weight: 600;
  text-decoration: none;
  transition: top 0.15s ease;
}
.cx-skip-link:focus {
  top: 12px;
  color: var(--k-on-accent);
}

/* Unstyled hook carried over from the previous system; kept as a scroll target so a deep
 * link does not land under the sticky header. */
.cx-anchor-target {
  scroll-margin-top: calc(var(--k-header-h) + 24px);
}

/* ---------------------------------------------------------------- typography */

h1,
h2,
h3,
h4 {
  margin: 0;
  color: var(--k-ink);
  line-height: 1.12;
  text-wrap: balance;
}

/* The instrument voice: sans, tight, weighted. This is the default because the chrome and
 * every evaluation-tier page live here. */
h1,
h2,
h3,
h4 {
  font-family: var(--k-font-sans);
  font-weight: 600;
  letter-spacing: -0.021em;
}

h1 {
  font-size: clamp(2.05rem, 4.4vw, 3.1rem);
  letter-spacing: -0.03em;
}
h2 {
  font-size: clamp(1.55rem, 2.8vw, 2.15rem);
  letter-spacing: -0.026em;
}
h3 {
  font-size: 1.14rem;
}
h4 {
  font-size: 1rem;
}

/* The warm voice: the editorial serif, larger, looser, lower-contrast letterspacing.
 * The typeface change IS the register change — it is the first thing a reader notices and
 * the last thing that can drift out of sync with the strategy. */
[data-register="warm"] h1,
[data-register="warm"] h2,
[data-register="warm"] h3 {
  font-family: var(--k-font-display);
  font-weight: 600;
  letter-spacing: -0.012em;
  line-height: 1.06;
}

[data-register="warm"] h1 {
  font-size: clamp(2.6rem, 7vw, 5rem);
}
[data-register="warm"] h2 {
  font-size: clamp(1.9rem, 4vw, 2.9rem);
}
[data-register="warm"] h3 {
  font-size: clamp(1.25rem, 2vw, 1.55rem);
}

p {
  margin: 0;
}

/* Headline emphasis. Replaces `.cx-grad-text`, which clipped the indigo->violet->cyan
 * gradient into the type -- the single most-repeated element of the old look and, per AC-2,
 * banned outright. A ruled underline in the accent does the same job, spends far less of the
 * accent budget than filling a whole phrase, wraps correctly across lines, and survives a
 * forced-colours mode where clipped-gradient text disappears entirely. */
.k-em {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--k-accent);
  text-decoration-thickness: 3px;
  text-decoration-skip-ink: none;
  text-underline-offset: 0.14em;
}

.cx-lede {
  color: var(--k-ink-soft);
  font-size: 1.05rem;
  max-width: 60ch;
}

[data-register="warm"] .cx-lede {
  color: var(--k-ink);
  font-size: clamp(1.08rem, 1.5vw, 1.3rem);
  line-height: 1.5;
  max-width: 46ch;
}

/* Mono, uppercase, tracked — the label voice, identical in both registers so the two tiers
 * read as one system rather than two sites. */
.cx-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: var(--k-font-mono);
  font-size: 0.69rem;
  font-weight: 600;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--k-ink-soft);
}

/* THE PULSING "LIVE" DOT IS GONE, and its markup with it. It was a green light animating
 * beside a label that made no claim -- a dashboard tell, and the one thing on the old site
 * that came closest to signalling state by colour alone. The warm tier is printed and still;
 * signals belong in the instrument tier, where they are chips carrying a glyph and a word. */

/* ------------------------------------------------------------------- layout */

/* `main.cx-shell` is now FULL WIDTH and its direct children carry the measure as padding.
 * That is what lets a `[data-register]` section paint an edge-to-edge band without a single
 * markup change, and without the `100vw` full-bleed trick, which overflows by the width of
 * the scrollbar. */
main.cx-shell {
  width: 100%;
  padding-block: 0;
}

main.cx-shell > * {
  padding-inline: max(var(--k-gutter), calc((100% - var(--k-max)) / 2));
}

/* Everything that is not a full-bleed band still needs the measure -- the footer's inner row
 * is the only one today. */
.cx-shell:not(main) {
  width: 100%;
  max-width: var(--k-max);
  margin: 0 auto;
  padding-inline: var(--k-gutter);
}

/* `references.html` is the one page that puts `cx-prose` on `<main>` itself. The generic
 * `.cx-prose` measure would clamp the whole band to 66ch and pin it against the left edge, so
 * the measure is applied to the CONTENT as padding instead, which keeps the band full width.
 * It is also the one page whose `<main>` has loose children before its first `.cx-section`,
 * so it needs the leading padding those sections would otherwise have supplied. */
main.cx-prose {
  max-width: none;
  padding-block-start: var(--k-section-y);
}

main.cx-prose > * {
  padding-inline: max(var(--k-gutter), calc((100% - var(--k-measure)) / 2));
}

.cx-section {
  padding-block: var(--k-section-y);
}

/* Each band paints its own OPAQUE committed ground, plus a texture. The texture replaces the
 * old stylesheet's fixed backdrop, which layered two radial "tint" gradients in indigo and
 * cyan over the whole viewport and was the single largest source of the slop cast. Ruled
 * ledger lines on the paper tier, a square instrument grid on the cold one: both are honest
 * about what the surface is, and neither glows. */
[data-register="warm"] {
  background-color: var(--k-warm-ground);
  background-image: linear-gradient(var(--k-warm-grid) 1px, transparent 1px);
  background-size: 100% 32px;
  color: var(--k-warm-ink);
}

/* OPERATOR RULING 2026-09-05: the square grid is removed. It was two crossed gradients at
 * 34px x 72px, and it read as a checkerboard rather than as instrument ruling. The ground is
 * now flat. The warm tier keeps its single-axis ledger rules above, which are horizontal and
 * read as paper, not as a grid -- if those should go too, delete the background-image there
 * and the tier is flat as well. --k-inst-grid is deliberately left declared and unused so a
 * reinstatement is one line rather than a re-derivation. */
[data-register="instrument"] {
  background-color: var(--k-inst-ground);
  color: var(--k-inst-ink);
}

/* Hairline between adjacent sections. Two bands of DIFFERENT registers are separated by
 * `.k-crossover`, which is not a `.cx-section`, so the boundary that matters visually is the
 * one place this rule deliberately does not fire. */
.cx-section + .cx-section {
  border-top: 1px solid var(--k-rule);
}

.cx-section-head {
  display: grid;
  gap: 12px;
  margin-bottom: 34px;
}

[data-register="warm"] .cx-section-head {
  gap: 18px;
  margin-bottom: 44px;
}

.cx-grid {
  display: grid;
  gap: 18px;
}
.cx-grid.cols-2 {
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.cx-grid.cols-3 {
  grid-template-columns: repeat(auto-fit, minmax(268px, 1fr));
}
.cx-grid.cols-4 {
  grid-template-columns: repeat(auto-fit, minmax(214px, 1fr));
}

/* ------------------------------------------------------------ the crossover */

/* The register change, and the only place the interpolated page ground is visible. It is
 * transparent by design: `body`'s `--k-page-ground` and the fixed rule texture show through,
 * and `RegisterScroll` sweeps them from paper to instrument black as this band traverses the
 * viewport.
 *
 * The band is tall on purpose. `RegisterScroll` ramps `--k-t` over only the middle 40% of the
 * traverse, so the ground has already reached a committed end before either neighbour is
 * back on screen — and the neighbours' own `--k-section-y` padding is what occupies the
 * slivers at the midpoint. No character is ever painted on an in-between colour.
 *
 * Its one piece of content sits on an OPAQUE plate, which is register-independent by
 * construction and therefore contrast-safe at every value of `--k-t`. */
.k-crossover {
  display: grid;
  place-items: center;
  min-height: clamp(440px, 76vh, 780px);
  padding-block: clamp(40px, 8vh, 96px);
  /* Fallback for a browser without `color-mix()`: a static paper-to-black sweep. Correct at
   * both seams, simply not scroll-linked. */
  background: linear-gradient(
    to bottom,
    var(--k-warm-ground),
    var(--k-inst-ground)
  );
}

/* THE SCROLL-LINKED SWEEP. The gradient's END STOPS are the two committed grounds, so the top
 * seam always matches the warm section above and the bottom seam always matches the instrument
 * section below -- at every value of `--k-t`, including 0 and 1. Only the MIDPOINT moves, which
 * is what makes the band appear to cool under the plate as the visitor scrolls without ever
 * showing an edge against either neighbour. */
@supports (background: color-mix(in oklab, #000 50%, #fff)) {
  .k-crossover {
    background: linear-gradient(
      to bottom,
      var(--k-warm-ground),
      var(--k-page-ground) 50%,
      var(--k-inst-ground)
    );
  }
}

/* THE BRAND PLATE, at the exact moment the page changes register: `#2B337F` itself, with its
 * own type, one brass rule and a brass mark.
 *
 * THE MARK IS BRASS ONLY BECAUSE THE MEASUREMENT SAYS SO. At the first brass value it was
 * 3.92:1 on navy -- enough for the 3px border, which is a boundary, and short of the 4.5:1
 * that small mono type needs -- so the mark was set in `--k-navy-on` instead, and the test
 * asserted brass was BELOW the text floor precisely so nobody could "fix" it by eye. The
 * lighter brass clears it at 5.53:1, the assertion flipped, and the mark moved. That sequence
 * is the whole argument for computing these ratios rather than judging them. */
.k-crossover-plate {
  display: grid;
  gap: 10px;
  max-width: 34rem;
  padding: 22px 26px;
  background: var(--k-navy);
  color: var(--k-navy-on);
  border-left: 3px solid var(--k-inst-accent);
}

.k-crossover-plate .k-mark {
  font-family: var(--k-font-mono);
  font-size: 0.69rem;
  font-weight: 600;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--k-inst-accent);
}

.k-crossover-plate p {
  color: var(--k-navy-on);
  font-size: 1.02rem;
  line-height: 1.45;
}

/* ------------------------------------------------------------------- header */

/* THE MASTHEAD IS THE BRAND PLATE: `#2B337F`, the exact colour of the wordmark's own field, so
 * the lockup dissolves into the bar instead of sitting on it as a visible navy rectangle. It is
 * PINNED — the same colour in every register and both themes. A masthead that changed colour
 * under the visitor would be a second contrast problem for no gain; pinned, it is one committed
 * surface, it reads as a Penguin spine over the cream tier, and it sits a deliberate step
 * lighter than the ground over the instrument tier. The old header's `backdrop-filter` blur is
 * gone — frosted glass is part of the signature this epic exists to escape. */
.cx-header {
  position: sticky;
  top: 0;
  z-index: 50;
  height: var(--k-header-h);
  display: flex;
  align-items: center;
  background: var(--k-navy);
  color: var(--k-navy-on);
  border-bottom: 1px solid var(--k-navy-rule);

  --k-ink: var(--k-navy-on);
  --k-ink-soft: var(--k-navy-on-soft);
  --k-rule: var(--k-navy-rule);
  --k-rule-strong: var(--k-navy-rule-strong);
  --k-accent: var(--k-inst-accent);
  --k-surface: var(--k-navy);
  --k-ground: var(--k-navy);
}

.cx-nav {
  display: flex;
  align-items: center;
  gap: 18px;
  width: 100%;
  max-width: var(--k-max);
  margin: 0 auto;
  padding-inline: var(--k-gutter);
}

/* These read the GENERIC tokens, which `.cx-header` has already re-pointed at the brand plate.
 * That way the chrome follows its plate automatically instead of naming a palette twice. */
.cx-brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--k-ink);
  text-decoration: none;
  flex: none;
}

.cx-brand-logo {
  height: 30px;
  width: auto;
  display: block;
  flex: none;
}

.cx-nav-links {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-left: auto;
  flex-wrap: wrap;
}

.cx-nav-links a {
  padding: 8px 10px;
  font-family: var(--k-font-mono);
  font-size: 0.76rem;
  letter-spacing: 0.06em;
  color: var(--k-ink-soft);
  text-decoration: none;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
}
.cx-nav-links a:hover {
  color: var(--k-ink);
}
.cx-nav-links a[aria-current="page"] {
  color: var(--k-ink);
  border-bottom-color: var(--k-accent);
}

.cx-nav-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
}

.cx-icon-btn {
  display: inline-grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: var(--k-radius);
  border: 1px solid var(--k-rule-strong);
  background: transparent;
  color: var(--k-ink-soft);
  font-size: 0.85rem;
  text-decoration: none;
}
.cx-icon-btn:hover {
  color: var(--k-ink);
  border-color: var(--k-ink);
}

.cx-menu-btn {
  display: none;
}

/* ------------------------------------------------------------------ buttons */

.cx-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 46px;
  padding: 12px 18px;
  border-radius: var(--k-radius);
  border: 1px solid var(--k-rule-strong);
  background: transparent;
  color: var(--k-ink);
  font-size: 0.93rem;
  font-weight: 600;
  text-decoration: none;
  transition:
    border-color 0.16s ease,
    background-color 0.16s ease,
    color 0.16s ease;
}
.cx-btn:hover {
  color: var(--k-ground);
  background: var(--k-ink);
  border-color: var(--k-ink);
  text-decoration: none;
}

/* THE ONE ACCENTED ELEMENT. Section 6.2: the accent appears at most once per viewport, so
 * `.primary` is reserved for the single leading action of a screen. Solid fill, no gradient. */
.cx-btn.primary {
  background: var(--k-accent);
  border-color: var(--k-accent);
  color: var(--k-on-accent);
}
.cx-btn.primary:hover {
  color: var(--k-on-accent);
  background: var(--k-accent);
  border-color: var(--k-ink);
}

.cx-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

/* ------------------------------------------------------------------- panels */

/* Flat, ruled, square. No backdrop blur, no drop shadow, no 11px radius, no gradient rail —
 * the four properties that made the old panels read as a generic dashboard card. */
.cx-panel {
  position: relative;
  padding: 22px;
  border: 1px solid var(--k-rule);
  border-radius: var(--k-radius);
  background: var(--k-surface);
}

/* The rail is INK, not accent: eight railed panels on one page would spend the accent eight
 * times over. `.cx-panel.railed.k-lead` is the single accented exception per screen. */
.cx-panel.railed::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: var(--k-ink);
}
.cx-panel.railed {
  padding-top: 24px;
}
.cx-panel.railed.k-lead::before {
  background: var(--k-accent);
}

.cx-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}

.cx-panel h3 {
  margin-bottom: 6px;
}

.cx-panel p {
  color: var(--k-ink-soft);
  font-size: 0.94rem;
}

/* ------------------------------------------------------- lead-capture form */

/* KWR-001 W5. The form is a `.cx-panel.railed.k-lead`, so it takes the one accented rail a
 * screen is allowed -- which is correct: on pricing and pre-release it IS the leading action,
 * and it replaced the `.cx-btn.primary` that used to hold that slot.
 *
 * EVERY COLOUR HERE IS A REGISTER TOKEN, never a literal. The form appears on instrument-tier
 * pages today and will appear inside a warm band the moment the home page grows one, so a
 * hard-coded input background would be invisible in one of the two registers. Tokens make it
 * inherit whichever band it lands in for free.
 *
 * FIELD GROUND IS `--k-sunk`, NOT `--k-surface`. The panel is already `--k-surface`; an input
 * painted the same value as its own panel has no edge except its border, which is the "form
 * disappears into the card" look. Sunk reads as a well cut into the panel in both registers. */

.k-waitlist {
  margin-top: 28px;
  scroll-margin-top: calc(var(--k-header-h) + 24px);
}

.k-waitlist h2 {
  margin-bottom: 6px;
}

.k-form {
  display: grid;
  gap: 16px;
  margin-top: 20px;
}

.k-form-row {
  display: grid;
  gap: 16px;
}

/* Two-up only where there is room for it. Below this the fields stack, which is also what
 * every phone gets: two 160px inputs side by side is worse than one full-width input. */
@media (min-width: 40em) {
  .k-form-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.k-field {
  display: grid;
  gap: 6px;
  min-width: 0;
}

.k-field label {
  font-family: var(--k-font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--k-ink);
  font-weight: 600;
}

/* "required" / "optional" ride inside the label so the requirement is announced with the field
 * name, not as a separate thing a screen reader visits later. Colour is never the only cue. */
.k-field-flag {
  font-weight: 400;
  letter-spacing: 0.05em;
  color: var(--k-ink-soft);
}

.k-field-hint {
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--k-ink-soft);
}

.k-field input,
.k-field select,
.k-field textarea {
  width: 100%;
  min-height: 46px;
  padding: 11px 13px;
  border: 1px solid var(--k-rule-strong);
  border-radius: var(--k-radius);
  background: var(--k-sunk);
  color: var(--k-ink);
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.4;
  transition:
    border-color 0.16s ease,
    box-shadow 0.16s ease;
}

.k-field textarea {
  resize: vertical;
  min-height: 88px;
}

/* `color-scheme` makes the native select arrow and the caret follow the register instead of
 * staying black-on-black when the instrument end is active. */
.k-field select {
  color-scheme: light dark;
  appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position:
    calc(100% - 19px) calc(50% + 1px),
    calc(100% - 14px) calc(50% + 1px);
  background-size:
    5px 5px,
    5px 5px;
  background-repeat: no-repeat;
  padding-right: 36px;
}

.k-field input::placeholder,
.k-field textarea::placeholder {
  color: var(--k-ink-soft);
}

/* A visible, high-contrast focus ring in both registers. `--k-accent` is the only token that
 * clears 3:1 against both the sunk grounds AND the panel, which is what WCAG 2.4.11 wants of a
 * focus indicator. The inner shadow doubles its apparent weight without moving layout. */
.k-field input:focus-visible,
.k-field select:focus-visible,
.k-field textarea:focus-visible {
  outline: 2px solid var(--k-accent);
  outline-offset: 1px;
  border-color: var(--k-accent);
}

.k-field input:disabled,
.k-field select:disabled,
.k-field textarea:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.k-form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
}

.k-form-status {
  margin: 0;
  min-width: 0;
}

.k-form-status:empty {
  display: none;
}

.k-form-note {
  margin: 0;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--k-ink-soft);
}

.k-form-note a {
  color: var(--k-ink);
}

/* THE HONEYPOT. Off-screen, not `display: none` and not `visibility: hidden` -- a competent
 * form-filling bot skips fields with either of those, which would defeat the point. One
 * absolutely-positioned pixel outside the viewport is invisible to a human, invisible to a
 * screen reader (the wrapper carries `aria-hidden`), and perfectly fillable by a bot. */
.k-honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* THE FORM-SHIELD CHALLENGE WIDGET (TRF-001 US-019). Cloudflare Turnstile renders into this box
 * once its script loads; reserving its minimum footprint up front avoids a layout jump when the
 * widget appears. An unconfigured build (no site key) never shows an empty box -- there is
 * nothing to render into it, so it collapses out of the form entirely. */
.k-challenge {
  min-height: 65px;
}
.k-challenge[data-shield-outcome="unconfigured"] {
  display: none;
}

/* ---------------------------------------------------------- measured values */

.cx-measure {
  display: grid;
  gap: 4px;
}

.cx-measure-value {
  font-family: var(--k-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.5rem, 2.8vw, 1.95rem);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.05;
  color: var(--k-ink);
}

.cx-measure-unit {
  font-size: 0.6em;
  font-weight: 400;
  color: var(--k-ink-soft);
  margin-left: 4px;
}

.cx-measure-label {
  font-size: 0.79rem;
  line-height: 1.4;
  color: var(--k-ink-soft);
}

.cx-measures,
.mini-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(178px, 1fr));
  gap: 18px 22px;
}

.cx-measures.pairs {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* --------------------------------------------------------- provenance stamp */

/* D-10: number provenance is a visible design element, not fine print. It keeps the dashed
 * rule and the mono uppercase voice, because that is what makes it read as a stamp on the
 * page rather than a caption under it. */
.cx-stamp {
  display: block;
  margin-top: 16px;
  padding-top: 10px;
  border-top: 1px dashed var(--k-rule-strong);
  font-family: var(--k-font-mono);
  font-size: 0.66rem;
  line-height: 1.6;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--k-ink-soft);
  overflow-wrap: anywhere;
}

.cx-stamp .k {
  color: var(--k-ink);
  font-weight: 600;
}

.cx-stamp a {
  color: var(--k-ink);
}

/* Code identifiers keep their real casing inside uppercased mono contexts. */
.cx-stamp code,
.cx-status code,
.cx-chip code,
.cx-table caption code,
.cx-evidence figcaption code {
  text-transform: none;
  font-size: 1em;
  padding: 0 3px;
}

/* -------------------------------------------------------------- claim chips */

/* D-10, and the property that must survive from the old system: state is GLYPH + WORD, never
 * colour alone. The glyph is generated from `data-glyph` and the word is the element's own
 * text, so a colour-blind or monochrome reader loses nothing.
 *
 * Restyled from pill to stamp: square corners, hairline border in the state colour, no fill.
 * The four capability labels (live / in flight / built-but-gated / planned) and the four
 * number kinds (measured / modeled / operator-reported / planning assumption) get their own
 * `data-state` values so `scripts/check-lang-parity.ts` can match them position-for-position
 * across an EN/ES pair. */
.cx-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border-radius: var(--k-radius);
  border: 1px solid var(--k-rule-strong);
  background: transparent;
  font-family: var(--k-font-mono);
  font-size: 0.655rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--k-ink-soft);
}
.cx-status::before {
  content: attr(data-glyph);
  font-size: 0.9em;
}

/* `--k-pass` is reserved EXCLUSIVELY for real pass states (section 6.2). */
.cx-status[data-state="pass"],
.cx-status[data-state="live"] {
  color: var(--k-pass);
  border-color: var(--k-pass);
}
.cx-status[data-state="warn"],
.cx-status[data-state="in-flight"] {
  color: var(--k-warn);
  border-color: var(--k-warn);
}
.cx-status[data-state="fail"],
.cx-status[data-state="evidence-invalid"] {
  color: var(--k-fail);
  border-color: var(--k-fail);
}
.cx-status[data-state="skipped"],
.cx-status[data-state="tool-missing"],
.cx-status[data-state="infra-error"],
.cx-status[data-state="planned"] {
  color: var(--k-ink-soft);
  border-color: var(--k-rule-strong);
}
.cx-status[data-state="gated"] {
  color: var(--k-ink);
  border-color: var(--k-ink);
  border-style: dashed;
}
.cx-status[data-state="signal"],
.cx-status[data-state="measured"],
.cx-status[data-state="modeled"],
.cx-status[data-state="operator-reported"],
.cx-status[data-state="assumption"] {
  color: var(--k-ink);
  border-color: var(--k-rule-strong);
}

.cx-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--k-radius);
  border: 1px solid var(--k-rule-strong);
  background: transparent;
  font-family: var(--k-font-mono);
  font-size: 0.655rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--k-ink-soft);
}

/* The status disclosure required on every public surface by the copy stance. It is a
 * first-class element, not a footnote. */
.k-disclosure {
  display: block;
  max-width: 56ch;
  padding-left: 12px;
  border-left: 2px solid var(--k-rule-strong);
  font-family: var(--k-font-mono);
  font-size: 0.7rem;
  line-height: 1.6;
  color: var(--k-ink-soft);
}

/* ------------------------------------------------------------ evidence block */

.cx-evidence {
  margin: 0;
  border: 1px solid var(--k-rule);
  border-radius: var(--k-radius);
  background: var(--k-sunk);
  overflow: hidden;
}

.cx-evidence figcaption {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 13px;
  border-bottom: 1px solid var(--k-rule);
  font-family: var(--k-font-mono);
  font-size: 0.655rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--k-ink-soft);
}

.cx-evidence pre {
  margin: 0;
  padding: 14px;
  overflow-x: auto;
  font-family: var(--k-font-mono);
  font-size: 0.8rem;
  line-height: 1.65;
  color: var(--k-ink);
  tab-size: 2;
}

/* Syntax colour is deliberately minimal: weight and the ink-soft tone carry it, so the
 * accent budget is not spent inside a code block. */
.cx-evidence .kw {
  color: var(--k-ink);
  font-weight: 600;
}
.cx-evidence .str {
  color: var(--k-accent);
}
.cx-evidence .cmt {
  color: var(--k-ink-soft);
}

code {
  font-family: var(--k-font-mono);
  font-size: 0.9em;
  padding: 1px 5px;
  border-radius: var(--k-radius);
  background: var(--k-sunk);
  border: 1px solid var(--k-rule);
}

/* ------------------------------------------------------------ readout table */

.cx-table-wrap {
  border: 1px solid var(--k-rule);
  border-radius: var(--k-radius);
  background: var(--k-surface);
  overflow-x: auto;
}

.cx-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.cx-table caption {
  padding: 13px 16px;
  text-align: left;
  border-bottom: 1px solid var(--k-rule);
  font-family: var(--k-font-mono);
  font-size: 0.655rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--k-ink-soft);
}

.cx-table th,
.cx-table td {
  padding: 12px 16px;
  text-align: left;
  vertical-align: top;
  border-top: 1px solid var(--k-rule);
}

.cx-table thead th {
  border-top: 0;
  font-family: var(--k-font-mono);
  font-size: 0.645rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--k-ink-soft);
  background: var(--k-sunk);
  white-space: nowrap;
}

.cx-table tbody th {
  font-weight: 600;
  color: var(--k-ink);
}

.cx-table td.num,
.cx-table th.num {
  font-family: var(--k-font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}

.cx-table tbody tr:hover {
  background: var(--k-sunk);
}

.cx-table td small {
  display: block;
  margin-top: 4px;
  color: var(--k-ink-soft);
  font-size: 0.83em;
}

/* -------------------------------------------------------------- step ladder */

.cx-ladder {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: k-step;
}

.cx-ladder > li {
  position: relative;
  counter-increment: k-step;
  display: grid;
  grid-template-columns: 46px 1fr;
  gap: 14px;
  padding: 16px 0;
  border-top: 1px solid var(--k-rule);
}
.cx-ladder > li:first-child {
  border-top: 0;
}

.cx-ladder > li::before {
  content: counter(k-step, decimal-leading-zero);
  font-family: var(--k-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--k-ink-soft);
  padding-top: 2px;
}

.cx-ladder h4 {
  margin-bottom: 3px;
}

.cx-ladder p {
  color: var(--k-ink-soft);
  font-size: 0.92rem;
}

/* ---------------------------------------------------------------- fact list */

.cx-facts {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
  display: grid;
  gap: 12px;
}

.cx-facts > li {
  display: grid;
  grid-template-columns: 14px 1fr;
  gap: 10px;
  font-size: 0.92rem;
}

.cx-facts > li::before {
  content: "";
  width: 6px;
  height: 6px;
  margin-top: 0.55em;
  background: var(--k-ink);
}

.cx-facts > li > strong,
.cx-facts > li > span {
  grid-column: 2;
}

.cx-facts strong {
  font-weight: 600;
}

.cx-facts span {
  color: var(--k-ink-soft);
  font-size: 0.9rem;
}

/* ------------------------------------------------------------- warm hero */

/* One column, one idea. The old hero was a two-column grid with the headline on the left and
 * a "Brain readout" panel of DuckDB table counts on the right -- which put a raw
 * `project_id conexus` chip second in the visitor's reading order and broke the copy stance's
 * own rule against leading with infrastructure before the outcome (KWR-001 section 2.2). The
 * readout is unchanged; it now opens the evidence layer, where it belongs.
 *
 * IT IS ALSO THE SCROLL WORLD'S STAGE (W3). `position: relative` plus the three layers below
 * are the mount contract: `.k-hero-media` is an empty full-bleed box for W3's video or canvas,
 * `.k-hero-scrim` guarantees the copy column keeps an opaque committed ground, and
 * `.k-hero-content` sits above both. With the media layer empty -- which is the state that can
 * be verified without a build -- the hero renders exactly as a plain cream hero, so nothing
 * here depends on W3 landing, and a clip that fails to load degrades to the static hero rather
 * than to a hole. */
.k-hero {
  position: relative;
  display: grid;
  align-content: center;
  min-height: clamp(560px, 78vh, 900px);
  padding-block: clamp(64px, 12vh, 148px) clamp(52px, 9vh, 116px);
  overflow: hidden;
}

/* The hero drops the shared inline padding so the media layer can reach the viewport edges.
 * An absolutely positioned child resolves `inset: 0` against its containing block's PADDING
 * box, so leaving the generic `main.cx-shell > *` padding in place would inset the scroll
 * world by a gutter on each side and it would not be full-bleed at all. The measure moves to
 * `.k-hero-content`, which is where the reading column belongs anyway. */
main.cx-shell > .k-hero {
  padding-inline: 0;
}

/* W3 mounts into this. Deliberately empty, with no background of its own. */
.k-hero-media {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.k-hero-media > video,
.k-hero-media > canvas,
.k-hero-media > img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* THE LEGIBILITY CONTRACT, and the reason the contrast test still means something once video
 * is playing behind the type. The copy column sits inside the FULLY OPAQUE zone of this scrim
 * -- solid `--k-warm-ground` out to 54% of the viewport, only then falling away -- so the
 * backdrop behind every character is the committed ground, exactly the value
 * `tests/contrast.test.mjs` measures, and never a frame of video. The world gets the right of
 * the frame and everything behind the fade; the message keeps the left. Text is never placed
 * in the transparent zone. If a later wave wants copy over open video, it needs an opaque
 * plate, not a softer scrim -- a scrim you can see through is a scrim you cannot measure. */
.k-hero-scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(
    to right,
    var(--k-warm-ground) 0 54%,
    transparent 88%
  );
}

.k-hero-content {
  position: relative;
  z-index: 2;
  justify-self: center;
  width: 100%;
  max-width: var(--k-max);
  padding-inline: var(--k-gutter);
  display: grid;
  gap: clamp(20px, 3vw, 30px);
  justify-items: start;
}

/* BELOW 900px THE WORLD STOPS, and this is a contract with W3 rather than a styling choice.
 * The copy stack is roughly 500px tall; 54% of a phone viewport is not enough opaque ground to
 * hold it, and a vertical scrim would put the last two elements over open video. So the media
 * layer is removed from the box tree entirely and the scrim goes solid. W3 should gate its
 * fetch on the same breakpoint: a clip downloaded here can never be seen. */
@media (max-width: 900px) {
  .k-hero-media {
    display: none;
  }
  .k-hero-scrim {
    background: var(--k-warm-ground);
  }
  .k-hero {
    min-height: 0;
  }
}

.k-hero h1 {
  max-width: 16ch;
}

/* The commissioned wordmark ships with its own solid field, so the artwork IS the plate --
 * no background, radius or shadow of ours behind it. It is small here because the headline,
 * not the logo, is what has to earn the next thirty seconds. */
.cx-hero-wordmark {
  display: block;
  width: 100%;
  max-width: 244px;
  height: auto;
}

/* -------------------------------------------------------------- the legend */

/* D-10 made visible: the four capability labels and the four number kinds, shown as a key
 * rather than described in prose. It is the first thing under the crossover because the
 * crossover has just promised that everything below carries one. */
.k-legend {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: grid;
  gap: 13px;
}

/* `justify-items: start` matters: a chip is `inline-flex`, and a grid item stretches by
 * default, so without it every label would be padded out to the full 11.5rem key column and
 * "live" would render as wide as "operator-reported". The column stays fixed so the
 * descriptions align; the chips size to their own text. */
.k-legend > li {
  display: grid;
  grid-template-columns: 11.5rem 1fr;
  justify-items: start;
  gap: 12px;
  align-items: baseline;
  font-size: 0.9rem;
  color: var(--k-ink-soft);
}

@media (max-width: 560px) {
  .k-legend > li {
    grid-template-columns: 1fr;
    gap: 5px;
  }
}

/* ------------------------------------------------------------ scroll world */

/* The W3 scene chain's band. Empty in the server-rendered HTML and empty whenever the world
 * does not run, so it must generate no box of its own until the engine fills it -- an empty
 * band with a min-height would be a blank screen between the hero and the beats on every
 * phone, with reduced motion, and on any failed mount.
 *
 * `engine-config.json` budgets 3.40 viewport-heights for three scenes and the engine manages
 * its own internal height, so nothing here sizes it. Full-bleed for the same reason the hero
 * is: the world reaches the viewport edges. */
.k-world:empty {
  display: none;
}

main.cx-shell > .k-world {
  padding-inline: 0;
}

/* --------------------------------------------------------- the warm beats */

/* One idea per screen: three short statements, serif, generously spaced, no card, no icon.
 * This is the warm tier's only content block besides the hero. */
.k-beats {
  display: grid;
  gap: clamp(34px, 5vw, 58px);
  counter-reset: k-beat;
}

.k-beats > li {
  counter-increment: k-beat;
  display: grid;
  justify-items: start;
  gap: 12px;
  max-width: 44ch;
  padding-top: 20px;
  border-top: 1px solid var(--k-rule-strong);
}

.k-beats > li::before {
  content: counter(k-beat, decimal-leading-zero);
  font-family: var(--k-font-mono);
  font-size: 0.69rem;
  font-weight: 600;
  letter-spacing: 0.17em;
  color: var(--k-ink-soft);
}

.k-beats p {
  color: var(--k-ink-soft);
  font-size: 1.02rem;
  line-height: 1.55;
}

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

/* --------------------------------------------------------------- long prose */

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

.cx-prose > * + * {
  margin-top: 1.05em;
}

.cx-prose h2 {
  margin-top: 2em;
  padding-top: 0.7em;
  border-top: 1px solid var(--k-rule);
}

.cx-prose h3 {
  margin-top: 1.7em;
  font-size: 1.08rem;
}

.cx-prose p,
.cx-prose li {
  color: var(--k-ink);
}

.cx-prose ul,
.cx-prose ol {
  padding-left: 1.25em;
}

.cx-prose li + li {
  margin-top: 0.4em;
}

.cx-prose blockquote {
  margin: 1.4em 0;
  padding: 2px 0 2px 18px;
  border-left: 2px solid var(--k-accent);
  color: var(--k-ink-soft);
  font-size: 1.02rem;
}

.cx-prose .cx-stamp {
  border-top-style: solid;
}

.cx-prose img {
  height: auto;
  border-radius: var(--k-radius);
}

/* ------------------------------------------------------------------- footer */

/* The brand plate again, closing the page the way the masthead opens it: every page is bound
 * top and bottom in `#2B337F`. It is also the legal and evidence end of the page, so pinning
 * it to one committed surface is one fewer thing to measure. */
.cx-footer {
  padding-block: 40px 52px;
  background: var(--k-navy);
  border-top: 1px solid var(--k-navy-rule);
  color: var(--k-navy-on-soft);
  font-size: 0.85rem;

  --k-ink: var(--k-navy-on);
  --k-ink-soft: var(--k-navy-on-soft);
  --k-rule: var(--k-navy-rule);
  --k-rule-strong: var(--k-navy-rule-strong);
  --k-ground: var(--k-navy);
}

.cx-footer .row {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 22px;
  align-items: flex-start;
  justify-content: space-between;
}

.cx-footer a {
  color: var(--k-ink-soft);
  text-decoration: none;
}
.cx-footer a:hover {
  color: var(--k-ink);
  text-decoration: underline;
}

.cx-footer nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
}

.k-footer-brand {
  display: grid;
  gap: 12px;
  max-width: 46ch;
}

.k-footer-descriptor {
  color: var(--k-ink);
  font-size: 0.9rem;
}

/* Legal-entity line (2026-09-06): names the LLC behind the product on every public page. */
.k-footer-entity {
  color: var(--k-ink-soft);
  font-size: 0.8rem;
}

/* -------------------------------------------------------------- PDF viewer */

.cx-viewer {
  margin-top: 22px;
  border: 1px solid var(--k-rule);
  border-radius: var(--k-radius);
  background: var(--k-surface);
  overflow: hidden;
}

.cx-viewer iframe,
.cx-viewer embed,
.cx-viewer object {
  display: block;
  width: 100%;
  /* RSP-001 — FILL THE WINDOW, MINUS THE CHROME THAT IS ACTUALLY THERE.
     `82vh` was a guess at "most of the screen" that ignored the 64px sticky
     masthead and the page's own heading block, so the viewer overflowed the fold
     on a laptop and still had to be scrolled to. Subtracting the masthead makes
     it fit by construction at any height.
     dvh, not vh: on a phone `vh` is the viewport with the browser's URL bar
     RETRACTED, so a 100vh-based box is taller than the window on first paint and
     the bottom of every PDF page sits under the toolbar. dvh tracks the live
     viewport. `svh` is the fallback for engines without dvh; plain vh is the last
     resort, and each later declaration simply overrides if supported. */
  height: min(82vh, 1080px);
  height: min(calc(100svh - var(--k-header-h) - 88px), 1080px);
  height: min(calc(100dvh - var(--k-header-h) - 88px), 1080px);
  /* A PDF rendered into a very short box is unreadable, so below this the answer
     is to scroll the page, not to shrink the document further. */
  min-height: 420px;
  border: 0;
}

/* RSP-001 — the honest mobile answer for an embedded PDF.
 *
 * iOS Safari does not render a PDF inside an iframe at all: it paints the first
 * page as a static, un-scrollable thumbnail, or nothing. Several Android browsers
 * do the same. On those engines the viewer is not a degraded reading experience,
 * it is an empty box the reader cannot do anything with — and it is the tallest
 * element on the page.
 *
 * So on small screens the frame is not given most of the window. It keeps a
 * preview-sized box, and the "Download PDF" control that the page already renders
 * above it becomes the primary path. That control is a real link to the file, so
 * this needs no JavaScript and no markup change. */
@media (max-width: 700px) {
  .cx-viewer iframe,
  .cx-viewer embed,
  .cx-viewer object {
    height: min(58dvh, 460px);
    min-height: 300px;
  }
}

/* ------------------------------------------------------- evaluation pages */

main.cx-shell > h1,
main.cx-shell > .cx-eyebrow,
main.cx-shell > .cx-lede {
  display: block;
}

main.cx-shell > h1 {
  margin-bottom: 14px;
}

main.cx-shell > .cx-lede {
  margin-bottom: 24px;
}

main.cx-shell details {
  border-top: 1px solid var(--k-rule);
  padding: 16px 0;
}

main.cx-shell details:last-child {
  border-bottom: 1px solid var(--k-rule);
}

main.cx-shell summary {
  cursor: pointer;
  font-weight: 600;
}

/* --- pricing --- */

.tier-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(248px, 1fr));
  gap: 18px;
  padding: 0;
  margin: 28px 0 18px;
  list-style: none;
}

.tier-card {
  display: grid;
  align-content: start;
  gap: 12px;
}

.layer-number,
.price,
.price-note {
  font-family: var(--k-font-mono);
  font-variant-numeric: tabular-nums;
}

.layer-number {
  color: var(--k-ink-soft);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.tier-band,
.price-note,
.tier-card s,
.detail {
  color: var(--k-ink-soft);
}

.price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 10px;
  margin: 6px 0 0;
}

.price strong {
  color: var(--k-ink);
  font-size: 2rem;
  letter-spacing: -0.03em;
}

.price-note {
  font-size: 0.76rem;
}

.feature-list,
.status-list {
  display: grid;
  gap: 12px;
  padding: 0;
  margin: 8px 0;
  list-style: none;
}

.feature-list li,
.status-list li {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 9px;
}

.feature-list li > span:last-child,
.status-list li > span:last-child {
  display: grid;
  gap: 2px;
}

.feature-list li > span:last-child > span,
.detail {
  color: var(--k-ink-soft);
  font-size: 0.9rem;
}

.check {
  color: var(--k-pass);
  font-family: var(--k-font-mono);
  font-weight: 600;
}

.overage-note {
  margin-top: 18px;
}

.evidence-panel,
.prerelease-band,
.cta-card {
  display: grid;
  gap: 12px;
}

.prerelease-band {
  grid-template-columns: 1fr auto;
  align-items: center;
}

.founding {
  justify-self: start;
}

/* --- pre-release --- */

.state-grid,
.evidence-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(248px, 1fr));
  gap: 18px;
  margin-top: 22px;
}

.state-card,
.evidence-card {
  display: grid;
  align-content: start;
  gap: 10px;
}

.evidence-card {
  color: inherit;
  text-decoration: none;
}

.evidence-card:hover {
  color: inherit;
  border-color: var(--k-ink);
}

/* --- references --- */

.scope-note {
  max-width: var(--k-measure);
  margin: 20px 0 4px;
  padding: 12px 16px;
  border-left: 2px solid var(--k-accent);
  background: var(--k-sunk);
  color: var(--k-ink-soft);
}

.source-list {
  display: grid;
  gap: 14px;
  padding-left: 2.2rem;
  max-width: var(--k-measure);
}

.source-list li {
  padding-left: 6px;
}

.ref-number {
  color: var(--k-ink-soft);
  font-family: var(--k-font-mono);
  font-variant-numeric: tabular-nums;
}

.source-note {
  display: block;
  margin-top: 4px;
  color: var(--k-ink-soft);
  font-size: 0.9rem;
}

.pulse {
  width: 6px;
  height: 6px;
  display: block;
  border-radius: 50%;
  background: var(--k-warn);
}

/* ------------------------------------------------------------- white paper */

/* `/whitepaper.html` and `/es/whitepaper.html` are the last two pages that were
 * still wearing the dashboard skin. They are GENERATED, not routed:
 * admin-dashboard's `scripts/publish-whitepaper-html.py` emits the whole shell as
 * an f-string and `scripts/stage-public.mjs` copies the result into `public/`.
 * W6 (US-605) pointed that shell's stylesheet link here, which is why this
 * section exists — every class below is one the Python renderer emits and
 * `conexus.css` used to style. D-18 keeps the renderer retargeted rather than
 * ported to React, so its markup is a fixed input and these rules meet it where
 * it is rather than the other way round.
 *
 * THE REGISTER GOES ON `<body>`, NOT ON `<main>`. Every other evaluation-tier
 * page is a route whose `SiteShell` puts `data-register="instrument"` on
 * `<main>`, because a route has nothing outside it. This document has a hero
 * SECTION between the header and main, so pinning main would leave the hero
 * resolving `:root`'s instrument ink against `body`'s cream ground — white on
 * cream, on the default theme, above the fold. Pinning the body is also the
 * honest description: a 97-reference white paper is evaluation tier from its
 * first line and never runs warm. */

.hero {
  max-width: var(--k-max);
  margin: 0 auto;
  padding-inline: var(--k-gutter);
  padding-block: var(--k-section-y) 44px;
}

.hero h1 {
  max-width: 22ch;
  margin: 8px 0 20px;
}

.edition {
  font-family: var(--k-font-mono);
  font-size: 0.69rem;
  font-weight: 600;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--k-ink-soft);
}

.metadata {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 24px;
  margin: 0 0 18px;
}

.metadata div {
  display: grid;
  gap: 2px;
}

.metadata dt {
  font-family: var(--k-font-mono);
  font-size: 0.655rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--k-ink-soft);
}

/* Tabular figures on the dates, which is the whole reason the metadata row is
 * set in the mono in the first place. */
.metadata dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.thesis {
  max-width: 62ch;
  font-size: 1.05rem;
  color: var(--k-ink);
}

.status {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 14px;
  font-family: var(--k-font-mono);
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--k-ink-soft);
}

.cx-contents-link {
  margin-top: 22px;
}

/* --- the two-column reading layout --- */

/* `:has()` rather than a class, because the class list on `<main>` is the
 * renderer's and this stylesheet does not get to add to it. The rail and the
 * article are siblings inside one `<main>` — a structure the renderer's own test
 * suite pins — so the container can be selected by what it contains. */
main.cx-shell:has(> .evidence-rail) {
  display: grid;
  grid-template-columns: minmax(210px, 260px) minmax(0, 1fr);
  gap: clamp(24px, 5vw, 68px);
  align-items: start;
  /* RSP-001 — the ONE surface that is deliberately wider than --k-max (1180px).
     The white paper is the site's only two-column document: a 260px evidence rail
     sits beside the article, so --k-max would leave the article
     1180 - 96 gutter - 260 rail - 68 gap = 756px, NARROWER than the 800px the
     page has today. Every other page is single-column and 1180px is right for it.
     1320px keeps the rail whole and gives the article ~896px, which is what the
     tables and code blocks below then have to work with. Measured at 1440px: the
     shell goes from 85% of the viewport to 92%. */
  max-width: 1320px;
  margin: 0 auto;
  padding: var(--k-section-y) var(--k-gutter) clamp(56px, 8vw, 104px);
}

/* The generic `main.cx-shell > *` rule pads every child by half the viewport to
 * carry the measure. Here the measure is on the grid container, so a child that
 * kept that padding would be inset to nothing inside its own column. */
main.cx-shell:has(> .evidence-rail) > * {
  padding-inline: 0;
}

/* Placement is explicit because auto-placement gets it wrong: `<main>` has four
 * children (rail, article, limitations, references) and a two-column flow would
 * drop `limitations` into column one, underneath the rail. */
main.cx-shell:has(> .evidence-rail) > .evidence-rail {
  grid-column: 1;
  grid-row: 1;
}

main.cx-shell:has(> .evidence-rail) > *:not(.evidence-rail) {
  grid-column: 2;
}

/* RSP-001 — the measure moves OFF the container and ONTO the prose.
 *
 * `#article { max-width: var(--k-measure) }` caps the whole element, so every
 * descendant inherits the reading width whether or not it is reading matter — a
 * `.table-scroll` set to `max-width: 100%` resolves to 100% of 754px and scrolls
 * sideways inside it while ~140px of the article's own column sits empty beside
 * it. Releasing the container and capping the text below is what lets the three
 * data tables actually use the width this change buys them.
 *
 * The ID in the original rule is why this needs the full descendant path: it has
 * to outrank `#article` on specificity to release it. */
main.cx-shell:has(> .evidence-rail) > #article,
main.cx-shell:has(> .evidence-rail) > section {
  max-width: none;
}

/* RSP-001 — PROSE KEEPS A MEASURE; DATA USES THE WHOLE COLUMN.
 *
 * Measured on the live white paper before this change: 92-107 characters a line,
 * at an 800px column and a locked 16px. The readability band is 45-75, so the
 * paper's densest sections were hardest to read on the widest screens — the exact
 * inversion of what a wide column is meant to buy. Widening the article column
 * without this would have made that worse, not better.
 *
 * So the column gets wider and the PROSE does not: text caps at --k-measure and
 * the extra width goes to the things that were genuinely cramped — the three
 * `.table-scroll` blocks, code, and figures, which now use the full ~896px rather
 * than being squeezed into the reading measure and scrolled sideways.
 *
 * Scoped to the two-column document so no other page's layout is touched. */
main.cx-shell:has(> .evidence-rail)
  :is(article, section)
  :is(p, ul, ol, dl, blockquote, h2, h3, h4) {
  max-width: var(--k-measure);
}

main.cx-shell:has(> .evidence-rail)
  :is(article, section)
  :is(.table-scroll, .cx-table-wrap, pre, figure, img) {
  max-width: 100%;
}

.evidence-rail {
  position: sticky;
  top: calc(var(--k-header-h) + 18px);
  padding: 16px;
  border: 1px solid var(--k-rule);
  border-radius: var(--k-radius);
  background: var(--k-surface);
}

.evidence-rail summary {
  cursor: pointer;
  font-family: var(--k-font-mono);
  font-size: 0.655rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--k-ink);
}

/* The four proof classes. Mono because they ARE the evidence key — the same
 * voice the claim chips and provenance stamps use everywhere else. */
.evidence-rail p {
  margin: 8px 0;
  font-family: var(--k-font-mono);
  font-size: 0.68rem;
  line-height: 1.5;
  color: var(--k-ink-soft);
}

.toc {
  margin-top: 16px;
  font-size: 0.92rem;
}

.toc ol {
  display: grid;
  gap: 6px;
  margin: 0;
  padding-left: 18px;
}

.toc a {
  color: var(--k-ink-soft);
  text-decoration: none;
}

.toc a:hover {
  color: var(--k-ink);
  text-decoration: underline;
}

.toc-level-3 {
  font-size: 0.88rem;
}

/* --- the document --- */

/* `.cx-prose`'s rules key off a class on the element that OWNS the prose, and
 * the renderer puts none on `<article>`. The id is what it does emit, and it is
 * unique by construction — `validate_document` fails the render on a duplicate
 * id — so it is a safe hook. */
#article {
  max-width: var(--k-measure);
}

#article > * + * {
  margin-top: 1.05em;
}

#article h2 {
  margin-top: 2em;
  padding-top: 0.7em;
  border-top: 1px solid var(--k-rule);
}

#article h3 {
  margin-top: 1.7em;
  font-size: 1.08rem;
}

#article p,
#article li {
  color: var(--k-ink);
}

#article ul,
#article ol {
  padding-left: 1.25em;
}

#article li + li {
  margin-top: 0.4em;
}

#article blockquote {
  margin: 1.4em 0;
  padding: 2px 0 2px 18px;
  border-left: 2px solid var(--k-accent);
  color: var(--k-ink-soft);
  font-size: 1.02rem;
}

/* Every heading is its own deep link. It must not look like body copy that
 * happens to be blue, so it inherits completely and the affordance is the
 * cursor plus the focus ring. */
.heading-link {
  color: inherit;
  text-decoration: none;
}

/* 105 of these, inline in running text. Mono and tabular so `[1]` and `[97]`
 * occupy predictable width, brass underline so a citation reads as a marker
 * rather than as emphasis. */
.citation {
  font-family: var(--k-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.8em;
  color: var(--k-ink);
  text-decoration-color: var(--k-accent);
}

/* --- the scrollable tables --- */

/* The renderer wraps wide tables in a focusable container with a visible hint,
 * which is a keyboard-scrolling affordance, not decoration — so the hint keeps
 * the label voice rather than being hidden. */
.table-scroll {
  overflow-x: auto;
  max-width: 100%;
  margin: 1.2em 0;
  border: 1px solid var(--k-rule);
  border-radius: var(--k-radius);
  background: var(--k-surface);
}

.table-scroll-hint {
  margin: 0;
  padding: 9px 13px;
  border-bottom: 1px solid var(--k-rule);
  font-family: var(--k-font-mono);
  font-size: 0.655rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--k-ink-soft);
}

.table-scroll table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.table-scroll th,
.table-scroll td {
  padding: 12px 16px;
  text-align: left;
  vertical-align: top;
  border-top: 1px solid var(--k-rule);
}

.table-scroll thead th {
  border-top: 0;
  background: var(--k-sunk);
  white-space: nowrap;
  font-family: var(--k-font-mono);
  font-size: 0.645rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--k-ink-soft);
}

/* --- the reference list --- */

#references ol {
  display: grid;
  gap: 14px;
  margin: 22px 0 0;
  padding-left: 2.2rem;
  max-width: var(--k-measure);
}

#references li {
  padding-left: 6px;
  color: var(--k-ink-soft);
  font-size: 0.92rem;
}

#references li::marker {
  font-family: var(--k-font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--k-ink-soft);
}

/* The rail stops being a rail. A 210px sticky column beside a 66ch measure needs
 * roughly 900px to exist at all; below that the grid collapses and the contents
 * become a `<details>` above the article, which is the shape it already has in
 * markup. `position: static` matters as much as `display: block` — a sticky
 * element in normal flow would pin the whole table of contents to the top of a
 * phone viewport for the length of a 97-reference document. */
@media (max-width: 900px) {
  main.cx-shell:has(> .evidence-rail) {
    display: block;
    padding-inline: var(--k-gutter);
  }

  .evidence-rail {
    position: static;
    margin-bottom: 28px;
  }
}

/* --------------------------------------------------------------- responsive */

/* ESP-001 D-I: the breakpoint is 1180px, not 1080px, because the language switcher is a
 * fourth icon button and the nav wrapped to a second row outside the header without the
 * headroom. */
@media (max-width: 1180px) {
  .cx-nav-links {
    display: none;
  }
  .cx-nav-links.open {
    display: grid;
    position: absolute;
    top: var(--k-header-h);
    left: 0;
    right: 0;
    gap: 0;
    padding: 8px var(--k-gutter) 16px;
    /* The drop panel is the masthead extended, so it uses the header's own plate. */
    background: var(--k-navy);
    border-bottom: 1px solid var(--k-navy-rule);
  }
  .cx-nav-links.open a {
    min-height: 46px;
    display: flex;
    align-items: center;
    border-bottom: 1px solid var(--k-navy-rule);
  }
  .cx-menu-btn {
    display: inline-grid;
  }
  .cx-nav-actions {
    margin-left: auto;
  }
}

@media (max-width: 720px) {
  .prerelease-band {
    grid-template-columns: 1fr;
  }
  .cx-measures.pairs {
    grid-template-columns: 1fr;
  }
  .k-crossover {
    min-height: clamp(360px, 62vh, 560px);
  }
}

@media (max-width: 640px) {
  /* RSP-001: `body { font-size: 15.5px }` was here. It made small screens smaller,
     which is backwards; body is a clamp now and scales the right way round. The
     density relief below is genuine and stays. */
  .cx-panel {
    padding: 17px;
  }
  .cx-table th,
  .cx-table td {
    padding: 11px 13px;
  }
}

/* --------------------------------------------------------------- motion */

/* The register cool is scroll-linked token interpolation, so honouring reduced motion means
 * `RegisterScroll` never installs a scroll listener at all and `--k-t` keeps its `0.5`
 * resting value: the crossover reads as a still paper-to-black sweep and nothing moves. */
@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;
  }
}

body.cx-reduce-motion *,
body.cx-reduce-motion *::before,
body.cx-reduce-motion *::after {
  animation-duration: 0.001ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.001ms !important;
}

/* ---------------------------------------------------------------- print */

@media print {
  .cx-header,
  .cx-skip-link,
  .k-crossover {
    display: none !important;
  }
  body,
  [data-register="warm"],
  [data-register="instrument"] {
    background: #fff !important;
    color: #000 !important;
  }
  .cx-panel,
  .cx-table-wrap,
  .cx-evidence {
    border-color: #999;
    background: #fff;
  }
}
