/* =====================================================================
   redlines.css — shared design system for "Architecture before enforcement"
   The single source of truth. Every interactive module inherits these
   tokens and primitives so the whole site reads as one artifact.
   Derived verbatim from arcadia_brief/brief.html.
   ===================================================================== */

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

:root {
  /* --- palette (from brief.html) --- */
  --navy: #0A1B36;
  --navy-2: #1B2F54;
  --navy-deep: #050E20;
  --ink: #14181F;
  --ink-soft: #4A5568;
  --ink-faint: #6C7488;  /* WCAG AA on --paper: 4.56:1 (was #8B95A6 = 2.94:1); stays lighter than --ink-soft */
  --rule: #C8D0DC;
  --rule-soft: #E4E8EF;
  --paper: #FDFCF8;
  --paper-2: #FAF8F2;
  --tint: #F2F4F8;
  --tint-2: #EAEEF5;
  --tint-3: #F6F8FB;
  --warn: #8B1F2A;
  --warn-soft: #F4DCDF;
  --warn-deep: #5C141C;
  --warn-tint: color-mix(in srgb, var(--warn-soft) 45%, var(--paper)); /* canonical faint warm wash (clocks/myth grounds) */
  --warn-line: color-mix(in srgb, var(--warn) 25%, transparent);        /* hairline warn rule */
  --gold: #B8924A;
  --gold-text: #82642A;       /* AA-safe darkened gold for SMALL text on light grounds
                                 (≥5.1:1 on cream/--paper-2/--tint-3); the brand --gold
                                 fails AA at small sizes. The modules each invented this
                                 same value locally — single-sourced here. */

  /* --- mandate palette (echoes Figure 1 legend) --- */
  --m-research: #2F5C86;     /* Research = blue */
  --m-standards: #3C6E55;    /* Standards & competitiveness = green */
  --m-reg: #7A1F28;          /* Regulatory & enforcement = red. A deliberate hair
                                deeper/cooler than --warn (#8B1F2A) so the mandate
                                red and the red-line/danger accent stay distinguishable
                                side by side (e.g. on an open EU tile, where the warn
                                caret meets this dot) while remaining one red family.
                                Clears AA on --m-reg-soft (#F4DCDF) at 7.86:1. */
  --m-early: #B8924A;        /* Early-stage = amber (= gold) */
  --m-research-soft: #E3ECF4;
  --m-standards-soft: #E2EEE8;
  --m-reg-soft: #F4DCDF;
  --m-early-soft: #F2E8D4;

  /* --- type --- */
  --serif: 'Source Serif 4', Georgia, 'Times New Roman', serif;
  --display: 'Fraunces', 'Source Serif 4', Georgia, serif;
  --sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --mono: 'JetBrains Mono', 'SF Mono', Consolas, monospace;

  /* --- motion --- */
  --spring: cubic-bezier(.2, .8, .2, 1);
  --ease: cubic-bezier(.4, 0, .2, 1);

  /* --- layout --- */
  --measure: 1080px;
  --measure-prose: 720px;
}

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  background-color: var(--paper);
  /* faint ground: the cream settles a touch warmer the deeper you read (paper -> paper-2) */
  background-image: linear-gradient(180deg, var(--paper) 0%, var(--paper-2) 100%);
  color: var(--ink);
  font-family: var(--serif);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-feature-settings: "kern", "liga";
}

/* ---------- layout shells ---------- */
.wrap { max-width: var(--measure); margin: 0 auto; padding: 0 28px; }
.prose { max-width: var(--measure-prose); }
.section {
  padding: clamp(40px, 5vh, 56px) 0;
  border-top: 1px solid var(--rule-soft);
  /* deep-link #anchor cushion: clears the sticky .topbar (index.base.html: 11px
     vertical padding + ~15px mark glyph + 1px border ≈ 38px desktop; taller when
     the masthead wraps on phones) so #s-* navigation never lands hidden under it. */
  scroll-margin-top: clamp(46px, 8vh, 64px);
}
.section:first-of-type { border-top: none; }
.section--flush { padding-top: clamp(28px, 3.5vh, 40px); }
/* deep-link #anchor targets that are NOT .section hosts (the mounted interactive
   modules #s-evaluator/#s-network/#s-clocks/#s-myth carry .mod-host; the paper
   footer #paper carries .site-foot, both styled in index.base.html) get the same
   sticky-topbar cushion so their #anchor navigation lands clear of the topbar. */
.mod-host, .site-foot { scroll-margin-top: clamp(46px, 8vh, 64px); }

/* ---------- typography primitives ---------- */
.kicker {
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--navy);
  margin: 0 0 9px;
}
.kicker--warn { color: var(--warn); }
.kicker::after {
  content: "";
  display: block;
  width: 38px;
  height: 2px;
  background: currentColor;
  margin-top: 9px;
  opacity: .9;
}
.kicker.kicker--bare::after { display: none; }

h1, .h1 {
  font-family: var(--display);
  font-variation-settings: "opsz" 144, "SOFT" 50;
  font-weight: 400;
  font-size: clamp(34px, 6vw, 60px);
  line-height: var(--lh-solid);
  letter-spacing: var(--track-hero);
  color: var(--navy-deep);
  margin: 0 0 16px;
}
h2, .h2 {
  font-family: var(--display);
  font-variation-settings: "opsz" 110, "SOFT" 35;
  font-weight: 500;
  font-size: clamp(26px, 4vw, 40px);
  line-height: 1.06;
  letter-spacing: -0.02em;
  color: var(--navy-deep);
  margin: 0 0 10px;
}
.section-h { display: flex; align-items: baseline; gap: 11px; }
.section-h .num {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--warn);
  flex-shrink: 0;
}
h3, .h3 {
  font-family: var(--display);
  font-variation-settings: "opsz" 60, "SOFT" 30;
  font-weight: 500;
  font-size: 22px;
  line-height: var(--lh-tight);
  letter-spacing: -0.015em;
  color: var(--navy-deep);
  margin: 0 0 10px;
}
.sub {
  font-family: var(--display);
  font-style: italic;
  font-weight: 300;
  font-variation-settings: "opsz" 60, "SOFT" 100;
  font-size: clamp(16px, 2.3vw, 21px);
  line-height: var(--lh-snug);
  color: var(--ink-soft);
  margin: 0 0 12px;
  max-width: 62ch;
}
p { margin: 0 0 14px; }
.body, .body p {
  font-family: var(--serif);
  font-size: var(--step-0);
  line-height: var(--lh-relaxed);
  color: var(--ink);
}
.body p { max-width: 70ch; }
.lead {
  font-family: var(--display);
  font-variation-settings: "opsz" 18, "SOFT" 60;
  font-weight: 400;
  font-style: italic;
  font-size: clamp(18px, 2.4vw, 22px);
  line-height: 1.36;
  color: var(--navy);
  margin: 0 0 18px;
  max-width: 54ch;
}
.micro {
  font-family: var(--sans);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
strong { font-weight: 600; color: var(--navy); }
em { font-style: italic; }
a { color: var(--warn); text-decoration: none; border-bottom: 1px solid rgba(139,31,42,.3); }
a:hover { color: var(--warn-deep); border-bottom-color: var(--warn-deep); }

/* ---------- callout (navy proposition block) ---------- */
.callout {
  background: var(--navy-deep);
  color: var(--paper);
  padding: 22px 26px;
  border-radius: 3px;
  position: relative;
  margin: 16px 0;
}
.callout::before {
  content: "";
  position: absolute;
  left: 26px; top: 0;
  width: 26px; height: 3px;
  background: var(--gold);
}
.callout .label {
  font-family: var(--sans);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 6px 0 8px;
}
.callout .text {
  font-family: var(--display);
  font-variation-settings: "opsz" 24, "SOFT" 60;
  font-size: clamp(16px, 2.2vw, 20px);
  font-style: italic;
  font-weight: 300;
  line-height: 1.34;
  color: var(--paper);
  margin: 0;
}

/* ---------- pullquote ---------- */
.pullquote {
  font-family: var(--display);
  font-style: italic;
  font-weight: 400;
  font-variation-settings: "opsz" 40, "SOFT" 80;
  font-size: clamp(17px, 2.6vw, 22px);
  line-height: 1.34;
  color: var(--warn-deep);
  border-left: 3px solid var(--warn);
  padding-left: 18px;
  margin: 22px 0;
  max-width: 56ch;
}

/* ---------- stat blocks ---------- */
.stat-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
.stat { border-top: 2px solid var(--navy-deep); padding-top: 8px; position: relative; }
.stat::before {
  content: ""; position: absolute; top: -2px; left: 0;
  width: 26px; height: 2px; background: var(--warn);
}
.stat-num {
  font-family: var(--display);
  font-variation-settings: "opsz" 144, "SOFT" 0;
  font-weight: 400;
  font-size: clamp(28px, 4vw, 40px);
  line-height: 1;
  color: var(--navy-deep);
  letter-spacing: -0.03em;
  margin-bottom: 6px;
}
.stat-num .unit {
  font-family: var(--display);
  font-variation-settings: "opsz" 60, "SOFT" 50;
  font-weight: 300;
  font-size: 0.56em;
  color: var(--warn);
  margin-left: 3px;
}
.stat-desc { font-family: var(--serif); font-size: 13.5px; line-height: 1.36; color: var(--ink); }

/* ---------- cards ---------- */
.card {
  background: var(--paper-2);
  border: 1px solid var(--rule-soft);
  border-radius: 3px;
  padding: 16px 18px;
}
.card--raised { background: #fff; box-shadow: 0 3px 14px rgba(10,27,54,.07); }

/* ---------- chips / badges ---------- */
.chip {
  font-family: var(--sans);
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  background: var(--tint);
  border-radius: 2px;
  padding: 3px 7px;
  white-space: nowrap;
  display: inline-block;
}
.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--sans);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.badge svg { width: 12px; height: 12px; }

/* ---------- meter (credibility / progress bars) ----------
   The trough reads as carved into the paper (inset depth-well rim, defined where
   the fill hasn't covered it); the fill carries a faint top-edge sheen so it sits
   in the channel like a polished bar rather than a painted rectangle. */
.track { height: 9px; background: var(--tint-2); border-radius: 5px; overflow: hidden; box-shadow: var(--atmo-depth-well); }
.fill { height: 100%; width: 0%; border-radius: 5px; background: var(--gold); box-shadow: var(--inset-top); transition: width .65s var(--spring), background .45s var(--ease); }
.fill.mid { background: var(--navy); }
.fill.high { background: var(--navy-deep); }
.fill.fail { background: var(--warn); }

/* ---------- controls / buttons ---------- */
.ctl {
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--navy);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 3px;
  padding: 9px 15px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  transition: background .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease), transform .2s var(--spring);
}
.ctl svg { width: 13px; height: 13px; }
.ctl:hover { background: var(--navy); color: var(--paper); border-color: var(--navy); }
.ctl:disabled { opacity: .4; cursor: not-allowed; }
.ctl--warn { color: var(--paper); background: var(--warn); border-color: var(--warn); }
.ctl--warn:hover { background: var(--warn-deep); border-color: var(--warn-deep); }
.ctl[aria-pressed="true"] { background: var(--navy); color: var(--paper); border-color: var(--navy); }

/* toggle group */
.toggle-row { display: inline-flex; border: 1px solid var(--rule); border-radius: 3px; overflow: hidden; }
.toggle-row button {
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  padding: 8px 14px; border: none; background: var(--paper); color: var(--ink-soft);
  cursor: pointer; transition: background .2s var(--ease), color .2s var(--ease);
}
.toggle-row button + button { border-left: 1px solid var(--rule); }
.toggle-row button[aria-pressed="true"] { background: var(--navy); color: var(--paper); }

/* ---------- entrance reveal (paired with reveal.js) ---------- */
.reveal { transition: opacity .6s var(--spring), transform .6s var(--spring); }
.reveal-armed .reveal:not(.in) { opacity: 0; transform: translateY(18px); }
.reveal-armed .reveal.stagger-1:not(.in) { transition-delay: .06s; }
.reveal-armed .reveal.stagger-2:not(.in) { transition-delay: .12s; }
.reveal-armed .reveal.stagger-3:not(.in) { transition-delay: .18s; }

/* ---------- shared keyframes ---------- */
@keyframes riseIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes shake {
  10%, 90% { transform: translateX(-1px); }
  20%, 80% { transform: translateX(2px); }
  30%, 50%, 70% { transform: translateX(-5px); }
  40%, 60% { transform: translateX(5px); }
}
@keyframes pulseWarn {
  0%, 100% { box-shadow: 0 0 0 0 rgba(139,31,42,.4); }
  50% { box-shadow: 0 0 0 7px rgba(139,31,42,0); }
}
@keyframes countSheen { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }

/* ---------- focus ----------
   Canonical focus treatment lives in the PREMIUM block below (offset 3px). This
   earlier rule is kept as the no-premium-block fallback; both share the gold ring
   so the cascade outcome is identical either way. */
:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; border-radius: 2px; }

/* ---------- responsive helpers ---------- */
@media (max-width: 860px) {
  .stat-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 560px) {
  .wrap { padding: 0 16px; }
  .section { padding: 32px 0; }
  .stat-grid { grid-template-columns: 1fr; gap: 16px; }
}

/* =====================================================================
   PREMIUM ELEVATION — depth, motion, micro-interaction, atmosphere
   ===================================================================== */
:root {
  /* Ink-tinted, layered elevation ramp. Each rung stacks lights on the navy ink
     (never neutral grey, so the cream never goes muddy): a crisp .5px contact
     ring, then a near key term, a mid cast, and a broad soft ambient — a four-
     tier falloff (sm collapses the mid into the key) so the penumbra reads as a
     continuous physical gradient rather than two discrete steps. Ratios between
     rungs hold near ~1.7 in blur / ~1.6 in offset for a consistent light. Peak
     alphas are tuned so a card at rest (sm) and a lifted control (md) sit a
     clear step apart, and the navy callout (lg) / hover-readout (xl) feel
     genuinely raised off the page rather than merely darker. */
  --shadow-sm:
    0 0 0 .5px rgba(10,27,54,.04),
    0 1px 1.5px rgba(10,27,54,.055),
    0 2px 5px rgba(10,27,54,.045);
  --shadow-md:
    0 0 0 .5px rgba(10,27,54,.045),
    0 1px 2px rgba(10,27,54,.05),
    0 3px 6px rgba(10,27,54,.055),
    0 9px 20px rgba(10,27,54,.07);
  --shadow-lg:
    0 0 0 .5px rgba(10,27,54,.05),
    0 2px 4px rgba(10,27,54,.05),
    0 6px 14px rgba(10,27,54,.07),
    0 20px 48px rgba(10,27,54,.12);
  --shadow-xl:
    0 0 0 .5px rgba(10,27,54,.055),
    0 3px 6px rgba(10,27,54,.06),
    0 10px 24px rgba(10,27,54,.09),
    0 34px 76px rgba(10,27,54,.17);
  /* Ramp endpoints. --shadow-xs is a bare contact shadow (the faintest legible
     lift — a hairline ring + 1px of cast) for surfaces that should read as
     resting on the page, not above it (inline chips, table rows, the topbar's
     under-edge). --shadow-2xl extends the geometric progression one rung past
     xl for the deepest floated layers (a lifted modal-like readout, the network
     map's hover card) so they don't have to top out at xl. Same navy ink and
     ~1.6 offset ratio as the rest of the ramp. */
  --shadow-xs:
    0 0 0 1px rgba(10,27,54,.02),
    0 1px 1px rgba(10,27,54,.04);
  --shadow-2xl:
    0 0 0 1px rgba(10,27,54,.045),
    0 14px 30px rgba(10,27,54,.11),
    0 48px 104px rgba(10,27,54,.20);
  /* Focused, near-the-surface lift for small interactive elements (chips,
     knobs, the active row in a toggle) where the broad ambient of --shadow-md
     would read as floating. */
  --shadow-key: 0 1px 2px rgba(10,27,54,.08), 0 3px 8px rgba(10,27,54,.06);
  /* Warm-cast lift for warn-coloured surfaces, so a red element's shadow doesn't
     read as an unrelated navy halo. */
  --shadow-warn: 0 4px 14px rgba(92,20,28,.16), 0 1px 3px rgba(92,20,28,.12);
  /* Warn channel (8B1F2A) and warn-deep channel (5C141C) as bare R,G,B triples,
     so warn-tinted glow rings/washes can be composed at an arbitrary alpha from
     one source instead of being hand-typed as rgba(139,31,42,…) at each call.
     Use: box-shadow: 0 0 0 4px rgba(var(--warn-rgb), .10).  These mirror the hex
     of --warn / --warn-deep exactly; if those hexes move, move these with them. */
  --warn-rgb: 139, 31, 42;
  --warn-deep-rgb: 92, 20, 28;
  --navy-rgb: 10, 27, 54;
  /* Canonical warn focus/active glow rings — the "this red element is live"
     halo the timeline nodes, evaluator fields, and clock markers each currently
     hand-roll. -soft is a hover hint; -ring is a paper-gapped active ring (the
     2px paper gap keeps the red off the element's own border); -bloom adds an
     outer atmospheric falloff for the single most-emphasised live element. */
  --glow-warn-soft: 0 0 0 4px rgba(var(--warn-rgb), .10);
  --glow-warn-ring: 0 0 0 2px var(--paper), 0 0 0 6px rgba(var(--warn-rgb), .28);
  --glow-warn-bloom:
    0 0 0 2px var(--paper),
    0 0 0 6px rgba(var(--warn-rgb), .28),
    0 0 18px rgba(var(--warn-rgb), .18);
  /* Inset finishing lights — the top-edge sheen and 1px inner frame that the
     module surfaces currently hand-roll as inset 0 1px 0 rgba(255,255,255,…).
     Compose with a drop shadow: box-shadow: var(--inset-top), var(--shadow-md). */
  --inset-top: inset 0 1px 0 rgba(255,255,255,.5);
  --inset-frame: inset 0 0 0 1px rgba(255,255,255,.35);
  --inset-press: inset 0 1px 3px rgba(10,27,54,.10), inset 0 0 0 1px rgba(10,27,54,.04);

  /* Spring + easing vocabulary. The three springs are load-bearing (surfaces are
     tuned against these exact curves); values are frozen. --spring-out is the
     house default for entrances and lifts; --spring-snap adds a slight overshoot
     for tactile snaps (knobs, dots); --ease-out / --ease-in-out fill the
     non-spring cases (colour, opacity, layout). */
  --spring-out: cubic-bezier(.16, 1, .3, 1);
  --spring-snap: cubic-bezier(.34, 1.56, .64, 1);
  --ease-out: cubic-bezier(.22, .61, .36, 1);
  --ease-in-out: cubic-bezier(.65, 0, .35, 1);
  /* Additional curves (additive; the three above stay frozen). --spring-glide is
     a gentler settle than --spring-out, with a longer tail and no overshoot — for
     large-displacement entrances or a magnetic element easing back to rest, where
     --spring-snap's bounce would read as fidgety. --ease-emphasis is a symmetric
     accelerate-then-decelerate for a single deliberate attention move. */
  --spring-glide: cubic-bezier(.22, 1, .36, 1);
  --ease-emphasis: cubic-bezier(.45, 0, .15, 1);

  /* Named durations — so motion across surfaces shares a tempo rather than each
     transition guessing its own milliseconds. */
  --dur-1: .15s;   /* micro: press, tint flip */
  --dur-2: .25s;   /* control hover, small move */
  --dur-3: .4s;    /* panel state, fill */
  --dur-4: .6s;    /* entrance reveal */
  --dur-5: .9s;    /* drawn rule, long sweep */

  /* Composed transition shorthands for the two combos surfaces repeat most: a
     resting interactive surface that lifts on hover (transform + shadow on the
     house spring), and a tint/colour flip. Optional — a surface can still spell
     out its own transition; these just name the common case. */
  --tr-lift: transform var(--dur-3) var(--spring-out), box-shadow var(--dur-3) var(--spring-out);
  --tr-tint: background-color var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease-out);

  /* Magnetic-hover + parallax depth knobs. Pure tokens (no behaviour on their
     own): a surface reads --lift-1/-2 as the translateY for its hover/press
     states so "how far things lift" is tuned in one place, and --parallax-near/
     -far as the scroll-parallax displacement ratio for layered depth. */
  --lift-1: -1px;   /* control / chip hover */
  --lift-2: -2px;   /* card / panel hover */
  --lift-3: -4px;   /* feature card hover */
  --parallax-near: 0.06;
  --parallax-far: 0.14;
}

::selection { background: var(--navy); color: var(--paper); }
::-moz-selection { background: var(--navy); color: var(--paper); }

html { scrollbar-color: var(--rule) transparent; scrollbar-width: thin; }
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: var(--paper-2); }
::-webkit-scrollbar-thumb { background: var(--rule); border-radius: 7px; border: 3px solid var(--paper-2); }
::-webkit-scrollbar-thumb:hover { background: var(--ink-faint); }

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

/* paper grain — fixed atmospheric overlay. The noise is a STATIC one-time-rendered
   SVG data-URI (not a live filter), and the layer is promoted to its own compositor
   tile (translateZ) so the fixed + multiply blend doesn't re-rasterise on scroll. */
.grain {
  position: fixed; inset: 0; z-index: 1; pointer-events: none;
  opacity: .05; mix-blend-mode: multiply;
  transform: translateZ(0); backface-visibility: hidden;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* scroll-progress red line */
.scrollbar { position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 60; background: rgba(200,208,220,.35); }
.scrollbar #scrollbar-fill { height: 100%; background: linear-gradient(90deg, var(--warn-deep), var(--warn)); transform: scaleX(0); transform-origin: left; will-change: transform; }

/* ---- the sequence spine: signature move #3 ----
   A fixed left-margin indicator that assembles the five institutional goods from
   the foundation up as you scroll — standards at the base, consequences at the
   crown — so the "build in order" thesis is felt, not just argued. Each good
   solidifies as the rising fill passes it; a gold cap marks the current build
   front. Decorative (aria-hidden); the hero's five-move nav is the real index.
   Shown only where the margin is wide enough; never on mobile. */
.spine {
  position: fixed; left: 28px; top: 50%; transform: translateY(-50%);
  height: min(540px, 66vh); width: 44px; z-index: 40; pointer-events: none;
}
/* the build-direction caption: a quiet horizontal axis-label seated in the clear
   margin BELOW the rail's foot (not inside the rail's lane, where the bottom-anchored
   gold fill would always overlap it). Aligned to the numeral gutter; the trailing
   up-arrow points back up into the column it labels — "this assembles upward". Reads
   at a legible 8px rather than the old 7px vertical sliver. Brightens to gold once
   the column is complete (see .spine.built). */
.spine-legend {
  position: absolute; left: -16px; top: 100%; margin-top: 13px;
  font-family: var(--sans); font-size: 8px; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-faint); opacity: .82; white-space: nowrap;
  transition: color .3s var(--ease), opacity .3s var(--ease);
}
.spine-rail { position: absolute; left: 0; top: 0; bottom: 0; width: 2px; border-radius: 2px; background: var(--rule-soft); overflow: hidden; }
.spine-rail-fill {
  position: absolute; left: 0; bottom: 0; width: 100%; height: 0%;
  /* 0deg runs base->front (bottom->top): densest --navy-deep at the poured,
     compacted foundation, lightening to --navy-2 at the rising build-front.
     The line says what it means — the structure is heaviest where it settled. */
  background: linear-gradient(0deg, var(--navy-deep), var(--navy-2));
  transition: height .2s var(--ease);
}
/* the gold build-front riding the leading (top) edge of the fill. Its glow leans
   UPWARD (negative y-offset) into the not-yet-built zone, so the light itself
   signals the direction of travel — the regime is rising toward the crown. */
.spine-rail-fill::after {
  content: ""; position: absolute; top: 0; left: -1px; right: -1px; height: 3px; border-radius: 2px;
  background: var(--gold);
  box-shadow: 0 -1px 9px color-mix(in srgb, var(--gold) 70%, transparent), 0 0 6px color-mix(in srgb, var(--gold) 50%, transparent);
}
.spine-list { list-style: none; margin: 0; padding: 0; position: absolute; inset: 0; display: flex; flex-direction: column; }
.spine-item { flex: 1; position: relative; display: flex; align-items: center; padding-left: 13px; }
.spine-dot {
  position: absolute; left: -3px; top: 50%; transform: translateY(-50%);
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--paper); border: 1.5px solid var(--rule);
  box-shadow: 0 0 0 3px var(--paper); transition: background .3s var(--ease), border-color .3s var(--ease), transform .3s var(--spring-snap);
}
.spine-name {
  writing-mode: vertical-rl; transform: rotate(180deg);
  font-family: var(--sans); font-size: 8.5px; font-weight: 700; letter-spacing: .15em;
  text-transform: uppercase; white-space: nowrap; color: var(--ink-faint);
  transition: color .3s var(--ease);
}
/* the build-order numerals: tiny tabular figures in the widened left gutter,
   dim until their course is poured, ink once it is. They restate the foundation-up
   SEQUENCE (1 at the base, 5 at the crown) — pure build-order, not data. */
.spine-ord {
  position: absolute; left: -16px; top: 50%; transform: translateY(-50%);
  font-family: var(--mono); font-size: 8px; font-weight: 600;
  font-variant-numeric: tabular-nums; line-height: 1;
  color: var(--ink-faint); opacity: .5;
  transition: color .3s var(--ease), opacity .3s var(--ease);
}
.spine-item.reached .spine-name { color: var(--ink); }
.spine-item.reached .spine-dot { background: var(--navy); border-color: var(--navy); transform: translateY(-50%) scale(1.12); }
.spine-item.reached .spine-ord { color: var(--navy); opacity: 1; }
/* the CURRENT layer (build-front): the top-most reached course — the live edge of
   what is built. Exactly one at a time, rendered in gold so the word level with the
   gold build-front cap IS gold. Placed after .reached so it wins on shared props.
   The gold TEXT is darkened (--gold blended 80/20 toward ink => #977A41, 3.95:1 on
   cream) so the spine's most important live cue clears the 3:1 non-text floor; the
   gold DOT below keeps the pure --gold + glow, which carries its own contrast. */
.spine-item.at-front .spine-name { color: color-mix(in srgb, var(--gold) 80%, var(--ink)); letter-spacing: .2em; }
.spine-item.at-front .spine-ord  { color: color-mix(in srgb, var(--gold) 80%, var(--ink)); opacity: 1; }
.spine-item.at-front .spine-dot  {
  background: var(--gold); border-color: var(--gold);
  transform: translateY(-50%) scale(1.18);
  box-shadow: 0 0 0 3px var(--paper), 0 0 8px color-mix(in srgb, var(--gold) 60%, transparent);
}
/* the live build-front breathes — the same slow gold tempo as the sequencing
   machine's "build this next" layer, so the spine's active frontier and the machine
   speak one motion language. Only the OUTER glow pulses; the crisp paper ring holds
   steady. Motion-gated via .motion-ok (absent under reduced motion => a steady dot),
   and suppressed once the column banks to gold (a complete regime rests, it doesn't
   pulse). */
@keyframes spineFrontBreath {
  0%, 100% { box-shadow: 0 0 0 3px var(--paper), 0 0 7px color-mix(in srgb, var(--gold) 52%, transparent); }
  50%      { box-shadow: 0 0 0 3px var(--paper), 0 0 13px color-mix(in srgb, var(--gold) 78%, transparent); }
}
.motion-ok .spine:not(.built) .spine-item.at-front .spine-dot { animation: spineFrontBreath 3.4s var(--ease-in-out) infinite; }
/* crown gold-resolution: at full progress the whole regime banks to gold — the
   foundation is complete and enforcement can finally stand on it. Placed last so
   .built wins on shared properties without a specificity fight. */
.spine.built .spine-rail-fill { background: linear-gradient(0deg, color-mix(in srgb, var(--gold) 72%, var(--navy-deep)), var(--gold)); }
.spine.built .spine-rail-fill::after { background: #E8C77A; box-shadow: 0 0 10px color-mix(in srgb, var(--gold) 80%, transparent); }
/* on completion the words/numerals/legend bank to the SAME darkened gold-text as
   the build-front (3.95:1 on cream), clearing the 3:1 floor; the rail-fill and dots
   keep the pure --gold + glow, which carry their own contrast against the cream. */
.spine.built .spine-name   { color: color-mix(in srgb, var(--gold) 80%, var(--ink)); }
.spine.built .spine-ord    { color: color-mix(in srgb, var(--gold) 80%, var(--ink)); opacity: 1; }
.spine.built .spine-dot    { background: var(--gold); border-color: var(--gold); }
.spine.built .spine-legend { color: color-mix(in srgb, var(--gold) 80%, var(--ink)); opacity: 1; }
@media (max-width: 1200px) { .spine { display: none; } }
@media (prefers-reduced-motion: reduce) {
  .spine-rail-fill, .spine-dot, .spine-name, .spine-ord, .spine-legend { transition: none; }
}

/* elevation on surfaces */
.card { box-shadow: var(--shadow-sm); transition: var(--tr-lift), border-color var(--dur-2) var(--ease); }
.card--raised { box-shadow: var(--inset-top), var(--shadow-md); }
/* The navy callout catches a faint top-lip specular so it reads as a raised
   slab lit from above, not a flat dark rectangle painted on the cream. */
.callout { box-shadow: var(--inset-top), var(--shadow-lg); }

/* control micro-interactions — lift distances come from the --lift-* tokens so
   "how far things rise" is tuned in one place across every interactive surface. */
.ctl { box-shadow: var(--shadow-sm); }
.ctl:hover { transform: translateY(var(--lift-1)); box-shadow: var(--shadow-md); }
.ctl:active { transform: translateY(0) scale(.985); box-shadow: var(--inset-press); }
.chip { transition: background .2s var(--ease), color .2s var(--ease), box-shadow .2s var(--ease), transform .25s var(--spring); }
.toggle-row { box-shadow: var(--shadow-sm); }
.toggle-row button { transition: background .3s var(--spring-out), color .2s var(--ease); }

/* richer spring entrance + directional variants */
.reveal { transition: opacity .7s var(--spring-out), transform .7s var(--spring-out); }
.reveal-armed .reveal:not(.in) { opacity: 0; transform: translateY(24px); }
.reveal-armed .reveal.r-scale:not(.in) { transform: scale(.965); }
.reveal-armed .reveal.r-stagger > * { transition: opacity .6s var(--spring-out), transform .6s var(--spring-out); }
.reveal-armed .reveal.r-stagger:not(.in) > * { opacity: 0; transform: translateY(16px); }
.reveal-armed .reveal.r-stagger.in > *:nth-child(2) { transition-delay: .07s; }
.reveal-armed .reveal.r-stagger.in > *:nth-child(3) { transition-delay: .14s; }
.reveal-armed .reveal.r-stagger.in > *:nth-child(4) { transition-delay: .21s; }
.reveal-armed .reveal.r-stagger.in > *:nth-child(5) { transition-delay: .28s; }

/* drawn hairline divider — the red-line motif as a section rule */
.rule-draw { height: 1px; background: var(--rule); position: relative; border: 0; margin: 24px 0; overflow: hidden; }
.rule-draw::after { content: ""; position: absolute; left: 0; top: 0; height: 100%; width: 72px; background: var(--warn); transform: scaleX(0); transform-origin: left; transition: transform .95s var(--spring-out); }
.rule-draw.in::after { transform: scaleX(1); }

/* =====================================================================
   EDITORIAL SYSTEM — modular type scale, micro-typography, spacing
   rhythm, opt-in atmosphere. All additive: new tokens + opt-in utility
   classes. The few global declarations here only set defaults the
   surfaces don't already specify (they never override module-local
   font-size / line-height / letter-spacing).
   ===================================================================== */
:root {
  /* --- modular type scale ---
     A minor-third progression (ratio ~1.2) that names the sizes already in use
     across the artifact: body 16 sits at --step-0; the steps below it (13.5,
     11.5/10) carry captions, micro-labels, chips; the steps above climb toward
     the display sizes (22 / 27 / 34 / 44). Each step is fluid (clamp) so it
     tightens on phones and opens up on wide measures without per-surface media
     queries. Optical sizing for Fraunces/Source Serif is still set per element
     via font-variation-settings; these tokens govern the size, not the opsz. */
  --step--2: clamp(9.5px, 0.55rem + 0.18vw, 11px);    /* micro labels, chips */
  --step--1: clamp(12px,  0.74rem + 0.18vw, 13.5px);  /* captions, fine print */
  --step-0:  16px;                                    /* body — the anchor */
  --step-1:  clamp(17px,  0.98rem + 0.55vw, 19px);    /* lead-in, large body */
  --step-2:  clamp(20px,  1.05rem + 1.1vw, 22px);     /* h3 */
  --step-3:  clamp(22px,  1.05rem + 2.0vw, 27px);     /* sub-display */
  --step-4:  clamp(26px,  1.1rem  + 4.0vw, 40px);     /* h2 */
  --step-5:  clamp(30px,  1.0rem  + 5.4vw, 50px);     /* large h2 / small h1 */
  --step-6:  clamp(34px,  0.9rem  + 6.0vw, 60px);     /* h1 / hero */

  /* --- line-height ladder --- */
  --lh-solid:   1.0;    /* display headlines */
  --lh-tight:   1.12;   /* h3, dense headings */
  --lh-snug:    1.34;   /* subs, pullquotes, callouts */
  --lh-normal:  1.5;    /* default UI / mixed copy */
  --lh-relaxed: 1.58;   /* long-form body prose */

  /* --- tracking (letter-spacing) ladder --- */
  --track-hero:    -0.025em;  /* large display */
  --track-tight:   -0.018em;  /* mid display / large headings */
  --track-normal:  0;         /* body */
  --track-label:   0.12em;    /* chips / fine uppercase */
  --track-kicker:  0.22em;    /* kicker / eyebrow uppercase */

  /* --- spacing rhythm ---
     An 8px-rooted scale that names the gaps already in the artifact (3/4/6/8/
     10/14/16/22/28/40/64/96). Use for padding/gap/margin so vertical and
     horizontal rhythm stays coherent across surfaces. */
  --space-3xs: 3px;
  --space-2xs: 4px;
  --space-xs:  6px;
  --space-sm:  8px;
  --space-md:  10px;
  --space-lg:  14px;
  --space-xl:  16px;
  --space-2xl: 22px;
  --space-3xl: 28px;
  --space-4xl: 40px;
  --space-5xl: 64px;
  --space-6xl: 96px;

  /* --- radius scale --- */
  --radius-xs: 2px;
  --radius-sm: 3px;   /* the house card/control radius */
  --radius-md: 6px;
  --radius-lg: 10px;
  --radius-pill: 999px;

  /* --- hairline weights --- */
  --hair: 1px;
  --hair-2: 1.5px;

  /* --- atmosphere (opt-in) ---
     Canonical hooks for the paper-light the modules currently hand-roll:
     a top-lit vignette ground, a faint warm wash welling up from the lower
     edge (the red-line motif as ambient warmth), an upper edge-light, and a
     cool reading tint. Apply via the .atmo-* utility classes below, or compose
     directly: background-image: var(--atmo-edge-light), var(--atmo-vignette); */
  --atmo-grain-opacity: .05;   /* drives the global .grain layer (see below) */
  --atmo-vignette:
    radial-gradient(120% 90% at 50% -8%, var(--paper) 0%, var(--paper-2) 60%, var(--tint) 100%);
  --atmo-warm-well:
    radial-gradient(150% 120% at 50% 118%, color-mix(in srgb, var(--warn) 6%, transparent) 0%, transparent 56%);
  --atmo-edge-light:
    linear-gradient(180deg, rgba(255,255,255,.6) 0%, transparent 18%);
  --atmo-cool-wash:
    linear-gradient(180deg, var(--tint-3) 0%, var(--paper) 60%);
  --atmo-spotlight:
    radial-gradient(80% 60% at 50% 0%, color-mix(in srgb, var(--navy) 4%, transparent) 0%, transparent 70%);
  /* A tighter, brighter top-edge specular than --atmo-edge-light: a 1px-ish line
     of white that makes a raised surface's upper lip catch the light. Compose as
     an inset box-shadow alongside a drop shadow (this is the token form of the
     hand-rolled inset 0 1px 0 rgba(255,255,255,…) the cards already use). */
  --atmo-hairline-sheen: inset 0 1px 0 rgba(255,255,255,.55), inset 0 0 0 1px rgba(255,255,255,.06);
  /* Soft inner depth for recessed surfaces (meter tracks, inset wells) so they
     read as carved into the paper rather than drawn on it. Navy-tinted, never
     grey. Compose: box-shadow: var(--atmo-depth-well). */
  --atmo-depth-well: inset 0 1px 2px rgba(var(--navy-rgb), .07), inset 0 0 0 1px rgba(var(--navy-rgb), .04);

  /* A finer, two-grain paper fibre than the global .grain layer: a tight high-
     frequency tooth (0.9) over a faint low-frequency mottle (0.012), so an opted-
     in surface gets real laid-paper texture instead of uniform tooth. Static SVG
     data-URI (rendered once, not a live filter); pair at low opacity over the
     cream, never above ~.06 or it stops reading as paper. Compose as a background
     layer on a surface that wants more tactility than the ambient .grain gives.
     Multiply-blended in the .atmo-fibre utility, so apply over LIGHT surfaces only
     (on a navy/dark slab multiply would muddy it — use --atmo-hairline-sheen there
     for tactility instead). */
  --atmo-paper-fibre:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9 0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Cfilter id='m'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.012' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23f)' opacity='0.5'/%3E%3Crect width='100%25' height='100%25' filter='url(%23m)' opacity='0.22'/%3E%3C/svg%3E");
  /* True outer-corner page vignette — the paper falls a hair darker at the four
     corners the way a real printed page does, distinct from --atmo-vignette's
     top-lit ground. Multiply-friendly (transparent centre, faint ink corners);
     drop it as the topmost background layer of a full-bleed reading surface. */
  --atmo-page-vignette:
    radial-gradient(130% 130% at 50% 50%, transparent 62%, rgba(var(--navy-rgb), .035) 100%);
  /* Directional raking light — a soft diagonal sheen from the upper-left, as if
     a low light grazes the page. Gives a large flat surface a sense of being lit
     from a specific direction rather than evenly flooded. Very low contrast by
     design; compose under content, above the base fill. */
  --atmo-rake:
    linear-gradient(118deg, rgba(255,255,255,.5) 0%, transparent 24%, transparent 82%, rgba(var(--navy-rgb), .025) 100%);
}

/* drive the existing global grain layer from the atmosphere token */
.grain { opacity: var(--atmo-grain-opacity); }

/* --- opt-in atmosphere utilities --- */
.atmo-paper   { background-image: var(--atmo-vignette); }
.atmo-warm    { background-image: var(--atmo-warm-well), var(--atmo-vignette); }
.atmo-cool    { background-image: var(--atmo-cool-wash); }
.atmo-edgelit { position: relative; }
.atmo-edgelit::before {
  content: ""; position: absolute; inset: 0; pointer-events: none; border-radius: inherit;
  background-image: var(--atmo-edge-light); z-index: 0;
}
.atmo-edgelit > * { position: relative; z-index: 1; }
/* Specular top-lip on a raised surface; pairs with a drop-shadow already on the
   element. (These set box-shadow, so apply to surfaces that don't already own a
   conflicting box-shadow, or compose by hand with the token directly.) */
.atmo-sheen { box-shadow: var(--atmo-hairline-sheen); }
.atmo-well  { box-shadow: var(--atmo-depth-well); }
/* Overlay-pseudo atmosphere (layers ON TOP of the surface's own background rather
   than replacing it, so a tinted card can still gain fibre / edge-vignette / rake).
   Same containment contract as .atmo-edgelit: the host goes position:relative and
   its direct children ride above the overlay. Stack classes freely — each adds one
   non-interactive layer. Auto-suppressed where the project keeps grain off. */
.atmo-fibre, .atmo-vignette-edge, .atmo-raked { position: relative; }
.atmo-fibre > *, .atmo-vignette-edge > *, .atmo-raked > * { position: relative; z-index: 1; }
.atmo-fibre::after, .atmo-vignette-edge::after, .atmo-raked::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  border-radius: inherit; z-index: 0;
}
.atmo-fibre::after { background-image: var(--atmo-paper-fibre); opacity: .5; mix-blend-mode: multiply; }
.atmo-vignette-edge::after { background-image: var(--atmo-page-vignette); }
.atmo-raked::after { background-image: var(--atmo-rake); }

/* --- micro-typography defaults (safe; only set what surfaces leave unset) --- */
/* Tabular figures wherever numbers must align in tables/meters/readouts.
   Opt-in so prose keeps proportional oldstyle figures. .tnum is a short alias. */
.nums-tabular, .tnum { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }
/* The big display counters and any stat/meter readout should hold tabular,
   lining figures: it stops the number from reflowing horizontally as digits
   change during a count-up, and keeps stacked figures optically aligned. These
   surfaces don't set font-variant-numeric themselves, so this only fills a gap;
   font-variation-settings (opsz/SOFT) is a different property and is untouched. */
.stat-num, .stat-num .unit { font-variant-numeric: tabular-nums lining-nums; }
/* Discretionary ligatures + contextual alternates for display headlines. */
h1, .h1, h2, .h2, .lead, .sub, .pullquote, .callout .text {
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
}
/* Headlines and short fragments wrap as balanced blocks; long prose wraps
   "pretty" to avoid orphans. text-wrap degrades gracefully where unsupported. */
h1, .h1, h2, .h2, h3, .h3, .sub, .lead, .pullquote, .callout .text, .stat-desc { text-wrap: balance; }
.body p, p { text-wrap: pretty; }
/* Tighten widows on the prose column. */
.prose { hanging-punctuation: first; }

/* --- widow / orphan / separator helpers (opt-in) ---
   text-wrap: balance handles most headline widows, but three cases need a hard
   guarantee the line-breaker can't override:
   (1) .balance — apply balanced wrapping to any block that isn't already covered
       above (sub-headers, captions, legend strings).
   (2) .no-orphan — pull the last two words together so a single trailing word can
       never wrap alone. (Wrap the final two words: <span class="no-orphan">…</span>
       at the tail, or apply to a short whole phrase.)
   (3) .nowrap-group — keep a short inline run intact as one unbreakable unit. This
       is the fix for the "A · B" pattern where the middle dot or the clause after
       it drops to its own line: wrap the whole "left · right" run so it breaks as
       a block, never between the parts. Pair with a non-breaking thin space around
       the separator for the tightest result. */
.balance      { text-wrap: balance; }
.no-orphan    { white-space: nowrap; }
.nowrap-group { display: inline-block; text-wrap: nowrap; }
/* A reusable mid-dot separator that carries its own breathing room and never
   strands the glyph: the dot is generated, hair-spaced, and tied to the run via
   the surrounding .nowrap-group so it cannot begin a line. The dot sits a step
   warmer than --rule so it reads as a deliberate divider, not a stray speck. */
.dot-sep::before {
  content: "\00B7"; margin: 0 .5ch;
  color: color-mix(in srgb, var(--warn) 38%, var(--rule));
}
/* Container-level guard for a two-part caption that already holds a raw "A · B"
   middot in its markup (i.e. can't be restructured into .dot-sep + .nowrap-group
   from the shared layer). It balances the wrap so the breaker distributes the two
   clauses evenly rather than stranding the separator, and reserves the dot its own
   optical gutter. NOTE: a raw inline "·" is still a legal break point — for the
   hard guarantee that the dot can never begin a line, wrap the "left · right" run
   in .nowrap-group at the markup level; this class is the no-markup-change
   mitigation, not a substitute. */
.sep-tight {
  text-wrap: balance;
  word-spacing: .04em;
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .reveal-armed .reveal:not(.in),
  .reveal-armed .reveal.r-stagger:not(.in) > * { opacity: 1; transform: none; }
  .rule-draw::after { display: none; }
}
