/*
 * COMPILED ARTIFACT, generated by build/build-styles.mjs. Do not hand-edit.
 * This is the single stylesheet consumers link. It is the modular sources under
 * tokens/ and parts/ concatenated in cascade order. To change it, edit the
 * sources and/or build/styles.manifest.css (the import order), then regenerate:
 *   cd build && npm run build:styles
 * Sources of truth: tokens/*.css, parts/*.css, ordered by build/styles.manifest.css.
 * Concatenated (not @import-ed) so an HTTP-served page makes one request instead
 * of an @import round-trip waterfall; file:// is unaffected either way.
 */

/* ===== tokens/colors.css ===== */
/* Foundational — Color tokens
   Near-black ink on cool near-white paper. ONE selective accent (signal),
   reserved for live / now / recommended wayfinding ONLY.
   Status colors are muted + harmonized; a chip always pairs a dot with a WORD. */

:root {
  /* color-scheme names which NATIVE-UI palette the browser paints — scrollbars,
     form controls, the default focus outline. Native UI ignores our tokens, so
     without this a dark page still paints the browser-default BRIGHT scrollbar,
     the loudest thing on an otherwise calm dark surface. Declared explicitly on
     BOTH sides (light here; dark in each dark path below) so native UI follows
     the theme rather than the reader's OS. */
  color-scheme: light;

  /* Ink ramp — text on paper, darkest to faintest.
     Neutral GRAPHITE ink: a whisper of cool, no navy, never pure black.
     Reads as refined printed ink rather than a saturated screen dark. */
  --ink:      #272a2e; /* primary headings, strongest text */
  --ink-2:    #454952; /* secondary headings, strong body */
  --body:     #636a73; /* default body copy */
  --mut:      #686e77; /* muted / supporting text — meets WCAG AA on paper (4.53) and panel (5.14) */
  --faint:    #aeb2b9; /* faintest text, disabled, hairline labels */

  /* Surfaces — cool near-white paper, white panels, hairlines */
  --paper:    #eef1f3; /* page background, cool near-white (future paper) */
  --panel:    #ffffff; /* flat content card / pane */
  --well:     #e8ecef; /* subordinate island set INTO the flow: code, figure media, callout. Recessed a touch below paper, no shadow (a panel lifts, a well recedes). Holds strong / body / code text at WCAG AA (ink-2 7.6, body 4.6); muted text stays on paper/panel, not here. */
  --line:     #e3e6ea; /* hairline border, default */
  --line-2:   #d5d9df; /* stronger hairline, dividers */
  --line-fine:#edf0f3; /* finer/lighter hairline for schematic rules */
  --scrim:    rgba(20, 21, 26, 0.46); /* dimming wash behind an overlay; theme-aware (dark gets a pure-black, higher-alpha override) */
  --tick:     #c7ccd3; /* minor ruler ticks */
  --tick-2:   #adb2ba; /* major ruler ticks */
  /* Event-marker drop-line colour — the quiet vertical a bar-chart annotation
     drops from its word to the bar it marks. A dedicated semantic name (not a raw
     --line). RF's intent for this indicator, verbatim, is "a very faint thin line";
     the meaning is carried by the WORD directly above it (colour is never alone), so
     the line stays a faint position tick, never a high-contrast rule. Aliased to
     --tick-2, the chart's own MAJOR-RULER-TICK strength (#adb2ba light / #565d66
     dark), so an event marker reads as one of the chart's quiet ruler lines, at home
     with the axis and grid rather than louder than them. It is faintest where it
     matters least (over a filled bar) and clearest over the open plot ground above
     the bar, exactly where the eye picks up the drop from the word. A single flat
     token cannot ALSO clear WCAG's 3:1 non-text floor over every possible bar fill;
     that floor does not bind here because the line conveys no information on its own
     (the word and the screen-reader summary carry the event), so honouring RF's
     faint-hairline intent is correct over forcing a heavier mark. Re-resolves per
     theme and in print (--tick-2 restates in the forced light palette). */
  --chart-mark: var(--tick-2);

  /* Signal accent — Cabin navy. LIVE / NOW / RECOMMENDED wayfinding only.
     Never a status. Never a content fill. */
  --signal:      #294a78; /* primary accent — navy with gravitas */
  --signal-deep: #1d3458; /* pressed / deep accent */
  --signal-soft: #eaedf3; /* faint accent wash (active row, selected) */
  --signal-line: #c6cfe0; /* accent hairline */

  /* Keyboard-focus ring — system-wide :focus-visible colour. Derived from
     --signal so it stays in the one accent family; calm but clearly visible.
     Use as: outline: 2px solid var(--focus); outline-offset: 2px. */
  --focus: color-mix(in srgb, var(--signal) 70%, transparent);

  /* Status — cleared / sage green */
  --ok-ink:  #3f5e4d;
  --ok-bg:   #eaeeeb;
  --ok-line: #d4ddd6;
  --ok-dot:  #5e8468;

  /* Status — in progress / muted ochre (warm, but chroma held to sage's level
     so it never reads as candy amber). */
  --warn-ink:  #7a611c;
  --warn-bg:   #efeae1;
  --warn-line: #ddd5c4;
  --warn-dot:  #9b8350;

  /* Status — blocked / muted clay (warm-red dialled back to the one muted level) */
  --blk-ink:  #74463b;
  --blk-bg:   #efe7e3;
  --blk-line: #ddccc6;
  --blk-dot:  #9f6356;

  /* Status — not started / cool stone (clearly neutral). Ink darkened from
     #6f7177 so the small "not started" word and the journey process kicker
     clear WCAG AA on their own --na-bg tint (was 4.06, now 5.16). */
  --na-ink:  #5f6166;
  --na-bg:   #eceae6;
  --na-line: #d9d7cf;
  --na-dot:  #abb0b4;

  /* Live-state beacon — the status triad for the animated indicator
     (sphere / status dot). A notch livelier than the muted chip dots on purpose:
     this is the one moving "how are things" signal. Still held off candy so it
     sits in the same calm family as the chip dots, never out-shouting --signal. */
  --state-ok:   #538d66; /* healthy / on track */
  --state-warn: #a88948; /* waiting / in progress */
  --state-blk:  #a85348; /* needs action now */

  /* Data-series ramp — a navy-to-faint sequence for share/stacked composition
     only (never status). Anchored on --signal so it stays in the one accent family. */
  --data-1: var(--signal);
  --data-2: #7a8fab;
  --data-3: #a9b7c9;
  --data-4: #d0d7e1;

  /* Semantic aliases — the names a few components happen to read; not a
     preferred layer. Most components read the raw ramp directly. */
  --text-body:    var(--body);
  --text-muted:   var(--mut);
  /* --text-faint is the alias that VISIBLE faint-weight labels read (kickers,
     column heads, measure captions). It must clear WCAG AA, so it resolves to
     --mut (5.14 light / 4.58 dark on panel), NOT the raw --faint (2.12 light /
     2.58 dark — fails AA). --faint stays for genuinely non-text or disabled
     uses only (placeholders, disabled caret/knob, the data-ramp tail). */
  --text-faint:   var(--mut);

  --surface-page:  var(--paper);

  --accent:        var(--signal);
  --accent-deep:   var(--signal-deep);
  --accent-line:   var(--signal-line);
  /* Emphasis-accent TEXT (Recommended marker, selected tab/filter/sidenav,
     open accordion label, pressed segmented control, in-prose accent bold).
     Distinct from --signal-deep (the pressed/deep FILL/border accent): as TEXT,
     --signal-deep clears WCAG AA in light but fails it on the dark surface
     (~3.8-4.2:1), so this alias flips to the brighter --signal in every dark
     path (explicit data-theme="dark" AND OS-driven auto-dark below). Fills and
     borders keep reading --signal-deep directly. */
  --accent-text:   var(--signal-deep);
  --on-accent:     #ffffff; /* text / glyphs on a signal-filled surface (button, live badge) */
}

/* Default paper is COOL "future-white" (the :root values above): a calm, precise,
   low-chroma near-white that holds the blue glass and the signal orb without a
   warm cast fighting them. data-theme="cool" is the default. The only theme
   override is DARK, below. */

/* DARK theme — the full ramp re-declared for a near-black cool graphite surface.
   "Always implement light AND dark." Every text colour resolves to the inverted
   ink ramp, surfaces to dark paper/panel/lines, status keeps the one muted level
   (lightened to read on dark, dot+word still paired), the one accent stays navy
   but lifts so it reads as light wayfinding on dark. Contrast verified:
   ink 15:1, ink-2 10:1, body 6.8:1, signal 6.7:1, status dots 4.8-6:1.
   Glass / mesh dark overrides live in tokens/mesh.css.

   SELECTION CONTRACT — which theme a page gets:
   • Explicit wins. A page that sets data-theme on <html> ("cool" or "dark")
     is PINNED to that theme; the reader's OS preference is ignored. This is the
     catalog's own behaviour (the runtime always sets data-theme).
   • No attribute = follow the OS. A page that sets NO data-theme auto-darkens
     when the reader's OS is in dark mode, via the @media block below, and stays
     light otherwise. This is the default for a static report or landing page
     handed to a person.
   The @media :root:not([data-theme]) ramp below MUST stay value-identical to this
   [data-theme="dark"] ramp. Plain CSS has no way to reference a declaration set,
   so the two are a synchronized pair: change one, change the other. */
[data-theme="dark"] {
  /* Native UI follows the theme: paint dark scrollbars / form controls, not the
     browser-default bright ones. Twin of the light-path color-scheme in :root. */
  color-scheme: dark;

  /* Ink ramp — light text, darkest (strongest) to faintest */
  --ink:#e9ebee; --ink-2:#c2c6cc; --body:#9aa0a8; --mut:#828892; --faint:#5a606a;

  /* Surfaces — near-black cool graphite paper, lifted panels, dark hairlines */
  --paper:#15171a; --panel:#1d2024; --well:#101216;
  --line:#2c3036; --line-2:#3a3f47; --line-fine:#23262b; --tick:#3f454d; --tick-2:#565d66;
  /* Scrim: pure black at higher alpha. The dark page is already near-black, so the
     light-theme near-black tint would not separate the dimmed area from the panel. */
  --scrim:rgba(0, 0, 0, 0.76);

  /* Signal accent — navy lifted to read as light wayfinding on dark.
     --focus inherits this via color-mix, no separate override needed. */
  --signal:#7da0d4; --signal-deep:#5e82bd; --signal-soft:#202833; --signal-line:#34415a;
  /* Emphasis-accent TEXT flips to the brighter --signal on dark (AA: ~5.6:1);
     --signal-deep stays the FILL/border accent. */
  --accent-text:var(--signal);
  --on-accent:#0f1114; /* dark glyphs on a lifted-navy fill */

  /* Status — cleared / sage (lightened, chroma held to the one muted level) */
  --ok-ink:#a6c9b0; --ok-bg:#222f26; --ok-line:#394c3e; --ok-dot:#6eaa7e;
  /* Status — in progress / muted ochre */
  --warn-ink:#ccbfa4; --warn-bg:#302b21; --warn-line:#4d4638; --warn-dot:#b09b6d;
  /* Status — blocked / muted clay */
  --blk-ink:#cea9a1; --blk-bg:#342623; --blk-line:#513d38; --blk-dot:#b87d6f;
  /* Status — not started / stone */
  --na-ink:#acb2b9; --na-bg:#282b2f; --na-line:#3f454a; --na-dot:#8c949b;

  /* Live-state beacon — a notch livelier than the chip dots, still off candy */
  --state-ok:#69b582; --state-warn:#bfa469; --state-blk:#c37065;

  /* Data-series ramp — anchored on the lifted signal, fading to faint on dark */
  --data-2:#5c7195; --data-3:#46556e; --data-4:#333d4d;

  /* Ink on the dark glass surface lives in tokens/mesh.css (single owner). */
}

/* OS-driven auto dark — honour the reader's operating-system preference for any
   page that has NOT pinned a theme. An explicit data-theme always wins because
   [data-theme="dark"] above and the [data-theme="cool"]-implied :root defaults
   both carry the attribute, so :root:not([data-theme]) excludes them. This is the
   synchronized twin of the [data-theme="dark"] ramp above. Keep the values identical. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    /* Native UI follows the theme (see the [data-theme="dark"] twin). Keep identical. */
    color-scheme: dark;

    /* Ink ramp — light text, darkest (strongest) to faintest */
    --ink:#e9ebee; --ink-2:#c2c6cc; --body:#9aa0a8; --mut:#828892; --faint:#5a606a;

    /* Surfaces — near-black cool graphite paper, lifted panels, dark hairlines */
    --paper:#15171a; --panel:#1d2024; --well:#101216;
    --line:#2c3036; --line-2:#3a3f47; --line-fine:#23262b; --tick:#3f454d; --tick-2:#565d66;
    --scrim:rgba(0, 0, 0, 0.76);

    /* Signal accent — navy lifted to read as light wayfinding on dark */
    --signal:#7da0d4; --signal-deep:#5e82bd; --signal-soft:#202833; --signal-line:#34415a;
    /* Emphasis-accent TEXT flips to the brighter --signal on dark (AA: ~5.6:1);
       --signal-deep stays the FILL/border accent. */
    --accent-text:var(--signal);
    --on-accent:#0f1114;

    /* Status — cleared / sage */
    --ok-ink:#a6c9b0; --ok-bg:#222f26; --ok-line:#394c3e; --ok-dot:#6eaa7e;
    /* Status — in progress / muted ochre */
    --warn-ink:#ccbfa4; --warn-bg:#302b21; --warn-line:#4d4638; --warn-dot:#b09b6d;
    /* Status — blocked / muted clay */
    --blk-ink:#cea9a1; --blk-bg:#342623; --blk-line:#513d38; --blk-dot:#b87d6f;
    /* Status — not started / stone */
    --na-ink:#acb2b9; --na-bg:#282b2f; --na-line:#3f454a; --na-dot:#8c949b;

    /* Live-state beacon */
    --state-ok:#69b582; --state-warn:#bfa469; --state-blk:#c37065;

    /* Data-series ramp */
    --data-2:#5c7195; --data-3:#46556e; --data-4:#333d4d;
  }
}

/* ===== tokens/typography.css ===== */
/* Foundational — Typography tokens
   System neutral sans. No web fonts (offline, timeless, ages well).
   No monospace as a reading or display look (reads sci-fi); mono is for code content only (see docs/USAGE.md). No serif.
   Hierarchy comes from WEIGHT (400 body / 600 headings+labels) and SIZE only. */

:root {
  --font-sans: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif;

  /* Weights — only two */
  --weight-body:    400;
  --weight-strong:  600;

  /* Type scale (px) — calm, limited steps */
  --text-svg-micro: 9px; /* in-SVG axis/donut micro-labels only; one notch below the chrome floor */
  --text-micro: 10px; /* tiny chrome only: tooltip values, meter flags. Not for reading copy. */
  --text-2xs:  11px;
  --text-xs:   12px;
  --text-sm:   13px;
  --text-base: 15px;
  --text-md:   17px;
  --text-lg:   20px;
  --text-xl:   25px;
  --text-2xl:  31px;
  --text-3xl:  39px;

  /* Line heights */
  --leading-tight:   1.2;
  --leading-snug:    1.35;
  --leading-normal:  1.5;
  --leading-relaxed: 1.6;

  /* Tracking — single source of truth for letter-spacing across the system.
     Label tracking has three steps (the uppercase-label voice was drifting to
     0.07 / 0.12 / 0.14 raw across parts/ and specimens; these tokens hold it). */
  --tracking-tighter: -0.04em; /* big display figures (countdown, donut centre) */
  --tracking-figure:  -0.03em; /* mid big-figure tightening (hero figure, KPI figure) */
  --tracking-tight:   -0.01em; /* large headlines */
  --tracking-normal:  0;
  --tracking-label:        0.07em; /* default uppercase label tracking */
  --tracking-label-loose:  0.12em; /* wider eyebrows / app-bar labels */
  --tracking-label-wide:   0.14em; /* widest tracked micro-labels */

  /* Label treatment — uppercase, tracked. Derives from the tracking scale so
     label tracking lives in exactly one place (no duplicate 0.07em literal). */
  --label-size:    var(--text-xs);
  --label-spacing: var(--tracking-label);
  --label-weight:  var(--weight-strong);

  /* Display / figure type — off the reading scale, for the one big number.
     Tokenized so the countdown and large figures retune with the system. */
  --text-display: 56px; /* large countdown / hero figure */
  --text-figure:  22px; /* donut centre value, mid-figure */
}

/* ===== tokens/spacing.css ===== */
/* Foundational — Spacing, radii, shadow, motion tokens
   Spacing on a small consistent scale. Don't hand-place px. */

:root {
  /* Spacing scale (4px base) */
  --space-0:  0;
  --space-1:  4px;
  --space-2:  8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  20px;
  --space-6:  24px;
  --space-8:  32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;
  --space-20: 80px;

  /* Radii */
  --radius-sm:   10px;
  --radius-md:   14px;
  --radius-lg:   20px;
  --radius-pill: 999px;

  /* Hairline width */
  --hair: 1px;

  /* Signature dot diameter (status + now dots) */
  --dot: 7px;

  /* Derived micro-dimensions & patterns for rendered and enhanced blocks
     (pacing, vintage, line chart, ledger). Each off-scale raw value lives here
     ONCE, named, so the blocks stay tokens-only and a change propagates from a
     single source. */
  --stroke-2: 2px;         /* a double-hairline: the vintage most-recent-forecast box; also focus-ring width/offset + line-chart legend rule */
  --legend-sw-w: 18px;     /* legend swatch width (pacing + vintage + line chart) */
  --legend-sw-h: 10px;     /* bar-style legend swatch height (pacing fills) */
  --legend-sw-h-box: 14px; /* cell-style legend swatch height (vintage cells, pacing target) */
  --legend-sw-r: 3px;      /* legend swatch corner */
  --vintage-min-w: 460px;  /* min table width before it scrolls horizontally on a phone */
  --caret-half: 4px;       /* caret triangle half-base (the ledger section toggle's CSS-border caret) */
  --caret-rise: 5px;       /* caret triangle height (the same caret) */

  /* Page container — the canonical content max-width (one source for page + app bar). */
  --page-max: 1180px;
  /* Reading-column frame for a composed READING page (document, decision,
     briefing): the one-width column plus its side padding (780px + 2 x space-8).
     Ratified 2026-07-06: a reading page carries ONE content width - prose, cards,
     tables and callouts all share both edges; the two-width layout (narrow prose
     inside wider furniture) read as imbalance. App surfaces (the dashboard) keep
     the wide --page-max frame: they are instruments, not reading columns. */
  --page-measure: 844px;
  /* Sticky app-bar height. An in-page anchor target (a section) leaves this much
     clearance above itself (scroll-margin-top) so a scrolled-to section is not
     hidden under the sticky bar. Tokenised so the value is not a raw magic number. */
  --appbar-clearance: 88px;

  /* Reading measures — the river's lane widths. Ratified 2026-07-06: the measure
     and wide lanes are the SAME 780px, because a reading page carries one content
     width (the one-width doctrine above); the lanes stay as named hooks so a page
     that ever needs a distinct wide lane changes one token, not its markup. Pixels,
     not ch: the column must equal the card/table width exactly, which is a page
     geometry, not a type metric. 780px sits at the wide edge of the reading-column
     convention (Confluence 760, Stripe ~760, Notion ~710/900) because our column
     also carries tables and cards. The lede stays shorter under a display figure. */
  --measure:      780px;  /* the one reading-page content width */
  --measure-wide: 780px;  /* same width: one-width doctrine (hook kept) */
  --measure-lede: 620px;  /* a lede line under a statement's display figure */
  --rail-aside:   18rem;  /* the sticky companion beside a main reading column */

  /* Shadows — soft, low, navy-tinted to match the glass header's light. */
  --shadow-flat:  0 1px 2px rgba(20, 21, 26, 0.04);
  --shadow-card:  0 1px 2px rgba(20, 21, 26, 0.04), 0 10px 30px -14px rgba(30, 42, 72, 0.14);
  --shadow-float: 0 1px 2px rgba(20, 21, 26, 0.04), 0 16px 40px -16px rgba(30, 42, 72, 0.20);
  /* Sticky app-bar: top hairline + a low drop, shown only once stuck. */
  --shadow-bar:   0 1px 0 var(--glass-hair), 0 8px 24px -18px rgba(30, 42, 72, 0.28);

  /* Glass — frosted veil for the floating / alive layer only.
     Surface, hair, edge, frost and glow tokens live in tokens/mesh.css. */

  /* Motion. Calm digital easing, no bounce on content.
     The motion CHARTER (docs/USAGE.md, "Motion charter: three verbs, then
     stillness"): light breathes, feedback answers, a value arrives, and nothing
     else moves. Each token below is tagged with the one verb it PRIMARILY serves
     (@motion ...). A couple of curves are shared, so the tag names the primary
     intent, not the only use; the lint (build/motion-lint.mjs) is what enforces the
     charter. The tag just puts the doctrine where an author reads the token. */
  --ease-out:   cubic-bezier(0.22, 0.61, 0.36, 1); /* @kind other */ /* @motion feedback-answers. the shared calm "out" curve */
  --ease-inout: cubic-bezier(0.45, 0.05, 0.2, 1); /* @kind other */ /* @motion light-breathes. the ambient loop curve (breathe, mesh, skeleton, progress) */
  /* Spring: one gentle overshoot then settle, never a bounce. The curve rises
     fast, edges a few percent past its target, and eases back to rest. Reserved
     for a value ARRIVING (a progress fill growing in), so it reads snappy while
     staying calm and precise. A cubic-bezier can only overshoot once, so there
     is no oscillation. */
  --ease-spring: cubic-bezier(0.34, 1.26, 0.64, 1); /* @kind other */ /* @motion value-arrives. reserved for a value arriving */
  --dur-snap:   120ms; /* @kind other */ /* @motion feedback-answers. instant hover/feedback micro-transition */
  --dur-fast:   140ms; /* @kind other */ /* @motion feedback-answers */
  --dur-base:   220ms; /* @kind other */ /* @motion feedback-answers */
  --dur-slow:   420ms; /* @kind other */ /* @motion value-arrives. a value's arrival (the meter fill grows to this over --dur-slow) */
  /* Loading / ambient loops breathe slowly and never hurry: they ride the mesh
     and hero cadence, not the functional micro-transition scale. */
  --dur-loading: 2.4s; /* @kind other */ /* @motion light-breathes. ambient / loading loop cadence */
  --dur-wait:    1.5s; /* @kind other */ /* @motion value-arrives. in-flight: indeterminate progress slide, snappier than the ambient loop */
}

/* ===== tokens/mesh.css ===== */
/* Foundational — living mesh tokens for the GLASS layer (header banner / hero / app bar).
   A slow-drifting pastel-blue gradient mesh: blobs multiply onto the near-white
   glass surface, heavy blur, each on its own slow keyframe.
   prefers-reduced-motion freezes all of it. */

:root {
  /* --- Shared glass surface tokens (canonical handoff values).
     Used by BOTH the mesh header/hero (parts/glass.css) and the .lg overlay
     (parts/overlay.css) so all glass in the system speaks one material language. --- */
  --glass-surface:   #fbfcfb; /* the panel itself reads almost white */
  --glass-hair:      rgba(120, 118, 110, 0.20); /* hairline border */
  --glass-edge:      rgba(255, 255, 255, 0.80); /* bright top light-catching edge */
  --glass-shadow:    0 1px 2px rgba(20, 21, 26, 0.05), 0 16px 40px -16px rgba(30, 42, 72, 0.22);
  --glass-frost-1:   rgba(251, 252, 251, 0.30);
  --glass-frost-2:   rgba(251, 252, 251, 0.52);
  --glass-frost-blur: 6px;
  --glass-glow:      color-mix(in srgb, var(--signal) 12%, transparent); /* faint signal light, cursor-aware — derived from --signal */

  /* --- Glass overlay (.lg): the canonical floating-glass surface. Real edge
     REFRACTION (the background bends at the rounded rim, centre stays clear),
     driven by liquid-glass.js; frosted-blur fallback on Safari/Firefox.
     Use for nav bars, docks, status pills, hovering cards. --- */
  --glass-tint:    rgba(247, 246, 242, 0.42); /* surface fill — lower alpha = clearer (canonical handoff value) */
  --glass-tint-solid: #f7f6f2;                /* opaque .lg fill where backdrop-filter is unsupported (the solid form of --glass-tint, mirrors --tip-bg-solid) */
  --glass-spec:    rgba(255, 255, 255, 0.55); /* specular highlight along the top edge */
  --glass-rim-top: rgba(255, 255, 255, 0.90); /* crisp lit top edge — glass caught from above */
  --glass-rim-bot: rgba(255, 255, 255, 0.22); /* faint light catch on the bottom inner edge */
  --glass-inner:   rgba(255, 255, 255, 0.18); /* faint inner hairline so the rim reads over busy content */
  --glass-fallback-blur: 14px;                /* frosted-blur amount where SVG refraction is unsupported */
  --glass-sat:           1.5;                 /* @kind other */ /* backdrop saturation behind the glass (refraction + fallback) */

  /* hover data box — standardized white-glass tooltip with edge refraction */
  --tip-bg:       rgba(255, 255, 255, 0.62);
  --tip-bg-solid: #ffffff;                  /* fallback where backdrop-filter is unsupported */
  --tip-border:   rgba(255, 255, 255, 0.85);
  --tip-rim-top:  rgba(255, 255, 255, 0.95);
  --tip-rim-bot:  rgba(255, 255, 255, 0.45);
  --tip-blur:     9px;
  --tip-sat:      155%;

  /* Header mesh — pastel blue, multiplied onto the near-white surface. Saturated
     enough to read as visible ambient blobs through the frost veil. */
  --gmesh-blob-1: #93b3e6;
  --gmesh-blob-2: #b3cbf0;
  --gmesh-blob-3: #9dbeee;
  --gmesh-blob-4: #a8c5ef;
  --gmesh-blur:   70px;
  --gmesh-blend:  multiply; /* @kind other */

  /* Dark ink reads on the light glass */
  --on-glass-ink:   #242a33;
  --on-glass-body:  #565a62;
  --on-glass-muted: #888b92;

  /* --- Horizon: the ambient living-light field carried across the canvas ---
     A present-but-calm signal light that drifts autonomously: enough to read as a soft
     living glow, never a spotlight, and it does not track the cursor (only the hero
     panel does). The field tints the page; the bloom lights the ONE element that
     matters (live step, accent figure). Restraint is the rule. */
  --horizon-glow:  color-mix(in srgb, var(--signal) 10.5%, transparent); /* @kind color — derived from --signal; brightened from 6% to a present-and-calm ambient light, RF-authorised 2026-07-03 */
  --horizon-tint:  color-mix(in srgb, var(--signal) 3.8%, transparent); /* @kind color — derived from --signal */
  --horizon-bloom: color-mix(in srgb, var(--signal) 55%, transparent);  /* @kind color — derived from --signal */
  /* Live-now bloom — the soft breathing glow on the ONE element that is live now
     (the lit node, the active step). One source of truth shared by .fd-lit,
     the journey now-node, and the pane now-node so the signature cannot drift. */
  --bloom-now: 0 0 28px -8px var(--horizon-bloom);
}

/* DARK theme — the glass material re-stated for a dark surface. The mesh now
   ADDS light (screen) on dark instead of multiplying it away, blobs become deep
   signal-navy ambient light, frost/edge invert to a faint light catch over a
   dark translucent panel. Keeps "glass floats, paper reads" in the dark. */
[data-theme="dark"] {
  --glass-surface:   #1f242c; /* dark translucent-reading panel */
  --glass-hair:      rgba(180, 192, 214, 0.16); /* faint light hairline */
  --glass-edge:      rgba(210, 222, 244, 0.22); /* soft top light-catching edge */
  --glass-shadow:    0 1px 2px rgba(0, 0, 0, 0.40), 0 16px 40px -16px rgba(0, 0, 0, 0.55);
  --glass-frost-1:   rgba(24, 28, 35, 0.30);
  --glass-frost-2:   rgba(24, 28, 35, 0.55);
  --glass-glow:      color-mix(in srgb, var(--signal) 17%, transparent); /* G5: raised from the :root 12% so the cursor-aware hero glow clears the dark perception floor (dark renders ~20-25% fainter) */

  /* Overlay (.lg) on dark */
  --glass-tint:    rgba(26, 31, 40, 0.46);
  --glass-tint-solid: #1a1f28;               /* opaque .lg fill where backdrop-filter is unsupported (solid form of dark --glass-tint) */
  --glass-spec:    rgba(210, 222, 244, 0.20);
  --glass-rim-top: rgba(210, 222, 244, 0.30);
  --glass-rim-bot: rgba(210, 222, 244, 0.08);
  --glass-inner:   rgba(210, 222, 244, 0.08);

  /* tooltip glass on dark */
  --tip-bg:       rgba(30, 35, 44, 0.72);
  --tip-bg-solid: #1f242c;
  --tip-border:   rgba(180, 192, 214, 0.22);
  --tip-rim-top:  rgba(210, 222, 244, 0.28);
  --tip-rim-bot:  rgba(210, 222, 244, 0.08);

  /* Mesh — deep signal-navy blobs that ADD light on the dark surface */
  --gmesh-blob-1: #2f4a78;
  --gmesh-blob-2: #3a5891;
  --gmesh-blob-3: #324f80;
  --gmesh-blob-4: #2a4470;
  --gmesh-blend:  screen; /* @kind other — add light on dark, not multiply */

  --on-glass-ink:   #e6e9ee;
  --on-glass-body:  #b0b6bf; /* AA-compliant on-glass supporting ink over the dark mesh worst case (was #aab0b9, sub-AA behind the brightest navy blob) */
  --on-glass-muted: #7d838c;
}

/* OS-driven auto dark for the glass material — twin of the [data-theme="dark"]
   block above, applied to any page that has NOT pinned a theme so the glass
   darkens in lock-step with the colour ramp (tokens/colors.css). Without this,
   an unattributed OS-dark page would render dark text on a LIGHT glass panel.
   Explicit data-theme still wins (the :not([data-theme]) gate excludes it).
   Keep these values identical to the [data-theme="dark"] block above. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --glass-surface:   #1f242c;
    --glass-hair:      rgba(180, 192, 214, 0.16);
    --glass-edge:      rgba(210, 222, 244, 0.22);
    --glass-shadow:    0 1px 2px rgba(0, 0, 0, 0.40), 0 16px 40px -16px rgba(0, 0, 0, 0.55);
    --glass-frost-1:   rgba(24, 28, 35, 0.30);
    --glass-frost-2:   rgba(24, 28, 35, 0.55);
    --glass-glow:      color-mix(in srgb, var(--signal) 17%, transparent); /* G5: raised from the :root 12% so the cursor-aware hero glow clears the dark perception floor (dark renders ~20-25% fainter) */

    /* Overlay (.lg) on dark */
    --glass-tint:    rgba(26, 31, 40, 0.46);
    --glass-tint-solid: #1a1f28;
    --glass-spec:    rgba(210, 222, 244, 0.20);
    --glass-rim-top: rgba(210, 222, 244, 0.30);
    --glass-rim-bot: rgba(210, 222, 244, 0.08);
    --glass-inner:   rgba(210, 222, 244, 0.08);

    /* tooltip glass on dark */
    --tip-bg:       rgba(30, 35, 44, 0.72);
    --tip-bg-solid: #1f242c;
    --tip-border:   rgba(180, 192, 214, 0.22);
    --tip-rim-top:  rgba(210, 222, 244, 0.28);
    --tip-rim-bot:  rgba(210, 222, 244, 0.08);

    /* Mesh — deep signal-navy blobs that ADD light on the dark surface */
    --gmesh-blob-1: #2f4a78;
    --gmesh-blob-2: #3a5891;
    --gmesh-blob-3: #324f80;
    --gmesh-blob-4: #2a4470;
    --gmesh-blend:  screen;

    --on-glass-ink:   #e6e9ee;
    --on-glass-body:  #b0b6bf; /* AA-compliant on-glass supporting ink over the dark mesh worst case (was #aab0b9, sub-AA behind the brightest navy blob) */
    --on-glass-muted: #7d838c;
  }
}

/* Mesh blob drift — translate + scale, long slow loops.
   Defined here so both header and sphere reuse them. */
@keyframes mesh-drift-1 {
  0%   { transform: translate(0%, 0%)      scale(1.00); }
  50%  { transform: translate(8%, 6%)      scale(1.18); }
  100% { transform: translate(0%, 0%)      scale(1.00); }
}
@keyframes mesh-drift-2 {
  0%   { transform: translate(0%, 0%)      scale(1.10); }
  50%  { transform: translate(-10%, 8%)    scale(0.92); }
  100% { transform: translate(0%, 0%)      scale(1.10); }
}
@keyframes mesh-drift-3 {
  0%   { transform: translate(0%, 0%)      scale(1.00); }
  50%  { transform: translate(6%, -8%)     scale(1.22); }
  100% { transform: translate(0%, 0%)      scale(1.00); }
}
@keyframes mesh-drift-4 {
  0%   { transform: translate(0%, 0%)      scale(1.05); }
  50%  { transform: translate(-7%, -6%)    scale(0.95); }
  100% { transform: translate(0%, 0%)      scale(1.05); }
}

@media (prefers-reduced-motion: reduce) {
  .mesh-blob { animation: none !important; }
}
/* B2 still mode twin (kept identical to the reduced-motion rule by lint-still-mode.mjs) */
:root[data-still] .mesh-blob { animation: none !important; }

/* ===== tokens/base.css ===== */
/* Foundational — base resets shared by cards and kits.
   Minimal; tokens carry the design. */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: var(--weight-body);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--text-body);
  background: var(--surface-page);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ===== parts/surface-fill.css ===== */
/* Foundational — the panel fill, declared once.
   A large family of components sits on the panel token: form controls, nav
   surfaces, chips, decision options, flow and journey nodes, timeline dots, the
   drawer and popover, the alert and toast. Each paints the SAME background
   (var(--panel)); only its border, radius and shadow differ (those stay with the
   component). Rather than copy that one panel-token fill into thirty-odd rules,
   it is declared ONCE here, as one grouped selector list.

   WHY THIS FILE LOADS FIRST. This is the base fill every listed component's own
   states and modifiers layer over: a journey node's per-type tint, a toggle's
   on/disabled wash, a decision option's selected wash, a link's hover. Declared
   before any of them (first part in build/styles.manifest.css), it is the lowest
   layer, so every later state or modifier (later in source, or higher in
   specificity) still wins exactly as it did when each rule carried its own fill.
   The consolidation is therefore pixel-for-pixel identical (snapshot-proven), not
   a restyle. A single grouped declaration also means a future change to how a
   panel is filled is made in one place, not hunted across eleven files.

   NOT here: `.fdn-card`. The public card carries the full panel recipe (fill,
   hairline, radius, shadow) in its own home (parts/composition.css), positioned
   after the decision option, flow node and tooltip so it wins an equal-specificity
   tie against them. Folding its fill into this first-loading group would move it
   BEFORE those and silently flip that order, so the card is deliberately excluded.
   `.fd-surface` / `.fd-pane` (the internal panel recipe) keep their border, radius
   and shadow in parts/pane.css and take only their fill from here. */

.fd-surface, .fd-pane,
.fd-dec__opt, .fd-node,
.fdn-seg, .fdn-tl-node__dot, .fdn-road__nowlabel, .fdn-scatter-dot--plain, .fdn-dep__node,
.fdn-btn--secondary,
.fdn-input, .fdn-select, .fdn-menu, .fdn-toggle, .fdn-control,
.fdn-pager__step, .fdn-pager__step[aria-disabled="true"], .fdn-sidenav__link:hover, .fdn-sidenav__tag,
.fdn-alert, .fdn-toast,
.fdn-code__copy, .fdn-code__pre, .fdn-figure__tag,
.fd-tag,
.fdn-journey__node, .fdn-journey__elabel, .fdn-journey__loop-label, .fdn-journey__pathbtn,
.fdn-drawer, .fdn-pop__trigger, .fdn-popover, .fdn-popover::before {
  background: var(--panel);
}

/* ===== parts/schematic.css ===== */
/* Foundational — schematic: a quiet precision vocabulary of thin lines.
   Registration corner marks, ruler ticks, fine rules, crosshair junctions, a
   faint measure grid. The "from the future" feeling comes from delicate,
   measured hairlines — NOT blueprint fonts or sci-fi type. Use sparingly:
   one or two of these per surface, never a full schematic overlay. */

/* --- Rules: thin dividers ------------------------------------------------- */
.fd-rule        { height: var(--hair); border: 0; margin: 0; background: var(--line); }
.fd-rule--fine  { background: var(--line-fine); }
.fd-rule--strong{ background: var(--line-2); }

.fd-vrule       { width: var(--hair); align-self: stretch; background: var(--line); }
.fd-vrule--fine { background: var(--line-fine); }

/* --- Registration corner marks ------------------------------------------- */
/* Wrap a surface in .fd-frame, then drop four bracket spans inside it:
   <i class="fd-corner fd-corner--tl"></i> … tr / bl / br.
   The signature precision detail: an L-bracket in fine hairline at each corner. */
.fd-frame  { position: relative; }
.fd-corner { position: absolute; width: 12px; height: 12px; color: var(--line-2); pointer-events: none; }
.fd-corner::before,
.fd-corner::after { content: ""; position: absolute; background: currentColor; }
.fd-corner::before { width: 100%; height: var(--hair); }   /* horizontal arm */
.fd-corner::after  { width: var(--hair); height: 100%; }   /* vertical arm   */
.fd-corner--tl { top: -1px; left: -1px;  }
.fd-corner--tl::before { top: 0; left: 0; }   .fd-corner--tl::after { top: 0; left: 0; }
.fd-corner--tr { top: -1px; right: -1px; }
.fd-corner--tr::before { top: 0; right: 0; }  .fd-corner--tr::after { top: 0; right: 0; }
.fd-corner--bl { bottom: -1px; left: -1px; }
.fd-corner--bl::before { bottom: 0; left: 0; } .fd-corner--bl::after { bottom: 0; left: 0; }
.fd-corner--br { bottom: -1px; right: -1px; }
.fd-corner--br::before { bottom: 0; right: 0; } .fd-corner--br::after { bottom: 0; right: 0; }

/* --- Ruler tick scale ----------------------------------------------------- */
/* A measured baseline: a thin top rule with minor ticks every 12px and major
   ticks every 60px. Reads "instrument", not "blueprint". */
.fd-tickrule {
  height: 10px;
  border-top: var(--hair) solid var(--line-2);
  background-image:
    repeating-linear-gradient(to right, var(--tick-2) 0, var(--tick-2) var(--hair), transparent var(--hair), transparent 60px),
    repeating-linear-gradient(to right, var(--tick)   0, var(--tick)   var(--hair), transparent var(--hair), transparent 12px);
  background-repeat: no-repeat, no-repeat;
  background-size: 100% 10px, 100% 5px;
  background-position: top left, top left;
}
.fd-tickrule--down { transform: scaleY(-1); } /* ticks hang upward instead */

/* --- Crosshair junction --------------------------------------------------- */
/* A tiny plus mark for an intersection or a measured point. */
.fd-crosshair { position: relative; width: 10px; height: 10px; flex: none; color: var(--line-2); }
.fd-crosshair::before,
.fd-crosshair::after { content: ""; position: absolute; background: currentColor; }
.fd-crosshair::before { left: 50%; top: 0; width: var(--hair); height: 100%; transform: translateX(-50%); }
.fd-crosshair::after  { top: 50%; left: 0; height: var(--hair); width: 100%; transform: translateY(-50%); }

/* --- Faint measure grid --------------------------------------------------- */
/* Extremely subtle 24px grid. A background utility for a single framed area;
   never the whole page. */
.fd-grid {
  background-image:
    linear-gradient(to right,  var(--line-fine) var(--hair), transparent var(--hair)),
    linear-gradient(to bottom, var(--line-fine) var(--hair), transparent var(--hair));
  background-size: 24px 24px;
}

/* --- Measure label -------------------------------------------------------- */
/* A small tracked caption for schematic annotations (no monospace). */
.fd-measure {
  font-size: var(--text-2xs);
  font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-faint);
}

/* ===== parts/journey.css ===== */
/* Foundational — Journey: an expected-behaviour flow (steps, decisions, branches,
   loops) on a deterministic vertical spine. The hydrator ([data-flow] in
   fdn-behaviors.js) lays nodes out by longest forward path (rows) and a
   primary-stays / secondary-steps DFS (columns), then measures the rendered node
   cards and draws SVG wires between them. Loops lift into a right-hand return
   corridor and re-enter a straight trunk; on a narrow phone the whole thing
   does NOT reflow. It scales to fit the viewport (and pinch-zooms) so the spine,
   branches, and loops stay readable as one diagram.

   FDS discipline:
   - Tokens only; light (cool) and dark both re-resolve every value.
   - Node TYPE is carried by SHAPE + a printed WORD (Start / Step / Decision /
     End), never colour. A decision also reads as a question.
   - Exactly ONE node — the live "now" node — uses the single --signal accent
     (ring + "• Now" + a breathing bloom). Nothing else touches --signal.
   - Every branch edge and every loop carries a WORD chip; a back wire also reads
     one faint luminance step below the forward wire, but the word carries the
     meaning. */

/* --- Stage: the measured coordinate space ----------------------------------- */
/* overflow:hidden clips the scaled stage's unscaled layout box so a phone-width
   page never gains horizontal scroll (the stage's transform shrinks the visual,
   but its layout box keeps its natural pixel width). */
.fdn-journey { position: relative; width: 100%; max-width: 100%; overflow: hidden; }
/* the transform value (scale-to-fit on a phone) is set inline by the hydrator;
   the wrapper height is also set inline by the hydrator to match the scaled box. */
.fdn-journey__stage { position: relative; width: 100%; transform-origin: top left; }
/* hydrator sets an explicit px width + height on the stage in wide mode so the
   absolutely-positioned nodes and the SVG share one pixel coordinate space. */
.fdn-journey__wires {
  position: absolute; inset: 0; pointer-events: none; overflow: visible; z-index: 0;
}

/* --- Node card: ONE shape for every type; COLOUR (paired with the WORD) denotes
   type ----------------------------------------------------------------------
   Every node, every type, is the SAME rounded rectangle. Shape no longer carries
   type, so shape can no longer be inconsistent. The uppercase kicker WORD
   (Start / Step / Decision / End) stays on every card and is the SOURCE OF TRUTH:
   the type tint is a scan accelerator layered on the word, never alone. The
   measured -bg fills are nearly inseparable in luminance (and collapse in dark),
   so the fill cannot carry type; the -ink kicker word, high-contrast on the fill
   in both themes, carries it and reads in pure greyscale. */
.fdn-journey__node {
  position: absolute; box-sizing: border-box; z-index: 1;
  min-width: 92px; max-width: 176px;
  display: grid; gap: 1px;
  padding: var(--space-2) var(--space-3);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-flat);
  text-align: center; cursor: default;
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
}
.fdn-journey__node:hover,
.fdn-journey__node.is-hot { box-shadow: var(--shadow-card); }
.fdn-journey__node:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

.fdn-journey__type {
  font-size: var(--text-micro); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-label-loose); text-transform: uppercase;
  color: var(--text-faint);
}
.fdn-journey__label {
  color: var(--ink-2); font-weight: var(--weight-strong); font-size: var(--text-sm);
  line-height: 1.2; overflow-wrap: anywhere;
}

/* TYPE COLOUR: each type maps to ONE muted status family (4-token set that
   re-resolves in dark): -bg fill, -line border, -ink kicker word. --signal is
   used by NO type; it is reserved for the single live 'now' node. The WORD on
   the card is the source of truth, so colour here is never alone.
   start = sage/ok ("begin here"); process/Step = neutral/na (the most common
   type, so the spine stays calm); decision = ochre/warn ("choose");
   terminal/End = clay/blk ("stop here"). */
.fdn-journey__node--start    { background: var(--ok-bg);   border-color: var(--ok-line); }
.fdn-journey__node--start    .fdn-journey__type { color: var(--ok-ink); }
.fdn-journey__node--process  { background: var(--na-bg);   border-color: var(--na-line); }
.fdn-journey__node--process  .fdn-journey__type { color: var(--na-ink); }
.fdn-journey__node--decision { background: var(--warn-bg); border-color: var(--warn-line); }
.fdn-journey__node--decision .fdn-journey__type { color: var(--warn-ink); }
.fdn-journey__node--terminal { background: var(--blk-bg);  border-color: var(--blk-line); }
.fdn-journey__node--terminal .fdn-journey__type { color: var(--blk-ink); }

/* endpoints prefer a single line: a short ending word stays on one line because
   it fits inside max-width. A long ending (e.g. "Closed: cannot reproduce") is
   allowed to wrap at its spaces rather than spill past the border — overflow-wrap
   stays normal so it only ever breaks between words, never mid-word, and the box
   is the same rounded rectangle as every other type. (A hard white-space:nowrap
   here forced long endings onto one line and clipped them past the right wall.) */
.fdn-journey__node--start .fdn-journey__label,
.fdn-journey__node--terminal .fdn-journey__label { overflow-wrap: normal; }

/* --- The ONE live node — single --signal accent, breathing bloom on a layer --- */
.fdn-journey__node--now { border-color: var(--signal-line); }
.fdn-journey__node--now::after {
  content: ""; position: absolute; inset: calc(-1 * var(--hair)); border-radius: inherit; z-index: -1;
  pointer-events: none;
  box-shadow: 0 0 0 var(--hair) var(--signal-line), var(--bloom-now);
  animation: fd-breathe 6s var(--ease-inout) infinite;
}
.fdn-journey__node--now .fdn-journey__type { color: var(--signal); }
.fdn-journey__now {
  font-size: var(--text-2xs); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--signal);
}
.fdn-journey__now::before { content: "• "; }
@media (prefers-reduced-motion: reduce) {
  .fdn-journey__node--now::after { animation: none !important; }
}
/* B2 still mode twin (kept identical to the reduced-motion rule by lint-still-mode.mjs) */
:root[data-still] .fdn-journey__node--now::after { animation: none !important; }

/* Loop targets need no marker: the drawn back-wire and its word carry the return.
   The class is kept for hooks but renders nothing extra. */

/* --- Wires (SVG; colours must come from CSS) -------------------------------- */
.fdn-journey__wire {
  fill: none; stroke: var(--line-2); stroke-width: 1.5;
  stroke-linejoin: round; stroke-linecap: round;
}
/* the return/loop wire is a FAINTER SOLID stroke one clear luminance step below the
   forward --line-2 wire (same 1.5 weight). --line vs --line-2 reads as a two-step
   line ladder in both themes and in print; the loop-label WORD carries the meaning. */
.fdn-journey__wire--back { stroke: var(--line); }
.fdn-journey__arrowhead { fill: var(--line-2); }

/* --- Edge word chips (DOM, so a word always backs a branch) ----------------- */
.fdn-journey__elabel,
.fdn-journey__loop-label {
  position: absolute; transform: translate(-50%, -50%); z-index: 2;
  padding: 1px var(--space-2); border-radius: var(--radius-pill);
  border: var(--hair) solid var(--line);
  font-size: var(--text-2xs); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--mut); white-space: nowrap; pointer-events: none;
}
.fdn-journey__loop-label { border-color: var(--line-2); color: var(--ink-2); }

/* --- Path highlighting: dim the off-path subset, leave the path at full read ---
   A pure post-layout overlay toggled by the hydrator when the data carries a
   "paths" array. Identity is carried by the path's word chip in the control bar,
   NOT by colour on the diagram, so this uses NO --signal / --accent (those stay
   unique to the single "now" node). The signal here is contrast of presence:
   off-path elements recede to a low opacity, the chosen path stays present. */
.fdn-journey__wire, .fdn-journey__arrowhead,
.fdn-journey__elabel, .fdn-journey__loop-label {
  transition: opacity var(--dur-fast) var(--ease-out);
}
.fdn-journey--pathed .fdn-journey__node.is-off,
.fdn-journey--pathed .fdn-journey__wire.is-off,
.fdn-journey--pathed .fdn-journey__arrowhead.is-off,
.fdn-journey--pathed .fdn-journey__elabel.is-off,
.fdn-journey--pathed .fdn-journey__loop-label.is-off { opacity: 0.22; }
/* on-path nodes gain a hair more presence via a stronger shadow only; the type
   tint border stays (colour identity is orthogonal to path identity), and the
   off-path opacity-dim already carries the presence contrast. On-path identity is
   the path's control-bar word, never colour, so no accent is added here. */
.fdn-journey--pathed .fdn-journey__node:not(.is-off) { box-shadow: var(--shadow-card); }
/* on-path WIRES darken so the chosen route (its forward and loop wires alike)
   reads as one connected line instead of the default faint hairline */
.fdn-journey--pathed .fdn-journey__wire:not(.is-off) { stroke: var(--mut); }
.fdn-journey--pathed .fdn-journey__arrowhead:not(.is-off) { fill: var(--mut); }

/* The control bar: a row of word chips above the stage. A SIBLING of the stage,
   so it is never wiped or scaled; it lives outside the measured pixel space. */
.fdn-journey__paths { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-3); }
.fdn-journey__pathbtn {
  appearance: none; -webkit-appearance: none; font: inherit; cursor: pointer;
  padding: var(--space-1) var(--space-3);
  border: var(--hair) solid var(--line); border-radius: var(--radius-pill);
  color: var(--mut);
  font-size: var(--text-2xs); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-label); text-transform: uppercase; white-space: nowrap;
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.fdn-journey__pathbtn:hover { color: var(--ink-2); border-color: var(--line-2); }
.fdn-journey__pathbtn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
/* active chip: raised ink + stronger hairline. The word + this lift + aria-pressed
   all carry the active state; colour is never the sole signal. */
.fdn-journey__pathbtn.is-on { color: var(--ink-2); border-color: var(--line-2); }

@media (prefers-reduced-motion: reduce) {
  .fdn-journey__node, .fdn-journey__wire, .fdn-journey__arrowhead,
  .fdn-journey__elabel, .fdn-journey__loop-label, .fdn-journey__pathbtn { transition: none !important; }
}
/* B2 still mode twin (kept identical to the reduced-motion rule by lint-still-mode.mjs) */
:root[data-still] .fdn-journey__node, :root[data-still] .fdn-journey__wire, :root[data-still] .fdn-journey__arrowhead,
:root[data-still] .fdn-journey__elabel, :root[data-still] .fdn-journey__loop-label, :root[data-still] .fdn-journey__pathbtn { transition: none !important; }

/* --- Mobile: the SAME 2D diagram, scaled to fit -----------------------------
   No reflow, no stacking, no hidden wires. The hydrator renders the full wide
   diagram at every width and, when the natural diagram is wider than the phone,
   scales the whole stage down with an inline transform so the entire flow is
   visible at a glance. The reader pinch-zooms to read individual node labels.
   .fdn-journey { overflow: hidden } above clips the scaled stage's layout box so
   the page never gains horizontal scroll. No block-specific mobile rule is needed:
   the layout is driven by the hydrator's inline transform + height, and every
   value is already a token that re-resolves in both themes. */

/* DARK THEME — nothing block-specific: every value above is a token that
   re-resolves under [data-theme="dark"]. The forward wire (--line-2) and the
   fainter back wire (--line, one step below) both re-resolve to stay visible on
   the near-black dark --paper; --line-fine would sink into it, so neither wire
   uses it. */

/* ===== parts/horizon.css ===== */
/* Foundational — Horizon: the living-light layer extended across the canvas.
   The glass header proved the path: a calm ambient light that drifts. Horizon carries
   that quality, faintly, to the whole surface — and lets the ONE element that matters
   (an accent figure, the live step) catch the light and breathe. Restraint is the rule:
   the field is present and calm, an autonomous drift that does not track the cursor
   (only the hero panel is cursor-aware, RF directive 2026-07-06) — a soft light, never
   a spotlight; only one element per view is lit. prefers-reduced-motion stills it. */

/* Ambient field — a present-and-calm signal light that drifts autonomously over the page.
   Wrap content in .fd-ambient; drop one .fd-ambient__field inside. JS writes
   --mx / --my (percent) with the same spring physics as the glass glow, but driven by a
   slow time-based drift instead of the cursor, canvas-wide and far fainter. The
   fallbacks are the at-rest position. */
.fd-ambient { position: relative; isolation: isolate; }
.fd-ambient__field {
  /* Fixed to the viewport, not absolutely sized to the body box, so the drifting
     light stays a fixed on-screen size: it no longer stretches down a tall document
     where the glow would dilute to a faint full-height smear. */
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    /* G4: gradient sized in viewport units so the lit ellipse is a fixed on-screen
       size independent of any element box; its centre rides the JS --mx / --my. */
    radial-gradient(58vw 48vh at var(--mx, 50%) var(--my, 36%), var(--horizon-glow), transparent 70%),
    radial-gradient(72vw 60vh at 82% 6%, var(--horizon-tint), transparent 72%);
  /* No background transition. The spring loop already smooths the drift frame to
     frame; a CSS transition restarted on every rAF write only added ~1-2s of lag,
     which made the light feel sticky and delayed. */
}
.fd-ambient > *:not(.fd-ambient__field) { position: relative; z-index: 1; }

/* A lit element — soft breathing signal bloom + hairline ring. Use on the
   single element that matters (the live flow node, an active tile). Always
   paired with a word. */
.fd-lit { position: relative; }
.fd-lit::after {
  content: ""; position: absolute; inset: calc(-1 * var(--hair)); border-radius: inherit; z-index: 0;
  pointer-events: none;
  box-shadow: 0 0 0 var(--hair) var(--signal-line), var(--bloom-now);
  animation: fd-breathe 6s var(--ease-inout) infinite;
}
.fd-lit > * { position: relative; z-index: 1; }

@keyframes fd-breathe {
  0%, 100% { opacity: 0.5; }
  50%      { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .fd-lit::after { animation: none !important; opacity: 0.85; }
  /* The ambient field is stilled live by the runtime under reduced motion (it
     parks the spring at rest); it carries no CSS transition to suppress here. */
}
/* B2 still mode — the composer-set twin of the reduced-motion rest state above,
   triggered by `<html data-still>`. Byte-identical declarations, held in lockstep with
   the reduced-motion path by build/lint-still-mode.mjs. The ambient field is parked by
   the runtime's motionStilled() gate, so it needs no rule here either. */
:root[data-still] .fd-lit::after { animation: none !important; opacity: 0.85; }

/* ===== parts/glass.css ===== */
/* Foundational — glass surface for the floating / alive layer ONLY.
   Header banner, sticky nav, floating dock, live indicators.
   A gentle light glass: a near-white panel over a diffused light-blue mesh, a
   bright top edge, and a faint signal glow that JS moves with fluid spring
   physics (the glow position is the CSS --mx / --my variables). */

.fd-glass {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--glass-surface);
  border: var(--hair) solid var(--glass-hair);
  border-top-color: var(--glass-edge);
  box-shadow: var(--glass-shadow);
  isolation: isolate;
}

/* Mesh — diffused light-blue pastels multiplied onto the near-white surface.
   The layer is pinned to the panel (inset:0) and clips its own children, so the
   decorative blobs can never report a box past the panel's right edge (which the
   overflow check reads per element). The blobs bleed left/top/bottom freely; only
   the right reach is held in, so the soft wash survives without false overflow. */
.fd-glass__mesh {
  position: absolute; inset: 0; z-index: 0; overflow: hidden;
  filter: blur(var(--gmesh-blur)) saturate(1.05);
  opacity: 0.85; pointer-events: none;
}
.fd-glass__mesh .mesh-blob {
  position: absolute; width: 60%; height: 60%; border-radius: 50%;
  mix-blend-mode: var(--gmesh-blend);
  will-change: transform;
}
.fd-glass__mesh .mesh-blob--1 { left: -4%; top: 2%;  background: radial-gradient(circle, var(--gmesh-blob-1), transparent 66%); animation: mesh-drift-1 26s ease-in-out infinite; }
.fd-glass__mesh .mesh-blob--2 { left: 36%; top: 10%; background: radial-gradient(circle, var(--gmesh-blob-2), transparent 66%); animation: mesh-drift-2 32s ease-in-out infinite; }
.fd-glass__mesh .mesh-blob--3 { left: 22%; top: 44%; background: radial-gradient(circle, var(--gmesh-blob-3), transparent 66%); animation: mesh-drift-3 28s ease-in-out infinite; }
.fd-glass__mesh .mesh-blob--4 { left: -6%; top: 48%; background: radial-gradient(circle, var(--gmesh-blob-4), transparent 66%); animation: mesh-drift-4 30s ease-in-out infinite; }

/* Frosted veil — light; lets the pastel mesh read through */
.fd-glass__veil {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(180deg, var(--glass-frost-1), var(--glass-frost-2));
  -webkit-backdrop-filter: blur(var(--glass-frost-blur));
  backdrop-filter: blur(var(--glass-frost-blur));
}

/* Cursor-aware glow — a faint signal light over the frost. NOT a ripple.
   JS sets the --mx / --my runtime variables (percentages) on this element with
   fluid spring physics; the fallbacks below are the at-rest position. */
.fd-glass__glow {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: radial-gradient(440px circle at var(--mx, 60%) var(--my, 32%), var(--glass-glow), transparent 60%);
}

/* Bright top edge highlight + resting specular sheen.
   The crisp inset line is the lit top edge; the gradient bakes a faint default
   specular into the resting state so the glass reads as "catching light" without
   waiting for the cursor-aware glow. Matches the .lg rim treatment (overlay.css):
   var(--glass-spec) fading out by ~34% of the height, blended so it only lifts. */
.fd-glass__edge {
  position: absolute; inset: 0; z-index: 1; border-radius: inherit; pointer-events: none;
  background: linear-gradient(180deg, var(--glass-spec), transparent 34%);
  mix-blend-mode: screen;
  box-shadow: inset 0 1px 0 var(--glass-edge);
}

.fd-glass__body { position: relative; z-index: 2; }

@media (prefers-reduced-motion: reduce) {
  .fd-glass__mesh .mesh-blob { animation: none !important; }
}
/* B2 still mode twin (kept identical to the reduced-motion rule by lint-still-mode.mjs) */
:root[data-still] .fd-glass__mesh .mesh-blob { animation: none !important; }

/* ===== parts/overlay.css ===== */
/* ============================================================
   Foundational — Glass Overlay (.lg)  [canonical]
   Pair with liquid-glass.js. Apply class "lg" to any FLOATING / live
   surface (nav bar, dock, status pill, hovering card) and give it its own
   border-radius — the engine shapes the refraction lens to it.

   Chromium  → real edge refraction (SVG feDisplacementMap in backdrop-filter)
   Safari/FF → automatic frosted-blur fallback (engine sets .lg-fallback on <html>)

   Per-element tuning via data-attrs (read by liquid-glass.js):
     data-lg-scale  refraction strength px (subtle ~28 · balanced ~56 · pronounced ~92)
     data-lg-radius corner radius the lens is shaped to (defaults to border-radius; "0" honoured)
     data-lg-blur   backdrop blur px (default 2)
   Global look via the --glass-* tokens (tokens/mesh.css); --glass-sat tunes saturation.

   Consumer gotchas: real refraction is Chromium-only (Safari/Firefox get the
   frosted-blur fallback automatically); glass needs something behind it to bend,
   so keep reading content on flat opaque panels; an ancestor with opacity < 1
   can swallow the effect (stacking context).
   ============================================================ */

.lg {
  position: relative;
  background: var(--glass-tint);
  border: var(--hair) solid var(--glass-hair);
  border-top-color: var(--glass-edge);
  box-shadow: var(--glass-shadow);
  isolation: isolate;
}

/* Specular highlight along the top edge — the "lit glass" cue */
.lg::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none; z-index: 1;
  background: linear-gradient(180deg, var(--glass-spec), transparent 34%);
  mix-blend-mode: screen;
}
/* Faint inner hairline + directional rim light: a crisp lit top edge and a
   faint light catch along the bottom inner edge, so the glass reads as lit from
   above (Apple liquid-glass dimensionality) rather than a flat translucent box. */
.lg::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none; z-index: 1;
  box-shadow:
    inset 0 0 0 var(--hair) var(--glass-inner),                 /* faint structural hairline, all round */
    inset 0 var(--hair) 0 var(--glass-rim-top),                 /* crisp lit top edge */
    inset 0 calc(-1 * var(--hair)) 0 var(--glass-rim-bot);      /* soft light catch on the bottom */
}
.lg > * { position: relative; z-index: 2; }

/* JS-INDEPENDENT legibility floor. The base .lg fill is ~42-46% translucent, so
   if liquid-glass.js is disabled, blocked (CSP/extension), or fails to load, the
   bare .lg surface would let content bleed through at near-half opacity with no
   blur and become mutually illegible. These two @supports rules give a pure-CSS
   floor that needs no JS and no .lg-fallback class:
     1. where backdrop-filter is supported  -> a frosted blur floor (legible),
     2. where it is NOT supported            -> an opaque tint (no bleed-through).
   JS stays a pure enhancement: liquid-glass.js sets backdrop-filter as an INLINE
   style for the Chromium refraction (and .lg-fallback .lg below supplies the
   frosted blur it chooses), and inline styles override these rules, so refraction
   still wins with zero regression when JS runs. */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .lg {
    -webkit-backdrop-filter: blur(var(--glass-fallback-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-fallback-blur)) saturate(var(--glass-sat));
  }
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .lg { background: var(--glass-tint-solid); }
}

/* Fallback where SVG-in-backdrop-filter is unsupported (Safari/Firefox).
   liquid-glass.js adds .lg-fallback to <html> automatically. */
.lg-fallback .lg {
  -webkit-backdrop-filter: blur(var(--glass-fallback-blur)) saturate(var(--glass-sat));
  backdrop-filter: blur(var(--glass-fallback-blur)) saturate(var(--glass-sat));
}

/* ============================================================
   Overlay blocks — component CSS promoted from specimens.
   Shared scrim (.fdn-scrim / .fdn-scrim--end), close cross (.fdn-x) and
   buttons (.fdn-btn) live in controls.css; these rules add only what each
   overlay needs beyond those primitives. Tokens only.
   ============================================================ */

/* Block: 49-modal. A titled dialog that rides the floating layer above a
   contained .fdn-scrim. Reuses .fdn-x for close and .fdn-btn for actions. */
.fdn-modal {
  position: relative; z-index: 1;
  width: 100%; max-width: 360px; min-width: 0;
  box-shadow: var(--shadow-float);
}
.fdn-modal__head { display: flex; align-items: flex-start; gap: var(--space-4); }
.fdn-modal__head .fdn-card__heading { flex: 1 1 auto; min-width: 0; }
.fdn-modal__body { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.fdn-modal__text { font-size: var(--text-sm); color: var(--body); line-height: var(--leading-normal); text-wrap: pretty; margin: 0; }
.fdn-modal__actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--space-3); padding-top: var(--space-4); border-top: var(--hair) solid var(--line); }

/* Block: 50-drawer. A side sheet pinned to the right edge of a contained
   .fdn-scrim--end. Reuses .fdn-x for close. */
.fdn-drawer {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; min-width: 0;
  width: 100%; max-width: 340px;
  border-left: var(--hair) solid var(--line-2);
  box-shadow: var(--shadow-float);
}
.fdn-drawer__head { display: flex; align-items: flex-start; gap: var(--space-4); padding: var(--space-5) var(--space-5) var(--space-4); border-bottom: var(--hair) solid var(--line); }
.fdn-drawer__head .fdn-card__heading { flex: 1 1 auto; min-width: 0; }
.fdn-drawer__body { display: flex; flex-direction: column; min-width: 0; padding: var(--space-2); }
.fdn-drawer__row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); min-width: 0; padding: var(--space-3) var(--space-4); border-radius: var(--radius-sm); color: var(--ink-2); font-size: var(--text-sm); text-decoration: none; transition: background var(--dur-snap) var(--ease-out); }
.fdn-drawer__row + .fdn-drawer__row { border-top: var(--hair) solid var(--line-fine); }
.fdn-drawer__row:hover { background: var(--line-fine); }
.fdn-drawer__row span:first-child { min-width: 0; overflow-wrap: anywhere; }
.fdn-drawer__val { flex: none; color: var(--mut); font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-tight); }

/* Block: 51-popover. A trigger button and a small floating panel with a
   pointer, sized to stay within its anchor frame at any width. */
.fdn-pop { position: relative; display: inline-block; min-width: 0; }
.fdn-pop__trigger {
  appearance: none; -webkit-appearance: none; font: inherit; font-size: var(--text-sm); font-weight: var(--weight-strong);
  display: inline-flex; align-items: center; gap: var(--space-2); cursor: pointer;
  color: var(--ink-2); border: var(--hair) solid var(--line-2); border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-4);
}
.fdn-pop__trigger::after { content: ""; width: 6px; height: 6px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: translateY(-1px) rotate(45deg); color: var(--mut); }
.fdn-pop__trigger:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.fdn-popover {
  position: absolute; left: 0; top: calc(100% + var(--space-3)); z-index: 1;
  width: 232px; max-width: 100%; min-width: 0;
  border: var(--hair) solid var(--line-2); border-radius: var(--radius-md);
  box-shadow: var(--shadow-float); padding: var(--space-4);
}
.fdn-popover::before {
  content: ""; position: absolute; top: -5px; left: var(--space-5); width: 9px; height: 9px;
  border-left: var(--hair) solid var(--line-2); border-top: var(--hair) solid var(--line-2);
  transform: rotate(45deg);
}
.fdn-popover__title { margin: 0 0 var(--space-2); font-size: var(--text-sm); font-weight: var(--weight-strong); letter-spacing: var(--tracking-tight); color: var(--ink-2); }
.fdn-popover__text { margin: 0 0 var(--space-3); font-size: var(--text-xs); line-height: var(--leading-normal); color: var(--body); text-wrap: pretty; }
.fdn-popover__link { font-size: var(--text-xs); font-weight: var(--weight-strong); color: var(--accent); text-decoration: underline; text-decoration-color: var(--accent-line); text-underline-offset: 2px; border-radius: var(--radius-sm); }
.fdn-popover__link:hover { text-decoration-color: var(--accent); }
.fdn-popover__link:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ===== parts/chip.css ===== */
/* Foundational — status chip (state) + live keyline (now).
   A chip always pairs a small dusty dot with a WORD. Never candy color. */

.fd-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  font-weight: var(--weight-strong);
  line-height: 1.3;
  white-space: nowrap;
  color: var(--ink-2);
}
.fd-chip__dot {
  width: var(--dot); height: var(--dot); border-radius: 50%; flex: none;
}

.fd-chip--ok   .fd-chip__dot { background: var(--ok-dot); }
.fd-chip--warn .fd-chip__dot { background: var(--warn-dot); }
.fd-chip--blk  .fd-chip__dot { background: var(--blk-dot); }
.fd-chip--na   .fd-chip__dot { background: var(--na-dot); }

/* Live "now" beacon — a PULSING dot for emphasis / attention.
   Neutral blue (--signal) by default = "live, no status claim". Tier it to a
   status with .fd-now--ok / --warn / --blk on the wrapper (or standalone
   .fd-now__dot--ok / --warn / --blk on a bare dot) to ride the live-state beacon
   triad (green / yellow / red). Pulsing only draws the eye; the colour is still
   paired with a WORD, never colour alone. The calm static read layer
   (.fd-chip / .fdn-stat dots) stays muted and is deliberately NOT tiered here. */
.fd-now {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--label-size); font-weight: var(--label-weight);
  letter-spacing: var(--label-spacing); text-transform: uppercase;
  color: var(--signal);
}
.fd-now--ok   { color: var(--state-ok); }
.fd-now--warn { color: var(--state-warn); }
.fd-now--blk  { color: var(--state-blk); }
.fd-now__dot {
  --now-color: var(--signal);
  width: var(--dot); height: var(--dot); border-radius: 50%; flex: none;
  background: var(--now-color);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--now-color) 45%, transparent);
  animation: fd-now-pulse 2.4s var(--ease-out) infinite;
}
.fd-now--ok   .fd-now__dot, .fd-now__dot--ok   { --now-color: var(--state-ok); }
.fd-now--warn .fd-now__dot, .fd-now__dot--warn { --now-color: var(--state-warn); }
.fd-now--blk  .fd-now__dot, .fd-now__dot--blk  { --now-color: var(--state-blk); }
@keyframes fd-now-pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--now-color) 40%, transparent); }
  70%  { box-shadow: 0 0 0 6px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .fd-now__dot { animation: none !important; }
}
/* B2 still mode twin (kept identical to the reduced-motion rule by lint-still-mode.mjs) */
:root[data-still] .fd-now__dot { animation: none !important; }

/* Neutral tag / label — distinct from the status .fd-chip above.
   A .fd-chip is a STATE primitive (a status dot + a word, --ok/--warn/--blk/--na).
   A .fd-tag carries NO status: it is a calm bordered pill for a plain label,
   category, or applied filter, where the word is the whole meaning. Used by the
   chip-set block (static labels) and the filter-bar block (selectable filters). */
.fd-tag {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-xs); font-weight: var(--weight-strong); line-height: 1.3;
  white-space: nowrap; color: var(--body);
  padding: var(--space-1) var(--space-3);
  border: var(--hair) solid var(--line-2); border-radius: var(--radius-pill);
}

/* Selectable filter tag — a .fd-tag that can be turned on. Render it as a
   <button> carrying aria-pressed so the state is in the accessibility tree.
   The SELECTED state is marked by an explicit check glyph and bold signal text,
   not by colour alone, so the choice survives greyscale and colourblind viewing
   (mirrors the filled-pill rule of the segmented control). */
.fd-tag--filter {
  appearance: none; font-family: inherit; cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.fd-tag--filter:hover { border-color: var(--signal-line); }
/* Keyboard focus uses the shared system focus ring (keyboard users only). */
.fd-tag--filter:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.fd-tag--filter[aria-pressed="true"] {
  background: var(--signal-soft); border-color: var(--signal-line);
  color: var(--accent-text); font-weight: var(--weight-strong);
}
/* A check glyph (CSS-drawn, no glyph font) leads the selected tag so the
   "selected" meaning never rides on the signal tint alone. */
.fd-tag--filter[aria-pressed="true"]::before {
  content: ""; flex: none; width: 8px; height: 5px;
  border-left: 2px solid var(--accent-text); border-bottom: 2px solid var(--accent-text);
  transform: rotate(-45deg); margin-top: -3px;
}

/* A wrapping row of tags (or chips), used by the chip-set block. The row wraps
   in place at any width (scale-to-fit, never a structural reflow). */
.fdn-chipset__row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }

/* ===== parts/pane.css ===== */
/* Foundational — the surface layer. FIVE surfaces, each with one role.
   A surface is chosen by the COMPOSITION, never by the content; paper is the
   default home for everything a person reads. A panel must EARN its border.

     Paper  (--paper)                     The page and ALL reading. The default.
                                          No border, no shadow.
     Well   (--well)                      A subordinate island set into the flow:
                                          code, figure media, a callout. Recessed,
                                          no shadow (a panel lifts, a well recedes).
     Panel  (--panel + hairline + shadow) A discrete entity that earned its border:
                                          a tile, an option, the escalation. What
                                          the public .fdn-card gives you.
     Glass  (fd-glass)                    Chrome and the one hero. Never reading.
     Scrim  (--scrim)                     Overlay dimming only.

   THE PANEL RECIPE, in two halves. The FILL (the panel token as a background) is
   declared once for every surface that sits on it, in parts/surface-fill.css (first
   so it is the base every state layers over). The FRAME below (hairline, radius,
   shadow) is the internal recipe .fd-surface; the once-orphaned .fd-pane primitive
   composes it through the same selector group at the same specificity. The public
   .fdn-card carries its own copy of this frame in parts/composition.css — after
   the decision option, flow node and tooltip, so it wins an equal-specificity tie
   with them; folding it in here would move it before them and flip that order. The
   layout tier (parts/layout.css) owns the space between panels, never their
   surface. `.fd-surface` is an INTERNAL recipe name; consumers reach a panel
   through `.fdn-card`, never by writing `.fd-surface`. */

.fd-surface, .fd-pane {
  position: relative;
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
}

.fd-pane--flat { box-shadow: var(--shadow-flat); }

/* Active / now pane — distinguished by its "• Now" label + a gentle lift,
   not a color border (the left-keyline pattern is overused). */
.fd-pane--now { box-shadow: var(--shadow-float); }

.fd-pane__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-bottom: var(--hair) solid var(--line);
}
.fd-pane__title {
  margin: 0;
  font-size: var(--text-md);
  font-weight: var(--weight-strong);
  color: var(--ink-2);
  letter-spacing: var(--tracking-tight);
}
.fd-pane__body { padding: var(--space-5); }

/* Uppercase tracked label helper */
.fd-label {
  font-size: var(--label-size);
  font-weight: var(--label-weight);
  letter-spacing: var(--label-spacing);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* ===== parts/panes.css ===== */
/* Foundational — understanding-pane classes (flat surfaces). */

/* ---- Gate table: reflows to stacked cards on mobile ---- */
.fd-gate { display: flex; flex-direction: column; }
.fd-gate__row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-top: var(--hair) solid var(--line);
}
.fd-gate__row:first-child { border-top: 0; }
.fd-gate__num {
  width: 26px; height: 26px; flex: none;
  display: grid; place-items: center;
  border-radius: var(--radius-pill);
  background: transparent; color: var(--mut);
  border: var(--hair) solid var(--line-2);
  font-size: var(--text-xs); font-weight: var(--weight-strong); font-variant-numeric: tabular-nums;
}
.fd-gate__row.is-now .fd-gate__num { background: var(--signal-soft); color: var(--signal); border-color: var(--signal-line); }
.fd-gate__name { color: var(--ink-2); font-weight: var(--weight-strong); font-size: var(--text-base); }
.fd-gate__note { color: var(--mut); font-size: var(--text-sm); }
.fd-gate__main { min-width: 0; }
.fd-gate__main .fd-gate__note { margin-top: 2px; }

@media (max-width: 460px) {
  .fd-gate__row { grid-template-columns: auto 1fr; grid-auto-flow: row; }
  .fd-gate__row .fd-chip { grid-column: 1 / -1; justify-self: start; }
}

/* ---- Decision options: recommended = filled signal radio + caps label ---- */
.fd-dec { display: flex; flex-direction: column; gap: var(--space-3); }
.fd-dec__opt {
  display: grid; grid-template-columns: auto 1fr; gap: var(--space-3);
  padding: var(--space-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-sm);
  text-align: left; width: 100%; cursor: pointer; font: inherit; color: inherit;
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.fd-dec__opt:hover { border-color: var(--line-2); }
.fd-dec__opt.is-sel { border-color: var(--signal-line); background: var(--signal-soft); }
.fd-radio {
  width: 18px; height: 18px; border-radius: 50%; flex: none; margin-top: 1px;
  border: 2px solid var(--line-2);
  display: grid; place-items: center;
  transition: border-color var(--dur-fast) var(--ease-out);
}
.fd-radio.is-on { border-color: var(--signal); }
.fd-radio__dot { width: 9px; height: 9px; border-radius: 50%; background: var(--signal); transform: scale(0); transition: transform var(--dur-fast) var(--ease-out); }
.fd-radio.is-on .fd-radio__dot { transform: scale(1); }
/* B2/reduced-motion: the decision-option hover, the radio selection ring, and the
   radio dot are feedback motion — they collapse to instant under BOTH the reader's
   reduced-motion preference and the composer's still mode. Byte-identical twins,
   held in lockstep by build/lint-still-mode.mjs. */
@media (prefers-reduced-motion: reduce) {
  .fd-dec__opt, .fd-radio, .fd-radio__dot { transition: none; }
}
:root[data-still] .fd-dec__opt,
:root[data-still] .fd-radio,
:root[data-still] .fd-radio__dot { transition: none; }
.fd-dec__title { display: block; color: var(--ink-2); font-weight: var(--weight-strong); font-size: var(--text-base); }
.fd-dec__desc { display: block; color: var(--body); font-size: var(--text-sm); margin-top: 3px; }
.fd-dec__rec {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--text-2xs); font-weight: var(--weight-strong);
  letter-spacing: var(--label-spacing); text-transform: uppercase;
  color: var(--signal); margin-top: var(--space-2);
}
.fd-dec__rec::before { content: "•"; }

/* ---- Comparison table: exactly one Recommended column ---- */
.fd-cmp { width: 100%; border-collapse: collapse; }
.fd-cmp th, .fd-cmp td {
  padding: var(--space-3) var(--space-4);
  text-align: left; font-size: var(--text-sm);
  border-bottom: var(--hair) solid var(--line);
  vertical-align: top;
}
.fd-cmp thead th { color: var(--ink-2); font-weight: var(--weight-strong); font-size: var(--text-base); }
.fd-cmp tbody th { color: var(--mut); font-weight: var(--weight-body); width: 34%; }
.fd-cmp td { color: var(--ink-2); }
/* recommended column reads as one clean vertical band — dividers never cut across it */
.fd-cmp .is-rec { background: var(--signal-soft); border-bottom-color: transparent; }
.fd-cmp thead th.is-rec { border-top-left-radius: var(--radius-sm); border-top-right-radius: var(--radius-sm); }
.fd-cmp tbody tr:last-child td.is-rec { border-bottom-left-radius: var(--radius-sm); border-bottom-right-radius: var(--radius-sm); }
.fd-cmp__why { color: var(--body); font-size: var(--text-sm); }

/* ---- Flowchart (schematic) ---- */
.fd-flow { display: flex; flex-direction: column; align-items: stretch; }
.fd-node {
  position: relative;
  display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-flat);
}
/* The live step catches the ambient living light: a soft breathing bloom
   instead of a hard ring. */
.fd-node--now {
  border-color: var(--signal-line);
  box-shadow: 0 0 0 var(--hair) var(--signal-line), var(--bloom-now);
  animation: fd-breathe 6s var(--ease-inout) infinite;
}
.fd-node__badge {
  width: 24px; height: 24px; flex: none; display: grid; place-items: center;
  border-radius: var(--radius-pill); background: transparent; color: var(--mut);
  border: var(--hair) solid var(--line-2);
  font-size: var(--text-xs); font-weight: var(--weight-strong); font-variant-numeric: tabular-nums;
}
.fd-node--now .fd-node__badge { background: var(--signal); color: var(--on-accent); border-color: var(--signal); }
.fd-node__label { display: block; color: var(--ink-2); font-weight: var(--weight-strong); font-size: var(--text-base); }
.fd-node__sub { display: block; color: var(--mut); font-size: var(--text-xs); margin-top: 1px; }
.fd-flow__arrow { stroke: var(--line-2); stroke-width: 1.5; fill: none; }
.fd-flow__arrowhead { fill: var(--line-2); }
.fd-flow__row { display: grid; gap: var(--space-3); }

@media (prefers-reduced-motion: reduce) {
  .fd-node--now { animation: none !important; }
}
/* B2 still mode twin (kept identical to the reduced-motion rule by lint-still-mode.mjs) */
:root[data-still] .fd-node--now { animation: none !important; }

@media (max-width: 520px) {
  .fd-flow__row { grid-template-columns: 1fr !important; }
}

/* ===== parts/chart.css ===== */
/* Foundational — chart classes.
   SVG presentation attributes can't take var(); colors must come from CSS. */

.fd-chart { display: block; width: 100%; height: auto; font-family: var(--font-sans); font-variant-numeric: tabular-nums; }

/* Structure — a three-step line ladder, faint to strong, that re-resolves in both
   themes: grid (--line-fine) < crosshair (--line) < axis/frame (--line-2). Each step
   is one clear luminance move, so structure reads by strength + position, never colour. */
.fd-chart__grid   { stroke: var(--line-fine); stroke-width: 1; }
.fd-chart__cross  { stroke: var(--line);      stroke-width: 1; }
.fd-chart__axis   { stroke: var(--line-2);    stroke-width: 1; }
.fd-chart__tick   { fill: var(--mut); font-size: var(--text-micro); font-weight: var(--weight-body); letter-spacing: var(--tracking-normal); }

/* Calm monochrome marks */
.fd-chart__line   { fill: none; stroke: var(--ink-2); stroke-width: 1.75; stroke-linejoin: round; stroke-linecap: round; }
.fd-chart__dot    { fill: var(--panel); stroke: var(--ink-2); stroke-width: 1.75; }
.fd-chart__cap    { fill: var(--signal); stroke: var(--panel); stroke-width: 2; filter: drop-shadow(0 0 3px var(--horizon-bloom)); }
.fd-chart__bar    { fill: var(--line-2); }
.fd-chart__bar-2  { fill: var(--mut); }

/* The ONE accent — the figure that matters, catching the living light */
.fd-chart__line--accent { stroke: var(--signal); stroke-width: 2; filter: drop-shadow(0 0 4px var(--horizon-bloom)); }
.fd-chart__bar--accent  { fill: var(--signal); }

/* data-lines non-accent series — solid strokes stepping down the navy data ramp.
   The distinction from the accent line is LUMINANCE (the ramp) plus the legend
   word, never a dash or a texture (standing taste rule); each survives greyscale. */
.fd-chart__line--muted  { stroke: var(--data-2); }
.fd-chart__line--muted2 { stroke: var(--data-3); }

/* Stacked bar segments — neutral ramp + one accent */
.fd-chart__seg-1 { fill: var(--data-1); }
.fd-chart__seg-2 { fill: var(--data-2); }
.fd-chart__seg-3 { fill: var(--data-3); }

/* Donut */
.fd-chart__ring     { fill: none; stroke: var(--line); stroke-width: 14; }
.fd-chart__ring-val { fill: none; stroke: var(--signal); stroke-width: 14; stroke-linecap: round; }

/* Content-aware highlight — guide line + lit mark, not a background wash */
.fd-chart__guide  { stroke: var(--signal-line); stroke-width: 1; }
.fd-chart__hit    { fill: transparent; cursor: pointer; }
/* Keyboard focus for the now-focusable hit areas (Chart.jsx gives each rect
   tabindex + focus handlers). Shows the shared focus ring without a pointer. */
.fd-chart__hit:focus-visible { outline: var(--stroke-2) solid var(--focus); outline-offset: var(--stroke-2); }
.fd-chart__bar.is-dim,
.fd-chart__bar-2.is-dim { opacity: 0.45; transition: opacity var(--dur-fast) var(--ease-out); }

/* Gauge hover host — relative wrapper the runtime adds around a [data-gauge] svg so
   its .fdn-tip can anchor; the value arc lights on hover (state class, no animation). */
.fdn-gauge-wrap { position: relative; display: inline-block; line-height: 0; }
.fdn-gauge-wrap .fdn-gauge { transition: filter var(--dur-snap) var(--ease-out); }
.fdn-gauge-wrap.is-hovering .fdn-gauge { filter: drop-shadow(0 0 6px var(--horizon-bloom)); }
.fdn-gauge path.is-hot { stroke-width: 16; }

/* Share-bar hover host — relative wrapper around .fdn-share__bar so its .fdn-tip can
   anchor; the hovered segment stays lit while the rest dim, the word carries meaning. */
.fdn-share-wrap { position: relative; display: block; width: 100%; }
.fdn-share-wrap .fdn-share__seg { transition: opacity var(--dur-snap) var(--ease-out); }
.fdn-share-wrap.is-hovering .fdn-share__seg { opacity: 0.4; }
.fdn-share-wrap.is-hovering .fdn-share__seg.is-hot { opacity: 1; }

/* Bar annotations (Wave 5) — event markers + x-axis labels.
   A marker is a faint hairline drop-line plus a word: the colour never carries
   the meaning alone, the word does. The label overlays live in .fdn-bars-wrap
   (position: relative, composition.css) and are positioned by percentage, so
   they track the bars through the chart's horizontal stretch. */

/* the drop-line, drawn in SVG space from a bar annotation's word down to the bar
   it marks; non-interactive so it never steals a bar's hover. Per RF it is a very
   faint THIN hairline: a single 1.5px stroke of --chart-mark (the chart's major
   ruler-tick strength), no casing and no cap. It reads as one of the chart's own
   quiet ruler lines, clearest over the open plot ground above a bar (where the eye
   picks up the drop from the word) and receding where it crosses a filled bar. The
   line carries no meaning on its own: the WORD above it and the screen-reader
   summary do, so it is a position indicator, not a graphic that must clear WCAG's
   3:1 non-text floor against every bar fill. See tokens/colors.css for the token. */
.fd-chart__mark-line { stroke: var(--chart-mark); stroke-width: 1.5; pointer-events: none; }

/* event-marker label strip, above the chart */
.fdn-bars-marks { position: relative; display: block; height: 1.35rem; }
.fdn-bars-mark   { position: absolute; bottom: 0; transform: translateX(-50%); display: flex; flex-direction: column; align-items: center; pointer-events: none; }
/* pointer-events: auto re-enables interaction the parent chip turns off, so a
   label ellipsized past 15ch surfaces its full text via the native title tooltip
   on hover. The word strip sits ABOVE the chart, never over the bars, so a
   hoverable label cannot steal a bar's hover. */
.fdn-bars-mark__label { max-width: 15ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--text-micro); font-weight: var(--weight-strong); line-height: 1; color: var(--ink-2); letter-spacing: var(--tracking-normal); text-align: center; pointer-events: auto; }

/* B4 — the bar-chart readout ANSWERS INSTANTLY on the first bar under the pointer,
   then tracks the pointer bar-to-bar like a cursor on an instrument, and releases
   gently on leave. Feedback-answers motion: user-caused, fast. The runtime JUMPS the
   tip into place on first appearance and its opacity SNAPS on with no fade (the first
   answer is never delayed), then keeps .is-gliding on so only bar-to-bar POSITION
   moves slide; on leave the runtime drops .is-gliding so the base rule below dissolves
   the tip out. Only the leave uses the fade — entry is a jump. Constants are the
   RF-adopted P-B4 pick, shipped verbatim: 90ms position glide on the shared --ease-out
   curve, --dur-fast (140ms) leave dissolve. Reduced motion AND still mode collapse
   every transition here to instant, so the value/meaning never depend on the glide —
   the two paths are kept identical by build/lint-still-mode.mjs. */
.fdn-bars-wrap .fdn-tip { transition: opacity var(--dur-fast) var(--ease-out); }
.fdn-bars-wrap .fdn-tip.is-gliding {
  /* POSITION only: opacity is deliberately absent here so the first answer, which the
     runtime paints while .is-gliding is already on, jumps in instantly. This rule
     outranks the base opacity transition (two classes), so no opacity fade on entry;
     the base rule governs opacity only on leave, once the runtime drops .is-gliding. */
  transition: left 90ms var(--ease-out),
              top 90ms var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
  .fdn-bars-wrap .fdn-tip, .fdn-bars-wrap .fdn-tip.is-gliding { transition: none; }
}
:root[data-still] .fdn-bars-wrap .fdn-tip,
:root[data-still] .fdn-bars-wrap .fdn-tip.is-gliding { transition: none; }

/* x-axis labels, below the axis, one centred under each bar */
.fdn-bars-xaxis { position: relative; display: block; height: 1.15rem; margin-top: var(--space-1); }
/* max-width is set inline by the runtime to each bar's slot width; overflow +
   ellipsis clip a label that would exceed its slot. */
.fdn-bars-xtick { position: absolute; top: 0; transform: translateX(-50%); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--text-micro); font-weight: var(--weight-body); line-height: 1; color: var(--mut); letter-spacing: var(--tracking-normal); text-align: center; }

/* data-lines host + legend. The wrap is the relative box the runtime anchors the
   .fd-tip and sr-only table into. The legend swatch is a short SOLID line in the
   series colour (never a dot or a patterned key), so the legend reads the same in
   greyscale as the chart: luminance ladder + word. */
.fdn-lines-wrap { position: relative; display: block; }
.fdn-lines-legend { display: flex; flex-wrap: wrap; gap: var(--space-4); margin: 0; padding: 0; list-style: none; }
.fdn-lines-legend__item { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); color: var(--mut); }
.fdn-lines-legend__sw { width: var(--legend-sw-w); height: 0; flex: none; border-top: var(--stroke-2) solid var(--data-2); border-radius: var(--radius-pill); }
.fdn-lines-legend__sw--s1 { border-top-color: var(--data-1); }
.fdn-lines-legend__sw--s2 { border-top-color: var(--data-2); }
.fdn-lines-legend__sw--s3 { border-top-color: var(--data-3); }
/* tip swatches for the line-chart readout rows — solid squares matching each
   series' ramp colour (the tip states the value in words beside them). */
.fd-tip__sw--s1 { background: var(--data-1); }
.fd-tip__sw--s2 { background: var(--data-2); }
.fd-tip__sw--s3 { background: var(--data-3); }

/* KPI sparkline */
.fd-spark__line { fill: none; stroke: var(--ink-2); stroke-width: 1.75; stroke-linejoin: round; stroke-linecap: round; }
.fd-spark__line--accent { stroke: var(--signal); }
.fd-spark__cap  { fill: var(--signal); }
/* C2: theme-aware strength for the under-line fill. The runtime draws the fill with a
   vertical --signal gradient plus a mask so it dissolves before every straight edge;
   this only sets HOW STRONG that fill is — fainter on dark paper, where --signal reads
   brighter, so the fill stays a quiet light under the line instead of a grey smudge. */
.fd-spark__fill { fill-opacity: 0.18; }
[data-theme="dark"] .fd-spark__fill { fill-opacity: 0.11; }
@media (prefers-color-scheme: dark) { :root:not([data-theme]) .fd-spark__fill { fill-opacity: 0.11; } }

/* Hover data box — standardized white glass with edge refraction (matches .fdn-tip).
   The refraction filter (#fdn-glass-refract) is rendered self-contained in Chart's <defs>. */
.fd-tip {
  position: absolute;
  z-index: 20;
  transform: translate(-50%, -100%);
  margin-top: -12px;
  padding: 9px 11px;
  border-radius: var(--radius-sm);
  background: var(--tip-bg);
  color: var(--ink-2);
  border: var(--hair) solid var(--tip-border);
  font-size: var(--text-xs);
  line-height: 1.4;
  white-space: nowrap;
  /* BASE: plain blur+saturate with no url(), so Firefox (and any browser without
     the Chromium url()-in-backdrop-filter refraction) keeps a working frosted
     backdrop. Per spec, an unsupported function in a backdrop-filter list drops
     the WHOLE declaration, so the url() must NOT live on the base line or Firefox
     loses the backdrop entirely and the chart marks bleed through the readout. */
  -webkit-backdrop-filter: blur(var(--tip-blur)) saturate(var(--tip-sat));
  backdrop-filter: blur(var(--tip-blur)) saturate(var(--tip-sat));
  box-shadow: var(--shadow-float), inset 0 1px 0 var(--tip-rim-top), inset 0 -1px 0 var(--tip-rim-bot);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
}
/* ADDITIVE: layer the Chromium-only edge refraction on top, gated so only a
   browser that actually supports url() in backdrop-filter applies it. */
@supports (backdrop-filter: url(#fdn-glass-refract)) {
  .fd-tip { backdrop-filter: blur(var(--tip-blur)) saturate(var(--tip-sat)) url(#fdn-glass-refract); }
}
/* LAST RESORT: opaque fill where there is no backdrop-filter at all. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .fd-tip { background: var(--tip-bg-solid); } }
.fd-tip.is-on { opacity: 1; }
.fd-tip__title { font-weight: var(--weight-strong); margin-bottom: 6px; color: var(--ink-2); }
.fd-tip__row { display: flex; align-items: center; gap: var(--space-2); }
.fd-tip__row + .fd-tip__row { margin-top: 3px; }
.fd-tip__sw { width: 8px; height: 8px; border-radius: 2px; flex: none; }
.fd-tip__k { color: var(--mut); font-weight: var(--weight-body); margin-right: 10px; }
.fd-tip__v { font-weight: var(--weight-strong); margin-left: auto; color: var(--ink-2); }
.fd-tip::after {
  content: ""; position: absolute; left: 50%; top: 100%;
  transform: translateX(-50%);
  border: 5px solid transparent; border-top-color: var(--tip-border);
  filter: drop-shadow(0 1px 0 var(--line));
}

/* B2 still mode / reduced motion — collapse every motion transition in this file to
   instant, so a reader who asked for stillness gets the same rest state with identical
   meaning: the dim-fade (opacity), the gauge hover glow (filter), the share-segment
   dim (opacity), and the tooltip fade (opacity). Colour crossfades stay; these do not.
   Held in lockstep with the positive origins above by build/lint-still-mode.mjs — the
   reduced-motion and still twins are byte-identical, and neither may drift or vanish. */
@media (prefers-reduced-motion: reduce) {
  .fd-chart__bar.is-dim,
  .fd-chart__bar-2.is-dim,
  .fdn-gauge-wrap .fdn-gauge,
  .fdn-share-wrap .fdn-share__seg,
  .fd-tip { transition: none; }
}
:root[data-still] .fd-chart__bar.is-dim,
:root[data-still] .fd-chart__bar-2.is-dim,
:root[data-still] .fdn-gauge-wrap .fdn-gauge,
:root[data-still] .fdn-share-wrap .fdn-share__seg,
:root[data-still] .fd-tip { transition: none; }

/* ===== parts/footer.css ===== */
/* Foundational — subtle product-credit footer.
   Barely-there by design: a thin hairline above, faint small text, the product
   name only one quiet step darker. Never competes with content. */

.fd-footer {
  display: block;
  text-align: center;
  margin-top: var(--space-12);
  padding: var(--space-5) 0 var(--space-4);
  border-top: var(--hair) solid var(--line);
  color: var(--text-faint);
  font-size: var(--text-2xs);
  font-weight: var(--weight-body);
  letter-spacing: var(--tracking-label);
}
.fd-footer strong { color: var(--text-muted); font-weight: var(--weight-strong); }

/* ===== parts/composition.css ===== */
/* Foundational — composition layer.
   The reusable building blocks the design system composes from:
   app bar + hero shell, section + 12-col grid, the card scaffold, status tags,
   figures, meters, rows, lists, and the data-viz hosts. These are REFERENCES, not
   fill-in templates: compose them freely; never force content to fit a card shape.
   Every value is a token — no raw colors, no hand-placed spacing. */

  /* screen-reader-only — CONTRACT: visually hidden, still announced by assistive tech.
     For labels/captions that the eye gets from layout but a non-visual reader needs. */
  .fd-sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  /* shared keyboard focus ring — CONTRACT: outline via var(--focus), shown only for
     keyboard users (:focus-visible), never for mouse clicks. */
  .fdn-appbar__cta:focus-visible,
  .fdn-seg button:focus-visible,
  .fdn-seg a:focus-visible,
  .fdn-row a:focus-visible,
  .fdn-cmp a:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
  }

  /* glass hero / state summary — uses the system glass surface (fd-glass) */
  .fdn-hero { margin: var(--space-8) 0 var(--space-10); }
  .fdn-hero__body { display: grid; grid-template-columns: 1fr auto; gap: var(--space-8); align-items: start; padding: var(--space-8); }
  .fdn-hero__main { min-width: 0; }
  .fdn-hero__eyebrow { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label-wide); text-transform: uppercase; color: var(--on-glass-body); }
  .fdn-hero__title { margin: var(--space-4) 0 0; font-size: var(--text-2xl); font-weight: var(--weight-strong); letter-spacing: var(--tracking-tight); line-height: var(--leading-tight); color: var(--ink); max-width: 22ch; text-wrap: balance; }
  .fdn-hero__lede { margin: var(--space-4) 0 0; max-width: 54ch; font-size: var(--text-base); line-height: var(--leading-snug); color: var(--on-glass-body); text-wrap: pretty; }
  .fdn-hero__side { display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-6); }
  .fdn-hero__figure { text-align: right; }
  /* The hero figure IS the one figure lockup (.fdn-fig), placed in the glass
     context: it keeps the shared lockup geometry but carries the mesh-tuned
     on-glass inks so the number and its unit stay AA over the brightest mesh
     blob (AP-9 / C4 consolidation). */
  .fdn-hero .fdn-fig { color: var(--ink); }
  .fdn-hero .fdn-fig__unit { color: var(--on-glass-body); }
  .fdn-hero__figcap { display: block; margin-top: var(--space-2); font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label-loose); text-transform: uppercase; color: var(--on-glass-body); }
  @media (max-width: 720px) { .fdn-hero__body { grid-template-columns: 1fr; } .fdn-hero__side { align-items: flex-start; } .fdn-hero__figure { text-align: left; } }
  /* the hero is the one ALIVE panel — distinctness comes from the drifting pastel MESH
     filling the pane; the cursor glow stays subtle (system default), never a big sweep */
  .fdn-hero .fd-glass__mesh { opacity: 1; }
  @media (prefers-reduced-motion: no-preference) { .fdn-hero .fd-glass__mesh { transform-origin: right center; animation: fdn-hero-breathe 14s var(--ease-inout) infinite; } }
  /* B2 still mode: the hero mesh breathe above is ENABLED only under no-preference, so
     reduced motion already stills it by not matching; still mode must cancel it
     explicitly. lint-still-mode.mjs holds this cancel in lockstep with that enable. */
  :root[data-still] .fdn-hero .fd-glass__mesh { animation: none !important; }
  @keyframes fdn-hero-breathe { 0%,100% { transform: scale(1); } 50% { transform: scale(1.05); } }
  .fdn-seg { display: inline-flex; gap: 2px; border: var(--hair) solid var(--line); border-radius: var(--radius-pill); padding: 3px; box-shadow: var(--shadow-flat); flex: none; }
  /* Two forms share ONE pill grammar: a same-page view switch is a <button>
     radiogroup (aria-checked); a page-navigation switch (period links, tabs that
     load a new URL) is a <nav> of <a> anchors (aria-current="page"). The active
     pill is the same filled shape in both, so the choice never reads as colour
     alone and the two forms stay visually one control. */
  .fdn-seg button, .fdn-seg a { appearance: none; border: 0; background: none; font: inherit; font-size: var(--text-xs); font-weight: var(--weight-strong); letter-spacing: 0.03em; color: var(--text-muted); text-decoration: none; padding: var(--space-2) var(--space-4); border-radius: var(--radius-pill); cursor: pointer; transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out); }
  .fdn-seg button[aria-checked="true"], .fdn-seg a[aria-current="page"] { background: var(--signal-soft); color: var(--accent-text); }

  .fdn-label { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label-wide); text-transform: uppercase; color: var(--text-muted); }
  .fdn-measure { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label-loose); text-transform: uppercase; color: var(--text-faint); white-space: nowrap; }

  /* section header */
  .fdn-sec { margin-top: var(--space-12); scroll-margin-top: var(--appbar-clearance); }
  .fdn-sec__head { display: flex; align-items: baseline; gap: var(--space-4); margin-bottom: var(--space-5); }
  .fdn-sec__no { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label-loose); color: var(--text-faint); font-variant-numeric: tabular-nums; }
  .fdn-sec__title { margin: 0; font-size: var(--text-lg); font-weight: var(--weight-strong); letter-spacing: var(--tracking-tight); color: var(--ink-2); }
  .fdn-sec__line { flex: 1; height: var(--hair); background: var(--line); }
  .fdn-sec__count { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label-loose); text-transform: uppercase; color: var(--text-faint); }

  /* grid.
     grid-auto-flow is plain `row` (NOT `dense`): dense backfills a later, smaller
     item into an earlier gap, which reorders the VISUAL sequence against DOM and
     screen-reader order — content must read in the order it is authored.
     grid-auto-columns: 0 is a guard: if a span ever exceeds the current column
     count (e.g. a 12-span row inside the 6-col tablet grid), the implicit tracks it
     spawns collapse to zero width instead of stealing width (as `auto`) from the
     real 1fr columns and squashing a neighbouring tile. Correct compositions never
     create implicit columns, so the guard is inert in normal use. */
  .fdn-cat { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--space-5); grid-auto-flow: row; grid-auto-columns: 0; }
  .fdn-sp3 { grid-column: span 3; } .fdn-sp4 { grid-column: span 4; } .fdn-sp6 { grid-column: span 6; }
  .fdn-sp8 { grid-column: span 8; } .fdn-sp12 { grid-column: span 12; }

  /* The public card declares its FULL panel recipe (fill, hairline, radius, shadow)
     here, in its own home — it is the second justified survivor of the W2-D2 fill
     consolidation, deliberately NOT in the parts/surface-fill.css group. This part
     loads after parts/panes.css (decision option, flow node) and parts/chart.css
     (tooltip), so an element that ever matched .fdn-card AND one of those at equal
     specificity resolves to the card — the win order the card must keep. Layout
     identity (flex column) rides on the same rule. */
  .fdn-card {
    position: relative;
    display: flex; flex-direction: column; min-width: 0;
    background: var(--panel);
    border: var(--hair) solid var(--line);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-card);
  }
  /* Panel weights — the standard card, plus two named steps. Declared right after
     the card so each modifier outranks the base by source order at equal
     specificity (the accent hairline and the flat shadow must win).
     LEAD: the one panel per page that carries what the reader must act on. It stays
     on the reading-content shadow tier (--shadow-card; the float tier is chrome-only
     per the room contract) and outranks its peers with an accent hairline; its
     greyscale/print-surviving mark is the filled .fdn-lead flag (parts/layout.css),
     never colour alone. At most one lead per page.
     QUIET: a compressed, headless panel that reads as one cell of a set (a KPI in a
     flush mosaic), not a card demanding its own attention. Flatter, tighter. */
  .fdn-card--lead { border-color: var(--signal-line); }
  .fdn-card--quiet { box-shadow: var(--shadow-flat); }
  .fdn-card--quiet > .fdn-card__body { padding: var(--space-4); gap: var(--space-2); }
  .fdn-card__head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); padding: var(--space-5) var(--space-5) 0; }
  .fdn-card__heading { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
  .fdn-card__title { margin: 0; font-size: var(--text-md); font-weight: var(--weight-strong); letter-spacing: var(--tracking-tight); color: var(--ink-2); line-height: var(--leading-tight); }
  .fdn-card__meta { flex: none; display: flex; align-items: center; gap: var(--space-3); }
  .fdn-card__rule { margin: var(--space-4) 0 0; }
  .fdn-card__body { padding: var(--space-5); display: flex; flex-direction: column; gap: var(--space-4); flex: 1; }
  .fdn-card__body--flush { gap: var(--space-0); } /* tight row-lists: let .fdn-row hairlines meet */

  /* Attachment slots — a control glued to the content it acts on (composition
     rule 6: same surface, no gap between cause and effect). __tools sits ABOVE
     the body (a filter bar over the table it narrows, its count pointing at the
     rows right below); __foot sits BELOW (pagination under the table it pages).
     Each is a strip on the card's own surface, divided from the body by a single
     hairline so the control reads as PART of the card, not a floating sibling.
     The slotted control owns its own inner layout (the filter bar, the pager);
     these rules give only the strip's padding and the dividing hairline. */
  .fdn-card__tools { padding: var(--space-4) var(--space-5); border-bottom: var(--hair) solid var(--line); }
  .fdn-card__foot  { padding: var(--space-4) var(--space-5); border-top: var(--hair) solid var(--line); }

  /* status tag — borderless dot+word (compact) */
  .fdn-stat { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; line-height: 1; white-space: nowrap; }
  .fdn-stat__dot { width: var(--dot); height: var(--dot); border-radius: 50%; flex: none; }
  .fdn-stat--ok   { color: var(--ink-2); }   .fdn-stat--ok .fdn-stat__dot   { background: var(--ok-dot); }
  .fdn-stat--warn { color: var(--ink-2); } .fdn-stat--warn .fdn-stat__dot { background: var(--warn-dot); }
  .fdn-stat--blk  { color: var(--ink-2); }  .fdn-stat--blk .fdn-stat__dot  { background: var(--blk-dot); }
  .fdn-stat--na   { color: var(--ink-2); }   .fdn-stat--na .fdn-stat__dot   { background: var(--na-dot); }

  /* Figure lockup — the one big-number primitive. ONE geometry everywhere; only
     the size changes, by variant (--sm, base, --display). Even-width (tabular)
     digits so a changing value never reflows the layout. Consolidates the former
     .fdn-hero__fig and .fdn-count lockups into this one (AP-9 / C4). */
  .fdn-fig { display: inline-flex; align-items: baseline; font-size: var(--text-3xl); font-weight: var(--weight-strong); letter-spacing: var(--tracking-figure); color: var(--ink-2); line-height: 1; font-variant-numeric: tabular-nums; }
  .fdn-fig--sm { font-size: var(--text-2xl); }
  .fdn-fig--display { font-size: var(--text-display); letter-spacing: var(--tracking-tighter); }
  .fdn-fig__unit { font-size: var(--text-lg); font-weight: var(--weight-strong); color: var(--mut); margin-left: 3px; letter-spacing: 0; }
  .fdn-fig--display .fdn-fig__unit { margin-left: var(--space-3); }
  .fdn-fig__row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4); }
  .fdn-fig__row .fdn-spark { align-self: center; } /* a chart centres against the figure; text qualifiers stay on its baseline */
  .fdn-fig__row:has(+ .fdn-meter) { margin-bottom: var(--space-4); } /* clear the meter's overhead target flag below a baseline-locked figure row */
  .fdn-sub { font-size: var(--text-xs); line-height: var(--leading-snug); color: var(--text-muted); }
  .fdn-sub b { color: var(--ink-2); font-weight: var(--weight-strong); }

  /* spark */
  .fdn-spark { width: 104px; height: 36px; flex: none; }
  .fdn-spark-wrap { position: relative; display: inline-block; width: 104px; height: 36px; }
  .fd-spark__hl { fill: var(--signal); opacity: 0; transition: opacity var(--dur-snap) var(--ease-out); }

  /* ---- standardized hover data box: white glass with edge refraction ---- */
  /* BASE backdrop is plain blur+saturate with NO url(): per spec an unsupported
     function drops the whole backdrop-filter declaration, so keeping url() off the
     base line lets Firefox keep a working frosted backdrop instead of losing it. */
  .fdn-tip { position: absolute; z-index: 5; transform: translate(-50%, -150%); padding: 3px 7px; border-radius: var(--radius-sm); font-size: var(--text-micro); font-weight: var(--weight-strong); line-height: 1.3; white-space: nowrap; text-align: center; pointer-events: none; opacity: 0; transition: opacity var(--dur-snap) var(--ease-out); font-variant-numeric: tabular-nums; color: var(--ink-2); background: var(--tip-bg); border: var(--hair) solid var(--tip-border); -webkit-backdrop-filter: blur(var(--tip-blur)) saturate(var(--tip-sat)); backdrop-filter: blur(var(--tip-blur)) saturate(var(--tip-sat)); box-shadow: var(--shadow-float), inset 0 1px 0 var(--tip-rim-top), inset 0 -1px 0 var(--tip-rim-bot); }
  .fdn-tip b { font-weight: var(--weight-strong); color: var(--ink-2); }
  .fdn-tip__sub { color: var(--mut); font-weight: var(--weight-strong); }
  /* ADDITIVE: Chromium-only edge refraction, gated to browsers that support url() in backdrop-filter */
  @supports (backdrop-filter: url(#fdn-glass-refract)) { .fdn-tip { backdrop-filter: blur(var(--tip-blur)) saturate(var(--tip-sat)) url(#fdn-glass-refract); } }
  /* LAST RESORT: opaque fill where backdrop-filter is unsupported entirely */
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .fdn-tip { background: var(--tip-bg-solid); } }

  /* budget meter — explicit scale + named target */
  .fdn-meter { display: flex; flex-direction: column; gap: var(--space-2); }
  .fdn-meter__track { position: relative; height: 8px; border-radius: var(--radius-pill); background: var(--line); }
  /* The fill renders SETTLED: no width transition, so the meter never grows in on
     load. The runtime paints its final width once, from the inline style, and the
     fill simply IS its value (AP-4 arrival budget, memo B1 — a value arrives already
     at rest, it never performs its arrival, so a page of meters is silent on load). */
  .fdn-meter__fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: var(--radius-pill); background: var(--ink-2); }
  /* The plan target is a threshold marker: a faint solid hairline in the house
     --chart-mark idiom (the same neutral marker line the bar charts use for an
     event drop-line), never a dash. The "Plan 83%" flag word above it carries
     the meaning, so the line stays quiet and the colour never carries it alone. */
  .fdn-meter__target { position: absolute; top: -5px; bottom: -5px; width: 0; border-left: var(--hair) solid var(--chart-mark); }
  .fdn-meter__flag { position: absolute; bottom: calc(100% + 3px); left: 50%; transform: translateX(-50%); font-size: var(--text-micro); font-weight: var(--weight-strong); letter-spacing: 0.06em; text-transform: uppercase; color: var(--mut); white-space: nowrap; }
  .fdn-meter__scale { display: flex; justify-content: space-between; font-size: var(--text-2xs); font-weight: var(--weight-strong); color: var(--text-faint); font-variant-numeric: tabular-nums; }

  /* target pacing — actual + forecast progress on a milestone scale.
     An EXTENSION of the budget meter, not a parallel system: in the markup the
     track, both fills, the target line and its flag ARE the .fdn-meter__
     primitives (the meter class sits on each element); the .fdn-pace__ classes
     below carry ONLY the delta (a squared inner fill edge, the forecast's lighter
     ink step, a faint hairline target). A change to a meter primitive therefore
     propagates here for free — nothing is re-implemented. The forecast is a SECOND
     meter fill that continues from the actual one; the two series are told apart by
     a LUMINANCE LADDER (a strong actual, a lighter forecast, the pale unfilled
     track) plus a two-word legend, never by colour alone. The ladder is pure
     neutral ink, so the distinction survives greyscale, print, and colourblind
     viewing without any hue doing the work.
     Milestones sit at authored percentages (the piecewise scale is the author's
     data, exactly as a meter's fill width is). One positioned node per milestone
     carries BOTH its label and its paired annotation value, so the two rows can
     never drift apart. The gap to target is STATED IN WORDS on the block face. */
  .fdn-pace { display: flex; flex-direction: column; gap: var(--space-4); }
  .fdn-pace__legend { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-5); }
  .fdn-pace__key { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint); }
  .fdn-pace__sw { width: var(--legend-sw-w); height: var(--legend-sw-h); border-radius: var(--legend-sw-r); flex: none; }
  .fdn-pace__sw--actual { background: var(--ink-2); }
  /* Forecast: the middle rung of the luminance ladder — a SOLID lighter ink than
     the actual, no texture. Reads clearly softer than the strong actual and
     clearly stronger than the pale unfilled track, in both themes and in print. */
  .fdn-pace__sw--fcst { background: var(--faint); }
  /* Target: a thin SOLID faint hairline, the same quiet ruler-tick idiom as the
     chart event-marker (--chart-mark aliases --tick-2). No dash. */
  .fdn-pace__sw--target { width: 0; height: var(--legend-sw-h-box); border-radius: 0; border-left: var(--stroke-2) solid var(--chart-mark); }
  /* ONE coordinate system: the track fills, the ticks, the milestone marks and
     the range bands are ALL positioned as a percent of the same __scale width, so
     a given percent lands on the SAME x in the bar, under it, and across the
     bands. (The bands were previously flex-basis of the full width while the marks
     were inset, which overstated every band by the inset end margins.) */
  .fdn-pace__scale { position: relative; }
  /* The actual + forecast are .fdn-meter__fill in the markup; these rules add only
     the deltas. The actual keeps its outer (left) end rounded and squares the
     inner (right) end so the forecast fill continues flush from it. */
  .fdn-pace__actual { border-radius: var(--radius-pill) 0 0 var(--radius-pill); }
  /* Forecast fill: a SOLID lighter-ink continuation of the actual (the middle rung
     of the dark/mid/pale luminance ladder), no texture. --faint sits clearly
     between the strong --ink-2 actual and the pale --line track in both themes. */
  .fdn-pace__fcst { background-color: var(--faint); background-image: none; border-radius: 0 var(--radius-pill) var(--radius-pill) 0; }
  /* The annual target is a thin SOLID faint hairline in the chart event-marker
     idiom (--chart-mark, the quiet major-ruler-tick strength): a quiet position
     line, faint not bold, in step with the meter's own solid plan line. The word
     TARGET on its flag carries the meaning; the line is only a position tick. */
  .fdn-pace__target { border-left: var(--hair) solid var(--chart-mark); }
  /* A target at the far end of the scale right-anchors its (reused meter) flag so
     the word never overflows the card edge. */
  .fdn-pace__flag--end { left: auto; right: 0; transform: none; }
  /* Period checkpoints (P1..P7) are SUBTLE minor-ruler ticks, not full-height cuts.
     Only the annual-target line cuts through the whole bar (--chart-mark, the MAJOR
     ruler-tick strength); the period ticks sit one rung quieter — a short --tick
     (the documented MINOR-ruler-tick ink) stub hanging just below the track base,
     centred on each P coordinate so it aligns with its P label underneath. Two axes
     of subordination to the target (shorter AND fainter) keep the periods as quiet
     reference gradations that no longer compete with the plan line. The border-left
     prints on its own and --tick restates in the print palette, so no print rule is
     needed. */
  .fdn-pace__tick { position: absolute; top: 100%; height: var(--space-1); width: 0; border-left: var(--hair) solid var(--tick); transform: translateX(-50%); }
  .fdn-pace__marks { position: relative; height: var(--space-12); margin-top: var(--space-1); }
  .fdn-pace__mark { position: absolute; top: 0; transform: translateX(-50%); display: flex; flex-direction: column; align-items: center; gap: var(--space-1); white-space: nowrap; }
  .fdn-pace__mark--first { align-items: flex-start; transform: none; }
  .fdn-pace__mark--last { align-items: flex-end; transform: translateX(-100%); }
  .fdn-pace__mlabel { font-size: var(--text-2xs); font-weight: var(--weight-strong); color: var(--ink-2); font-variant-numeric: tabular-nums; }
  .fdn-pace__mval { font-size: var(--text-2xs); color: var(--mut); font-variant-numeric: tabular-nums; }
  .fdn-pace__annk { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint); }
  /* Range bands share the marks' coordinate: each band is placed by the SAME
     percent as the marks it spans — a leading margin to its start mark, a width to
     its end mark — so a band's edges land exactly on its two milestones and it
     never overstates its interval. */
  .fdn-pace__ranges { display: flex; }
  .fdn-pace__range { flex: none; min-width: 0; padding-top: var(--space-2); border-top: var(--hair) solid var(--line-2); display: flex; flex-direction: column; gap: var(--space-1); }
  .fdn-pace__rt { font-size: var(--text-2xs); font-weight: var(--weight-strong); color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .fdn-pace__rs { font-size: var(--text-2xs); color: var(--mut); }
  .fdn-pace__gap { font-size: var(--text-sm); line-height: var(--leading-snug); color: var(--body); }
  .fdn-pace__gap b { color: var(--ink-2); font-weight: var(--weight-strong); }

  /* item rows (lists) */
  .fdn-rows { display: flex; flex-direction: column; }
  .fdn-row { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) 0; border-top: var(--hair) solid var(--line-fine); }
  .fdn-row:first-child { border-top: 0; }
  .fdn-row__main { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
  .fdn-row__t { font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--ink-2); line-height: var(--leading-snug); }
  .fdn-row__s { font-size: var(--text-2xs); color: var(--mut); line-height: var(--leading-snug); }
  .fdn-row__r { margin-left: auto; flex: none; display: flex; align-items: center; gap: var(--space-3); }

  /* numbered index node */
  .fdn-ix { width: 22px; height: 22px; flex: none; display: grid; place-items: center; border-radius: var(--radius-pill); border: var(--hair) solid var(--line-2); color: var(--mut); font-size: var(--text-2xs); font-weight: var(--weight-strong); font-variant-numeric: tabular-nums; }
  .fdn-ix--done { color: var(--ok-dot); border-color: var(--ok-line); }
  .fdn-ix--now { color: var(--signal); border-color: var(--signal); }

  /* avatar (initials) */
  .fdn-ava { width: 28px; height: 28px; flex: none; display: grid; place-items: center; border-radius: var(--radius-pill); background: var(--paper); border: var(--hair) solid var(--line-2); font-size: var(--text-2xs); font-weight: var(--weight-strong); color: var(--ink-2); letter-spacing: 0.02em; }

  /* trend / signed KPI delta — CONTRACT: a small glyph ALWAYS paired with the value;
     colour is never carried by the word alone (the glyph is the redundant cue).
     Two families, because direction and valence are NOT the same thing:
       VALENCE variants (--up/--down/--flat): the move is judged good or bad, so
         colour asserts it. Risk context only — rising risk is bad (red), falling
         risk is good (green). Reach for these only where up/down carries meaning.
       NEUTRAL variants (--rise/--fall/--steady): the glyph reports which way a
         metric moved; the move itself is NOT judged. Colour stays calm body/mut
         and never claims good or bad. Reach for these for plain metric deltas
         (revenue, headcount, latency) where the reader decides what the move means. */
  .fdn-trend { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); display: inline-flex; align-items: center; gap: var(--space-1); color: var(--mut); font-variant-numeric: tabular-nums; }
  .fdn-trend::before { font-size: var(--text-micro); line-height: 1; }
  .fdn-trend--up   { color: var(--blk-ink); } .fdn-trend--up::before   { content: "\2191"; } /* ↑ rising = bad (risk) */
  .fdn-trend--down { color: var(--ok-ink); }  .fdn-trend--down::before { content: "\2193"; } /* ↓ falling = good (risk) */
  .fdn-trend--flat { color: var(--mut); }     .fdn-trend--flat::before { content: "\2192"; } /* → */
  /* valence-neutral: glyph shows direction, colour stays calm (no good/bad claim) */
  .fdn-trend--rise    { color: var(--body); } .fdn-trend--rise::before    { content: "\2191"; } /* ↑ */
  .fdn-trend--fall    { color: var(--body); } .fdn-trend--fall::before    { content: "\2193"; } /* ↓ */
  .fdn-trend--steady  { color: var(--mut); }  .fdn-trend--steady::before  { content: "\2192"; } /* → */

  /* mini bullet (risk severity / check) */
  .fdn-sev { width: 8px; height: 8px; border-radius: 2px; flex: none; }
  .fdn-sev--hi { background: var(--blk-dot); } .fdn-sev--med { background: var(--warn-dot); } .fdn-sev--lo { background: var(--na-dot); }
  /* Severity word paired with the square so colour never stands alone (mirrors
     the dot+word pairing in 09-health-matrix). Fixed width aligns the titles. */
  .fdn-sevword { flex: none; width: 4rem; font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--body); }

  /* check mark box */
  .fdn-chk { width: 18px; height: 18px; flex: none; border-radius: 6px; border: var(--hair) solid var(--line-2); display: grid; place-items: center; }
  .fdn-chk--on { background: var(--ok-bg); border-color: var(--ok-line); }
  .fdn-chk--on::after { content: ""; width: 9px; height: 5px; border-left: 2px solid var(--ok-dot); border-bottom: 2px solid var(--ok-dot); transform: rotate(-45deg); margin-top: -2px; }
  .fdn-chk--now { border-color: var(--signal); }

  /* two-column split (scope) */
  .fdn-split { display: grid; grid-template-columns: 1fr 1fr; gap: 0; }
  .fdn-split__col { padding: 0 var(--space-5); }
  .fdn-split__col:first-child { padding-left: 0; border-right: var(--hair) solid var(--line); }
  .fdn-split__col:last-child { padding-right: 0; }
  .fdn-split__h { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-3); }
  .fdn-scope-item { display: flex; gap: var(--space-2); align-items: baseline; font-size: var(--text-sm); color: var(--body); line-height: var(--leading-snug); padding: var(--space-2) 0; }
  .fdn-scope-item__m { color: var(--text-faint); flex: none; }

  /* RAG matrix */
  .fdn-matrix { display: grid; gap: 0; }
  .fdn-matrix__row { display: grid; grid-template-columns: 1.4fr repeat(3, 1fr); align-items: center; gap: var(--space-3); padding: var(--space-3) 0; border-top: var(--hair) solid var(--line-fine); }
  .fdn-matrix__row:first-child { border-top: 0; }
  .fdn-matrix__head { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint); padding-bottom: var(--space-2); border-top: 0; }
  .fdn-matrix__name { font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--ink-2); }
  .fdn-cell { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-2xs); color: var(--mut); }
  .fdn-cell__dot { width: 10px; height: 10px; border-radius: 50%; flex: none; }

  /* milestone timeline */
  .fdn-timeline { position: relative; padding: var(--space-8) 0 var(--space-6); }
  .fdn-timeline__track { position: relative; height: var(--hair); background: var(--line-2); }
  .fdn-timeline__done { position: absolute; left: 0; top: 0; height: 2px; margin-top: -0.5px; background: var(--ok-dot); }
  .fdn-tl-node { position: absolute; top: 50%; transform: translate(-50%, -50%); display: flex; flex-direction: column; align-items: center; }
  .fdn-tl-node__dot { width: 12px; height: 12px; border-radius: 50%; border: 2px solid var(--line-2); }
  .fdn-tl-node.is-done .fdn-tl-node__dot { background: var(--ok-dot); border-color: var(--ok-dot); }
  .fdn-tl-node.is-now .fdn-tl-node__dot { border-color: var(--signal); box-shadow: 0 0 0 4px var(--signal-soft); }
  .fdn-tl-node__lbl { position: absolute; top: 18px; white-space: nowrap; font-size: var(--text-2xs); font-weight: var(--weight-strong); color: var(--ink-2); }
  .fdn-tl-node__date { position: absolute; bottom: 18px; white-space: nowrap; font-size: var(--text-2xs); color: var(--text-faint); font-variant-numeric: tabular-nums; }

  /* roadmap lanes. Phase name labels each row; the bar is pure duration.
     The ROW gap is the breathing room the hover tip nests into: a tip lifts above
     its bar and sits in the gutter, so the gutter is sized so the tip clears the
     resting bar in the lane above without grazing it. */
  .fdn-road { position: relative; isolation: isolate; display: grid; grid-template-columns: 124px 1fr; gap: var(--space-5) var(--space-4); align-items: center; }
  .fdn-road__cols { display: grid; grid-template-columns: repeat(4, 1fr); }
  /* colhead keeps its border-right: it coincides exactly with the overlay's
     quarter rule on screen (so no double line) but, being a BORDER not a
     background, it still prints the quarter divisions when a reader prints with
     backgrounds off — the overlay's interior rules are a background and would
     drop. The now line (a solid accent border) and the "Now" word (text) print too. */
  .fdn-road__colhead { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint); text-align: center; padding-bottom: var(--space-2); border-right: var(--hair) solid var(--line-fine); }
  .fdn-road__lane { font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--ink-2); white-space: nowrap; }
  .fdn-road__cells { position: relative; height: 24px; }
  /* CONTINUOUS quarter grid: ONE plot-spanning layer draws the four-quarter rules
     once, so they run unbroken down every lane instead of restarting as a stub
     inside each row (the old per-row background left tick fragments broken by the
     row gap). It spans the cells column exactly (past the 124px lane column + its
     column gap) and is isolated behind the bars, so it never leaks behind the card. */
  .fdn-road__grid, .fdn-road__nowtag { position: absolute; top: 0; bottom: 0; left: calc(124px + var(--space-4)); right: 0; pointer-events: none; }
  .fdn-road__grid { z-index: -1; border-left: var(--hair) solid var(--line);
    background-image: repeating-linear-gradient(to right, transparent 0, transparent calc(25% - var(--hair)), var(--line-fine) calc(25% - var(--hair)), var(--line-fine) 25%); }
  /* NOW marker: a vertical accent line (in the grid layer, behind the bars, so it
     reads as a reference passing through the live phase) PLUS the word "Now" above
     the bars. Colour AND word: greyscale keeps the word, so the mark never rides on
     colour alone. --road-now sets the position as a percent of the plot width. */
  .fdn-road__now { position: absolute; top: 0; bottom: 0; left: var(--road-now, 58%); border-left: var(--hair) solid var(--signal); }
  .fdn-road__nowtag { z-index: 1; }
  .fdn-road__nowlabel { position: absolute; left: var(--road-now, 58%); top: 0; transform: translate(-50%, -100%);
    font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label);
    color: var(--signal); padding: 0 var(--space-1); white-space: nowrap; }
  .fdn-road__bar { position: absolute; top: 7px; height: 10px; border-radius: var(--radius-pill); background: var(--line-2); cursor: default; transition: background var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }
  .fdn-road__bar--accent { background: var(--signal); }
  /* Calm hover: the bar deepens a step and gains a soft float shadow so it lifts
     off the lane, and a .fdn-tip (anchored in the relative .fdn-road__cells)
     names the phase and its quarter span in WORDS, so the read never rides on
     the bar's length or colour alone. */
  .fdn-road__bar:hover { background: var(--ink-2); box-shadow: var(--shadow-float); }
  .fdn-road__bar--accent:hover { background: var(--signal-deep); }
  @media (prefers-reduced-motion: reduce) { .fdn-road__bar { transition: none; } }
  /* B2 still mode twin (kept identical to the reduced-motion rule by lint-still-mode.mjs) */
  :root[data-still] .fdn-road__bar { transition: none; }

  /* phase gates inline */
  .fdn-gates { display: flex; align-items: stretch; gap: 0; }
  .fdn-gate { flex: 1; display: flex; flex-direction: column; gap: var(--space-2); position: relative; padding-right: var(--space-4); }
  .fdn-gate:last-child { flex: 0 0 auto; padding-right: 0; }
  .fdn-gate__top { display: flex; align-items: center; gap: var(--space-2); }
  .fdn-gate__line { flex: 1; height: 2px; background: var(--line-2); border-radius: var(--radius-pill); }
  .fdn-gate.is-done .fdn-gate__line { background: var(--signal); }
  .fdn-gate.is-now .fdn-gate__line { background: linear-gradient(to right, var(--signal) 0 55%, var(--line-2) 55% 100%); }
  .fdn-gate__name { font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--ink-2); }
  .fdn-gate__sub { font-size: var(--text-2xs); color: var(--mut); }

  /* chart host (donut/gauge/bars) */
  .fdn-viz { display: flex; align-items: center; justify-content: center; gap: var(--space-5); }
  .fdn-viz__svg { display: block; }
  .fdn-donut { width: 132px; height: 132px; flex: none; }
  .fdn-gauge { width: 150px; height: 96px; flex: none; }
  .fdn-bars { width: 100%; height: 96px; }
  .fdn-bars-wrap { position: relative; display: block; }
  .fdn-bars .fd-chart__bar { transition: opacity var(--dur-snap) var(--ease-out); }
  /* B2/reduced-motion: the hover dim/undim is feedback motion, so it collapses to
     instant under BOTH the reader's reduced-motion preference and the composer's
     still mode. Byte-identical twins, held in lockstep by build/lint-still-mode.mjs. */
  @media (prefers-reduced-motion: reduce) {
    .fdn-bars .fd-chart__bar { transition: none; }
  }
  :root[data-still] .fdn-bars .fd-chart__bar { transition: none; }
  .fdn-bars-wrap.is-hovering .fd-chart__bar { opacity: 0.4; }
  .fdn-bars-wrap.is-hovering .fd-chart__bar.is-hot { opacity: 1; }
  .fdn-scatter { width: 100%; height: auto; display: block; }
  .fdn-scatter-wrap { position: relative; width: 100%; }
  .fdn-scatter-dot { position: absolute; width: 12px; height: 12px; border-radius: 50%; transform: translate(-50%, -50%); box-sizing: border-box; z-index: 2; transition: box-shadow var(--dur-snap) var(--ease-out); cursor: default; }
  .fdn-scatter-dot.is-hot { box-shadow: 0 0 0 5px var(--signal-soft); }
  .fdn-scatter-dot--plain { border: 1.5px solid var(--tick-2); }
  .fdn-sc-axis { fill: var(--faint); font-size: var(--text-svg-micro); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label-loose); }
  .fdn-sc-axis--end { fill: var(--mut); }
  .fdn-don-val { fill: var(--ink-2); font-size: var(--text-figure); font-weight: var(--weight-strong); font-variant-numeric: tabular-nums; }
  .fdn-don-cap { fill: var(--mut); font-size: var(--text-svg-micro); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; }
  .fdn-sc-lbl { fill: var(--mut); font-size: var(--text-svg-micro); font-weight: var(--weight-strong); }

  /* composition share bar */
  .fdn-share { display: flex; flex-direction: column; gap: var(--space-5); width: 100%; }
  .fdn-share__bar { display: flex; height: 16px; gap: 2px; border-radius: var(--radius-pill); overflow: hidden; }
  .fdn-share__seg { height: 100%; min-width: 2px; }
  .fdn-share__legend { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3) var(--space-5); }
  .fdn-share__leg { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--text-muted); }
  .fdn-share__leg b { color: var(--ink-2); font-weight: var(--weight-strong); margin-left: auto; font-variant-numeric: tabular-nums; }
  .fdn-share__sw { width: 10px; height: 10px; border-radius: 3px; flex: none; }

  /* pull quote */
  .fdn-quote { font-size: var(--text-md); line-height: var(--leading-snug); color: var(--ink-2); font-weight: var(--weight-strong); letter-spacing: var(--tracking-tight); text-wrap: pretty; }
  .fdn-quote__mark { color: var(--signal); }
  .fdn-quote__by { display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-4); }

  /* comparison matrix */
  .fdn-cmp { width: calc(100% + 2 * var(--space-3)); margin-left: calc(-1 * var(--space-3)); margin-right: calc(-1 * var(--space-3)); border-collapse: collapse; }
  .fdn-cmp th, .fdn-cmp td { text-align: left; padding: var(--space-3) var(--space-3); border-top: var(--hair) solid var(--line-fine); font-size: var(--text-sm); }
  .fdn-cmp thead th { border-top: 0; font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint); }
  .fdn-cmp td:first-child, .fdn-cmp th:first-child { border-top-left-radius: var(--radius-sm); border-bottom-left-radius: var(--radius-sm); }
  .fdn-cmp td:last-child, .fdn-cmp th:last-child { border-top-right-radius: var(--radius-sm); border-bottom-right-radius: var(--radius-sm); }
  /* Base row-name cell, shared by the comparison table and the data table (one
     definition, not duplicated per table). The .fdn-cmp tr.is-rec override below
     repaints it for the recommended row. */
  .fdn-name { font-weight: var(--weight-strong); color: var(--ink-2); }
  .fdn-cmp tr.is-rec td { background: var(--signal-soft); border-top-color: transparent; }
  .fdn-cmp tr.is-rec + tr td { border-top-color: transparent; }
  .fdn-cmp tr.is-rec .fdn-name { color: var(--accent-text); }
  /* Recommended marker: the word pairs with the navy band so the recommendation
     survives greyscale and colourblindness (the decision rule: one marked
     Recommended). The one-line why is the .fdn-cmp__why caption below the table. */
  .fdn-cmp__rec { margin-left: var(--space-2); font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--accent-text); white-space: nowrap; }
  .fdn-cmp__why { margin: var(--space-4) 0 0; font-size: var(--text-sm); color: var(--body); line-height: var(--leading-snug); }
  .fdn-cmp__why b, .fdn-cmp__why strong { color: var(--accent-text); font-weight: var(--weight-strong); }

  /* data table — a general numeric/text reporting table.
     Sibling to .fdn-cmp but carries NO decision meaning: no is-rec band, no
     Recommended marker. Reach for .fdn-matrix for status-at-a-glance, this for
     aligned numeric line items. Numeric columns are right-aligned with
     tabular-nums so digits line up; the header reuses the .fdn-matrix__head
     uppercase treatment. The first cell is an optional row name. The phone
     reflow (in the <=640px block below) reuses the .fdn-cmp stacked-card
     pattern via data-label, so a wide table never horizontal-scrolls. */
  .fdn-data { width: 100%; border-collapse: collapse; color: var(--body); }
  /* Cell colour comes from the table (inherited), NOT a td rule, so the unscoped
     .fdn-name base above (a class on the cell) wins for the row-name cell instead
     of losing the cascade to a higher-specificity ".fdn-data td" colour. */
  .fdn-data th, .fdn-data td { padding: var(--space-3) var(--space-3); border-top: var(--hair) solid var(--line-fine); font-size: var(--text-sm); text-align: left; }
  .fdn-data thead th { border-top: 0; font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint); }
  .fdn-data th:first-child, .fdn-data td:first-child { padding-left: 0; }
  .fdn-data th:last-child, .fdn-data td:last-child { padding-right: 0; }
  /* Numeric column: right-aligned, lined-up digits. Put .fdn-data__num on both
     the header cell and the body cells of a numeric column. Scoped to th/td so it
     wins the cascade over the base "text-align: left" cell rule above. */
  .fdn-data th.fdn-data__num, .fdn-data td.fdn-data__num { text-align: right; font-variant-numeric: tabular-nums; }

  /* forecast vintage matrix — a stagger grid of how each period's estimate stood
     at successive snapshots, locking to the actual once the period has occurred.
     A REAL table (scope row/col headers + a caption), so it is announced correctly
     and prints as a table. Three cell states, each carried by MORE than colour:
       - locked actuals read by WEIGHT (bold) + a tint, and a legend word;
       - the single most recent forecast is BOXED with an outline (prints, and does
         not shift the grid) plus a legend word;
       - cells beyond a snapshot's forecast horizon carry a faint EN-DASH marker
         glyph (the quiet "no figure here" idiom) plus an off-screen word.
     Born FULL WIDTH. On a narrow screen it does NOT card-stack like the
     data-table: a matrix loses its meaning when the columns are split up, so it
     stays a grid and SCROLLS horizontally instead (the wide-table counterpart to
     the data-table's card stack), with the axis labels kept OUTSIDE the scroll so
     they never scroll away. */
  .fdn-vintage__axis { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint); margin-bottom: var(--space-3); }
  /* Calm tokened scrollbar in the system's own hairline vocabulary (faint thumb on
     no track), so the well scrolls without a loud bar. color-scheme (tokens/colors.css)
     is what lets native UI take these — a scrollbar ignores tokens until it is declared. */
  .fdn-vintage__wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: thin; scrollbar-color: var(--line-2) transparent; }
  .fdn-vintage { border-collapse: collapse; width: 100%; min-width: var(--vintage-min-w); color: var(--body); font-variant-numeric: tabular-nums; }
  .fdn-vintage caption { text-align: left; font-size: var(--text-xs); line-height: var(--leading-snug); color: var(--mut); margin-bottom: var(--space-3); }
  .fdn-vintage th, .fdn-vintage td { padding: var(--space-2) var(--space-3); border: var(--hair) solid var(--line-fine); font-size: var(--text-sm); text-align: right; white-space: nowrap; }
  .fdn-vintage thead th { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint); }
  .fdn-vintage th[scope="row"] { text-align: left; font-weight: var(--weight-strong); color: var(--ink-2); }
  /* locked actual: weight + tint (+ legend word). */
  .fdn-vintage__act { background: var(--signal-soft); font-weight: var(--weight-strong); color: var(--ink-2); }
  /* most recent forecast: an inset outline so it prints and never shifts the grid. */
  .fdn-vintage__now { outline: var(--stroke-2) solid var(--signal-line); outline-offset: calc(-1 * var(--stroke-2)); font-weight: var(--weight-strong); color: var(--ink-2); }
  /* beyond the forecast horizon: no value yet. A QUIET cell carrying a centred
     en-dash marker glyph (the "no figure here" idiom the scope-split and ledger
     blocks use), plus an off-screen "not yet estimated" word for a screen reader.
     No fill, no texture: the empty cell is simply the calm paper with a faint dash,
     which reads the same in both themes, in greyscale, and in print. */
  .fdn-vintage__empty { text-align: center; color: var(--text-faint); }
  .fdn-vintage__legend { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-5); margin-top: var(--space-4); }
  .fdn-vintage__key { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint); }
  .fdn-vintage__sw { width: var(--legend-sw-w); height: var(--legend-sw-h-box); border-radius: var(--legend-sw-r); flex: none; }
  .fdn-vintage__sw--act { background: var(--signal-soft); }
  .fdn-vintage__sw--now { outline: var(--stroke-2) solid var(--signal-line); outline-offset: calc(-1 * var(--stroke-2)); background: var(--panel); }
  /* Empty key: the same faint en-dash marker glyph the empty cell uses, centred in
     the swatch slot so it lines up with the tint and box keys. No fill. */
  .fdn-vintage__sw--empty { display: inline-flex; align-items: center; justify-content: center; background: none; color: var(--text-faint); }
  /* financial statement ledger — a wide, period-by-period statement (a profit and
     loss, a balance sheet) built on the same cell vocabulary as .fdn-data but with
     the grammar a statement needs: section bands, indented line items, subtotal and
     calc (result) rows, an all-year total set off by a rule, a marked current
     period, and a variance column. The name column is the only pinned column: the
     year total sits second from the right (the variance column is rightmost), so it
     is distinguished by a strong rule and strong weight, not a position:sticky pin
     that would collide with the variance column. It is the WIDE-TABLE COUNTERPART
     to .fdn-data's phone
     card-stack, not a contradiction of it: a narrow report stacks into cards on a
     phone, but a statement has too many period columns to stack, so it stays a
     table and SCROLLS sideways inside .fdn-ledger__scroll with the line-item column
     pinned (scale-to-fit, never reflow). House rules held throughout: section
     bands, subtotals and calc rows read by WEIGHT and RULE, never colour; the
     current period, the year total, and the variance direction each carry a WORD so
     nothing rests on colour alone; negative figures keep an explicit minus sign and
     never lean on red; a period with no data shows an en-dash, never a fake zero.
     Ships fully expanded and fully readable with ZERO JavaScript. */

  /* Scroll well: the statement scrolls sideways INSIDE here, capped at the card
     width and momentum-contained, so the wide table never pushes the page itself
     sideways. Reuses the code-block overflow idiom. */
  /* Calm tokened scrollbar (faint thumb on no track), the system's own hairline
     vocabulary, so the wide statement scrolls without a loud bar. color-scheme
     (tokens/colors.css) is the enabler — native UI ignores tokens until it is declared. */
  .fdn-ledger__scroll { max-width: 100%; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: thin; scrollbar-color: var(--line-2) transparent; }

  /* Separate borders (not collapse): position:sticky on a cell is reliable across
     Chromium AND Safari/iOS with separate borders, where border-collapse drops the
     sticky cell's borders. border-spacing:0 keeps the grid tight; every rule below
     is a single cell edge, so nothing doubles. */
  .fdn-ledger { min-width: 100%; border-collapse: separate; border-spacing: 0; color: var(--body); font-variant-numeric: tabular-nums; }
  .fdn-ledger th, .fdn-ledger td { padding: var(--space-2) var(--space-3); font-size: var(--text-sm); text-align: left; white-space: nowrap; vertical-align: baseline; }
  /* Numeric columns: right-aligned, lined-up digits. Scoped to th/td so it wins
     over the base left-align (same pattern as .fdn-data__num). */
  .fdn-ledger th.fdn-ledger__num, .fdn-ledger td.fdn-ledger__num { text-align: right; font-variant-numeric: tabular-nums; }

  /* Column headers: the uppercase eyebrow treatment shared across the system. */
  .fdn-ledger thead th { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint); border-bottom: var(--hair) solid var(--line-2); vertical-align: bottom; }

  /* Line-item (name) column, PINNED. It stays put while the periods scroll; an
     opaque panel fill and a right-hand rule separate it from the scrolling body. */
  .fdn-ledger__name { font-weight: var(--weight-body); color: var(--ink-2); }
  .fdn-ledger th[scope="row"], .fdn-ledger thead th:first-child {
    position: sticky; left: 0; z-index: 1; background: var(--panel);
    border-right: var(--hair) solid var(--line-2);
  }
  /* Indent by hierarchy depth. th.class beats the base ".fdn-ledger th" padding. */
  .fdn-ledger th.fdn-ledger__name--l0 { padding-left: var(--space-3); }
  .fdn-ledger th.fdn-ledger__name--l1 { padding-left: var(--space-5); }
  .fdn-ledger th.fdn-ledger__name--l2 { padding-left: var(--space-8); }
  .fdn-ledger th.fdn-ledger__name--percent { color: var(--mut); font-size: var(--text-xs); font-weight: var(--weight-body); }

  /* Row hairlines: a fine rule between line items. */
  .fdn-ledger__row td, .fdn-ledger__row th { border-top: var(--hair) solid var(--line-fine); }

  /* Section band: a full-width label that opens a section. Strong weight, an
     uppercase eyebrow, and a strong rule above carry it. The band CELL spans the
     full width for the rule and fill; the LABEL is an inner sticky element so the
     section name stays pinned at the left while the periods scroll. (A full-width
     sticky cell cannot pin, because it exactly fills its own containing block and
     so has no room to slide; the pin has to live on the inner span.) */
  .fdn-ledger__band { padding: 0; background: var(--panel); border-top: var(--hair) solid var(--line-2); text-align: left; }
  .fdn-ledger__section:first-of-type .fdn-ledger__band { border-top: 0; }
  .fdn-ledger__band-label {
    position: sticky; left: 0; z-index: 1;
    display: inline-block;
    padding: var(--space-4) var(--space-3) var(--space-2);
    font-size: var(--text-2xs); font-weight: var(--weight-strong);
    letter-spacing: var(--tracking-label); text-transform: uppercase;
    color: var(--accent-text); background: var(--panel);
  }

  /* Sub-group header (a line that rolls up its indented children): stronger name. */
  .fdn-ledger__row--sub .fdn-ledger__name { font-weight: var(--weight-strong); color: var(--ink-2); }

  /* Subtotal rows: a strong rule above and strong weight mark the roll-up. */
  .fdn-ledger__row--subtotal td, .fdn-ledger__row--subtotal th {
    border-top: var(--hair) solid var(--line-2);
    font-weight: var(--weight-strong); color: var(--ink-2);
  }
  /* Calc (result) rows — EBITDA, profit before tax: the darkest ink, ruled above
     and below, so the bottom line reads as the answer. */
  .fdn-ledger__row--calc td, .fdn-ledger__row--calc th {
    border-top: var(--hair) solid var(--line-2);
    border-bottom: var(--hair) solid var(--line-2);
    font-weight: var(--weight-strong); color: var(--ink);
  }
  /* Placeholder subtotal styling lives after the no-data rule below, so its numeric
     cells win the specificity tie and carry the subtotal's weight (see there). */

  /* Current period — the "now" column. Two accent marks in the system's live/now
     wayfinding carry it: a 2px accent rule across the TOP of the column (the same
     selected-marker weight the active tab uses) and a faint accent wash down its
     length. Every period header stays a SINGLE line ("P6" like its peers) so the
     header rhythm is even; the WORD that keeps colour from standing alone moves out
     of the header line into the caption ("The current period, P6, is the column
     topped by an accent rule") and the reading-conventions note below. */
  .fdn-ledger__cell-current { background: var(--signal-soft); }
  /* th.class beats the base ".fdn-ledger thead th" colour so the current header
     reads in the accent (AA on the wash), not the faint eyebrow grey (which the
     wash drops below AA). The 2px accent rule sits on the header cell's top edge to
     flag the top of the column; it is a border, so it survives the background-free
     print where the wash drops out. */
  .fdn-ledger__colhead--current { background: var(--signal-soft); border-top: var(--stroke-2) solid var(--accent); }
  .fdn-ledger thead th.fdn-ledger__colhead--current { color: var(--accent-text); }

  /* FY total column: set off by a strong rule and carried in strong weight. */
  .fdn-ledger td.fdn-ledger__total { border-left: var(--hair) solid var(--line-2); font-weight: var(--weight-strong); color: var(--ink-2); }
  .fdn-ledger__colhead--total { border-left: var(--hair) solid var(--line-2); }
  /* th.class beats the base ".fdn-ledger thead th" colour (1 class + 2 elements) so
     the FY-total header carries the stronger ink, not the faint eyebrow grey — the
     same compound-selector pattern the current-period header uses above. */
  .fdn-ledger thead th.fdn-ledger__colhead--total { color: var(--ink-2); }

  /* Variance column: the current period against plan. The SIGN and the WORD carry
     the direction (plus / "ahead" = better than plan, minus / "behind" = worse than
     plan, stated in the caption); the tint reinforces, so the meaning survives
     greyscale, colourblindness, and a background-free print. Ahead and behind sit in
     two clearly separated status families from tokens/colors.css — sage GREEN (ok)
     for ahead, clay RED (blk) for behind — so the difference reads at a glance
     rather than in near-neighbour tints. Every ink-on-tint pair clears WCAG AA in
     both themes: word 6.14 (light) / 7.72 (dark) ahead, 6.42 / 6.78 behind; number
     7.70 / 8.15 ahead, 7.39 / 8.46 behind. This is the repair of the original's
     colour-alone variance signal, not a reproduction of it. */
  .fdn-ledger td.fdn-ledger__var, .fdn-ledger__colhead--var { border-left: var(--hair) solid var(--line); }
  .fdn-ledger__var-n { font-variant-numeric: tabular-nums; font-weight: var(--weight-strong); color: var(--ink-2); }
  .fdn-ledger__var-w { display: block; font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; }
  .fdn-ledger__var--ahead { background: var(--ok-bg); }
  .fdn-ledger__var--ahead .fdn-ledger__var-w { color: var(--ok-ink); }
  .fdn-ledger__var--behind { background: var(--blk-bg); }
  .fdn-ledger__var--behind .fdn-ledger__var-w { color: var(--blk-ink); }

  /* No-data marker: a muted (AA-legible) en-dash, never a fabricated zero. */
  .fdn-ledger td.fdn-ledger__na, .fdn-ledger__na { color: var(--mut); font-weight: var(--weight-body); }
  /* A no-data en-dash that also falls in the current-period wash: --mut on the wash
     measures 4.38:1 (light) / 4.17:1 (dark), under AA 4.5. Step it up one ink to
     --body (4.66:1 / 5.64:1 on the wash), still a supporting tone, now AA in both
     themes. The compound selector (2 classes + td) beats the plain no-data rule. */
  .fdn-ledger td.fdn-ledger__cell-current.fdn-ledger__na { color: var(--body); }

  /* Placeholder subtotal: a grouping line the source carries with no
     figure of its own — its detail sits on the line below (stated in the caption).
     Weight it like the subtotal it is: the NAME and every NUMERIC cell carry the
     subtotal's strong weight and the --line-2 top rule, while the en-dash markers
     stay. Placed after the no-data rule so the numeric cells (td.fdn-ledger__na)
     win the specificity tie and take the strong weight over the muted default. */
  .fdn-ledger__row--placeholder th,
  .fdn-ledger__row--placeholder td.fdn-ledger__na {
    border-top: var(--hair) solid var(--line-2);
    font-weight: var(--weight-strong);
  }
  .fdn-ledger__row--placeholder th { color: var(--ink-2); }

  /* Caption: a plain line above the statement stating its units and how to read
     the current-period, variance, and en-dash conventions. */
  .fdn-ledger__cap { caption-side: top; text-align: left; margin-bottom: var(--space-3); }

  /* data-fdn-ledger progressive enhancements (block 69). The runtime adds all of
     this ONLY when scripts run; with scripts off none of it exists, so the table
     stays complete. The scroll well is wrapped in .fdn-ledger__host (position:
     relative) so the per-cell .fd-tip can anchor OUTSIDE the well's overflow clip.
     A collapsed section's detail rows take .fdn-ledger__row--hidden (display:none,
     an instant fold, no motion). The section toggle is a real button carrying the
     section name plus a CSS caret (a bordered triangle, no glyph font); it inherits
     the band label's accent-text/eyebrow styling, so a collapsible band reads
     exactly like a static one, only now operable. */
  .fdn-ledger__host { position: relative; }
  .fdn-ledger__row--hidden { display: none; }
  .fdn-ledger__toggle {
    display: inline-flex; align-items: center; gap: var(--space-2);
    margin: 0; padding: 0; border: 0; background: none; cursor: pointer;
    font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  }
  .fdn-ledger__toggle:focus-visible { outline: var(--stroke-2) solid var(--focus); outline-offset: var(--stroke-2); border-radius: var(--radius-sm); }
  /* caret: a small solid triangle from borders — points down when expanded, right
     when collapsed. The state change is a static class swap on click (no motion). */
  .fdn-ledger__caret {
    width: 0; height: 0; flex: none;
    border-left: var(--caret-half) solid transparent; border-right: var(--caret-half) solid transparent;
    border-top: var(--caret-rise) solid var(--mut);
  }
  .fdn-ledger__toggle.is-collapsed .fdn-ledger__caret {
    border-top: var(--caret-half) solid transparent; border-bottom: var(--caret-half) solid transparent;
    border-left: var(--caret-rise) solid var(--mut); border-right: 0;
  }

  /* filter bar — a search field, a row of selectable filter tags (.fd-tag--filter),
     and a result count, to narrow a list or table. The search field reuses
     .fdn-input (forms.css) and the tags reuse .fd-tag--filter (chip.css); these
     rules are just the bar's own column layout. The tag row wraps in place on a
     phone (scale-to-fit, never a structural reflow). */
  .fdn-filterbar { display: flex; flex-direction: column; gap: var(--space-4); }
  .fdn-filterbar__tags { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
  .fdn-filterbar__count { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint); }

  /* escalation (the one lit card) */
  .fdn-esc .fdn-card__body { gap: var(--space-3); }
  .fdn-esc__big { font-size: var(--text-base); font-weight: var(--weight-strong); color: var(--ink); line-height: var(--leading-snug); letter-spacing: var(--tracking-tight); text-wrap: pretty; }

  /* dependency mini */
  .fdn-dep { display: flex; flex-direction: column; gap: var(--space-3); }
  .fdn-dep__row { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: var(--space-3); }
  .fdn-dep__node { font-size: var(--text-xs); font-weight: var(--weight-strong); color: var(--ink-2); padding: var(--space-2) var(--space-3); border: var(--hair) solid var(--line); border-radius: var(--radius-sm); text-align: center; }
  .fdn-dep__node--ext { color: var(--mut); font-weight: var(--weight-body); background: var(--paper); }
  .fdn-dep__arrow { color: var(--text-faint); font-size: var(--text-sm); text-align: center; }

  /* glossary */
  .fdn-gloss { display: flex; flex-direction: column; }
  .fdn-gloss__row { display: grid; grid-template-columns: 120px 1fr; gap: var(--space-4); padding: var(--space-3) 0; border-top: var(--hair) solid var(--line-fine); }
  .fdn-gloss__row:first-child { border-top: 0; }
  .fdn-gloss__t { font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--ink-2); }
  .fdn-gloss__d { font-size: var(--text-xs); color: var(--body); line-height: var(--leading-snug); }

  .fdn-foot { margin-top: var(--space-16); padding-top: var(--space-6); border-top: var(--hair) solid var(--line); }
  .fdn-foot p { font-size: var(--text-sm); line-height: var(--leading-relaxed); color: var(--text-muted); max-width: 80ch; }
  .fdn-foot b { color: var(--ink-2); font-weight: var(--weight-strong); }

  /* Tablet band (620-920px): the 12-col grid coarsens to 6 columns. Every span is
     remapped so it never exceeds 6 (a leftover 12- or wider span would spawn
     phantom implicit columns and squash the row). Full and two-thirds go full
     width; half, third and quarter become a half so the band reads as a clean
     2-up. sp6 stays a half (span 3) rather than going full so a side-by-side pair
     survives the tablet band and only stacks at the 620px phone breakpoint below
     (scale-to-fit, never an early reflow). */
  @media (max-width: 920px) {
    .fdn-cat { grid-template-columns: repeat(6, 1fr); }
    .fdn-sp12 { grid-column: span 6; } .fdn-sp8 { grid-column: span 6; }
    .fdn-sp6 { grid-column: span 3; } .fdn-sp4 { grid-column: span 3; } .fdn-sp3 { grid-column: span 3; }
  }
  @media (max-width: 620px) {
    .fdn-cat { grid-template-columns: 1fr; }
    .fdn-sp3, .fdn-sp4, .fdn-sp6, .fdn-sp8, .fdn-sp12 { grid-column: span 1; }
    .fdn-split { grid-template-columns: 1fr; }
  }
  /* sticky header bar (full-page) */
  .fdn-appbar { position: sticky; top: 0; z-index: 50; height: auto; border-radius: 0; background: none; isolation: isolate; transition: box-shadow var(--dur-base) var(--ease-out); }
  /* win over .fd-ambient > *:not(field){position:relative} now that the bar lives inside the ambient */
  .fd-ambient > .fdn-appbar { position: sticky; top: 0; z-index: 50; }
  .fdn-appbar__inner { position: relative; z-index: 2; max-width: var(--page-max); margin: 0 auto; display: flex; align-items: center; gap: var(--space-5); padding: var(--space-5) var(--space-8); }
  /* The bar's glass is the canonical .lg overlay (liquid-glass.js drives the refraction).
     It's a full-bleed layer that fades in only once the bar is stuck. */
  .fdn-appbar__glass { position: absolute; inset: 0; z-index: 0; pointer-events: none; opacity: 0; transition: opacity var(--dur-base) var(--ease-out); border-radius: 0; }
  .fdn-appbar__glass.lg { border-left: 0; border-right: 0; box-shadow: none; } /* full-bleed: no side rims, no own drop-shadow (header carries it) */
  .fdn-appbar.is-stuck .fdn-appbar__glass { opacity: 1; }
  .fdn-appbar.is-stuck { box-shadow: var(--shadow-bar); }
  /* Brand: the wordmark grows and TRUNCATES (never pushes the bar wider); the
     optional project label and divider sit beside it and never shrink. */
  .fdn-appbar__brand { display: flex; align-items: center; gap: var(--space-3); min-width: 0; flex: 1 1 auto; }
  .fdn-appbar__name { font-size: var(--text-md); font-weight: var(--weight-strong); letter-spacing: var(--tracking-tight); color: var(--ink-2); line-height: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .fdn-appbar__div { width: 1px; height: 14px; background: var(--line-2); flex: none; }
  .fdn-appbar__proj { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label-loose); text-transform: uppercase; color: var(--text-faint); line-height: 1; white-space: nowrap; flex: none; }
  /* Optional action zone. The bar is BRAND-ONLY by default; add a single action
     here ONLY when the page has a genuine, strong call to action. Most pages won't. */
  .fdn-appbar__actions { flex: none; display: flex; align-items: center; gap: var(--space-3); }
  .fdn-appbar__cta { appearance: none; border: 0; background: var(--signal); color: var(--on-accent); font: inherit; font-size: var(--text-xs); font-weight: var(--weight-strong); padding: var(--space-2) var(--space-4); border-radius: var(--radius-pill); cursor: pointer; white-space: nowrap; }
  @media (max-width: 600px) { .fdn-appbar__proj, .fdn-appbar__div { display: none; } }

  /* B2 still mode / reduced motion — collapse every motion transition declared in this
     file to instant, so a reader who asked for stillness gets the same rest state with
     identical meaning: the spark highlight (opacity), the hover tooltip (opacity), the
     scatter-dot hover ring (box-shadow), and the sticky app bar's shadow + glass fade
     (box-shadow, opacity). These sit at the top level (no breakpoint), so their twins do
     too. Held byte-for-byte in lockstep with the origins above by build/lint-still-mode.mjs
     (positive coverage); the reduced-motion and still twins may not drift or vanish. */
  @media (prefers-reduced-motion: reduce) {
    .fd-spark__hl,
    .fdn-tip,
    .fdn-scatter-dot,
    .fdn-appbar,
    .fdn-appbar__glass { transition: none; }
  }
  :root[data-still] .fd-spark__hl,
  :root[data-still] .fdn-tip,
  :root[data-still] .fdn-scatter-dot,
  :root[data-still] .fdn-appbar,
  :root[data-still] .fdn-appbar__glass { transition: none; }

  /* ============================================================
     Mobile reflow (<=640px) — the desktop row layouts that cannot
     fit a phone are restructured here so nothing clips or overlaps.
     Navy --signal stays exactly where it is on desktop; only layout
     changes. Four fixes: app bar header, comparison table (stacked to
     labelled cards), milestone timeline, gate stepper.
     ============================================================ */
  @media (max-width: 640px) {

    /* --- App bar: the brand-only bar (and its optional single action) fit one
       line at any width because the wordmark truncates, so the bar just tightens
       its padding on a phone. --- */
    .fdn-appbar__inner { padding: var(--space-3) var(--space-5); }

    /* --- Comparison table: stack each option into a labelled key/value card so
       every attribute (including the decision-bearing GATE column) is visible on
       a phone with no horizontal scroll. The header row is removed from view but
       kept for assistive tech; each cell self-labels via its data-label. The
       Recommended row keeps its navy tint. --- */
    .fdn-cmp { display: block; width: 100%; margin-left: 0; margin-right: 0; }
    .fdn-cmp thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }
    /* clip the header CELLS too: clip on <thead> hides the paint but each <th>
       keeps its intrinsic content width, whose box still spills past a narrow
       viewport. Collapse the cell boxes so no element overflows on a phone. */
    .fdn-cmp thead th { width: 1px; max-width: 1px; padding: 0; overflow: hidden; }
    .fdn-cmp tbody, .fdn-cmp tr, .fdn-cmp td { display: block; width: 100%; }
    .fdn-cmp tbody tr {
      border: var(--hair) solid var(--line);
      border-radius: var(--radius-sm);
      padding: var(--space-2) var(--space-4);
      margin-top: var(--space-3);
    }
    .fdn-cmp tbody tr.is-rec { background: var(--signal-soft); }
    .fdn-cmp td {
      display: flex; align-items: baseline; justify-content: space-between;
      gap: var(--space-4); text-align: right;
      padding: var(--space-2) 0; border-top: var(--hair) solid var(--line-fine);
    }
    .fdn-cmp td::before {
      content: attr(data-label); text-align: left; flex: none;
      font-size: var(--text-2xs); font-weight: var(--weight-strong);
      letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint);
    }
    /* first cell is the option name — a full-width heading, no key label */
    .fdn-cmp td:first-child {
      border-top: 0; justify-content: flex-start; text-align: left;
      font-size: var(--text-md); color: var(--ink-2);
    }
    .fdn-cmp td:first-child::before { content: none; }

    /* --- Data table: same stacked-card reflow as the comparison table, minus
       the decision band. Each row becomes a labelled key/value card so every
       column stays visible on a phone with no horizontal scroll; the header row
       is hidden from view but kept for assistive tech, and each cell self-labels
       via its data-label. Numeric cells lose their right-align (the value sits
       to the right of its label in the stacked row already). --- */
    .fdn-data { display: block; width: 100%; }
    .fdn-data thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }
    .fdn-data thead th { width: 1px; max-width: 1px; padding: 0; overflow: hidden; }
    .fdn-data tbody, .fdn-data tr, .fdn-data td { display: block; width: 100%; }
    .fdn-data tbody tr {
      border: var(--hair) solid var(--line);
      border-radius: var(--radius-sm);
      padding: var(--space-2) var(--space-4);
      margin-top: var(--space-3);
    }
    .fdn-data td {
      display: flex; align-items: baseline; justify-content: space-between;
      gap: var(--space-4); text-align: right;
      padding: var(--space-2) 0; border-top: var(--hair) solid var(--line-fine);
    }
    .fdn-data td.fdn-data__num { text-align: right; }
    .fdn-data td::before {
      content: attr(data-label); text-align: left; flex: none;
      font-size: var(--text-2xs); font-weight: var(--weight-strong);
      letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint);
    }
    /* first cell is the row name — a full-width heading, no key label */
    .fdn-data td:first-child {
      border-top: 0; justify-content: flex-start; text-align: left;
      padding-left: 0;
      font-size: var(--text-md); color: var(--ink-2);
    }
    .fdn-data td:last-child { padding-right: 0; }
    .fdn-data td:first-child::before { content: none; }

    /* --- Milestone timeline: the desktop track positions nodes by left:% which
       collide on a phone (the last two sit ~44px apart and overprint into
       illegible glyphs, the last clipping the card edge). Reflow to a vertical
       list — each node a full-width row of dot · label · date; the horizontal
       progress fill is dropped since the node states already carry progress.
       Navy --signal stays on the live node exactly as on desktop. --- */
    .fdn-timeline { padding: var(--space-2) 0; }
    .fdn-timeline__track { position: static; height: auto; background: none; display: flex; flex-direction: column; }
    .fdn-timeline__done { display: none; }
    .fdn-tl-node {
      position: static; transform: none;
      width: 100%; display: flex; align-items: center; gap: var(--space-3);
      padding: var(--space-3) 0; border-top: var(--hair) solid var(--line-fine);
    }
    .fdn-tl-node:first-child { border-top: 0; }
    .fdn-tl-node__dot { order: 1; }
    .fdn-tl-node__lbl { order: 2; flex: 1; position: static; top: auto; }
    .fdn-tl-node__date { order: 3; position: static; bottom: auto; white-space: nowrap; color: var(--text-faint); }

    /* --- Gate stepper: turn the horizontal pipeline into a vertical timeline.
       Each gate becomes a full-width row; the connector line rotates to a
       short vertical stem on the left so the sequence still reads top-to-bottom
       with no node pushed past the card edge. Navy --signal stays on the
       done/now stems exactly as on desktop. --- */
    .fdn-gates { flex-direction: column; align-items: stretch; gap: var(--space-4); }
    .fdn-gate, .fdn-gate:last-child { flex: 1 1 auto; padding-right: 0; padding-left: var(--space-5); }
    .fdn-gate__top {
      position: absolute; left: 0; top: 0; bottom: 0;
      flex-direction: column; align-items: center; gap: var(--space-2);
    }
    .fdn-gate__line { flex: 1; width: 2px; height: auto; }
    /* vertical "done" / "now" fills mirror the horizontal desktop fills */
    .fdn-gate.is-now .fdn-gate__line { background: linear-gradient(to bottom, var(--signal) 0 55%, var(--line-2) 55% 100%); }
  }

/* ===== parts/layout.css ===== */
/* Foundational — the layout tier (the system's second rhythm).
   A document is not only a grid of cards. These primitives arrange things on
   PAPER: they own all the space BETWEEN things and own nothing inside a thing's
   border (surfaces own that). Content picks its LANE, never its surface. The
   mosaic and the lead flag are print-safe (parts/print.css). Loaded after
   parts/composition.css so the grid and card modifiers here layer over their bases.

   TOKENS, honestly. Every colour, space, radius and reading MEASURE is a token
   (the lane widths and rail width are --measure* / --rail-aside in tokens/
   spacing.css; the flag's line-height is --leading-snug). Two kinds of value stay
   literal on purpose: a media-query breakpoint (a CSS media condition cannot read a
   custom property, so raw px in @media is the house idiom used across parts/), and
   a display figure's line-height of 1 (the canonical "cap the line box to the
   glyph" for a number-first opener, which no leading token expresses and which the
   type-token freeze does not add). The shared 2px focus ring (outline width and
   offset) is the system-wide idiom, identical in every interactive part, left as
   the literal it is everywhere. Both light and dark resolve either way. */

/* ─────────────────────────  River  ─────────────────────────
   The document spine. A centred reading column with three named lanes: content
   sits in the MEASURE lane (a comfortable ~66-character line) by default; a
   figure or wide table opts into the WIDE lane, and a full-bleed band into the
   FULL lane, WITHOUT leaving the flow ("the chart above" disappears because the
   figure is right here). Capped by measure, so it scales down to a phone with no
   reflow — the lanes just narrow. */
.fdn-river { display: flex; flex-direction: column; align-items: center; row-gap: var(--space-6); width: 100%; }
.fdn-river > * { width: 100%; max-width: min(var(--measure), 100%); }        /* measure lane (default) */
.fdn-river > .fdn-river--wide { max-width: min(var(--measure-wide), 100%); } /* wide lane: figures, tables */
.fdn-river > .fdn-river--full { max-width: 100%; }                           /* full lane: a band edge to edge */
/* A card, figure or callout riding the river sits in its own STRATUM: it gets
   more breath than the paragraph rhythm (ratified 2026-07-06: 32px total, the
   24px river gap + this margin), so furniture reads as its own layer without
   leaving the column. The frames stay on the column edges; the inset feel is the
   card's own internal padding, never a narrower frame. */
.fdn-river > .fdn-river--wide, .fdn-river > .fdn-river--full, .fdn-river > .fdn-card { margin-block: var(--space-2); }

/* ─────────────────────────  Flush mosaic  ─────────────────────────
   A modifier of the .fdn-cat grid: the tiles share ONE hairline frame, one
   radius and one shadow, seamed by hairline gutters, so the set reads as a single
   quiet instrument (a KPI strip) rather than a scatter of separate cards. The
   gap becomes the seam: the group's own surface shows through the 1px gutters as
   the divider, and each tile drops its own border/radius/shadow. */
.fdn-cat--flush {
  gap: var(--hair);
  background: var(--line);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
.fdn-cat--flush > .fdn-card { border: 0; border-radius: 0; box-shadow: none; }

/* ─────────────────────────  Stack  ─────────────────────────
   Vertical rhythm. Owns the space between its children; the children own nothing
   outside their box. One gap, set once (tight / loose steps for a denser or airier
   run). */
.fdn-stack { display: flex; flex-direction: column; gap: var(--space-5); }
.fdn-stack--tight { gap: var(--space-3); }
.fdn-stack--loose { gap: var(--space-8); }

/* ─────────────────────────  Cluster  ─────────────────────────
   An inline group that wraps: tags, metadata, a row of actions. Owns the gap in
   both axes; wraps in place on a phone (scale-to-fit, never a structural reflow). */
.fdn-cluster { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.fdn-cluster--between { justify-content: space-between; }

/* ─────────────────────────  Duo  ─────────────────────────
   Two equal columns that scale together and drop to one only at the phone
   breakpoint. No divider (the divided two-column is the older .fdn-split); a duo
   is a plain pair of equal lanes. */
.fdn-duo { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-6); }

/* ─────────────────────────  Pair  ─────────────────────────
   Side-by-side that SCALES rather than stacks: the two children keep their
   side-by-side relationship at every width and shrink together (scale-to-fit,
   never reflow). Reach for it when the pairing itself is the meaning (a
   before/after, a label beside its value) and stacking would lose it. */
.fdn-pair { display: flex; gap: var(--space-5); align-items: flex-start; }
.fdn-pair > * { flex: 1 1 0; min-width: 0; }

/* ─────────────────────────  Rail  ─────────────────────────
   A main reading column beside a companion that stays in view: a sticky aside for
   a summary, a table of contents, or the one decision. Asymmetric main + aside
   (the symmetric divided two-column is the older .fdn-split). Drops the rail below
   the main at the phone breakpoint. */
.fdn-rail { display: grid; grid-template-columns: 1fr minmax(0, var(--rail-aside)); gap: var(--space-8); align-items: start; }
.fdn-rail__aside { position: sticky; top: var(--appbar-clearance); align-self: start; }

/* Lead-aside rail: the same instrument with the companion on the LEFT. The aside
   leads (first column, first in the DOM), the reading column follows on the
   right. Reach for it when the companion is a section navigation the reader
   moves through before reading, so it wants to sit where the eye starts. Author
   the children aside-first in the markup; the aside keeps its sticky behaviour
   and the gap is unchanged. Because the aside is first in document order, the
   phone stack (below) puts the navigation ABOVE the reading, which matches the
   reading order for a nav you consult first. */
.fdn-rail--lead-aside { grid-template-columns: minmax(0, var(--rail-aside)) 1fr; }

/* ─────────────────────────  Statement  ─────────────────────────
   A display-scale, number-first opener on PAPER (never a card). The one figure
   that frames the whole page ("Checkout was down 38 minutes."). Reuses the display
   type; its surface is paper, so it belongs to the reading, not to a panel. */
.fdn-statement { display: flex; flex-direction: column; gap: var(--space-3); }
.fdn-statement__fig {
  font-size: var(--text-display); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-tighter); line-height: 1;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
.fdn-statement__unit { font-size: var(--text-xl); font-weight: var(--weight-strong); color: var(--mut); margin-left: var(--space-1); letter-spacing: var(--tracking-tight); }
.fdn-statement__lede { font-size: var(--text-md); line-height: var(--leading-snug); color: var(--body); max-width: var(--measure-lede); text-wrap: pretty; }

/* ─────────────────────────  Lead flag + act band  ─────────────────────────
   The lead and the act band are ONE instrument: at most one lead per page, and it
   is where the reader's actions are collected. The band is the lead's canonical,
   full-width form; the escalation card (block 21) is its card-shaped form. The
   flag is FILLED (a dark navy band, not coloured text) so the "act on this" mark
   survives greyscale and print, never riding on colour alone. */
.fdn-lead {
  display: inline-flex; align-items: center; gap: var(--space-2); align-self: flex-start;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--signal); color: var(--on-accent);
  font-size: var(--text-2xs); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-label); text-transform: uppercase; line-height: var(--leading-snug);
}

.fdn-act {
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-6);
  background: var(--signal-soft);
  border: var(--hair) solid var(--signal-line);
  border-radius: var(--radius-md);
}
.fdn-act__main { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.fdn-act__ask { font-size: var(--text-md); font-weight: var(--weight-strong); color: var(--ink); letter-spacing: var(--tracking-tight); line-height: var(--leading-snug); }
.fdn-act__sub { font-size: var(--text-sm); color: var(--body); line-height: var(--leading-snug); }
.fdn-act__cta {
  appearance: none; border: 0; flex: none; cursor: pointer; font: inherit;
  background: var(--signal); color: var(--on-accent);
  font-size: var(--text-sm); font-weight: var(--weight-strong);
  padding: var(--space-3) var(--space-5); border-radius: var(--radius-pill); white-space: nowrap;
}
.fdn-act__cta:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ─────────────────────────  Scale-to-fit breakpoints  ─────────────────────────
   The layout tier honours scale-to-fit-never-reflow: only where a side-by-side
   would truly crowd on a phone does it drop to a single column. Duo and rail
   stack; pair deliberately does NOT (its whole point is to stay side-by-side). */
@media (max-width: 720px) {
  .fdn-rail, .fdn-rail--lead-aside { grid-template-columns: 1fr; }
  .fdn-rail__aside { position: static; }
}
@media (max-width: 620px) {
  .fdn-duo { grid-template-columns: 1fr; }
}

/* ===== parts/controls.css ===== */
/* parts/controls.css
   Shared interactive controls used across the new web-UI blocks (forms, overlays,
   feedback). Tokens only. Extracted so button, close, and scrim are defined once. */

/* Button: one primary, one secondary. Used by form-section and the overlays. */
.fdn-btn { appearance: none; -webkit-appearance: none; font: inherit; font-size: var(--text-sm); font-weight: var(--weight-strong); letter-spacing: var(--tracking-tight); line-height: 1; cursor: pointer; white-space: nowrap; border-radius: var(--radius-pill); padding: var(--space-3) var(--space-5); border: var(--hair) solid transparent; transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
.fdn-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.fdn-btn--primary { background: var(--signal); color: var(--on-accent); }
.fdn-btn--secondary { color: var(--ink-2); border-color: var(--line-2); }

/* Close: a CSS-drawn cross in a square hit area. Used by modal, drawer, toast. */
.fdn-x { flex: none; width: 26px; height: 26px; padding: 0; border: 0; background: transparent; border-radius: var(--radius-sm); position: relative; cursor: pointer; color: var(--mut); transition: color var(--dur-snap) var(--ease-out), background var(--dur-snap) var(--ease-out); }
.fdn-x::before, .fdn-x::after { content: ""; position: absolute; top: 50%; left: 50%; width: 12px; height: 2px; border-radius: 2px; background: currentColor; }
.fdn-x::before { transform: translate(-50%, -50%) rotate(45deg); }
.fdn-x::after { transform: translate(-50%, -50%) rotate(-45deg); }
.fdn-x:hover { color: var(--ink-2); background: var(--line-fine); }
.fdn-x:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* Contained scrim: the dimmed wash behind an overlay shown inside a specimen.
   In a live page the scrim would be position:fixed; here it is contained so it
   renders in the gallery. --end pins the panel to the right edge (drawer). */
.fdn-scrim { position: relative; isolation: isolate; display: flex; justify-content: center; align-items: flex-start; padding: var(--space-6); min-width: 0; border-radius: var(--radius-md); background: var(--scrim); }
.fdn-scrim--end { justify-content: flex-end; align-items: stretch; padding: 0; overflow: hidden; min-height: 300px; }

/* ===== parts/forms.css ===== */
/* parts/forms.css. Shared component CSS for the Forms & input category.
   Promoted out of the individual block specimens so the gallery (which inlines
   styles.css and strips each block's local <style>) renders them fully.
   Tokens only; light and dark both render cleanly. Shared primitives (button,
   close cross, scrim) live in controls.css and are reused, not redefined. */

/* The field shell, label, and helper line are shared across text-field and
   select so a text input and a dropdown sit level beside each other. */
.fdn-field { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.fdn-field__label { font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--ink-2); line-height: var(--leading-snug); }
.fdn-help { font-size: var(--text-2xs); color: var(--mut); line-height: var(--leading-snug); }

/* ── 29-text-field ─────────────────────────────────────────────────────────
   The input control: same fill, hairline, and radius as the select control. */
.fdn-input {
  display: block; width: 100%; box-sizing: border-box;
  padding: var(--space-3) var(--space-4);
  border: var(--hair) solid var(--line-2);
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-strong);
  line-height: var(--leading-snug);
  transition: border-color var(--dur-snap) var(--ease-out), box-shadow var(--dur-snap) var(--ease-out);
}
.fdn-input::placeholder { color: var(--mut); font-weight: var(--weight-body); }
.fdn-input:hover { border-color: var(--signal-line); }
/* Keyboard focus uses the system focus ring (keyboard users only). */
.fdn-input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
/* Invalid control: clay edge with a soft clay ring, so the field reads as
   wrong before any word is read. The word "Error" still rides below. */
.fdn-input.is-invalid { border-color: var(--blk-line); box-shadow: 0 0 0 3px var(--blk-bg); }
.fdn-input.is-invalid:hover { border-color: var(--blk-line); }
/* Error line: a clay dot plus the word "Error" (the shared fdn-stat--blk
   dot+word), then the plain message. Colour never carries the meaning alone. */
.fdn-error { display: flex; align-items: baseline; gap: var(--space-2); min-width: 0; }
.fdn-error .fdn-stat { flex: none; align-self: center; }
.fdn-error__msg { min-width: 0; font-size: var(--text-2xs); color: var(--blk-ink); line-height: var(--leading-snug); }

/* ── 30-select ─────────────────────────────────────────────────────────────
   The closed control: a chosen value on the left, a caret on the right. */
.fdn-select {
  display: flex; align-items: center; gap: var(--space-3); min-width: 0;
  width: 100%; box-sizing: border-box;
  padding: var(--space-3) var(--space-4);
  border: var(--hair) solid var(--line-2);
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-size: var(--text-sm); font-weight: var(--weight-strong);
  line-height: var(--leading-snug);
  cursor: pointer;
  transition: border-color var(--dur-snap) var(--ease-out), box-shadow var(--dur-snap) var(--ease-out);
}
.fdn-select:hover { border-color: var(--signal-line); }
/* Keyboard focus uses the system focus ring (keyboard users only). */
.fdn-select:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.fdn-select__value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fdn-select__value--placeholder { color: var(--mut); font-weight: var(--weight-body); }
/* Caret drawn from a border, no glyph (matches the no-glyph house style). */
.fdn-caret { flex: none; width: 8px; height: 8px; margin-top: -3px;
  border-right: 2px solid var(--mut); border-bottom: 2px solid var(--mut);
  transform: rotate(45deg); transition: transform var(--dur-fast) var(--ease-out); }
/* Open / active state: one honest container. The open select becomes a single
   bordered surface that holds an inner trigger row and the menu as children, so
   there is only one border, one radius, one shadow, and no shared edge between
   two surfaces. Overflow clips the inner rows to the radius. */
.fdn-select.is-open { display: block; padding: 0; border-color: var(--signal); border-radius: var(--radius-sm); box-shadow: var(--shadow-card); overflow: hidden; }
/* The inner trigger row: value on the left, caret on the right. Its bottom
   hairline IS the seam, painted once, between the value row and the options. */
.fdn-select.is-open .fdn-select__trigger { display: flex; align-items: center; gap: var(--space-3); min-width: 0; padding: var(--space-3) var(--space-4); border-bottom: var(--hair) solid var(--signal-line); }
.fdn-select.is-open .fdn-caret { transform: rotate(-135deg); margin-top: 1px; border-color: var(--signal); }
/* The nested menu drops all of its own chrome; the container owns the border,
   radius, shadow, and fill. */
.fdn-select.is-open .fdn-menu { margin: 0; border: 0; border-radius: 0; box-shadow: none; background: transparent; }
/* Disabled control reads as not-now: stone text, faint fill, word says so. */
.fdn-select.is-disabled { cursor: default; background: var(--na-bg); border-color: var(--na-line); color: var(--na-ink); }
.fdn-select.is-disabled .fdn-caret { border-color: var(--faint); }
/* The open list, shown beneath the control. Each option pairs a CSS dot
   with its word so selection never rides on colour alone. */
.fdn-menu { margin-top: calc(var(--space-2) * -1 + var(--space-1));
  border: var(--hair) solid var(--line-2); border-radius: var(--radius-sm);
  box-shadow: var(--shadow-card); overflow: hidden; }
.fdn-opt { display: flex; align-items: center; gap: var(--space-3); min-width: 0;
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm); color: var(--ink-2); line-height: var(--leading-snug);
  border-top: var(--hair) solid var(--line-fine); cursor: pointer; }
.fdn-opt:first-child { border-top: 0; }
.fdn-opt:hover { background: var(--signal-soft); }
.fdn-opt__t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fdn-opt__mark { flex: none; font-size: var(--text-2xs); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--signal); white-space: nowrap; }
/* Selected option: signal-tinted, bold, dot + the word "Selected". */
.fdn-opt.is-selected { background: var(--signal-soft); color: var(--ink); font-weight: var(--weight-strong); }
.fdn-opt__dot { flex: none; width: var(--dot); height: var(--dot); border-radius: 50%;
  background: var(--line-2); }
.fdn-opt.is-selected .fdn-opt__dot { background: var(--signal); }

/* ── 31-toggle ─────────────────────────────────────────────────────────────
   The switch: a track with a knob. Position carries the state; the word beside
   it (in the markup) spells it out so colour never stands alone. Promoted from
   the block's inline styles into a reusable primitive. */
.fdn-toggle { position: relative; flex: none; width: 38px; height: 22px;
  border-radius: var(--radius-pill);
  border: var(--hair) solid var(--line-2); box-shadow: var(--shadow-flat);
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out); }
.fdn-toggle__knob { position: absolute; top: 2px; left: 2px; width: 16px; height: 16px;
  border-radius: 50%; background: var(--mut);
  transition: transform var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out); }
/* On: signal track, knob slides to the end and takes the signal fill. */
.fdn-toggle--on { background: var(--signal-soft); border-color: var(--signal-line); }
.fdn-toggle--on .fdn-toggle__knob { background: var(--signal); transform: translateX(16px); }
/* Disabled: faint, not-now; the row also carries the word "Disabled". */
.fdn-toggle--disabled { background: var(--paper); border-color: var(--line); box-shadow: none; cursor: not-allowed; }
.fdn-toggle--disabled .fdn-toggle__knob { background: var(--faint); }
/* Keyboard focus: the switch is reachable (tabindex in the markup) and shows the
   system focus ring; the disabled switch stays out of the tab order. */
.fdn-toggle:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ── 32-choice-group ───────────────────────────────────────────────────────
   The choice card groups a radio set and a checkbox set. Each set lays out as
   shared rows (.fdn-rows / .fdn-row) with shared marks (.fd-radio / .fdn-chk);
   this wrapper just keeps the groups from overflowing their column. */
.fdn-choice { min-width: 0; }
.fdn-choice .fdn-rows { min-width: 0; }

/* ── 33-form-section ───────────────────────────────────────────────────────
   The fieldset: stacked fields and one action row. Buttons reuse the shared
   .fdn-btn primitive from controls.css. */
.fdn-form { display: flex; flex-direction: column; gap: var(--space-5); border: 0; margin: 0; padding: 0; min-width: 0; }
/* A <legend> does not receive the fieldset's flex gap (browser quirk), so it
   needs an explicit margin to read as a balanced section heading above the
   first field, neither cramped nor excessive. */
.fdn-form__legend { padding: 0; margin: 0 0 var(--space-3); font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--ink-2); letter-spacing: var(--tracking-tight); }
.fdn-form__fields { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.fdn-field__hint { font-size: var(--text-2xs); color: var(--mut); line-height: var(--leading-snug); }
/* Control surface: flat panel, hairline, signal focus ring. The active field
   shows the focused state in a static render. This is the native input/select
   control of the form section, distinct from the custom .fdn-select dropdown
   above. Its native <select> uses .fdn-form__select (not .fdn-select) for the
   chevron so the two never collide. */
.fdn-control {
  appearance: none; -webkit-appearance: none;
  width: 100%; box-sizing: border-box; min-width: 0;
  font: inherit; font-size: var(--text-sm); color: var(--ink-2);
  border: var(--hair) solid var(--line-2); border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4); line-height: var(--leading-snug);
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.fdn-control::placeholder { color: var(--faint); }
.fdn-control:focus, .fdn-control:focus-visible,
.fdn-control.is-active {
  outline: 2px solid var(--focus); outline-offset: 2px;
  border-color: var(--signal);
}
/* Native select gets a CSS-drawn chevron so no glyph font is required. */
.fdn-form__select { padding-right: var(--space-8); cursor: pointer; background-image:
    linear-gradient(45deg, transparent 50%, var(--mut) 50%),
    linear-gradient(135deg, var(--mut) 50%, transparent 50%);
  background-position: right var(--space-4) center, calc(100% - var(--space-4) + 5px) center;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; }

/* ── native-control accent ───────────────────────────────────────────────────
   accent-color paints the parts of a native control the engine draws itself —
   the checkbox tick, the radio dot, the range fill, the <progress> bar, and, on
   engines that honour it, the <select> highlight — in the FDS signal family
   instead of the OS default blue. --signal already flips per theme, so this one
   declaration serves light and dark. It inherits, so a native <input> a builder
   composes into an FDS form inherits the calm accent with no extra class. The
   design system owns the base layer, so this sits on the bare controls. */
input, select, textarea, progress, .fdn-control { accent-color: var(--signal); }

/* ── native <select> open popup ──────────────────────────────────────────────
   The open <select> menu is drawn by the browser and, on macOS, by the OS. This
   stylesheet cannot repaint an OS-owned surface, so it reaches for the levers the
   platform does offer, weakest first, each degrading silently where unsupported.
   1 — color-scheme lets the native popup follow the active theme instead of
   always rendering light. It sits on the control (not a theme root) so it holds
   under both dark paths and will not collide with a theme-root color-scheme if
   one is added there later. */
.fdn-form__select { color-scheme: light; }
@media (prefers-color-scheme: dark) { .fdn-form__select { color-scheme: dark; } }
:root[data-theme="dark"] .fdn-form__select { color-scheme: dark; }
:root[data-theme="light"] .fdn-form__select { color-scheme: light; }
/* 2 — <option> colours: Chromium honours background/color on options on some
   platforms and ignores them where the menu is OS-drawn, so they land in the FDS
   panel and ink where they can and cost nothing where they cannot. */
.fdn-form__select option { background: var(--panel); color: var(--ink-2); }
.fdn-form__select option:checked { color: var(--ink); font-weight: var(--weight-strong); }
/* 3 — the customizable-select API (Chromium, progressive enhancement): where the
   engine supports appearance: base-select it hands the whole popup to CSS, and we
   paint it in full FDS vocabulary, mirroring the custom .fdn-select dropdown — a
   --panel picker on a hairline with the card radius and shadow, hairline-separated
   rows, a quiet --signal-soft hover wash (never the OS blue), and the selected row
   marked by a --signal check beside its word, so selection never rides on colour
   alone. Engines without the API keep the native popup improved by 1 and 2. */
@supports (appearance: base-select) {
  .fdn-form__select, .fdn-form__select::picker(select) { appearance: base-select; }
  .fdn-form__select::picker(select) {
    border: var(--hair) solid var(--line-2); border-radius: var(--radius-sm);
    background: var(--panel); box-shadow: var(--shadow-card); overflow: hidden;
  }
  .fdn-form__select option {
    display: flex; align-items: center; gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    color: var(--ink-2); background: var(--panel);
    font-size: var(--text-sm); line-height: var(--leading-snug);
    border-top: var(--hair) solid var(--line-fine);
  }
  .fdn-form__select option:first-child { border-top: 0; }
  .fdn-form__select option:hover,
  .fdn-form__select option:focus { background: var(--signal-soft); color: var(--ink); }
  .fdn-form__select option:checked { background: var(--signal-soft); color: var(--ink); font-weight: var(--weight-strong); }
  .fdn-form__select option::checkmark { color: var(--signal); order: 1; margin-left: auto; }
  /* base-select supplies its own ::picker-icon; the closed control keeps the
     existing CSS-drawn chevron above, so the native one is hidden to avoid a
     double caret and leave the closed control's paint byte-for-byte unchanged. */
  .fdn-form__select::picker-icon { display: none; }
}

/* The honest limit: on every engine WITHOUT the customizable-select API — Safari,
   Firefox, and Chromium where the menu is handed to the OS — the open <select>
   popup's surface, highlight row, and checkmark stay OS-owned. color-scheme and
   the <option> colours above nudge it toward the theme; they cannot repaint it.
   We enhance the popup where the platform allows and never fake it where it does
   not. The custom .fdn-select dropdown above is the fully-FDS alternative for a
   surface that must read as ours on every engine. */

/* Action row: one primary, one secondary. Primary leads, sits at the end. */
.fdn-form__actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--space-3); padding-top: var(--space-4); border-top: var(--hair) solid var(--line); }

/* ── 34-validation-summary ─────────────────────────────────────────────────
   Collects every field error at the top of a form in one place, with a jump to
   each. Built on shared rows (.fdn-rows / .fdn-row / .fdn-sev). The field links
   and the "Go to field" jump are the only block-specific pieces. */
.fdn-validation .fdn-row__t a { color: var(--ink-2); text-decoration: none; }
.fdn-validation .fdn-row__t a:hover { text-decoration: underline; text-underline-offset: 2px; }
.fdn-validation .fdn-jump { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--signal); text-decoration: none; white-space: nowrap; }
.fdn-validation .fdn-jump:hover { color: var(--accent-text); }

/* B2 still mode / reduced motion — collapse every motion transition in this file to
   instant, so a reader who asked for stillness gets the rest state with identical meaning:
   the focus-ring box-shadows (.fdn-input / .fdn-select / .fdn-control) and the transforms
   the caret rotate and the toggle knob slide are motion. Colour crossfades on the same
   controls stay; these do not. Held byte-for-byte in lockstep with the origins above by
   build/lint-still-mode.mjs (positive coverage); the two twins may not drift or vanish. */
@media (prefers-reduced-motion: reduce) {
  .fdn-input,
  .fdn-select,
  .fdn-caret,
  .fdn-toggle__knob,
  .fdn-control { transition: none; }
}
:root[data-still] .fdn-input,
:root[data-still] .fdn-select,
:root[data-still] .fdn-caret,
:root[data-still] .fdn-toggle__knob,
:root[data-still] .fdn-control { transition: none; }

/* ===== parts/nav.css ===== */
/* parts/nav.css — component CSS for the Navigation category.
   Tokens only. Shared so the gallery (which inlines styles.css) renders these
   blocks fully and the component CSS is defined once. */

/* ── 35-tabs ────────────────────────────────────────────────────────────── */
/* Tabs — a horizontal tab bar with an underline indicator on the active tab.
   The active tab is marked three ways, never colour alone: selected state
   (aria-selected), the underline indicator, and the signal text. Inactive
   tabs are muted with no indicator. On a narrow screen the bar wraps to a
   second line rather than overflowing or hiding tabs behind a scroll. */
.fdn-tabs { display: flex; flex-direction: column; min-width: 0; }

.fdn-tabs__bar {
  display: flex; flex-wrap: wrap; gap: var(--space-1);
  border-bottom: var(--hair) solid var(--line);
  min-width: 0;
}
.fdn-tabs__tab {
  appearance: none; border: 0; background: none; font: inherit; cursor: pointer;
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  margin-bottom: calc(-1 * var(--hair)); /* sit the underline on the bar hairline */
  border-bottom: 2px solid transparent;
  font-size: var(--text-sm); font-weight: var(--weight-strong); letter-spacing: 0.01em;
  color: var(--text-muted); white-space: nowrap; min-width: 0;
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.fdn-tabs__tab:hover { color: var(--ink-2); }
.fdn-tabs__tab[aria-selected="true"] {
  color: var(--accent-text);
  border-bottom-color: var(--signal);
}
.fdn-tabs__tab:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: var(--radius-sm); }

/* a small count chip riding alongside a tab label, neutral */
.fdn-tabs__count {
  font-size: var(--text-2xs); font-weight: var(--weight-strong); font-variant-numeric: tabular-nums;
  color: var(--text-faint); line-height: 1;
  padding: 2px var(--space-2); border-radius: var(--radius-pill);
  border: var(--hair) solid var(--line-2);
}
.fdn-tabs__tab[aria-selected="true"] .fdn-tabs__count {
  color: var(--accent-text); border-color: var(--signal-line); background: var(--signal-soft);
}

.fdn-tabs__panel { padding-top: var(--space-5); display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.fdn-tabs__panel-title { margin: 0; font-size: var(--text-sm); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint); }
.fdn-tabs__panel p { margin: 0; }

/* ── 36-breadcrumbs ───────────────────────────────────────────────────────── */
/* Breadcrumbs — the trail of where this page sits, from the top level down to
   the page in view. Each level except the last is a link back up the tree.
   The last item is the current page: it is not a link, it carries
   aria-current, and it is the only item in heading ink, so the current
   location reads three ways and never by colour alone. A small chevron sits
   between levels, marked aria-hidden so a screen reader hears only the names.
   The trail wraps to the next line on a narrow screen rather than overflowing
   or hiding levels behind a sideways scroll. */
.fdn-crumbs {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-1) var(--space-2);
  min-width: 0; margin: 0; padding: 0; list-style: none;
}
.fdn-crumbs__item { display: inline-flex; align-items: center; min-width: 0; }

.fdn-crumbs__link {
  display: inline-flex; align-items: center; min-width: 0;
  max-width: 100%;
  font-size: var(--text-sm); font-weight: var(--weight-strong); letter-spacing: 0.01em;
  color: var(--text-muted); text-decoration: none; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
  border-radius: var(--radius-sm);
  transition: color var(--dur-fast) var(--ease-out);
}
.fdn-crumbs__link:hover { color: var(--ink-2); text-decoration: underline; text-underline-offset: 3px; }
.fdn-crumbs__link:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* the current page: heading ink, heavier, and not a link */
.fdn-crumbs__current {
  display: inline-flex; align-items: center; min-width: 0; max-width: 100%;
  font-size: var(--text-sm); font-weight: var(--weight-strong); letter-spacing: 0.01em;
  color: var(--ink-2); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}

/* a small leading marker on the top level, so the start of the trail reads at a glance */
.fdn-crumbs__home { flex: none; width: 14px; height: 14px; margin-right: var(--space-2); color: var(--text-faint); }

/* the chevron between levels, decorative only */
.fdn-crumbs__sep { flex: none; width: 12px; height: 12px; color: var(--text-faint); }

/* ── 37-pagination ────────────────────────────────────────────────────────── */
/* Pagination — a row of numbered pages between a previous and a next control.
   The current page is marked three ways, never colour alone: it carries
   aria-current, it is the only filled item (signal wash), and it reads as the
   current page to a screen reader. Skipped ranges collapse to an ellipsis
   marked aria-hidden, so the reader sees the shape of the set without every
   number. Previous and next are real controls; at the ends one is disabled and
   drops out of the tab order. On a narrow screen the row wraps to a second line
   rather than overflowing or hiding pages behind a sideways scroll. */
.fdn-pager {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-1) var(--space-2);
  min-width: 0; margin: 0; padding: 0; list-style: none;
}
.fdn-pager__item { display: inline-flex; min-width: 0; }

/* shared base for every page number and the two ends: quiet and boxless. Only
   the current page is a filled chip; every other control is plain text that
   shifts ink on hover, nothing more. The row stays calm and light, and the
   state line beside it ("Page 4 of 12") carries the story. */
.fdn-pager__page,
.fdn-pager__step {
  appearance: none; box-sizing: border-box; font: inherit; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--space-2);
  min-width: 36px; height: 36px; padding: 0 var(--space-2);
  border: 0; background: none; border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: var(--text-sm); font-weight: var(--weight-strong);
  font-variant-numeric: tabular-nums; letter-spacing: 0.01em;
  text-decoration: none; white-space: nowrap;
  transition: color var(--dur-fast) var(--ease-out);
}
/* quiet hover: the ink shifts, no box grows back in */
.fdn-pager__page:hover,
.fdn-pager__step:hover {
  color: var(--ink-2);
}
.fdn-pager__page:focus-visible,
.fdn-pager__step:focus-visible {
  outline: 2px solid var(--focus); outline-offset: 2px;
}

/* the current page: the ONE filled element, in the single accent */
.fdn-pager__page[aria-current="page"] {
  color: var(--on-accent); background: var(--signal);
  cursor: default;
}
.fdn-pager__page[aria-current="page"]:hover {
  color: var(--on-accent);
}

/* previous / next: a small chevron beside a word, so direction never reads by
   glyph alone */
.fdn-pager__step { padding: 0 var(--space-3); }
.fdn-pager__chev { flex: none; width: 12px; height: 12px; }
.fdn-pager__steplabel { min-width: 0; }

/* an end control with nowhere to go: faint, not a target, out of tab order */
.fdn-pager__step[aria-disabled="true"] {
  color: var(--text-faint); cursor: default;
}
.fdn-pager__step[aria-disabled="true"]:hover {
  color: var(--text-faint);
}

/* the gap standing in for skipped pages, decorative */
.fdn-pager__gap {
  display: inline-flex; align-items: flex-end; justify-content: center;
  min-width: 24px; height: 36px; padding-bottom: var(--space-2);
  color: var(--text-faint); font-size: var(--text-sm); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-label-loose); line-height: 1; user-select: none;
}

/* on very narrow screens, let the word on previous/next fall away so the
   chevrons still wrap to one tidy line with the numbers */
@media (max-width: 360px) {
  .fdn-pager__steplabel { display: none; }
  .fdn-pager__step { padding: 0 var(--space-2); }
}

/* Glued into a card foot, the pager sits in a between-cluster beside the
   page-state text. That cluster wraps: on a wide foot the state and the pager
   share one line (state at the left, pager at the right); as the foot narrows
   the state wraps to its own line ABOVE the pager, and only the pager itself
   must still hold a single row. A foot that broke the PAGER into extra rows
   would be a structural reflow, not the house scale-to-fit. So at the phone
   width a foot-bound pager compacts EARLY (before the standalone 360 step): the
   previous/next words fall to their chevrons and the pager row stops wrapping,
   so its numbers stay on one tidy line. The standalone pager (not in a foot)
   keeps its later 360 breakpoint and its wrap. */
@media (max-width: 480px) {
  .fdn-card__foot .fdn-pager { flex-wrap: nowrap; }
  .fdn-card__foot .fdn-pager .fdn-pager__steplabel { display: none; }
  .fdn-card__foot .fdn-pager .fdn-pager__step { padding: 0 var(--space-2); }
}

/* In the same nowrap band a long foot pager (many numbered pages, an ellipsis
   on each side) cannot keep its single row inside the card: measured, its full
   numbers only fit down to ~456px, so the shed must fire wherever the nowrap
   rule does — one band, no gap where the fixed row overflows a still-narrowing
   card. The current page stays marked on the control itself (aria-current, the
   filled step), so the interior page numbers are redundant weight here. Shed
   them to the essentials the reader still needs (previous, next, and the
   current page) so the row shrinks to fit the narrowest card without overflow.
   A short foot pager sheds its interior numbers the same way, landing on the
   same tidy prev / current / next. */
@media (max-width: 480px) {
  .fdn-card__foot .fdn-pager__item:has(.fdn-pager__page:not([aria-current="page"])),
  .fdn-card__foot .fdn-pager__item:has(.fdn-pager__gap) {
    display: none;
  }
}

/* ── 38-side-nav ──────────────────────────────────────────────────────────── */
/* Side nav — a vertical list of links grouped into labelled sections. Each
   group carries a quiet uppercase label so the reader sees the shape of the
   navigation before reading every link. One link is the page in view: it is
   marked three ways and never by colour alone. It carries aria-current, a
   filled indicator bar runs down its leading edge, and it shows a small
   "Current" tag in words, so the location survives a greyscale print or a
   colourblind reader. The other links stay muted until chosen. Every link is
   a full-width target that wraps to a second line on a narrow column rather
   than overflowing or clipping its label. */
.fdn-sidenav {
  display: flex; flex-direction: column; gap: var(--space-5);
  min-width: 0; margin: 0; padding: 0;
}

.fdn-sidenav__group { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }

/* the group label: a quiet uppercase eyebrow above its links */
.fdn-sidenav__label {
  margin: 0 0 var(--space-1); padding: 0 var(--space-3);
  font-size: var(--text-2xs); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--text-faint);
}

.fdn-sidenav__list { display: flex; flex-direction: column; gap: 2px; min-width: 0; margin: 0; padding: 0; list-style: none; }
.fdn-sidenav__item { display: flex; min-width: 0; }

/* a link: a full-width target. A leading indicator rail sits at the start,
   transparent until the link is current. */
.fdn-sidenav__link {
  position: relative; display: flex; align-items: center;
  gap: var(--space-2); width: 100%; min-width: 0; box-sizing: border-box;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--text-muted); text-decoration: none;
  font-size: var(--text-sm); font-weight: var(--weight-strong); letter-spacing: 0.01em;
  transition: color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
/* the indicator rail down the leading edge, drawn for every link but only
   inked on the current one */
.fdn-sidenav__link::before {
  content: ""; flex: none; align-self: stretch;
  width: 3px; margin: 2px 0; border-radius: var(--radius-pill);
  background: transparent;
}
.fdn-sidenav__link:hover { color: var(--ink-2); }
.fdn-sidenav__link:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* the page in view: filled rail, faint accent wash, accent text */
.fdn-sidenav__link[aria-current="page"] {
  color: var(--accent-text); background: var(--signal-soft); cursor: default;
}
.fdn-sidenav__link[aria-current="page"]::before { background: var(--signal); }
.fdn-sidenav__link[aria-current="page"]:hover { color: var(--accent-text); background: var(--signal-soft); }

/* the link label: wraps rather than clipping on a narrow column */
.fdn-sidenav__text { min-width: 0; flex: 1 1 auto; }

/* the word that names the current location, so it reads without colour */
.fdn-sidenav__tag {
  flex: none; font-size: var(--text-2xs); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--accent-text); line-height: 1;
  padding: 2px var(--space-2); border-radius: var(--radius-pill);
  border: var(--hair) solid var(--signal-line);
}

/* ===== parts/feedback.css ===== */
/* parts/feedback.css — component CSS for the feedback category.
   Tokens only. Shared primitives (button, close cross, scrim) live in
   controls.css and are reused, not redefined here. */

/* ── alert (39-alert) ─────────────────────────────────────────────────────
   Inline alert / banner primitive. The kind is always named in words and
   carries a distinct icon SHAPE, so meaning survives a colourblind reader or
   a greyscale print: colour never stands alone. */
.fdn-alerts { display: flex; flex-direction: column; gap: var(--space-3); }

.fdn-alert {
  display: flex; align-items: flex-start; gap: var(--space-3); min-width: 0;
  padding: var(--space-3) var(--space-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-sm);
}
.fdn-alert__main { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.fdn-alert__kind { font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--ink-2); line-height: var(--leading-snug); }
.fdn-alert__msg { font-size: var(--text-xs); color: var(--body); line-height: var(--leading-snug); text-wrap: pretty; }

/* Icon chip: a token-tinted square holding a CSS-drawn mark, one shape per kind.
   The shape is the redundant cue; no glyph font, no colour-only meaning. */
.fdn-alert__icon {
  flex: none; width: 22px; height: 22px; border-radius: var(--radius-sm);
  display: grid; place-items: center; position: relative;
}
.fdn-alert__icon::before, .fdn-alert__icon::after { content: ""; position: absolute; }

/* Info — signal navy. Mark: a lower-case "i" (dot over a stem). */
.fdn-alert--info .fdn-alert__icon { background: var(--signal-soft); }
.fdn-alert--info .fdn-alert__icon::before { width: 3px; height: 3px; border-radius: 50%; background: var(--signal); transform: translateY(-4px); }
.fdn-alert--info .fdn-alert__icon::after { width: 3px; height: 7px; border-radius: 2px; background: var(--signal); transform: translateY(2px); }

/* Success — sage. Mark: a check tick. */
.fdn-alert--success .fdn-alert__icon { background: var(--ok-bg); }
.fdn-alert--success .fdn-alert__icon::after {
  width: 9px; height: 5px; margin-top: -2px;
  border-left: 2px solid var(--ok-dot); border-bottom: 2px solid var(--ok-dot);
  transform: rotate(-45deg);
}

/* Warning — muted ochre. Mark: an exclamation (stem over a dot). */
.fdn-alert--warning .fdn-alert__icon { background: var(--warn-bg); }
.fdn-alert--warning .fdn-alert__icon::before { width: 3px; height: 7px; border-radius: 2px; background: var(--warn-ink); transform: translateY(-3px); }
.fdn-alert--warning .fdn-alert__icon::after { width: 3px; height: 3px; border-radius: 50%; background: var(--warn-ink); transform: translateY(5px); }

/* Danger — muted clay. Mark: a cross. */
.fdn-alert--danger .fdn-alert__icon { background: var(--blk-bg); }
.fdn-alert--danger .fdn-alert__icon::before,
.fdn-alert--danger .fdn-alert__icon::after { width: 11px; height: 2px; border-radius: 2px; background: var(--blk-ink); }
.fdn-alert--danger .fdn-alert__icon::before { transform: rotate(45deg); }
.fdn-alert--danger .fdn-alert__icon::after { transform: rotate(-45deg); }

/* ── toast (40-toast) ─────────────────────────────────────────────────────
   Transient toast primitive. It rides the floating layer (--shadow-float),
   the same depth the system reserves for things that hover above the page.
   The kind is always a coloured dot PLUS a word, and the success mark is a
   drawn CHECK SHAPE, so meaning survives a colourblind reader or a greyscale
   print: colour never stands alone. The dismiss control reuses the shared
   .fdn-x close cross from controls.css. */
.fdn-toasts { display: flex; flex-direction: column; gap: var(--space-3); }

.fdn-toast {
  display: flex; align-items: flex-start; gap: var(--space-3); min-width: 0;
  padding: var(--space-3) var(--space-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-float);
}

/* Icon chip: a token-tinted disc holding a CSS-drawn mark. The shape is the
   redundant cue; no glyph font, no colour-only meaning. */
.fdn-toast__icon {
  flex: none; width: 20px; height: 20px; border-radius: var(--radius-pill);
  display: grid; place-items: center; position: relative; margin-top: 1px;
}
.fdn-toast__icon::after { content: ""; position: absolute; }

/* Success — sage. Mark: a check tick. */
.fdn-toast--success .fdn-toast__icon { background: var(--ok-bg); }
.fdn-toast--success .fdn-toast__icon::after {
  width: 8px; height: 4px; margin-top: -1px;
  border-left: 2px solid var(--ok-dot); border-bottom: 2px solid var(--ok-dot);
  transform: rotate(-45deg);
}

/* Neutral — stone. Mark: a small solid dot, the system's plain status dot. */
.fdn-toast--neutral .fdn-toast__icon { background: var(--na-bg); }
.fdn-toast--neutral .fdn-toast__icon::after {
  width: var(--dot); height: var(--dot); border-radius: var(--radius-pill);
  background: var(--na-dot);
}

.fdn-toast__main { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; }
.fdn-toast__kind {
  font-size: var(--text-2xs); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--mut); line-height: var(--leading-snug);
}
.fdn-toast__msg {
  font-size: var(--text-sm); color: var(--body);
  line-height: var(--leading-snug); text-wrap: pretty;
}

/* ── empty-state (41-empty-state) ─────────────────────────────────────────
   A calm placeholder for an empty list, with one plain action to add the
   first item. Neutral surface ink: an empty list is neither good nor bad, so
   colour carries no meaning. The "Nothing here yet" line says the state in
   words, so it reads without colour. */
.fdn-empty {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--space-4); padding: var(--space-8) var(--space-3) var(--space-6);
}
.fdn-empty__mark {
  display: grid; place-items: center; flex: none;
  width: 56px; height: 56px; border-radius: var(--radius-md);
  border: var(--hair) solid var(--line-2); background: var(--paper);
  color: var(--text-faint);
}
.fdn-empty__mark svg { max-width: 100%; }
.fdn-empty__copy { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; max-width: 34ch; }
.fdn-empty__title {
  margin: 0; font-size: var(--text-md); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-tight); line-height: var(--leading-tight);
  color: var(--ink-2);
}

/* ── skeleton (42-skeleton) ───────────────────────────────────────────────
   Loading skeleton primitive. The skeleton carries NO colour meaning. It is
   plain surface ink, not a status, because waiting is neither good nor bad.
   The fact that this is a loading state is always said in WORDS (the "Loading"
   line, plus role="status"), so it reads for a colourblind reader, a greyscale
   print, or a reader who has turned animation off. */
.fdn-skeleton { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }

/* The "Loading" line: the meaning in words, with the system's resting state dot
   so the placeholder is never colour or motion alone. */
.fdn-skeleton__status {
  display: flex; align-items: center; gap: var(--space-2); min-width: 0;
  font-size: var(--text-2xs); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--mut); line-height: var(--leading-snug);
}
.fdn-skeleton__status::before {
  content: ""; flex: none; width: var(--dot); height: var(--dot);
  border-radius: var(--radius-pill); background: var(--na-dot);
}

/* A single placeholder bar. Neutral surface tint on the panel, a soft shimmer
   sweeping across it to read as "still arriving". Every bar is the same token
   surface; size is the only difference between a title, a line, and a figure. */
.fdn-skeleton__bar {
  position: relative; overflow: hidden; min-width: 0;
  background: var(--line);
  border-radius: var(--radius-sm);
}
.fdn-skeleton__bar::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--line-fine) 50%,
    transparent 100%
  );
  transform: translateX(-100%);
  animation: fdn-skeleton-shimmer var(--dur-loading) var(--ease-inout) infinite;
}
@keyframes fdn-skeleton-shimmer {
  from { transform: translateX(-100%); }
  to   { transform: translateX(100%); }
}

/* The placeholder shapes. Heights ride the type and spacing scale so the
   skeleton keeps the same rhythm as the real card it stands in for. */
.fdn-skeleton__title { height: var(--text-lg); width: 62%; border-radius: var(--radius-sm); }
.fdn-skeleton__lines { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.fdn-skeleton__line  { height: var(--text-sm); }
.fdn-skeleton__line--short { width: 78%; }
.fdn-skeleton__figure {
  height: var(--space-20); width: 100%; max-width: 100%;
  border-radius: var(--radius-md);
}

/* Calm by default: if the reader prefers reduced motion, the shimmer holds
   still and the resting surface plus the "Loading" word still carry the meaning. */
@media (prefers-reduced-motion: reduce) {
  .fdn-skeleton__bar::after { animation: none; transform: translateX(0); opacity: 0.5; }
}
/* B2 still mode twin (kept identical to the reduced-motion rule by lint-still-mode.mjs) */
:root[data-still] .fdn-skeleton__bar::after { animation: none; transform: translateX(0); opacity: 0.5; }

/* ── progress (43-progress) ───────────────────────────────────────────────
   Progress primitive. The determinate bar reuses the shared .fdn-meter track
   and fill (so it inherits the runtime "Now NN%" hover read-out) but drops the
   plan target: progress is its own meaning, not a number against a plan. The
   plain consequence ("3 of 5 steps done") always sits beside the bar, so the
   reading never rides on the fill length alone, and survives a greyscale print.
   Progress is shown in NEUTRAL ink, not a status colour: being part-way through
   is not good or bad, so colour carries no meaning here. */
.fdn-progress { display: flex; flex-direction: column; gap: var(--space-5); min-width: 0; }
.fdn-progress__item { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.fdn-progress__row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-3); min-width: 0;
}
.fdn-progress__label { font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--ink-2); min-width: 0; }
.fdn-progress__count {
  flex: none; font-size: var(--text-xs); font-weight: var(--weight-strong);
  color: var(--body); font-variant-numeric: tabular-nums;
}

/* Indeterminate variant: amount of work is unknown, so there is no fill width
   and no percent. A short segment travels the track to mean "working", and the
   word "Working" names that meaning so it reads without motion or colour. */
.fdn-progress__track--wait { position: relative; overflow: hidden; }
.fdn-progress__wait {
  position: absolute; top: 0; bottom: 0; left: 0; width: 38%;
  border-radius: var(--radius-pill); background: var(--ink-2);
  animation: fdn-progress-slide var(--dur-wait) var(--ease-inout) infinite alternate;
}
@keyframes fdn-progress-slide {
  from { left: 0; }
  to   { left: 62%; }
}
/* Calm by default: if the reader prefers reduced motion, the segment holds a
   resting position rather than travelling, and the word still carries meaning. */
@media (prefers-reduced-motion: reduce) {
  .fdn-progress__wait { animation: none; left: 31%; }
}
/* B2 still mode twin (kept identical to the reduced-motion rule by lint-still-mode.mjs) */
:root[data-still] .fdn-progress__wait { animation: none; left: 31%; }

/* ===== parts/content.css ===== */
/* parts/content.css. Component CSS for the content & media category.
   Promoted verbatim from the block specimens so the gallery (which inlines
   styles.css) renders each block fully. Tokens only; light and dark both clean. */

/* ── prose (44-prose) ───────────────────────────────────────────────────────
   Long-form prose primitive, a comfortable reading column on the type scale.
   Body copy sits at --text-base with relaxed leading; the measure is capped so
   lines stay readable. Headings derive from --ink-2, links carry the one signal
   accent AND an underline so colour never carries meaning alone.

   SCOPE CONTRACT. .fdn-prose styles the full markdown element set (heading,
   paragraph, list, quote, table, code, figure, rule) by matching elements, so a
   renderer's raw HTML drops in and renders house-styled. It wraps MARKDOWN
   CONTENT: in a document a figure, log, chart, or card sits as a SIBLING of the
   prose in the river (the archetypes), never nested inside it. So that a
   composed block nested inside prose (against that contract) is not corrupted,
   Prose wraps a MARKDOWN document. Composed fdn- blocks are its SIBLINGS in the
   river (the archetypes compose them side by side), never its children. That
   sibling contract is the real isolation boundary, because no prose primitive
   can fully wall itself off from a composed block nested INSIDE it: CSS can stop
   prose from OVERRIDING a property the block declares, but not from FILLING IN a
   property the block leaves at its default (a nested code viewer that never sets
   white-space, a nested nav list that never resets its list marker). To make the
   override half airtight, every prose rule is written with :where(.fdn-prose), so
   its specificity is a bare element (0,0,x) and any composed block class (0,1,x)
   always wins for the properties it sets; the rhythm and heading-spacing rules
   use :where(.fdn-prose) > (direct child) so they only add outer margin to a
   direct child, and the figure image rule matches only a direct-child <img> (a
   markdown figure). The whole prose cascade keeps its own relative order because
   every rule shifts down by the same amount; block 44 (the only prose consumer)
   renders identically. The fill-in half is closed by the sibling contract, not
   by CSS: do not nest a composed block inside prose (see ADOPT.md / the river
   archetypes). Reader text on a well stays strong / body / code weight; muted
   text sits on paper or a panel (--mut is at the AA floor on paper, so a recessed
   well cannot hold it). */
/* In a river, the LANE owns the width (one-width doctrine): prose fills it like
   every other element, so text and cards share both edges. Outside a river (a
   prose well inside a card, the gallery specimen) the 64ch cap still guards the
   line length. */
.fdn-river .fdn-prose { max-width: 100%; }
.fdn-prose { max-width: 64ch; color: var(--text-body); font-size: var(--text-base); line-height: var(--leading-relaxed); }
:where(.fdn-prose) > * { margin: 0; }
:where(.fdn-prose) > * + * { margin-top: var(--space-4); }

:where(.fdn-prose) h2 { font-size: var(--text-lg); font-weight: var(--weight-strong); letter-spacing: var(--tracking-tight); line-height: var(--leading-tight); color: var(--ink-2); text-wrap: balance; }
:where(.fdn-prose) > h2 + p { margin-top: var(--space-3); }
:where(.fdn-prose) p { text-wrap: pretty; }
:where(.fdn-prose) strong { font-weight: var(--weight-strong); color: var(--ink-2); }

/* inline link: signal accent plus a persistent underline (the redundant cue) */
:where(.fdn-prose) a { color: var(--accent); text-decoration: underline; text-decoration-color: var(--accent-line); text-underline-offset: 2px; text-decoration-thickness: 1px; border-radius: var(--radius-sm); }
:where(.fdn-prose) a:where(:hover) { text-decoration-color: var(--accent); }
:where(.fdn-prose) a:where(:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }

/* bulleted list: a small signal marker plus the words; comfortable row rhythm.
   Scoped to a DIRECT child of a ul so a nested ordered list keeps its number
   marker instead of also getting a dot, and so the marker never leaks onto a
   list that belongs to a composed block dropped inside the prose. */
:where(.fdn-prose) ul { list-style: none; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
:where(.fdn-prose) ul > li { position: relative; padding-left: var(--space-5); text-wrap: pretty; }
:where(.fdn-prose) ul > li::before { content: ""; position: absolute; left: var(--space-1); top: calc(var(--leading-relaxed) * 0.5em); width: 5px; height: 5px; border-radius: 50%; background: var(--accent); transform: translateY(-50%); }

/* ordered list: a signal-accent number plus the words. Styles the NATIVE
   ::marker on a real decimal list, so a markdown renderer's <ol start>, <ol
   reversed>, and per-item <li value> all number correctly — the marker follows
   the list's true ordinal, never a reset-to-1 (a private CSS counter cannot read
   those attributes). The marker sits outside in the list padding, right-aligned,
   so single- and multi-digit numbers line up on one edge; tabular figures hold
   the alignment. ::marker only takes colour/font, which is all the accent needs. */
:where(.fdn-prose) ol { padding-left: var(--space-8); }
:where(.fdn-prose) ol > li { text-wrap: pretty; }
:where(.fdn-prose) ol > li + li { margin-top: var(--space-2); }
:where(.fdn-prose) ol > li::marker { color: var(--accent-text); font-weight: var(--weight-strong); font-variant-numeric: tabular-nums; }

/* nested list: give an inner list a little air below its parent line */
:where(.fdn-prose) li > ul, :where(.fdn-prose) li > ol { margin-top: var(--space-2); }

/* subheadings: h3 and h4 carry the ink-2 heading voice one and two steps down
   from h2, sized by the scale, never picked for size. A heading binds to what
   follows it: more space above, less below. Direct-child combinators keep the
   rhythm from reaching a nested block's own headings. */
:where(.fdn-prose) h3 { font-size: var(--text-md); font-weight: var(--weight-strong); letter-spacing: var(--tracking-tight); line-height: var(--leading-snug); color: var(--ink-2); text-wrap: balance; }
:where(.fdn-prose) h4 { font-size: var(--text-base); font-weight: var(--weight-strong); letter-spacing: var(--tracking-tight); line-height: var(--leading-snug); color: var(--ink-2); text-wrap: balance; }
:where(.fdn-prose) > * + h3, :where(.fdn-prose) > * + h4 { margin-top: var(--space-6); }
:where(.fdn-prose) > h3 + *, :where(.fdn-prose) > h4 + * { margin-top: var(--space-3); }

/* inline code: a monospace run set into a subtle well so it reads as a literal
   without shouting. Strong ink on the well clears AA in both themes. Mono is
   sanctioned for code only (see docs/USAGE.md). Excludes the <code> inside a
   <pre> (the block below owns that). */
:where(.fdn-prose) :not(pre) > code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.9em; color: var(--ink-2); background: var(--well); border-radius: var(--radius-sm); padding: 0.1em 0.36em; overflow-wrap: anywhere; }

/* code block: a monospace surface on the well. In a reading document a code
   block WRAPS to the measure rather than opening a horizontal scroll: it keeps
   the page from ever scrolling sideways AND avoids a scroll region a keyboard
   user cannot reach. The dedicated code viewer (block 46, .fdn-code__pre) wraps
   the same way, so both surfaces keep every character in the frame. A long
   unbreakable token still breaks (overflow-wrap) so nothing spills past the
   frame. No shadow: a well is a recessed island, not a raised panel. :where()
   keeps it below a nested composed .fdn-code__pre (on --panel), which always
   wins. */
:where(.fdn-prose) pre { margin: 0; min-width: 0; max-width: 100%; white-space: pre-wrap; overflow-wrap: anywhere; background: var(--well); border: var(--hair) solid var(--line); border-radius: var(--radius-md); padding: var(--space-4) var(--space-5); font-size: var(--text-xs); line-height: var(--leading-relaxed); color: var(--ink-2); }
:where(.fdn-prose) pre code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: inherit; color: inherit; background: none; padding: 0; border-radius: 0; }

/* table: the .fdn-data reporting treatment for a plain markdown <table> —
   uppercase faint column heads, hairline row rules, first/last cells flush to
   the measure. A real width:100% table (like .fdn-data), so the cells WRAP to
   fit the reading column at any width instead of the table becoming its own
   horizontal scroller: a scrolled-off column in an overflow box is unreachable
   for a keyboard-only user, and the page must never scroll sideways either.
   Wrapping serves both. Long unbreakable tokens break rather than force
   overflow. The real <table> semantics (a thead of column headers) are
   preserved for assistive tech. A genuinely wide dataset belongs in the
   composition .fdn-data block (which reflows each row to a labelled card on a
   phone), not raw in prose. Wrapped in :where() so its specificity is a bare
   element: a real .fdn-data / .fdn-cmp table nested inside prose keeps its own
   classed rules (including the phone stacked-card reflow), which would otherwise
   tie on specificity and lose to these later-loaded prose rules. */
:where(.fdn-prose) table { width: 100%; border-collapse: collapse; color: var(--body); font-size: var(--text-sm); }
:where(.fdn-prose) th, :where(.fdn-prose) td { padding: var(--space-3) var(--space-3); border-top: var(--hair) solid var(--line-fine); text-align: left; vertical-align: top; text-wrap: pretty; overflow-wrap: anywhere; }
:where(.fdn-prose) thead th { border-top: 0; font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint); }
/* the :first-child / :last-child are themselves wrapped in :where so the
   pseudo-class adds no specificity; otherwise a bare :first-child (0,1,0) would
   lift these to 0,1,1 and tie (then beat, on load order) a nested .fdn-cmp /
   .fdn-data cell rule. They still win over the base cell rule above by source
   order at equal 0,0,1 specificity. */
:where(.fdn-prose) th:where(:first-child), :where(.fdn-prose) td:where(:first-child) { padding-left: 0; }
:where(.fdn-prose) th:where(:last-child), :where(.fdn-prose) td:where(:last-child) { padding-right: 0; }

/* figure: captioned media. The media sits on the well (a subordinate island);
   the caption stays on paper in the quiet measure voice, below the frame, never
   on the well (muted text belongs on paper/panel). :where() keeps every rule
   below a nested composed .fdn-figure (whose .fdn-figure__caption wins); the
   image rule matches only an <img> that is a DIRECT child of the figure (a
   markdown figure), never a composed .fdn-figure's <img> nested in its
   .fdn-figure__media wrapper. */
:where(.fdn-prose) figure { margin: 0; display: flex; flex-direction: column; gap: var(--space-3); }
:where(.fdn-prose) figure > img { display: block; width: 100%; max-width: 100%; height: auto; border: var(--hair) solid var(--line); border-radius: var(--radius-sm); background: var(--well); }
:where(.fdn-prose) figure > figcaption { font-size: var(--text-sm); color: var(--mut); line-height: var(--leading-snug); text-wrap: pretty; }

/* thematic break: a hairline rule with breathing room on both sides. :where()
   keeps it below a nested composed divider (.fd-rule / .fdn-card__rule), which
   owns its own filled treatment and always wins. */
:where(.fdn-prose) hr { border: 0; border-top: var(--hair) solid var(--line-2); margin: var(--space-6) 0; }

/* blockquote: set off by the one signal accent on a left rule, ink-2 voice */
:where(.fdn-prose) blockquote { padding-left: var(--space-5); border-left: 2px solid var(--accent); color: var(--ink-2); font-size: var(--text-md); line-height: var(--leading-snug); letter-spacing: var(--tracking-tight); text-wrap: pretty; }

/* ── accordion (45-accordion) ────────────────────────────────────────────────
   Accordion, a vertical stack of disclosure rows. Each header is a real
   button that carries its own expanded or collapsed state, so a screen
   reader and the keyboard both know what is open. The chevron is a second,
   redundant cue: it points down when the row is folded and rotates up when
   the row is open, so the state never rests on colour alone. One answer is
   shown at a time; the rest stay folded to keep the page calm.
   Built only from tokens so it sits beside the other blocks and renders
   cleanly in light and dark. */
.fdn-accordion { display: flex; flex-direction: column; min-width: 0; }

.fdn-accordion__item {
  border-top: var(--hair) solid var(--line);
  min-width: 0;
}
.fdn-accordion__item:first-child { border-top: 0; }

/* The header is the disclosure control: full-width button, label left,
   chevron right. No fill until it is open or hovered, so the stack reads
   as quiet rows rather than buttons. */
.fdn-accordion__header {
  appearance: none; border: 0; background: none; font: inherit; cursor: pointer;
  width: 100%; min-width: 0;
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-4) var(--space-1);
  text-align: left;
  color: var(--ink-2);
  transition: color var(--dur-fast) var(--ease-out);
}
.fdn-accordion__header:hover { color: var(--ink); }
.fdn-accordion__header:focus-visible {
  outline: 2px solid var(--focus); outline-offset: 2px; border-radius: var(--radius-sm);
}

.fdn-accordion__label {
  flex: 1; min-width: 0;
  font-size: var(--text-base); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-tight); line-height: var(--leading-snug);
  text-wrap: pretty;
}

/* Chevron: two short strokes drawn with borders, pointing down when folded.
   It rotates a half turn when the header is open. Token motion, no glyph. */
.fdn-accordion__chevron {
  flex: none; width: 18px; height: 18px; position: relative;
  display: grid; place-items: center;
  color: var(--text-muted);
  transition: transform var(--dur-base) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.fdn-accordion__chevron::before {
  content: ""; width: 8px; height: 8px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  border-radius: 1px;
  transform: translateY(-2px) rotate(45deg);
}
.fdn-accordion__header:hover .fdn-accordion__chevron { color: var(--ink-2); }

/* Open state, marked three ways, never colour alone: the button reports
   aria-expanded="true", the chevron rotates to point up, and the open label
   lifts to the signal accent for live wayfinding. */
.fdn-accordion__header[aria-expanded="true"] .fdn-accordion__chevron {
  transform: rotate(180deg);
  color: var(--signal);
}
.fdn-accordion__header[aria-expanded="true"] .fdn-accordion__label {
  color: var(--accent-text);
}

/* Panel: the answer. Hidden entirely when collapsed (the [hidden] attribute),
   so only the open answer takes space. */
.fdn-accordion__panel { padding: 0 var(--space-1) var(--space-5); min-width: 0; }
.fdn-accordion__panel[hidden] { display: none; }
.fdn-accordion__panel p {
  margin: 0; font-size: var(--text-sm); color: var(--body);
  line-height: var(--leading-relaxed); text-wrap: pretty;
}
.fdn-accordion__panel p + p { margin-top: var(--space-3); }

/* ── code-block (46-code-block) ──────────────────────────────────────────────
   Code-block primitive. The system has no shared code surface yet, so this block
   defines its own from tokens. Two parts a plain card does not have: a head row
   carrying the language label and a copy control, and the code surface itself.
   A long line WRAPS onto the next line inside the pre (white-space: pre-wrap,
   capped at 100% width) rather than widening the page or hiding characters behind
   a horizontal scroll, so every character stays in the frame at any width, on
   screen and on paper. */
.fdn-code { min-width: 0; }
.fdn-code__bar { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-3); }
.fdn-code__lang { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-tight); color: var(--mut); }
.fdn-code__copy {
  appearance: none; -webkit-appearance: none; font: inherit; font-size: var(--text-2xs); font-weight: var(--weight-strong);
  display: inline-flex; align-items: center; gap: var(--space-2); cursor: pointer;
  color: var(--ink-2); border: var(--hair) solid var(--line-2); border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-3);
  transition: color var(--dur-snap) var(--ease-out), background var(--dur-snap) var(--ease-out);
}
.fdn-code__copy::before { content: ""; width: 10px; height: 10px; border: var(--hair) solid currentColor; border-radius: 2px; box-shadow: 3px 3px 0 -1px var(--panel), 4px 4px 0 -1px currentColor; }
.fdn-code__copy:hover { color: var(--ink); background: var(--line-fine); }
.fdn-code__copy:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.fdn-code__pre {
  /* This viewer WRAPS a long line onto the next line rather than opening a
     horizontal scroll, so every character stays inside the frame at any width
     and nothing is ever clipped at the right edge (on screen or on paper). It
     declares its line-breaking explicitly rather than leaning on the bare <pre>
     default: pre-wrap keeps the author's own newlines and indentation while
     letting an over-long line fold, and overflow-wrap: anywhere breaks a single
     unbreakable token (a long path or URL) so it too stays in the frame. This
     also means placing the component inside .fdn-prose changes nothing: both
     surfaces now wrap, and .fdn-code__pre owns its own breaking.
     One honest limit of this wrap: a folded continuation RESTARTS at the left
     margin, it does not hang-indent under where the line began, so a wrapped
     line is not visually distinct from the author's own next line. That is the
     right trade because this block carries COMMANDS and short SNIPPETS, not deep
     multi-line listings where a hanging indent would earn its keep. It is not a
     scrollable region (there is nothing to scroll), so the <pre> takes no
     tabindex. */
  white-space: pre-wrap; overflow-wrap: anywhere;
  margin: 0; min-width: 0; max-width: 100%;
  border: var(--hair) solid var(--line); border-radius: var(--radius-md);
  padding: var(--space-4) var(--space-5);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--text-xs); line-height: var(--leading-relaxed); color: var(--ink-2);
}

/* ── figure (47-figure) ──────────────────────────────────────────────────────
   Figure primitive, a captioned media area with a credit line. The image area
   is a placeholder surface (token gradient plus a grid texture and an "Image"
   tag) that any real <img> can replace; aspect-ratio holds the frame and
   max-width keeps it inside the column. Caption uses ink-2 voice, credit uses
   the quiet measure label. Tokens only, light and dark both clean. */
.fdn-figure { margin: 0; display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.fdn-figure__media { position: relative; width: 100%; max-width: 100%; aspect-ratio: 16 / 9; border-radius: var(--radius-sm); border: var(--hair) solid var(--line); overflow: hidden; background: linear-gradient(135deg, var(--signal-soft) 0%, var(--panel) 55%, var(--paper) 100%); }
.fdn-figure__grid { position: absolute; inset: 0; background-image: repeating-linear-gradient(to right, transparent 0, transparent calc(12.5% - 1px), var(--line-fine) calc(12.5% - 1px), var(--line-fine) 12.5%), repeating-linear-gradient(to bottom, transparent 0, transparent calc(25% - 1px), var(--line-fine) calc(25% - 1px), var(--line-fine) 25%); }
.fdn-figure__tag { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); display: inline-flex; align-items: center; gap: var(--space-2); padding: var(--space-2) var(--space-3); border-radius: var(--radius-pill); border: var(--hair) solid var(--line-2); box-shadow: var(--shadow-flat); font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--mut); white-space: nowrap; }
.fdn-figure__tag-dot { width: var(--dot); height: var(--dot); border-radius: 50%; background: var(--na-dot); flex: none; }
.fdn-figure__caption { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.fdn-figure__cap-text { color: var(--ink-2); }
.fdn-figure__credit { white-space: normal; }

/* ── card-grid (48-card-grid) ────────────────────────────────────────────────
   Card grid: equal cards in a row that reflow to one column on a phone. The
   grid rides the shared .fdn-cat columns, so the gallery still reflows correctly.
   The active card carries a dot-plus-word tag for meaning; the border here is a
   quiet reinforcement that degrades cleanly if styles are not present. */
.fdn-cardgrid .fdn-card { gap: var(--space-2); }
.fdn-cardgrid .fdn-card.is-active { border-color: var(--accent-line); box-shadow: var(--shadow-card); }

/* ── event log (69-event-log) ────────────────────────────────────────────────
   A vertical, timestamped event log: the spine of an incident report or an audit
   trail — the one thing the milestone timeline (a plan-progress device of
   hand-placed dots) could never say. Each row carries a MACHINE-READABLE <time>
   so a person and a machine read the same instant, an optional duration, a marker
   on a hairline spine, and the event in plain words with a link to the fuller
   account. It works two ways from the SAME markup: BARE in the river (events
   interleaved with the reading, on paper) and BOXED in a card (one panel in a
   grid). Tokens only, no motion. The marker is a dot PLUS the time, the words,
   and — on the live row — the word "Now", so nothing rests on colour. Muted time
   sits on paper/panel (above AA there); the spine and dots are structure. */
.fdn-log { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; --log-dot-c: calc(var(--space-1) + var(--dot) / 2); }
.fdn-log__row { display: grid; grid-template-columns: max-content var(--space-5) minmax(0, 1fr); column-gap: var(--space-4); }
.fdn-log__time { font-size: var(--text-xs); font-weight: var(--weight-strong); line-height: var(--leading-snug); color: var(--mut); font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-tight); white-space: nowrap; }
/* the spine: a single hairline down the middle column, trimmed at the ends so it
   starts and stops at the first and last dot rather than overshooting. */
.fdn-log__mark { position: relative; display: flex; justify-content: center; }
.fdn-log__mark::before { content: ""; position: absolute; top: 0; bottom: 0; left: calc(50% - var(--hair) / 2); width: var(--hair); background: var(--line-2); }
.fdn-log__row:first-child .fdn-log__mark::before { top: var(--log-dot-c); }
.fdn-log__row:last-child  .fdn-log__mark::before { bottom: calc(100% - var(--log-dot-c)); }
.fdn-log__dot { position: relative; margin-top: var(--space-1); width: var(--dot); height: var(--dot); border-radius: 50%; background: var(--mut); }
.fdn-log__row--now .fdn-log__dot { background: var(--signal); }
.fdn-log__body { min-width: 0; padding-bottom: var(--space-5); }
.fdn-log__row:last-child .fdn-log__body { padding-bottom: 0; }
.fdn-log__what { margin: 0; font-size: var(--text-sm); font-weight: var(--weight-strong); line-height: var(--leading-snug); color: var(--ink-2); text-wrap: pretty; }
.fdn-log__now { margin-left: var(--space-2); font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--accent-text); }
.fdn-log__dur { margin-left: var(--space-2); font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint); white-space: nowrap; }
.fdn-log__note { margin: var(--space-1) 0 0; font-size: var(--text-xs); line-height: var(--leading-snug); color: var(--body); text-wrap: pretty; }
.fdn-log__link { color: var(--accent); text-decoration: underline; text-decoration-color: var(--accent-line); text-underline-offset: 2px; text-decoration-thickness: 1px; border-radius: var(--radius-sm); }
.fdn-log__link:where(:hover) { text-decoration-color: var(--accent); }
.fdn-log__link:where(:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ── aside (70-aside) ─────────────────────────────────────────────────────────
   Marginalia: a secondary note that runs ALONGSIDE the reading without breaking
   it — "what we got lucky on", a definition at first use, a reviewer's remark.
   Quieter than a callout: it stays on PAPER (not pulled onto a well), set off by
   one faint hairline on its leading edge and a labelled eyebrow, so the reader
   can take it or leave it. The eyebrow WORD names the kind of note; colour never
   carries it. Body sits on paper where --body clears AA. */
.fdn-aside { border-left: var(--hair) solid var(--line-2); padding-left: var(--space-5); display: flex; flex-direction: column; gap: var(--space-2); }
.fdn-aside__label { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--mut); }
.fdn-aside__body { margin: 0; font-size: var(--text-sm); line-height: var(--leading-snug); color: var(--body); text-wrap: pretty; }
.fdn-aside__body strong { color: var(--ink-2); font-weight: var(--weight-strong); }

/* ── editorial callout (71-callout) ──────────────────────────────────────────
   A point pulled OUT of the reading so it lands: a caveat, a key takeaway, a
   definition worth stopping on. Sits on the WELL (a recessed island in the flow,
   no shadow — a well recedes, a panel lifts), with a labelled eyebrow so the KIND
   of note is a WORD, never a colour. Reader text on a well stays body/ink weight:
   --mut is below AA on a well, so the callout never uses it (Wave 1 SCOPE
   CONTRACT). More present than an aside (it has a surface), less than a panel. */
.fdn-callout { background: var(--well); border-radius: var(--radius-md); padding: var(--space-5); display: flex; flex-direction: column; gap: var(--space-2); }
.fdn-callout__label { font-size: var(--text-2xs); font-weight: var(--weight-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--accent-text); }
.fdn-callout__body { margin: 0; font-size: var(--text-sm); line-height: var(--leading-snug); color: var(--body); text-wrap: pretty; }
.fdn-callout__body strong { color: var(--ink-2); font-weight: var(--weight-strong); }

/* B2 still mode / reduced motion — the accordion chevron's half-turn (transform) is the
   one motion transition in this file; collapse it to instant so open/closed reads the same
   at rest, with identical meaning (aria-expanded + label still carry the state). The colour
   crossfade on the same chevron stays. Held byte-for-byte in lockstep by
   build/lint-still-mode.mjs (positive coverage); the twins may not drift or vanish. */
@media (prefers-reduced-motion: reduce) {
  .fdn-accordion__chevron { transition: none; }
}
:root[data-still] .fdn-accordion__chevron { transition: none; }

/* ===== parts/print.css ===== */
/* Foundational — print stylesheet. Imported LAST in styles.css so it wins the
   cascade for @media print without !important.

   Why this exists: the FDS charter lists reports and status briefings among the
   artifacts handed to a person, and those get printed or exported to PDF. By
   default browsers do NOT print background colours, so a dark-theme artifact
   prints as near-invisible light text on white, scrims and glass lose their
   boundaries, and the decorative mesh/horizon/glow layers either waste ink or
   drop inconsistently across browsers. This sheet makes a screen-clean artifact
   print legibly:
     1. force the LIGHT palette (so dark-theme pages print readable),
     2. hide purely decorative layers (mesh, veil, glow, ambient field, bloom),
     3. neutralize translucent glass to an opaque light panel,
     4. set print-color-adjust:exact ONLY where a colour fill is load-bearing and
        no adjacent word already carries the meaning (chart series/marks, meters).

   Token-driven; no raw colour literals beyond the chart's own swatch fills which
   are already token-driven inline. Out of scope (noted for a follow-up): no
   forced-colors / prefers-reduced-transparency guards yet. */

@media print {
  /* 1. FORCE LIGHT PALETTE.
     Dark pages set [data-theme="dark"] which re-declares the ink/surface ramp to
     dark values. Re-state those same tokens back to their light-theme values for
     print so the dark ramp can never reach the page. :root is restated too so an
     explicit data-theme="cool"/no-theme page is unaffected and an unknown future
     dark-ish attribute still lands on light.
     :root:not([data-theme]) is restated explicitly so the OS-driven auto-dark
     ramp (tokens/colors.css + mesh.css, applied to an unattributed page when the
     reader's OS is dark) is overridden for print. That auto-dark selector has
     higher specificity than a bare :root, so it would otherwise win and print
     dark; matching its specificity here, and importing last, keeps print light. */
  :root,
  :root:not([data-theme]),
  [data-theme="dark"] {
    /* Ink ramp */
    --ink:#272a2e; --ink-2:#454952; --body:#636a73; --mut:#686e77; --faint:#aeb2b9;
    /* Surfaces */
    --paper:#ffffff; --panel:#ffffff;
    /* Well on paper: a barely-there off-white so a code/figure island still reads
       as recessed if a browser is set to print backgrounds; its hairline border
       carries the figure-ground either way, so nothing is lost when backgrounds
       are dropped (the default). No bleed. */
    --well:#f4f6f7;
    --line:#e3e6ea; --line-2:#d5d9df; --line-fine:#edf0f3; --tick:#c7ccd3; --tick-2:#adb2ba;
    --scrim:transparent;
    /* Signal accent — print uses the deep navy for emphasis text on white;
       restate --accent-text so a dark-sourced page does not carry the lifted
       --signal into print. */
    --signal:#294a78; --signal-deep:#1d3458; --signal-soft:#eaedf3; --signal-line:#c6cfe0;
    --accent-text:var(--signal-deep);
    --on-accent:#ffffff;
    /* Status — --na-ink darkened to #5f6166 (matches the light token) so the
       "not started" word and the journey process kicker clear AA on --na-bg. */
    --ok-ink:#3f5e4d; --ok-bg:#eaeeeb; --ok-line:#d4ddd6; --ok-dot:#5e8468;
    --warn-ink:#7a611c; --warn-bg:#efeae1; --warn-line:#ddd5c4; --warn-dot:#9b8350;
    --blk-ink:#74463b; --blk-bg:#efe7e3; --blk-line:#ddccc6; --blk-dot:#9f6356;
    --na-ink:#5f6166; --na-bg:#eceae6; --na-line:#d9d7cf; --na-dot:#abb0b4;
    /* Live-state beacon */
    --state-ok:#538d66; --state-warn:#a88948; --state-blk:#a85348;
    /* Data-series ramp */
    --data-1:#294a78; --data-2:#7a8fab; --data-3:#a9b7c9; --data-4:#d0d7e1;
    /* Glass + tooltip surfaces (mesh.css) reset to light, neutralized to opaque below */
    --glass-surface:#fbfcfb; --glass-tint:#fbfcfb; --glass-tint-solid:#fbfcfb;
    --glass-hair:rgba(120,118,110,0.20);
    --tip-bg:#ffffff; --tip-bg-solid:#ffffff; --tip-border:#d5d9df;
    --on-glass-ink:#242a33; --on-glass-body:#565a62; --on-glass-muted:#888b92;
    /* Print on a white sheet — never paint a page background */
    background:#ffffff;
  }

  body { background:#ffffff; }

  /* 2. HIDE DECORATIVE LAYERS. None carry meaning; on paper they waste ink or
     print as muddy boxes. The .lg / .fd-glass specular and rim pseudo-elements go
     too — they only read as "lit glass" on screen. */
  .fd-glass__mesh,
  .fd-glass__veil,
  .fd-glass__glow,
  .fd-glass__edge,
  .fd-ambient__field,
  .lg::before,
  .lg::after,
  .fd-lit::after { display:none !important; }

  /* 3. NEUTRALIZE GLASS TO AN OPAQUE PANEL. On screen glass is a translucent
     blurred surface; with no backdrop printing it would be a faint wash. Drop the
     blur and give it a flat opaque light fill so the content on it reads. */
  .lg,
  .fd-glass,
  .fd-tip,
  .fdn-tip {
    background:#ffffff !important;
    -webkit-backdrop-filter:none !important;
    backdrop-filter:none !important;
    box-shadow:none !important;
  }
  .fd-glass { border:var(--hair) solid var(--line); }

  /* The dimming scrim has no place on paper; it would print as a grey block over
     the very content the overlay is presenting. */
  .fdn-scrim,
  .fdn-scrim--end { background:transparent !important; }

  /* 4. PRESERVE LOAD-BEARING FILLS. These colour fills ARE the data and are not
     all accompanied by a printed word, so force the browser to print them.
       - chart series fills/strokes (lines, bars, ring, donut segments, caps),
       - the event-marker drop-line (--chart-mark resolves to the --tick-2 ruler
         strength, which the forced light palette restates to greyscale; without
         forcing it the browser drops the stroke and the marker loses the faint line
         that ties its word to a bar),
       - the sparkline highlight and tooltip swatch,
       - meter fill + track (the bar length plus its colour is the reading).
     The LEAD FLAG and the ACT-BAND CTA carry a filled navy fill with light text
     (.fdn-lead / .fdn-act__cta): drop the fill and the light word vanishes on
     white. They ARE the "act on this" mark the report requires to survive print,
     so force the fill; the dark band then reads in greyscale too.
     Status dots, chip dots, and the beacon are deliberately NOT forced here:
     each is paired with a WORD per the house rule, so the meaning survives in
     greyscale and the ink is better spent elsewhere. The same rule chooses what
     of the pacing/vintage blocks is forced: the pacing bar fills carry the reading
     (bar length plus the actual/forecast/track luminance ladder, a strong/light/pale
     ink step) so they are forced — and, being .fdn-meter__fill / .fdn-meter__track
     in the markup, they are covered by the meter selectors below; only the pacing
     legend swatches need naming. The vintage EMPTY cell needs no forcing: its cue
     is now a faint en-dash GLYPH plus an off-screen word, and text prints on its
     own; the vintage actual TINT is likewise NOT forced (its bold weight and legend
     word survive print, so the ink is spared). */
  .fdn-lead, .fdn-act__cta,
  .fd-chart,
  .fd-chart__line, .fd-chart__line--accent,
  .fd-chart__mark-line,
  .fd-chart__bar, .fd-chart__bar-2, .fd-chart__bar--accent,
  .fd-chart__dot, .fd-chart__cap,
  .fd-chart__seg-1, .fd-chart__seg-2, .fd-chart__seg-3,
  .fd-chart__ring, .fd-chart__ring-val,
  .fd-spark__hl, .fdn-spark,
  .fd-tip__sw,
  .fdn-pace__sw--actual, .fdn-pace__sw--fcst,
  .fdn-meter__fill, .fdn-meter__track {
    -webkit-print-color-adjust:exact;
    print-color-adjust:exact;
  }

  /* 5. PRINT THE MARKER SUMMARY IN FULL. The event-marker word strip caps each
     label at 15ch with an ellipsis; on screen the full text of a long label is
     reachable through the label's title (hover) and the .fd-sr-only summary
     (assistive tech). Paper has NEITHER channel, so a marker like "Regulatory
     migration" would print as a meaningless truncated strip and lose its meaning.
     Un-clip the bar chart's own sr-only summary for print so the FULL label of
     every marker, with its position, prints as one plain sentence with the chart.
     Scoped to .fdn-bars-wrap so no unrelated sr-only text is surfaced. Tokens
     only. The truncated visual strip stays as the at-a-glance index; this sentence
     is the complete, greyscale-safe record the reader keeps on paper. */
  .fdn-bars-wrap .fd-sr-only {
    position:static;
    width:auto; height:auto;
    margin:var(--space-1) 0 0;
    padding:0;
    overflow:visible;
    clip:auto;
    clip-path:none;
    white-space:normal;
    font-size:var(--text-micro);
    line-height:1.4;
    color:var(--ink-2);
  }

  /* 6. FLUSH-MOSAIC SEAMS SURVIVE A BACKGROUND-DROPPING ENGINE. On screen a flush
     mosaic (.fdn-cat--flush) seams its tiles with the container's own fill showing
     through 1px gutters, and each tile drops its border. A print engine that omits
     background colours (the default) erases that fill and the seams together, while
     the borderless tiles collapse into one unbordered blob. Borders print even when
     backgrounds do not, so give each tile a real hairline back for print and close
     the gutter, so adjacent hairlines meet as one crisp seam and every cell is
     divided on paper. The container keeps its own printed frame (its border prints).
     No forced background: forcing the container fill would flood the whole box grey
     behind tiles whose own fill the engine has dropped, so the border is the safe
     mechanism. */
  .fdn-cat--flush { gap:0; }
  .fdn-cat--flush > .fdn-card { border:var(--hair) solid var(--line); }

  /* 7. EVENT-LOG SPINE SURVIVES A BACKGROUND-DROPPING ENGINE. The time spine
     (.fdn-log__mark::before) and its node dots (.fdn-log__dot) are token-driven
     colour fills on positioned elements — the SAME connective-line construction as
     the chart event-marker line forced in section 4: the spine ties each dot to the
     next and the dots sit each row on it, so the log reads as a timeline rather than
     a loose list. A print engine that omits background colours (the default) erases
     both and the spine collapses. Borders would distort the hairline spine and the
     round dots, so force these fills instead. They are greyscale-safe (--line-2 and
     --mut restate to greys; the --now dot to navy) and every row still carries its
     own machine-readable time and words, so the forced ink only restores the
     structure and never becomes the sole carrier of meaning. */
  .fdn-log__mark::before,
  .fdn-log__dot {
    -webkit-print-color-adjust:exact;
    print-color-adjust:exact;
  }
  /* 8. WIDE LEDGER. On screen the financial statement scrolls sideways with a
     pinned line-item column; paper has no scroll, so a clipped overflow box would
     drop every period past the fold. Un-pin the sticky cells and open the overflow
     so the whole statement flows and paginates. Tighten cell padding and step the
     figures down one size so more columns fit the sheet; the indent, weight, and
     rule hierarchy (which is what carries the statement, not the dropped tints)
     stays intact. A wide statement prints best in landscape. */
  .fdn-ledger__scroll { overflow: visible; }
  .fdn-ledger th[scope="row"],
  .fdn-ledger thead th:first-child,
  .fdn-ledger__band-label { position: static; }
  /* white-space:normal: on screen the cells are nowrap so the columns
     line up under a sideways scroll. Paper has no scroll and a fixed sheet width, so
     15 nowrap columns overflow and CLIP the last periods off a portrait A4. Allow
     the cells to wrap on paper so every period survives the sheet, in portrait or
     landscape; the rule/weight hierarchy still carries the statement. */
  .fdn-ledger th, .fdn-ledger td { padding: var(--space-1) var(--space-2); font-size: var(--text-2xs); white-space: normal; }
  /* Reset the on-screen tints explicitly: the current-period wash and
     the variance green/red washes are reinforcement only. The current column keeps
     its 2px accent top rule (a border, not a background, so it survives) plus the
     caption word; the variance sign and word, and the rule/weight hierarchy, carry
     every other meaning without the tints. Reset here rather than trust the browser's
     default background suppression, which print-color-adjust:exact elsewhere could
     otherwise override, so a background-free statement is enforced, not accidental
     (D-FS2-7). */
  .fdn-ledger__cell-current,
  .fdn-ledger__colhead--current,
  .fdn-ledger__var--ahead,
  .fdn-ledger__var--behind { background: transparent; }
}
