/* ==== Estate DS + AlexVillarroel page layer — concatenated in PATTERN order:
   tokens -> preset-sites -> components(icons,button,card,forms,badge,nav) -> page
   DS files are vendored byte-identical in ./ds/ (read-only). This bundle is the
   deployed artifact; edit ds/*.css never, page.css for page styling. ==== */
/* =============================================================================
   ESTATE DESIGN SYSTEM — TOKEN FOUNDATION (SEED)
   Phase 1, step 1 · STAGED v1.3 PROPOSAL — NOT frozen, pending design-fidelity gate.

   One consumed token source. Vanilla CSS. CSP-strict. No CDN. No external font.
   No build step required to consume. Generalized from Git/aurora-ds tokens +
   doctrine into the estate target per CURATION_design-system-phase0_2026-08-14.

   v1.2 base (GATED): STAGE_ds-seed-v1_2_2026-08-15/tokens.css.
   v1.3 is the CLOSING size/scale + contrast-floor pass (see CHANGELOG-v1.3.md),
   adjudicated by Constellation in REVIEW_ds-components-m3_2026-08-15.md (§2 + Qs).
   It changes EXACTLY these things, nothing else moves:
     1. BINDING GATE FIX — --status-success-fg (LIGHT) #0a7c56 -> #097850 so it
        clears WCAG AA (>=4.5) as text on --surface-2 too (was 4.486, a 0.014 miss).
     2. WIDTH / MEASURE SCALE — --size-xs..xl + --measure (one shared container/
        dialog width ramp; modal's --modal-w sm/md/lg adopts --size-sm/md/lg).
     3. BREAKPOINT SCALE — a DOCUMENTED convention (--bp-sm/md/lg/xl), NOT runtime
        custom props (@media cannot read var()). See the breakpoint block below.
     4. --elevation-hover — a register-aware hover-elevation step (substrate
        default here; the presets set the per-register value).
     5. --control-edge FLOOR NOTE — its true floor is 3.10:1 on --surface-2 DARK
        (the table row-rule worst case), not the 3.51 on --surface. Documented so
        no future edit erodes it. (Comment only — no value change.)
     6. LUNA kid-floor cast — lives in preset-apps.css (a Register-B EXCEPTION,
        NOT a 3rd register); overrides SIZE/DENSITY tokens only.
   DECLINED (per review §2.3): a line-width / hairline scale — nobody re-themes 1px.

   THREE-STATE THEME (the ai-prompt-sync triad, canonical):
     1. bare :root                        -> the complete LIGHT palette
     2. @media (prefers-color-scheme:dark):root:not([data-theme="light"]) -> DARK
     3. :root[data-theme="dark"]          -> explicit DARK override (toggle wins)
   Every color has a definition on bare :root so nothing is dark-only.

   🔴 EVERY hex + px below is a PROPOSAL. Contrast-tested for WCAG AA on both
   grounds (see AA table in the build report). Values are not presented as final.
   ============================================================================ */

/* ---------------------------------------------------------------------------
   1 · TYPOGRAPHY — self-hosted variable woff2 intent, NO CDN.
   The @font-face declares the SHAPE (self-hosted, subset, swap). The binaries
   are not shipped in this seed; the family tokens fall back to a system stack,
   so the reference renders with zero external requests. Drop the woff2 into
   /fonts/ to activate. Estate serif (Fraunces intent) is a Register-A accent.
   --------------------------------------------------------------------------- */
@font-face {
  font-family: "Estate Sans";
  src: url("/fonts/estate-sans-var.woff2") format("woff2-variations"),
       url("/fonts/estate-sans-var.woff2") format("woff2");
  font-weight: 100 900;
  font-stretch: 75% 125%;
  font-style: normal;
  font-display: swap;
  /* latin subset only (basic latin + latin-1 supplement + punctuation) */
  unicode-range: U+0000-00FF, U+2000-206F, U+2192, U+2212, U+2013-2014;
}
@font-face {
  font-family: "Estate Serif";
  src: url("/fonts/estate-serif-var.woff2") format("woff2-variations"),
       url("/fonts/estate-serif-var.woff2") format("woff2");
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+2000-206F;
}

:root {
  /* family tokens — self-hosted first, robust system fallback stack */
  --font-sans: "Estate Sans", system-ui, -apple-system, "Segoe UI", Roboto,
    Helvetica, Arial, sans-serif;
  --font-serif: "Estate Serif", Georgia, "Times New Roman", serif; /* [A] accent */
  --font-mono: ui-monospace, "SFMono-Regular", "Cascadia Code", Menlo, Consolas,
    monospace;

  /* one type scale (px intent shown; rem so it honors user zoom) */
  --fs-2xs: 0.625rem;   /* 10px — meta, counts, pills (HUD floor) */
  --fs-xs:  0.6875rem;  /* 11px — eyebrow, status, dense labels */
  --fs-sm:  0.8125rem;  /* 13px — dense body / table cells (Register B) */
  --fs-base: 0.875rem;  /* 14px — default body */
  --fs-md:  1rem;       /* 16px — comfortable body (Register A) */
  --fs-lg:  1.25rem;    /* 20px — subheads */
  --fs-xl:  1.625rem;   /* 26px — section titles */
  --fs-2xl: 2.125rem;   /* 34px — page / hero titles */

  --lh-tight: 1.2;
  --lh-base: 1.45;
  --lh-loose: 1.6;

  --fw-regular: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;

  /* micro-label eyebrow — the shared estate instinct (uppercase + tracked) */
  --tracking-eyebrow: 0.08em;
  --tracking-tight: -0.01em;

  /* -------------------------------------------------------------------------
     2 · SPACING — one 4px ramp (Register B is dense; A pulls padding from it)
     ------------------------------------------------------------------------- */
  --sp-0: 0;
  --sp-1: 2px;
  --sp-2: 4px;
  --sp-3: 8px;
  --sp-4: 12px;
  --sp-5: 16px;
  --sp-6: 24px;
  --sp-7: 32px;
  --sp-8: 48px;
  --sp-9: 64px;

  /* -------------------------------------------------------------------------
     2b · WIDTH / MEASURE SCALE (v1.3 MINT) — one shared container/dialog width
     ramp so components stop each picking rem literals. Justified by 3 real
     consumers (modal dialog widths, card container width, nav rail width). The
     spacing ramp above sizes PADDING/GAP; this ramp sizes CONTENT-BOX WIDTH.
       - The modal adopts these: .modal--sm=--size-sm, --md=--size-md, --lg=--size-lg
         (generalizing its former local 24/34/48rem slots).
       - --measure caps prose line-length on text-heavy cards/containers.
     These are substrate-neutral (do NOT flip per theme) -> bare :root only.
     ------------------------------------------------------------------------- */
  --size-xs: 20rem;    /* 320px — narrow rail / minimal panel */
  --size-sm: 24rem;    /* 384px — modal sm: confirms, alerts */
  --size-md: 34rem;    /* 544px — modal md: default forms / content */
  --size-lg: 48rem;    /* 768px — modal lg: rich content, tables */
  --size-xl: 64rem;    /* 1024px — wide container / dashboard column */
  --measure: 65ch;     /* prose line-length cap (~65 characters) */

  /* -------------------------------------------------------------------------
     2c · BREAKPOINT SCALE (v1.3) — the canonical responsive breakpoints, as a
     DOCUMENTED CONVENTION, not consumable custom properties.
     🔴 @media conditions CANNOT resolve var(--*): `@media (max-width: var(--bp-md))`
        does not work in ANY browser. So these are NOT CSS variables — they are a
        NAMED CONVENTION that every component's @media references by the SAME rem
        literal, so the whole system folds at shared widths. This block is the one
        source of truth for those literals; components must not invent new ones.

        --bp-sm : 40rem   (640px)  — table switches to own-box scroll  (table.css @media 40rem)
        --bp-md : 48rem   (768px)  — nav collapses to the toggle       (nav.css   @media 48rem) [canonical, N3]
        --bp-lg : 64rem   (1024px) — multi-column dashboard reflow
        --bp-xl : 80rem   (1280px) — max content column / wide grids

     (Real M3 consumers today: table stacks at 40rem = --bp-sm; nav collapses at
     48rem = --bp-md. New components pick the nearest name, never a fresh literal.)
     ------------------------------------------------------------------------- */

  /* -------------------------------------------------------------------------
     3 · RADIUS — one ramp. The 32px outlier is RETIRED. A register picks its
     default via --r-default (squared for [A], rounded for [B]).
     ------------------------------------------------------------------------- */
  --r-0: 0;
  --r-1: 3px;
  --r-2: 6px;
  --r-3: 10px;
  --r-4: 14px;
  --r-pill: 999px;
  --r-default: var(--r-2); /* substrate neutral; presets override */

  /* -------------------------------------------------------------------------
     4 · MOTION — OPT-IN. Durations/eases are always defined, but transition +
     transform are only APPLIED inside prefers-reduced-motion:no-preference
     (see the utilities block). Static is the default.
     ------------------------------------------------------------------------- */
  --motion-fast: 120ms;
  --motion-base: 200ms;
  --motion-slow: 320ms;    /* ceiling for a single INTERACTION transition */
  --motion-loop: 1400ms;   /* v1.2: ambient/continuous LOOP (spinner, pulse,
                              skeleton shimmer). Distinct from the <=320ms
                              interaction durations — a loop is not a response. */
  --ease-standard: cubic-bezier(0.2, 0, 0.2, 1);
  --ease-spring: cubic-bezier(0.2, 0.8, 0.2, 1);
  --press-scale: 0.97;

  /* -------------------------------------------------------------------------
     5 · FOCUS — one ring, rendered on every tappable via :focus-visible.
     Accent-based, so it clears 3:1 against both grounds.
     ------------------------------------------------------------------------- */
  --focus-ring-width: 2px;
  --focus-ring-offset: 2px;
  --focus-ring-color: var(--accent);

  /* =========================================================================
     6 · COLOR — LIGHT PALETTE (bare :root = the default light theme)
     ========================================================================= */
  color-scheme: light;

  /* surfaces (two-tone banding source for the flat register) */
  --bg: #ffffff;          /* page ground */
  --surface: #f6f7f9;     /* raised panel / card / alt band / readonly control */
  --surface-2: #eceef2;   /* deeper panel / header band / hover */
  --edge: #d7dbe0;        /* hairline separator (decorative — see AA notes) */
  --edge-strong: #b8bec8; /* heavier divider */

  /* control boundary (v1.2) — the HONEST field/control edge. Tuned to ~3.5:1
     (lighter than the borrowed --ink-dim ~5.8 stopgap, with margin over the 3:1
     floor). Clears 3:1 on BOTH grounds AND on the --surface readonly bg:
       #7d8490 on --bg(#ffffff) = 3.77:1 · on --surface(#f6f7f9) = 3.51:1
     🔴 v1.3 FLOOR NOTE: --control-edge's TRUE tightest point is on --surface-2,
        NOT --surface. On --surface-2 it reads 3.24:1 LIGHT (#7d8490 on #eceef2)
        and 3.10:1 DARK (#687181 on #1f2630) — the table interior row-rule case
        (M3 review §2). 3.10 dark is the SINGLE tightest contrast in the whole
        system (0.10 over the 3:1 floor). Do NOT lighten --control-edge or darken
        --surface-2 without re-checking THIS pair. Never ship a table whose only
        row separator is a sub-3:1 rule (use --control-edge, not bare --edge).
     --control-edge-hover mixes toward --ink so contrast RISES on interaction;
     it references two flipping tokens (--control-edge + --ink) so it self-flips
     per theme without a per-scope redefinition (same idiom as --focus-ring-color). */
  --control-edge: #7d8490;
  --control-edge-hover: color-mix(in srgb, var(--control-edge) 65%, var(--ink));

  /* ink */
  --ink: #191c22;         /* primary text        — 17.1:1 on bg */
  --ink-2: #474d59;       /* secondary text      — 8.5:1  on bg */
  --ink-dim: #5f6672;     /* muted / tertiary    — 5.8:1  on bg (AA at small) */
  --ink-onfill: #ffffff;             /* canonical: ink on ANY solid fill (accent OR status) */
  --ink-onaccent: var(--ink-onfill); /* back-compat alias — external M1 consumers */

  /* brand accent (Register A may override --accent with its brand hue) */
  --accent: #2557d6;      /* 6.2:1 on bg; white text on it = 6.2:1 */
  --accent-hover: #1e46b0;
  --accent-ink: var(--ink-onfill);

  /* --- SEMANTIC STATUS — one set. HUE is invariant across both themes.
     Each hue carries: the raw hue (dot/border/icon/wash-fill), a low-opacity
     WASH (color-mix over --bg, recomputes per theme), and a per-theme TEXT
     token that is AA on that ground. success = #10b981 is CANON. --- */
  --status-success: #10b981;
  --status-warn:    #f0b429;
  --status-danger:  #e5484d;
  --status-info:    #38bdf8;
  --status-neutral: var(--ink-dim);

  /* low-opacity washes — 12% hue over the current ground */
  --wash-success: color-mix(in srgb, var(--status-success) 12%, var(--bg));
  --wash-warn:    color-mix(in srgb, var(--status-warn) 12%, var(--bg));
  --wash-danger:  color-mix(in srgb, var(--status-danger) 12%, var(--bg));
  --wash-info:    color-mix(in srgb, var(--status-info) 12%, var(--bg));
  --wash-neutral: color-mix(in srgb, var(--ink-dim) 10%, var(--bg));
  --wash-accent:  color-mix(in srgb, var(--accent) 12%, var(--bg));

  /* AA-safe TEXT tokens for LIGHT ground (raw hues fail as text on white).
     These are darkened same-hue variants; all clear AA (>=4.5) on bg + surface
     + surface-2 (the raised hover/header band) + their own wash.
     🔴 v1.3 BINDING FIX: --status-success-fg nudged #0a7c56 -> #097850. The old
        value read 4.486 on --surface-2 (a 0.014 AA miss the M3 table exposed);
        #097850 reads 4.74 on --surface-2 while still clearing bg (5.51) and
        surface (5.14). All four -fg now clear 4.5 on --surface-2 (see build
        report table). Belt-and-suspenders rule: status -fg text is CHIP-ONLY
        over raised bands — status text lives in a chip on its own wash. */
  --status-success-fg: #097850; /* 5.51 bg · 5.14 surface · 4.74 surface-2 (v1.3) */
  --status-warn-fg:    #8a5a00; /* 5.93 bg · 5.53 surface · 5.10 surface-2 */
  --status-danger-fg:  #c0303a; /* 5.64 bg · 5.26 surface · 4.85 surface-2 */
  --status-info-fg:    #0e6f9e; /* 5.55 bg · 5.17 surface · 4.78 surface-2 */
  --status-neutral-fg: var(--ink-dim);

  /* =========================================================================
     7 · HONESTY GRAMMAR — certainty as SHAPE (from aurora / aurora-ds).
     FACT solid · DERIVED half · ASSUMPTION hollow. A mark can NEVER render more
     certain than its data: assumption carries the LEAST ink (hollow ring),
     derived a hard 50/50 half, fact a full disc. Unknown fails closed to hollow.
     These are substrate-wide primitives (tokens + utility classes below).
     ========================================================================= */
  --prov-fact: var(--status-success);   /* solid  */
  --prov-derived: var(--status-warn);    /* half   */
  --prov-assumption: var(--ink-dim);     /* hollow */
  --prov-mark-size: 0.72em;              /* inline dot sizes to its text */

  /* =========================================================================
     8 · ELEVATION — substrate raw shadows. Register presets pick --elevation:
       [A] flat = none (hairline + two-tone bands do the separation)
       [B] one glassy-bevel token (inset top-highlight + soft drop)
     Bevel is theme-aware (defined again in each dark block).
     ========================================================================= */
  --shadow-sm: 0 1px 2px rgba(16, 20, 30, 0.06), 0 1px 1px rgba(16, 20, 30, 0.04);
  --shadow-md: 0 4px 12px rgba(16, 20, 30, 0.10), 0 2px 4px rgba(16, 20, 30, 0.06);
  --shadow-pop: 0 8px 24px rgba(16, 20, 30, 0.16);
  --shadow-bevel:
    inset 0 1px 0 rgba(255, 255, 255, 0.7),
    0 1px 2px rgba(16, 20, 30, 0.06),
    0 6px 18px rgba(16, 20, 30, 0.12);
  --elevation: none; /* substrate neutral; presets set the register default */

  /* hover-elevation step (v1.3 ADD) — the RAISE-on-interaction elevation.
     Substrate-neutral default = the resting --elevation (so a flat register
     stays flat: A's hover feedback is a border-strengthen, never a shadow).
     The presets set the per-register value like they set --elevation:
       [A] --elevation-hover: var(--elevation)  (= none, stays flat)
       [B] --elevation-hover: var(--shadow-md)  (a shadow step above the bevel)
     The card's --card-elevation-hover slot reads this, so B-card hover is one
     register-scoped SEED line, not a component fork (M3 review C3). */
  --elevation-hover: var(--elevation);

  /* modal backdrop (v1.2) — NON-FLIPPING. A scrim always dims toward DARK on
     BOTH grounds (it is an overlay, not a palette surface), so it is defined
     ONCE here and NOT redefined in the dark scopes: light and dark resolve to
     the SAME dark scrim. Built from the seed's own shadow ink #10141e (the
     rgba(16,20,30,...) used by every --shadow-*) at 62% alpha. */
  --scrim: rgba(16, 20, 30, 0.62); /* = #10141e @ 62% (hex8 #10141e9e) */
}

/* =============================================================================
   DARK PALETTE — state 2: system-dark, unless an explicit light theme is set.
   ============================================================================= */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --bg: #0e1117;
    --surface: #161b23;
    --surface-2: #1f2630;
    --edge: #2b333f;
    --edge-strong: #3a4453;

    /* control boundary (v1.2) — dark value. Tuned to ~3.5:1 on the WORST of
       {input bg, --surface}, which on dark is --surface (the lighter ground):
         #687181 on --bg(#0e1117) = 3.84:1 · on --surface(#161b23) = 3.51:1
       🔴 v1.3 FLOOR NOTE: on --surface-2(#1f2630) this reads 3.10:1 — the single
          tightest contrast in the whole system (table row-rule). See the :root
          --control-edge note; do NOT erode this pair.
       --control-edge-hover self-flips (defined on :root; references --ink). */
    --control-edge: #687181;

    --ink: #e7eaf0;        /* 15.7:1 on bg */
    --ink-2: #aab2c0;      /* 8.9:1  on bg */
    --ink-dim: #8b93a3;    /* 6.1:1  on bg */
    --ink-onfill: #0e1117;             /* canonical: ink on ANY solid fill */
    --ink-onaccent: var(--ink-onfill); /* back-compat alias */

    --accent: #5b8cf0;     /* 5.8:1 on bg; dark ink on it = 5.8:1 */
    --accent-hover: #7aa2f5;
    --accent-ink: var(--ink-onfill);

    /* status HUES stay invariant; only the AA TEXT tokens change for the dark
       ground (raw hues pass as text on dark, except danger which is lifted).
       All four clear 4.5 on --surface-2 dark too (success 6.01, warn 8.17,
       danger 5.08, info 7.11) — unchanged in v1.3 (the binding fix was LIGHT only). */
    --status-success-fg: var(--status-success); /* 7.45 bg · 6.01 surface-2 */
    --status-warn-fg:    var(--status-warn);     /* 10.14 bg · 8.17 surface-2 */
    --status-danger-fg:  #f5666b;                /* lifted -> 6.30 bg · 5.08 surface-2 */
    --status-info-fg:    var(--status-info);     /* 8.82 bg · 7.11 surface-2 */
    --status-neutral-fg: var(--ink-dim);

    /* washes are 14% hue over the dark ground for enough presence */
    --wash-success: color-mix(in srgb, var(--status-success) 14%, var(--bg));
    --wash-warn:    color-mix(in srgb, var(--status-warn) 14%, var(--bg));
    --wash-danger:  color-mix(in srgb, var(--status-danger) 14%, var(--bg));
    --wash-info:    color-mix(in srgb, var(--status-info) 14%, var(--bg));
    --wash-neutral: color-mix(in srgb, var(--ink-dim) 12%, var(--bg));
    --wash-accent:  color-mix(in srgb, var(--accent) 14%, var(--bg));

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
    --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.55);
    --shadow-pop: 0 8px 24px rgba(0, 0, 0, 0.6);
    --shadow-bevel:
      inset 0 1px 0 rgba(255, 255, 255, 0.08),
      0 1px 2px rgba(0, 0, 0, 0.5),
      0 6px 18px rgba(0, 0, 0, 0.5);
    /* --elevation-hover self-resolves per register (defined on :root; presets
       set it). --scrim is NON-FLIPPING — intentionally NOT redefined here. */
  }
}

/* =============================================================================
   DARK PALETTE — state 3: explicit [data-theme="dark"] (toggle wins both ways).
   Same values as the system-dark block.
   ============================================================================= */
:root[data-theme="dark"] {
  color-scheme: dark;

  --bg: #0e1117;
  --surface: #161b23;
  --surface-2: #1f2630;
  --edge: #2b333f;
  --edge-strong: #3a4453;

  /* control boundary (v1.2) — dark value (see the system-dark block for ratios
     + the v1.3 3.10-on-surface-2 floor note). */
  --control-edge: #687181;

  --ink: #e7eaf0;
  --ink-2: #aab2c0;
  --ink-dim: #8b93a3;
  --ink-onfill: #0e1117;             /* canonical: ink on ANY solid fill */
  --ink-onaccent: var(--ink-onfill); /* back-compat alias */

  --accent: #5b8cf0;
  --accent-hover: #7aa2f5;
  --accent-ink: var(--ink-onfill);

  --status-success-fg: var(--status-success);
  --status-warn-fg:    var(--status-warn);
  --status-danger-fg:  #f5666b;
  --status-info-fg:    var(--status-info);
  --status-neutral-fg: var(--ink-dim);

  --wash-success: color-mix(in srgb, var(--status-success) 14%, var(--bg));
  --wash-warn:    color-mix(in srgb, var(--status-warn) 14%, var(--bg));
  --wash-danger:  color-mix(in srgb, var(--status-danger) 14%, var(--bg));
  --wash-info:    color-mix(in srgb, var(--status-info) 14%, var(--bg));
  --wash-neutral: color-mix(in srgb, var(--ink-dim) 12%, var(--bg));
  --wash-accent:  color-mix(in srgb, var(--accent) 14%, var(--bg));

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.55);
  --shadow-pop: 0 8px 24px rgba(0, 0, 0, 0.6);
  --shadow-bevel:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    0 1px 2px rgba(0, 0, 0, 0.5),
    0 6px 18px rgba(0, 0, 0, 0.5);
  /* --elevation-hover self-resolves per register (defined on :root; presets
     set it). --scrim is NON-FLIPPING — intentionally NOT redefined here. */
}

/* =============================================================================
   BASE ELEMENT DEFAULTS (minimal — the substrate paints its own ground so a
   consuming surface never borrows a host theme).
   ============================================================================= */
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: var(--lh-base);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* =============================================================================
   SUBSTRATE UTILITY CLASSES (the reusable primitives)
   ============================================================================= */

/* --- numeric / data typography: tabular figures everywhere numbers align --- */
.u-tnum { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

/* --- the uppercase tracked micro-label eyebrow (shared estate instinct) --- */
.u-eyebrow {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* --- visually hidden (v1.2): the standard screen-reader-only utility, hoisted
       into the seed from loading's local copy so every component shares ONE
       accessible-label primitive (spinner labels, "Loading…" text, dialog
       descriptions, icon-only control labels). Content stays in the a11y tree
       and is removed from the visual layout without display:none. --- */
.u-visually-hidden {
  position: absolute !important;
  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;
}

/* --- focus: one ring on every tappable via :focus-visible --- */
.u-focus:focus-visible,
a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* --- status chip: wash background + AA text + hue dot; render never sets a
       green it did not derive. --- */
.status-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: 1;
}
.status-chip::before {
  content: "";
  width: 0.5em;
  height: 0.5em;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
.status-chip.is-success { background: var(--wash-success); color: var(--status-success-fg); }
.status-chip.is-warn    { background: var(--wash-warn);    color: var(--status-warn-fg); }
.status-chip.is-danger  { background: var(--wash-danger);  color: var(--status-danger-fg); }
.status-chip.is-info    { background: var(--wash-info);    color: var(--status-info-fg); }
.status-chip.is-neutral { background: var(--wash-neutral); color: var(--status-neutral-fg); }

/* --- HONESTY GRAMMAR marks: certainty as shape ---------------------------- */
/* A verbatim carry of the aurora-ds fill/border treatment, tokenized.
   FACT = solid disc · DERIVED = hard 50/50 half · ASSUMPTION = hollow ring.
   The shape survives grayscale and fails closed; color is secondary. */
.prov-dot {
  display: inline-block;
  width: var(--prov-mark-size);
  height: var(--prov-mark-size);
  border-radius: 50%;
  vertical-align: -0.05em;
  box-sizing: border-box;
}
.prov-dot.is-fact {
  background: var(--prov-fact);
  border: 1px solid var(--prov-fact);
}
.prov-dot.is-derived {
  /* hard-edged half — never a soft/ambiguous fill */
  background: linear-gradient(90deg, var(--prov-derived) 50%, transparent 50%);
  border: 1px solid var(--prov-derived);
}
.prov-dot.is-assumption,
.prov-dot { /* default = hollow: unknown grades fail closed to ASSUMPTION */
  background: transparent;
  border: 1px solid var(--prov-assumption);
}
.prov-dot.is-fact { background: var(--prov-fact); } /* re-assert after fail-closed */

/* provenance text label — pairs the shape with an AA-safe word */
.prov-label { display: inline-flex; align-items: center; gap: var(--sp-2); }
.prov-label.is-fact       { color: var(--status-success-fg); }
.prov-label.is-derived    { color: var(--status-warn-fg); }
.prov-label.is-assumption { color: var(--ink-dim); }

/* =============================================================================
   MOTION — authored ONLY inside no-preference. Static is the default; a
   reduced-motion user gets zero transition/transform from these utilities.
   ============================================================================= */
@media (prefers-reduced-motion: no-preference) {
  .u-transition {
    transition: background-color var(--motion-base) var(--ease-standard),
                color var(--motion-base) var(--ease-standard),
                border-color var(--motion-base) var(--ease-standard),
                box-shadow var(--motion-base) var(--ease-standard),
                transform var(--motion-fast) var(--ease-spring);
  }
  .u-press:active { transform: scale(var(--press-scale)); }
}
/* =============================================================================
   REGISTER A — MARKETING SITES preset  (preset-sites.css)  · v1.3
   Layers on tokens.css. Surfaces: alexvillarroel · streamline · constellationlabs
   · shopmarshal. Flat/print-like, brand-committed theme acceptable, static.

   Overrides ONLY the axes that legitimately differ between registers:
     - ELEVATION: shadowless-flat (separation via 1px hairline + two-tone bands)
     - RADIUS DEFAULT: squared (--r-1, 3px) — the "catalogue" identity
     - THEME DEFAULT: a site MAY brand-commit to one theme (below), but it MUST
       author on the triad's token contract so a lift stays possible.
   ALL semantic tokens (status hues, ink, spacing, honesty grammar) stay
   INVARIANT — they are inherited from tokens.css untouched.

   v1.3: sets --elevation-hover (register-aware hover-elevation step). Register A
   stays FLAT on hover — its interactive feedback is a border-strengthen, never a
   shadow — so --elevation-hover equals the resting --elevation (none).

   Apply at the surface root in production: <html class="register-sites">.
   Scoped to a class so both registers can be shown side by side in reference.
   ============================================================================= */

.register-sites {
  /* flat elevation — no drop shadow anywhere; hairline + banding do the work */
  --elevation: none;
  --elevation-hover: var(--elevation); /* v1.3: A stays flat on hover (= none) */
  --elevation-border: 1px solid var(--edge);

  /* squared identity */
  --r-default: var(--r-1);

  /* Register A leans on the comfortable body size + the serif accent */
  --font-size-body: var(--fs-md);
  --font-display: var(--font-serif); /* [A] brand accent — headings may opt in */

  /* two-tone banding source (flat separation): alternate bg / surface */
  --band-a: var(--bg);
  --band-b: var(--surface);
}

/* A card in Register A: hairline box, no shadow. */
.register-sites .card {
  background: var(--bg);
  border: var(--elevation-border);
  border-radius: var(--r-default);
  box-shadow: var(--elevation);      /* = none */
  padding: var(--sp-6);
}

/* Alternating two-tone bands are the flat register's depth substitute. */
.register-sites .band:nth-child(odd)  { background: var(--band-a); }
.register-sites .band:nth-child(even) { background: var(--band-b); }

/* -----------------------------------------------------------------------------
   BRAND-THEME COMMIT (optional): a marketing site may commit to a single theme
   by pinning [data-theme] at its root. It still inherits the full triad token
   contract, so lifting it into a theme-aware context later needs no re-author.
   A site swaps its brand hue by overriding --accent only:
     .register-sites { --accent: #c2410c; }   (example brand override)
   --------------------------------------------------------------------------- */
/* =============================================================================
   ICON SYSTEM — icons.css   (component milestone 1)
   Consumes the gated ds-seed-v0. Adds NO new design values: sizing is `em`
   (tracks text), color is `currentColor` (tracks text color), motion reads the
   seed's motion tokens and is authored ONLY inside no-preference.

   USAGE
     Decorative (default):   <svg class="icon" aria-hidden="true"><use href="#i-check"/></svg>
     Meaningful (labelled):  <svg class="icon" role="img" aria-label="Delete"><use href="#i-trash"/></svg>
   The sprite (sprite.svg) is pasted inline once at the top of <body>.
   ============================================================================= */

.icon {
  /* tracks TEXT SIZE — 1em against the current font-size */
  width: 1em;
  height: 1em;
  /* tracks TEXT COLOR — line glyphs stroke with the inherited ink */
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* line-box behaviour: sit on the text baseline, never stretch a flex row */
  display: inline-block;
  vertical-align: -0.125em;
  flex: none;
  /* the stroke width is authored on the 24 grid; keep it visually constant
     as the glyph scales with font-size */
  vector-effect: non-scaling-stroke;
}

/* filled-glyph opt-in (if a later glyph is authored as a solid shape) */
.icon.is-filled { fill: currentColor; stroke: none; }

/* size helpers (still em-relative, so they scale with the local text) */
.icon.icon-sm { font-size: 0.875em; }
.icon.icon-lg { font-size: 1.25em; }

/* chevron rotations — one glyph, four directions */
.icon.rot-90  { transform: rotate(90deg); }
.icon.rot-180 { transform: rotate(180deg); }
.icon.rot-270 { transform: rotate(270deg); }

/* -----------------------------------------------------------------------------
   SPINNER — the loading glyph. The ROTATION is motion, so it is authored ONLY
   inside prefers-reduced-motion:no-preference (the seed's opt-in law). Under
   reduced-motion the arc renders STATIC — still a visible "busy" mark, just not
   spinning — and any aria-busy / live-region text carries the state.
   --------------------------------------------------------------------------- */
.icon-spinner { /* the arc is intentionally open (3/4 circle) so rotation reads */ }

@media (prefers-reduced-motion: no-preference) {
  @keyframes ds-spin { to { transform: rotate(360deg); } }
  .icon-spinner { animation: ds-spin var(--motion-slow, 320ms) linear infinite; }
}
/* =============================================================================
   BUTTON SYSTEM — button.css   (component milestone 1)
   Consumes the gated ds-seed-v0. CSS-first (no JS). Works in BOTH registers by
   inheritance: radius from --r-default, density from the Register-B --pad-control-*
   tokens (with a ramp fallback for Register A), color/status/focus/motion all
   read seed tokens. Adds NO raw hex.

   AXES
     variant:  .btn--solid | .btn--ghost | .btn--subtle
     color:    (default accent) | .btn--success | .btn--warn | .btn--danger | .btn--info | .btn--neutral
     size:     (default md) | .btn--sm
     shape:    .btn--icon (square, icon-only — REQUIRES aria-label on the button)
     state:    :hover  :active  :disabled/[aria-disabled]/.is-disabled  .is-loading

   🔴 Q3 RULE (binding, from REVIEW_ds-seed-v0 §3): a SOLID status button's FILL
   is the darkened per-theme text token --status-*-fg, NEVER the raw hue. White on
   #10b981 is ~2.5:1 (fails AA); the seed's --status-success-fg (#0a7c56 on light,
   raw hue on dark) + --ink-onaccent flips per theme so the pairing clears AA on
   BOTH grounds. See the color modifiers below.
   ============================================================================= */

.btn {
  /* ---- component color slots (a color modifier sets --btn-edge; the rest derive) */
  --btn-fill: var(--accent);
  --btn-fill-hover: var(--accent-hover);
  --btn-ink: var(--accent-ink);
  --btn-edge: var(--accent);
  /* faint tint for ghost/subtle grounds — derived from the color slot.
     (accent has no seed wash token; deriving via color-mix follows the seed's
      own wash pattern rather than hardcoding — see build report / seed gaps.) */
  --btn-wash: color-mix(in srgb, var(--btn-edge) 12%, var(--bg));
  --btn-wash-hover: color-mix(in srgb, var(--btn-edge) 20%, var(--bg));

  /* ---- density: read Register-B control padding; fall back to the 4px ramp */
  --btn-pad-y: var(--pad-control-y, var(--sp-3));
  --btn-pad-x: var(--pad-control-x, var(--sp-5));

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  position: relative;

  font-family: inherit;
  font-size: var(--font-size-body, var(--fs-base));
  font-weight: var(--fw-semibold);
  line-height: 1.2;
  letter-spacing: var(--tracking-tight);
  white-space: nowrap;
  text-decoration: none;             /* for <a class="btn"> */

  padding: var(--btn-pad-y) var(--btn-pad-x);
  border: 1px solid transparent;
  border-radius: var(--r-default);   /* squared in Register A, rounded in Register B */
  cursor: pointer;
  user-select: none;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  color: var(--btn-edge);
}

/* ---- SIZES (both stay density-aware; md inherits register control padding) --- */
.btn--sm {
  --btn-pad-y: var(--sp-2);
  --btn-pad-x: var(--sp-4);
  font-size: var(--fs-sm);
  gap: var(--sp-1);
}

/* ---- COLOR MODIFIERS — each sets the slots; Q3 solid fill = --status-*-fg ---- */
.btn--success { --btn-edge: var(--status-success-fg); --btn-fill: var(--status-success-fg); --btn-ink: var(--ink-onaccent); --btn-fill-hover: color-mix(in srgb, var(--status-success-fg) 88%, var(--ink)); --btn-wash: var(--wash-success); }
.btn--warn    { --btn-edge: var(--status-warn-fg);    --btn-fill: var(--status-warn-fg);    --btn-ink: var(--ink-onaccent); --btn-fill-hover: color-mix(in srgb, var(--status-warn-fg) 88%, var(--ink));    --btn-wash: var(--wash-warn); }
.btn--danger  { --btn-edge: var(--status-danger-fg);  --btn-fill: var(--status-danger-fg);  --btn-ink: var(--ink-onaccent); --btn-fill-hover: color-mix(in srgb, var(--status-danger-fg) 88%, var(--ink));  --btn-wash: var(--wash-danger); }
.btn--info    { --btn-edge: var(--status-info-fg);    --btn-fill: var(--status-info-fg);    --btn-ink: var(--ink-onaccent); --btn-fill-hover: color-mix(in srgb, var(--status-info-fg) 88%, var(--ink));    --btn-wash: var(--wash-info); }
.btn--neutral { --btn-edge: var(--ink-2);             --btn-fill: var(--ink-2);             --btn-ink: var(--bg);           --btn-fill-hover: color-mix(in srgb, var(--ink-2) 88%, var(--ink));             --btn-wash: var(--wash-neutral); }

/* ---- VARIANTS ------------------------------------------------------------- */

/* SOLID — filled. accent uses --accent-ink; status uses --ink-onaccent (both flip
   per theme, so the darkened -fg fill + flipped ink clears AA on both grounds). */
.btn--solid {
  background: var(--btn-fill);
  color: var(--btn-ink);
  border-color: transparent;
}
.btn--solid:hover { background: var(--btn-fill-hover); }

/* GHOST — transparent ground, colored text, faint tinted hairline. Text color is
   the -fg / accent token → AA on the page ground (verified in the seed). */
.btn--ghost {
  background: transparent;
  color: var(--btn-edge);
  border-color: color-mix(in srgb, var(--btn-edge) 42%, transparent);
}
.btn--ghost:hover { background: var(--btn-wash); border-color: var(--btn-edge); }

/* SUBTLE — wash ground, colored text, no border. Same wash+ -fg pairing as the
   seed's .status-chip / .wash-box (AA-verified). */
.btn--subtle {
  background: var(--btn-wash);
  color: var(--btn-edge);
  border-color: transparent;
}
.btn--subtle:hover { background: var(--btn-wash-hover); }

/* ---- ICON-ONLY — square footprint. MUST carry an aria-label on the button. --- */
.btn--icon { padding: var(--btn-pad-y); aspect-ratio: 1 / 1; }
.btn--icon.btn--sm { padding: var(--sp-2); }

/* the .icon inside a button rides the button's font-size (em), so it scales with
   the label and the size modifier automatically — no per-size icon rule needed. */
.btn .icon { font-size: 1.05em; }

/* =============================================================================
   FOCUS — reassert the seed's one ring on the component itself (covers <a class=btn>
   and any element), using the seed focus tokens. Never remove the outline.
   ============================================================================= */
.btn:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* =============================================================================
   DISABLED — visually dimmed + inert. (WCAG 1.4.3 exempts disabled controls from
   the contrast floor, so the opacity dim is compliant.) Covers the real attribute,
   the ARIA form (for <a>/div triggers), and a utility hook.
   ============================================================================= */
.btn:disabled,
.btn[aria-disabled="true"],
.btn.is-disabled {
  opacity: 0.55;
  cursor: not-allowed;
  box-shadow: none;
}
.btn:disabled,
.btn.is-disabled { pointer-events: none; }
/* aria-disabled stays focusable (announced), so neutralise the press instead */
.btn[aria-disabled="true"]:active { transform: none; }

/* =============================================================================
   LOADING — spinner + aria-busy. The label is hidden (kept for width) and the
   spinner overlays centered. Set aria-busy="true" and disable interaction in
   markup. The spinner's ROTATION is opt-in motion (see icons.css); under
   reduced-motion it renders static and aria-busy carries the state.
   ============================================================================= */
.btn__spinner {
  display: none;
  position: absolute;
  inset: 0;
  align-items: center;
  justify-content: center;
  color: inherit;
}
.btn.is-loading { cursor: progress; pointer-events: none; }
.btn.is-loading .btn__label,
.btn.is-loading .icon:not(.icon-spinner) { visibility: hidden; }
.btn.is-loading .btn__spinner { display: inline-flex; }

/* =============================================================================
   MOTION — authored ONLY inside no-preference (the seed's opt-in law). Static is
   the default; a reduced-motion user gets no transition and no press transform.
   ============================================================================= */
@media (prefers-reduced-motion: no-preference) {
  .btn {
    transition: background-color var(--motion-base) var(--ease-standard),
                border-color var(--motion-base) var(--ease-standard),
                color var(--motion-base) var(--ease-standard),
                box-shadow var(--motion-base) var(--ease-standard),
                transform var(--motion-fast) var(--ease-spring);
  }
  .btn:not(:disabled):not([aria-disabled="true"]):not(.is-loading):active {
    transform: scale(var(--press-scale));
  }
}
/* =============================================================================
   CARD SYSTEM — card.css   (component milestone 3)
   Consumes the gated ds-seed (tokens v1.2 + the two register presets). Works in
   BOTH registers by inheritance and adds NO raw hex/px/ms/ease:
     - ELEVATION reads the register's --elevation (flat none in [A], glassy-bevel
       in [B]). The card NEVER invents a shadow.
     - RADIUS reads --r-default (squared [A] / rounded [B]).
     - BORDER reads --elevation-border (the preset's hairline).
     - DENSITY/padding mirrors the register panel padding via --pad-panel with a
       4px-ramp fallback for [A].
     - color / ink / focus / motion all read seed tokens.

   SLOTS (BEM): .card__media · .card__header (eyebrow/title/subtitle/actions) ·
                .card__body · .card__footer (lead + actions) · .card__divider.
   MODIFIERS:   .card--interactive (whole-card link) · .card--accent-top (accent bar).

   🔴 REGISTER-SKIN OVERLAP (open question for review): the seed-v0 presets ship an
   illustrative `.register-sites .card` / `.register-apps .card` stub (background +
   border + radius + shadow + padding) that predates this component. Those stubs are
   MORE specific (0,2,0) than `.card` (0,1,0), so in-register they supply the box
   skin. Every value they set is IDENTICAL to what this file derives from the same
   tokens, and --card-pad is defined to EQUAL the preset's padding in both registers
   (so the bleed math below always matches). Nothing conflicts today; but two owners
   of the card box is a latent hazard — recommend the stubs be retired in favor of
   this component in a later seed pass. This file is authored to stand alone (it sets
   the full skin itself) so it renders correctly with OR without the stub present.
   ============================================================================= */

.card {
  /* ---- component slots -------------------------------------------------- */
  /* panel padding mirrors the register: [B] --pad-panel (dense), [A] --sp-6.
     This EQUALS the value the preset stub applies to the card box, so the
     full-bleed negative margins below always cancel the real inset exactly. */
  --card-pad: var(--pad-panel, var(--sp-6));
  --card-gap: var(--sp-4);

  /* resting elevation = the register's --elevation (none in [A], bevel in [B]).
     Never an invented shadow — the whole point of the register split. */
  --card-elevation: var(--elevation);
  /* interactive hover elevation: defaults to the RESTING elevation, so a flat
     register stays flat and nothing is invented. A Register-B app MAY opt into a
     stronger SEED shadow token without forking the component, e.g.:
       .register-apps .card--interactive { --card-elevation-hover: var(--shadow-md); } */
  --card-elevation-hover: var(--card-elevation);

  /* ---- box (self-sufficient; the preset stub, if present, sets the same) --- */
  display: flex;
  flex-direction: column;
  gap: var(--card-gap);

  background: var(--surface);                 /* raised panel ground (the preset
                                                 sets [A]=--bg / [B]=--surface;
                                                 this is the standalone fallback) */
  color: var(--ink);
  border: var(--elevation-border, 1px solid var(--edge));
  border-radius: var(--r-default);            /* squared [A], rounded [B] */
  box-shadow: var(--card-elevation);
  padding: var(--card-pad);
  overflow: hidden;                           /* clip media/bands/dividers to the
                                                 radius (outline is unaffected) */
}

/* =============================================================================
   MEDIA — a full-bleed image/graphic at the TOP of the card. Cancels the card
   padding on the top + both sides so the media reaches the rounded corners
   (clipped by the card's overflow:hidden). Authored as the FIRST child.
   ============================================================================= */
.card__media {
  margin: calc(-1 * var(--card-pad)) calc(-1 * var(--card-pad)) 0;
}
.card__media > img,
.card__media > svg,
.card__media > picture > img {
  display: block;
  width: 100%;
  height: auto;
}
/* if a fixed media height is set inline, cover it without distortion */
.card__media[style*="height"] > img { height: 100%; object-fit: cover; }

/* =============================================================================
   HEADER — eyebrow + title + subtitle, with an optional trailing action cluster
   (e.g. an icon-button menu). Uses the seed .u-eyebrow for the micro-label.
   ============================================================================= */
.card__header {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
}
.card__headings { flex: 1 1 auto; min-width: 0; }

.card__title {
  margin: 0;
  font-size: var(--fs-lg);
  line-height: var(--lh-tight);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-tight);
  color: var(--ink);
}
.card__subtitle {
  margin: var(--sp-1) 0 0;
  font-size: var(--font-size-body, var(--fs-base));
  line-height: var(--lh-base);
  color: var(--ink-2);
}
/* trailing header actions (icon-buttons) sit above the stretched link (see the
   interactive z-index rule) so they stay independently clickable */
.card__header-actions {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  margin: calc(var(--sp-2) * -1) calc(var(--sp-2) * -1) 0 0; /* optical nudge into pad */
}

/* =============================================================================
   BODY — the content region. Secondary ink so the title stays the loudest mark.
   ============================================================================= */
.card__body {
  color: var(--ink-2);
  font-size: var(--font-size-body, var(--fs-base));
  line-height: var(--lh-base);
}
.card__body > :first-child { margin-top: 0; }
.card__body > :last-child  { margin-bottom: 0; }

/* =============================================================================
   FOOTER — trailing actions, optionally with a leading meta slot pushed left.
   ============================================================================= */
.card__footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
}
.card__footer-lead {
  margin-right: auto;                         /* pushes actions to the right */
  color: var(--ink-dim);
  font-size: var(--fs-xs);
}

/* =============================================================================
   DIVIDER — an OPTIONAL full-bleed hairline between sections. Cancels the side
   padding so the rule spans edge-to-edge (decorative separator; the seed marks
   --edge decorative). Use as <hr class="card__divider">.
   ============================================================================= */
.card__divider {
  height: 0;
  border: 0;
  border-top: 1px solid var(--edge);
  margin: 0 calc(-1 * var(--card-pad));
}

/* =============================================================================
   ACCENT-TOP — an OPTIONAL brand/accent bar across the card cap. Decorative
   (non-text, AA floor N/A); reads --accent so a Register-A brand override or a
   [data-room] cast recolors it for free. Clipped to the radius.
   ============================================================================= */
.card--accent-top { border-top: 3px solid var(--accent); }

/* =============================================================================
   INTERACTIVE CARD — the WHOLE card is one link, but the tab stop + accessible
   name live on a REAL <a class="card__link"> (never a tabindex div). The link's
   ::after stretches over the card to make the entire surface the hit target.
   ============================================================================= */
.card--interactive { position: relative; }

.card__link { color: inherit; text-decoration: none; }
.card__link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;                     /* hit area follows the card corners */
}

/* Any OTHER real interactive target inside the card (a secondary link, a footer
   button, a header icon-button) must ride ABOVE the stretched ::after so it stays
   independently clickable/focusable. */
.card--interactive :is(a, button, input, select, textarea, .btn, [tabindex]):not(.card__link) {
  position: relative;
  z-index: 1;
}

/* hover/active feedback — register-safe: strengthen the hairline + apply the
   hover-elevation slot (which defaults to the resting elevation, so flat [A]
   stays flat and nothing is invented). */
.card--interactive { cursor: pointer; }
.card--interactive:hover {
  border-color: var(--edge-strong);
  box-shadow: var(--card-elevation-hover);
}

/* keyboard focus: the ring belongs on the WHOLE card, not just the inline title
   text — map the inner link's :focus-visible onto the card box via :has()
   (evergreen; same feature era the M2 modal already relies on). Never removed. */
.card--interactive:has(.card__link:focus-visible) {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}
/* Fallback for any engine without :has — at minimum ring the link's own box so a
   keyboard user is never left without a visible focus indicator. */
@supports not selector(:has(*)) {
  .card__link:focus-visible {
    outline: var(--focus-ring-width) solid var(--focus-ring-color);
    outline-offset: var(--focus-ring-offset);
  }
}

/* =============================================================================
   MOTION — authored ONLY inside prefers-reduced-motion:no-preference (the seed's
   opt-in law). Static is the default: a reduced-motion user still gets the
   border/elevation hover feedback (a color/shadow change, not motion), just no
   lift animation.
   ============================================================================= */
@media (prefers-reduced-motion: no-preference) {
  .card {
    transition:
      border-color var(--motion-base) var(--ease-standard),
      box-shadow var(--motion-base) var(--ease-standard),
      transform var(--motion-fast) var(--ease-spring);
  }
  .card--interactive:hover  { transform: translateY(-2px); }
  .card--interactive:active { transform: translateY(0); }
}
/* =============================================================================
   FORM CONTROL SET — forms.css   (component milestone 2)
   Consumes the gated ds-seed-v1.1. CSS-first; the ONLY JS the estate law allows
   here (none required) is omitted — every control is a native element, so state,
   keyboard, and focus come from the platform. Works in BOTH registers by
   inheritance: radius from --r-default, density from --pad-control-* (with a 4px
   ramp fallback for Register A), color/status/focus/motion all read seed tokens.
   Adds NO raw hex, px, ms, or ease.

   BLOCKS
     .field            the wrapper: label + control + help + error, one column
     .input            text input  (type=text/email/number/… ) + .textarea
     .select           native <select> wrapper (appearance:none + sprite chevron)
     .check            checkbox     (native input, styled box, sprite checkmark)
     .radio            radio        (native input, styled ring, inner dot)

   AXES
     size:   (default md) | .field--sm  (sm tightens padding + type)
     state:  :hover  :focus-visible  :disabled  [readonly]  [aria-invalid="true"]

   🔴 NON-TEXT CONTRAST (WCAG 1.4.11, AA): a form control's boundary is the sole
   indicator of the field, so the RESTING border must clear 3:1 on both grounds.
   The seed's --edge / --edge-strong are hairline/divider tokens (~1.8:1 on white,
   decorative by the seed's own note) and FAIL that floor as a control border.
   This component now consumes seed v1.2's dedicated --control-edge token, tuned to
   ~3.5:1 (a lighter honest hairline: 3.77 on --bg / 3.51 on --surface in light;
   3.84 / 3.51 in dark — all clear 3:1, worst-on-surface ~3.5). It REPLACES the
   --ink-dim (~5.8) stopgap this component borrowed before the token existed.
   --control-edge-hover mixes toward --ink so the hover contrast rises.
   ============================================================================= */

/* -----------------------------------------------------------------------------
   1 · FIELD WRAPPER — owns vertical rhythm; label/help/error stack in one column.
   --------------------------------------------------------------------------- */
.field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  /* one place owns the control geometry; every control below reads these slots */
  --field-bg: var(--bg);
  --field-ink: var(--ink);
  --field-placeholder: var(--ink-dim);
  --field-edge: var(--control-edge);                             /* v1.2: ~3.5:1 both grounds incl --surface */
  --field-edge-hover: var(--control-edge-hover);                 /* v1.2: mixes toward --ink → contrast rises */
  --field-edge-focus: var(--accent);
  --field-edge-invalid: var(--status-danger);                    /* raw hue: 3.9:1 L / 4.9:1 D */
  --field-accent: var(--accent);
  --field-accent-ink: var(--ink-onfill);
  --field-radius: var(--r-default);
  --field-pad-y: var(--pad-control-y, var(--sp-3));
  --field-pad-x: var(--pad-control-x, var(--sp-4));
  --field-font: var(--font-size-body, var(--fs-base));
}

/* the label — primary ink so it leads the group */
.field__label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  color: var(--ink);
}

/* required marker — decorative; real semantics live on the control (required +
   aria-required). --status-danger-fg is the seed's AA-on-both danger TEXT token. */
.field__req {
  color: var(--status-danger-fg);
  margin-inline-start: var(--sp-1);
}

/* help / hint text — secondary; --ink-dim is AA as text on the ground (5.8/6.1) */
.field__help {
  font-size: var(--fs-xs);
  line-height: var(--lh-base);
  color: var(--ink-dim);
}

/* error message — hidden until the field is flagged; wired to the control via
   aria-describedby in markup. --status-danger-fg is AA text on both grounds. */
.field__error {
  display: none;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-xs);
  line-height: var(--lh-base);
  color: var(--status-danger-fg);
}
.field__error .icon { flex: none; }

/* reveal the error whenever the field's control is flagged invalid. Two hooks:
   a .field--invalid modifier on the wrapper, or aria-invalid on the control. */
.field--invalid > .field__error,
.field:has([aria-invalid="true"]) > .field__error { display: flex; }

/* -----------------------------------------------------------------------------
   2 · TEXT INPUT / TEXTAREA / SELECT — shared control surface.
   --------------------------------------------------------------------------- */
.input,
.textarea,
.select__input {
  display: block;
  width: 100%;
  font-family: inherit;
  font-size: var(--field-font);
  line-height: var(--lh-base);
  color: var(--field-ink);
  background: var(--field-bg);
  padding: var(--field-pad-y) var(--field-pad-x);
  border: 1px solid var(--field-edge);
  border-radius: var(--field-radius);
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
}
.textarea {
  min-height: calc(var(--fs-md) * 4);
  resize: vertical;
}

/* placeholder — --ink-dim is AA as text on the ground (5.8/6.1) */
.input::placeholder,
.textarea::placeholder {
  color: var(--field-placeholder);
  opacity: 1; /* Firefox dims placeholders by default; keep the AA token intact */
}

/* HOVER — border moves toward --ink (darker), so contrast can only rise. */
.input:hover:not(:disabled):not([readonly]),
.textarea:hover:not(:disabled):not([readonly]),
.select__input:hover:not(:disabled) {
  border-color: var(--field-edge-hover);
}

/* FOCUS — accent border + the seed's one :focus-visible ring, reasserted here. */
.input:focus-visible,
.textarea:focus-visible,
.select__input:focus-visible {
  border-color: var(--field-edge-focus);
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}
/* native <select> fires :focus (not always :focus-visible) on some engines when
   opened by pointer; keep the ring visible for keyboard users either way. */
.select__input:focus-visible { border-color: var(--field-edge-focus); }

/* INVALID — danger border (raw hue clears the 3:1 non-text floor on both grounds).
   The control also sets aria-invalid="true" in markup, which reveals the error. */
.input[aria-invalid="true"],
.textarea[aria-invalid="true"],
.select__input[aria-invalid="true"] {
  border-color: var(--field-edge-invalid);
}
.input[aria-invalid="true"]:focus-visible,
.textarea[aria-invalid="true"]:focus-visible,
.select__input[aria-invalid="true"]:focus-visible {
  /* keep the danger identity on the border; the accent ring still signals focus */
  border-color: var(--field-edge-invalid);
}

/* READONLY — legible but locked: muted ground + default cursor. Text stays --ink
   (readonly content must remain AA — it is NOT disabled). */
.input[readonly],
.textarea[readonly] {
  background: var(--surface);
  cursor: default;
}

/* DISABLED — dimmed + inert. WCAG 1.4.3 exempts disabled controls from the
   contrast floor, so the opacity dim is compliant (mirrors the button). */
.input:disabled,
.textarea:disabled,
.select__input:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  background: var(--surface);
}
.select:has(.select__input:disabled) { cursor: not-allowed; opacity: 0.55; }
.select:has(.select__input:disabled) .select__input { opacity: 1; } /* don't double-dim */

/* -----------------------------------------------------------------------------
   3 · SELECT — native <select> with appearance:none + a decorative sprite chevron
   positioned in the padding gutter. The chevron is aria-hidden and click-through,
   so all interaction + keyboard land on the real <select>.
   --------------------------------------------------------------------------- */
.select { position: relative; display: block; }
.select__input {
  /* room for the chevron on the trailing edge */
  padding-inline-end: calc(var(--field-pad-x) + var(--sp-5));
  cursor: pointer;
}
.select__chevron {
  position: absolute;
  inset-block: 0;
  inset-inline-end: var(--field-pad-x);
  margin: auto 0;
  color: var(--ink-2);
  pointer-events: none;      /* clicks fall through to the <select> */
}

/* -----------------------------------------------------------------------------
   4 · CHECKBOX + RADIO — native input drives state/keyboard/focus; the input is
   laid transparently over the styled box so pointer + focus target the real
   control. The box is a sibling that reads :checked / :focus-visible / :disabled
   / [aria-invalid] off the input. Checkmark reuses the M1 sprite (#i-check).
   Markup:
     <label class="check">
       <input type="checkbox" class="check__input">
       <span class="check__box"><svg class="check__mark icon" aria-hidden="true"><use href="#i-check"/></svg></span>
       <span class="check__text">Label</span>
     </label>
   --------------------------------------------------------------------------- */
.check,
.radio {
  display: inline-flex;
  align-items: flex-start;
  gap: var(--sp-3);
  position: relative;
  cursor: pointer;
  font-size: var(--field-font);
  line-height: var(--lh-base);
  color: var(--ink);
}

/* the real control — transparent, sized + positioned over its box so it owns the
   hit area and the native focus. Kept in the a11y tree (never display:none). */
.check__input,
.radio__input {
  position: absolute;
  top: 0;
  inset-inline-start: 0;
  width: 1.15em;
  height: 1.15em;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

/* the visible box / ring — square for checkbox, round for radio */
.check__box,
.radio__box {
  flex: none;
  position: relative;
  width: 1.15em;
  height: 1.15em;
  background: var(--field-bg, var(--bg));
  border: 1px solid var(--field-edge, var(--control-edge));  /* v1.2: ~3.5:1 both grounds */
  display: inline-grid;
  place-items: center;
}
.check__box  { border-radius: var(--r-1); }
.radio__box  { border-radius: var(--r-pill); }

/* the checkmark glyph — hidden until checked */
.check__mark {
  font-size: 0.9em;
  color: var(--field-accent-ink, var(--ink-onfill));
  opacity: 0;
  stroke-width: 3;           /* denser check reads at control size */
}
/* the radio dot — inner disc, hidden until checked */
.radio__box::after {
  content: "";
  width: 0.5em;
  height: 0.5em;
  border-radius: var(--r-pill);
  background: var(--field-accent-ink, var(--ink-onfill));
  transform: scale(0);
}

/* HOVER — border darkens toward --ink (contrast rises) */
.check:hover .check__input:not(:disabled) ~ .check__box,
.radio:hover .radio__input:not(:disabled) ~ .radio__box {
  border-color: var(--field-edge-hover, var(--control-edge-hover));
}

/* CHECKED — accent fill + border; reveal the mark / dot. accent + --ink-onfill is
   the seed's verified accent pairing (6.2:1 light / 5.8:1 dark). */
.check__input:checked ~ .check__box,
.radio__input:checked ~ .radio__box {
  background: var(--field-accent, var(--accent));
  border-color: var(--field-accent, var(--accent));
}
.check__input:checked ~ .check__box .check__mark { opacity: 1; }
.radio__input:checked ~ .radio__box::after { transform: scale(1); }

/* FOCUS — the seed's one ring, drawn on the box when the native input has
   keyboard focus. */
.check__input:focus-visible ~ .check__box,
.radio__input:focus-visible ~ .radio__box {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* INVALID — danger border on the box (e.g. a required box left unchecked). */
.check__input[aria-invalid="true"] ~ .check__box,
.radio__input[aria-invalid="true"] ~ .radio__box {
  border-color: var(--field-edge-invalid, var(--status-danger));
}

/* DISABLED — dim the whole label + neutralize the press. */
.check:has(.check__input:disabled),
.radio:has(.radio__input:disabled) {
  opacity: 0.55;
  cursor: not-allowed;
}
.check__input:disabled,
.radio__input:disabled { cursor: not-allowed; }

/* group of checkboxes / radios — vertical stack sharing the field rhythm */
.check-group,
.radio-group {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

/* -----------------------------------------------------------------------------
   5 · SIZE — sm tightens padding + type. md (default) inherits the register's
   control density. Applied on the .field so every control in the group tracks it.
   --------------------------------------------------------------------------- */
.field--sm {
  --field-pad-y: var(--sp-2);
  --field-pad-x: var(--sp-3);
  --field-font: var(--fs-sm);
}
.field--sm .field__label { font-size: var(--fs-xs); }

/* a standalone sm checkbox/radio (outside a .field) */
.check--sm,
.radio--sm { font-size: var(--fs-sm); }

/* -----------------------------------------------------------------------------
   6 · MOTION — authored ONLY inside no-preference (the seed's opt-in law). Static
   is the default; a reduced-motion user gets no transition on any control.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .input,
  .textarea,
  .select__input,
  .check__box,
  .radio__box {
    transition: border-color var(--motion-base) var(--ease-standard),
                background-color var(--motion-base) var(--ease-standard),
                box-shadow var(--motion-base) var(--ease-standard);
  }
  .check__mark { transition: opacity var(--motion-fast) var(--ease-standard); }
  .radio__box::after { transition: transform var(--motion-fast) var(--ease-spring); }
}
/* =============================================================================
   BADGE + CHIP SET — badge.css   (component milestone 3)
   Consumes the gated ds-seed-v1.2. CSS-first (the removable chip's DOM removal is
   the only glue that has no declarative form — see chip.js). Works in BOTH
   registers by inheritance: radius from --r-default, density from the Register-B
   --pad-control-* / --font-size-body tokens (with a 4px-ramp fallback for
   Register A), color/status/focus/motion all read seed tokens. Adds NO raw hex,
   px, ms, or ease.

   Closes the audit's "status-vocabulary + pills-everywhere" gap (AUDIT §status;
   CURATION "ONE success/status set … status+~12%-wash drives every badge").

   BLOCKS
     .badge            status badge (default = SOFT: wash ground + AA -fg text + dot)
     .badge-count      numeric count pill (solid) + .badge-dot (bare notification dot)
     .chip             removable chip (neutral cell) with an accessible remove button

   AXES
     status color:  .is-success | .is-warn | .is-danger | .is-info | .is-neutral
     badge variant: (default SOFT/wash) | .badge--solid (Q3 fill) | .badge--outline
     size:          (default md) | .badge--sm

   🔴 THE STATUS RULE (binding, seed line 168-192 + AUDIT 2026-08-15 correction):
   a status hue is NEVER used as body TEXT. The raw hue #10b981 is only 2.54:1 as
   text on white. A SOFT badge pairs the seed WASH ground with the per-theme AA
   --status-*-fg TEXT token; the hue survives as the DOT / ICON (which is drawn in
   currentColor = the -fg token, so it too clears the graphic floor). A SOLID badge
   follows the M1 Q3 trick: fill = the darkened --status-*-fg, text = --ink-onfill,
   both flip per theme → AA on both grounds. Every pairing was computed on BOTH
   grounds (see the build report / demo banner).
   ============================================================================= */

/* -----------------------------------------------------------------------------
   1 · STATUS BADGE — shared shell. One block scope owns the color slots; a
   status modifier sets --badge-fg / --badge-wash, the variants read them. This
   mirrors the seed's own .status-chip (wash + -fg + currentColor dot), promoted
   to a full component with solid/outline variants + size + icon slot.
   --------------------------------------------------------------------------- */
.badge {
  /* color slots — default NEUTRAL; a status modifier overrides these two */
  --badge-fg: var(--status-neutral-fg);
  --badge-wash: var(--wash-neutral);

  /* density: read Register-B control padding; fall back to the 4px ramp. Badges
     are tighter than controls, so the y-pad steps one below --pad-control-y. */
  --badge-pad-y: var(--sp-1);
  --badge-pad-x: var(--sp-3);

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  vertical-align: baseline;

  padding: var(--badge-pad-y) var(--badge-pad-x);
  border: 1px solid transparent;         /* only .badge--outline paints it */
  border-radius: var(--r-pill);          /* pill by design (status vocabulary) */

  font-family: inherit;
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: 1;
  letter-spacing: var(--tracking-tight);
  white-space: nowrap;

  /* SOFT (default): wash ground + AA -fg text. fg-on-wash computed AA on both
     grounds (light min 4.66:1 success · dark min 5.23:1 neutral). */
  background: var(--badge-wash);
  color: var(--badge-fg);
}

/* the hue DOT — currentColor (= --badge-fg), so it carries the status hue while
   still clearing the graphic floor (>=4.86:1 on both grounds). Mirrors the seed
   .status-chip::before. Suppressed when a leading icon is present. */
.badge__dot {
  width: 0.5em;
  height: 0.5em;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

/* leading status icon (M1 sprite via <use>) — rides the text color + size (em).
   Decorative when the badge text already names the status; label it at the use
   site only if the badge is icon-only. */
.badge .icon { font-size: 1.05em; }

/* ---- STATUS COLOR MODIFIERS — set the two slots; everything else derives ---- */
.badge.is-success { --badge-fg: var(--status-success-fg); --badge-wash: var(--wash-success); }
.badge.is-warn    { --badge-fg: var(--status-warn-fg);    --badge-wash: var(--wash-warn); }
.badge.is-danger  { --badge-fg: var(--status-danger-fg);  --badge-wash: var(--wash-danger); }
.badge.is-info    { --badge-fg: var(--status-info-fg);    --badge-wash: var(--wash-info); }
.badge.is-neutral { --badge-fg: var(--status-neutral-fg); --badge-wash: var(--wash-neutral); }

/* ---- VARIANTS ------------------------------------------------------------- */

/* SOLID — Q3 fill: the darkened per-theme -fg token as FILL + --ink-onfill text
   (both flip per theme). onfill-on-fill computed AA on both grounds (light min
   5.21:1 success · dark min 5.81:1 accent-not-used-here / 6.30:1 danger). The
   solid dot inverts to --ink-onfill so it stays visible on the fill. */
.badge--solid {
  background: var(--badge-fg);
  color: var(--ink-onfill);
  border-color: transparent;
}
.badge--solid .badge__dot { background: var(--ink-onfill); }

/* OUTLINE — transparent ground, -fg text, -fg hairline. Text = the same AA -fg
   token on the PAGE ground (computed 5.21-6.15:1 light · 6.12-10.14:1 dark).
   The border is a colored 1px hairline made from the -fg token, well over 3:1. */
.badge--outline {
  background: transparent;
  color: var(--badge-fg);
  border-color: color-mix(in srgb, var(--badge-fg) 55%, transparent);
}

/* ---- SIZE — sm drops to the HUD-floor type + tighter pad ------------------- */
.badge--sm {
  --badge-pad-y: var(--sp-0);
  --badge-pad-x: var(--sp-2);
  font-size: var(--fs-2xs);
  gap: var(--sp-1);
}

/* -----------------------------------------------------------------------------
   2 · COUNT PILL + NOTIFICATION DOT
   --------------------------------------------------------------------------- */

/* COUNT PILL — a numeric badge. Default = accent solid (--accent + --ink-onfill,
   the seed's verified accent pairing 6.15:1 light / 5.81:1 dark). Status-colored
   counts reuse the .is-* slots via the Q3 solid pairing. Tabular figures keep
   multi-digit counts from jittering. Min-inline-size makes a 1-digit count round. */
.badge-count {
  --count-fill: var(--accent);
  --count-ink: var(--ink-onfill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 1.4em;
  padding: 0 var(--sp-2);
  border-radius: var(--r-pill);
  background: var(--count-fill);
  color: var(--count-ink);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
.badge-count.is-success { --count-fill: var(--status-success-fg); }
.badge-count.is-warn    { --count-fill: var(--status-warn-fg); }
.badge-count.is-danger  { --count-fill: var(--status-danger-fg); }
.badge-count.is-info    { --count-fill: var(--status-info-fg); }
/* neutral count = --ink-2 fill + --bg text (the M1 neutral-button pairing:
   8.49:1 light / 8.85:1 dark). */
.badge-count.is-neutral { --count-fill: var(--ink-2); --count-ink: var(--bg); }

/* NOTIFICATION DOT — bare status dot, no text. Drawn in the AA -fg token (not the
   raw hue) so it clears the 1.4.11 graphic floor on bg AND surface (min 4.86:1).
   Meaning MUST be carried by an aria-label at the use site (it has no text). */
.badge-dot {
  display: inline-block;
  width: 0.6em;
  height: 0.6em;
  border-radius: 50%;
  background: var(--status-neutral-fg);
  vertical-align: baseline;
  flex: none;
}
.badge-dot.is-success { background: var(--status-success-fg); }
.badge-dot.is-warn    { background: var(--status-warn-fg); }
.badge-dot.is-danger  { background: var(--status-danger-fg); }
.badge-dot.is-info    { background: var(--status-info-fg); }
.badge-dot.is-neutral { background: var(--status-neutral-fg); }

/* COUNT-ON-ANCHOR — position a count/dot on the corner of an icon/avatar/button.
   The anchor is any positioned box carrying .has-badge; the count/dot is its
   last child. Offset is em-relative so it tracks the anchor's type size. */
.has-badge { position: relative; display: inline-flex; }
.has-badge > .badge-count,
.has-badge > .badge-dot {
  position: absolute;
  top: 0;
  inset-inline-end: 0;
  transform: translate(35%, -35%);
}

/* -----------------------------------------------------------------------------
   3 · REMOVABLE CHIP — a neutral cell (filter token / applied facet) with an
   accessible remove button using the M1 #i-x glyph. The chip is a container; the
   remove control is a REAL <button> so it gets platform keyboard + focus. Text is
   --ink on --surface (15.9:1 / 14.3:1). Border = --control-edge (the honest cell
   boundary, ~3.5:1 non-text on surface). chip.js removes the chip on activate.
   Markup:
     <span class="chip">
       <span class="chip__label">Graybar</span>
       <button class="chip__remove" type="button" aria-label="Remove Graybar">
         <svg class="icon" aria-hidden="true"><use href="#i-x"/></svg>
       </button>
     </span>
   --------------------------------------------------------------------------- */
.chip {
  --chip-bg: var(--surface);
  --chip-ink: var(--ink);
  --chip-edge: var(--control-edge);

  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding-block: var(--sp-1);
  padding-inline: var(--sp-3) var(--sp-1);   /* tighter trailing pad for the button */

  background: var(--chip-bg);
  color: var(--chip-ink);
  border: 1px solid var(--chip-edge);
  border-radius: var(--r-pill);

  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  line-height: 1;
  white-space: nowrap;
  max-width: 100%;
}

/* an optional leading dot/icon inside a chip (e.g. a status facet) */
.chip .badge-dot,
.chip .icon { flex: none; }

.chip__label {
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* status-tinted chip (still --ink text on a faint wash — AA 14.96:1 / 13.40:1) */
.chip.is-success { --chip-bg: var(--wash-success); }
.chip.is-warn    { --chip-bg: var(--wash-warn); }
.chip.is-danger  { --chip-bg: var(--wash-danger); }
.chip.is-info    { --chip-bg: var(--wash-info); }
.chip.is-neutral { --chip-bg: var(--wash-neutral); }

/* THE REMOVE BUTTON — icon-only, so it REQUIRES an aria-label at the use site.
   Round hit target; glyph = --ink-2 (7.9:1 / 8.1:1), darkening toward --ink on
   hover/focus so contrast rises. Focus draws the seed's one ring. */
.chip__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5em;
  height: 1.5em;
  padding: 0;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  flex: none;
}
.chip__remove .icon { font-size: 0.85em; stroke-width: 2.5; }
.chip__remove:hover,
.chip__remove:focus-visible {
  color: var(--ink);
  background: color-mix(in srgb, var(--ink) 12%, transparent);
}
.chip__remove:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* DISABLED chip (locked facet) — dim + inert remove. WCAG 1.4.3 exempts disabled
   controls from the contrast floor, so the opacity dim is compliant. */
.chip.is-disabled { opacity: 0.55; }
.chip.is-disabled .chip__remove { pointer-events: none; cursor: not-allowed; }

/* a wrapping row of chips (applied-filters bar) */
.chip-set { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }

/* the leaving state chip.js sets for one frame before removal (opt-in motion) */
.chip.is-leaving { pointer-events: none; }

/* =============================================================================
   FOCUS — the seed's one ring, reasserted on any focusable badge shell (e.g. a
   badge used as a filter <button>). The chip remove button asserts its own above.
   ============================================================================= */
.badge:focus-visible,
.badge-count:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* =============================================================================
   MOTION — authored ONLY inside no-preference (the seed's opt-in law). Static is
   the default; a reduced-motion user gets no transition and no leave animation —
   the chip is simply removed from the DOM by chip.js with no visual tween.
   ============================================================================= */
@media (prefers-reduced-motion: no-preference) {
  .chip,
  .chip__remove,
  .badge {
    transition: background-color var(--motion-base) var(--ease-standard),
                border-color var(--motion-base) var(--ease-standard),
                color var(--motion-base) var(--ease-standard),
                opacity var(--motion-fast) var(--ease-standard),
                transform var(--motion-fast) var(--ease-spring);
  }
  .chip.is-leaving { opacity: 0; transform: scale(var(--press-scale)); }
}
/* =============================================================================
   NAV SYSTEM — nav.css   (component milestone 3)
   Consumes the gated ds-seed-v1.2. The audit flagged NAV DIVERGENCE across
   surfaces (§ synthesis): every product+site rolled its own nav chrome, active
   state, and collapse. This is the ONE authored nav — a top BAR + a vertical
   SIDE variant, authored once, both registers by inheritance, both grounds by
   the seed triad.

   Works in BOTH registers by inheritance: radius from --r-default (squared A /
   rounded B), density padding from the Register-B --pad-control-* tokens (with a
   4px-ramp fallback for Register A), color/ink/edge/focus/motion all read seed
   tokens. Adds NO raw hex, no raw ms, no raw ease. (Border/indicator WIDTHS are
   literal px — the seed tokenizes no line-width scale and writes every hairline
   as a literal `1px`, e.g. preset `border: 1px solid var(--edge)`, button/modal
   `border: 1px solid …`; this file follows that established seed practice.)

   LANDMARK: the block renders on a <nav> element (implicit role="navigation").
   Multiple navs on a page each take an aria-label to disambiguate (see demo).

   AXES
     variant:  .nav--bar   (horizontal top bar, collapses to a menu on narrow)
               .nav--side  (vertical rail / sidebar; supports grouped sections)
     item:     .nav__link  = icon + label cell (an <a>, or a <button> disclosure)
     state:    :hover  :focus-visible  [aria-current] (the CURRENT page)
     collapse: .nav__toggle (M1 icon-button) + [aria-expanded] drives the bar's
               narrow-width dropdown (CSS :has() reveals it; JS only flips aria).

   SEED GAPS (flagged, NOT hardcoded-around — see the build report):
     1. 🔴 NO responsive-breakpoint / container scale. The collapse point is a
        component-local `48rem`. It CANNOT read a token even if one existed:
        `@media` conditions cannot resolve `var(--*)`. So the literal in the
        media query is unavoidable; the seed should still mint a documented
        breakpoint scale so every component collapses at the SAME widths rather
        than each picking its own (this is the nav-shaped instance of the same
        "no size scale" gap modal raised for dialog widths).
   ============================================================================= */

.nav {
  /* ---- component color slots (a theme/variant reads these; one place decides) */
  --nav-bg: var(--surface);              /* the nav ground (a defined pairing base) */
  --nav-fg: var(--ink);                  /* brand / title ink */
  --nav-item-ink: var(--ink-2);          /* resting item — 7.9:1 light / 8.1 dark on surface */
  --nav-item-ink-hover: var(--ink);      /* hover item  — 14.7 / 12.6 on surface-2 */
  --nav-item-bg-hover: var(--surface-2); /* seed's named "hover" band */
  --nav-active-ink: var(--accent);       /* CURRENT item — 5.2 / 4.9 on wash-accent, 5.7 / 5.3 on surface */
  --nav-active-bg: var(--wash-accent);   /* CURRENT item ground (side + dropdown) */
  --nav-accent: var(--accent);           /* the indicator rail (non-text, 5.7 / 5.3 :1) */
  --nav-edge: var(--edge);               /* decorative hairline (per seed AA notes) */

  /* ---- density: read Register-B control padding; fall back to the 4px ramp */
  --nav-pad-y: var(--pad-control-y, var(--sp-3));
  --nav-pad-x: var(--pad-control-x, var(--sp-4));
  --nav-gap: var(--sp-1);
  --nav-indicator: 2px;                  /* underline (bar) / rail (side) thickness */

  display: block;
  background: var(--nav-bg);
  color: var(--nav-fg);
  font-family: inherit;
  font-size: var(--font-size-body, var(--fs-base));
}

/* =============================================================================
   ITEMS — the shared icon+label cell. An <a> for links; a <button> for a
   disclosure (side groups) or the collapse toggle. Authored once; the variants
   only change axis + indicator edge.
   ============================================================================= */
.nav__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: var(--nav-gap);
}

.nav__link {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--nav-pad-y) var(--nav-pad-x);
  border: 0;
  border-radius: var(--r-default);       /* squared in A, rounded in B */
  background: transparent;
  color: var(--nav-item-ink);
  font: inherit;
  font-weight: var(--fw-medium);
  line-height: 1.2;
  letter-spacing: var(--tracking-tight);
  text-decoration: none;                 /* for <a> */
  white-space: nowrap;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

/* the label truncates gracefully in a fixed-width side rail */
.nav__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* icon rides the item's font-size (em), so it scales with the label + register */
.nav__link .icon { font-size: 1.1em; color: currentColor; }

/* HOVER — seed's named hover band + primary ink. Never applied to the current
   item (which keeps its accent + own ground, so no new pairing is introduced). */
.nav__link:hover {
  color: var(--nav-item-ink-hover);
  background: var(--nav-item-bg-hover);
}

/* CURRENT PAGE — aria-current is the source of truth (any value = current). */
.nav__link[aria-current] {
  color: var(--nav-active-ink);
  font-weight: var(--fw-semibold);
}
.nav__link[aria-current]:hover {
  /* keep the accent + own ground on hover so contrast stays on the verified
     pairing (accent/surface or accent/wash-accent), never accent/surface-2 */
  color: var(--nav-active-ink);
  background: transparent;
}

/* =============================================================================
   FOCUS — reassert the seed's one ring on the component (covers <a> and <button>
   nav cells explicitly). Never removed. Inset offset keeps the ring off adjacent
   cells in a dense rail.
   ============================================================================= */
.nav__link:focus-visible,
.nav__toggle:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: calc(-1 * var(--focus-ring-width));
}

/* =============================================================================
   VARIANT A — TOP BAR (.nav--bar)
   ============================================================================= */
.nav--bar {
  position: relative;                    /* anchor for the narrow-width dropdown */
}
.nav--bar .nav__inner {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: 0 var(--nav-pad-x);
  min-height: var(--sp-9);               /* 64px bar (comfortable tap target row) */
  border-bottom: 1px solid var(--nav-edge);
}
.nav--bar .nav__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-weight: var(--fw-bold);
  font-size: var(--fs-md);
  color: var(--nav-fg);
  text-decoration: none;
  letter-spacing: var(--tracking-tight);
}
.nav--bar .nav__brand .icon { font-size: 1.2em; color: var(--accent); }

/* the menu wrapper holds the list; at wide it's inline and pushed to the right */
.nav--bar .nav__menu {
  display: flex;
  margin-left: auto;
}
.nav--bar .nav__list {
  flex-direction: row;
  align-items: stretch;
}

/* BAR current item = an underline indicator (no wash) — the top-bar idiom.
   inset box-shadow follows the radius + causes no layout shift. */
.nav--bar .nav__link[aria-current] {
  background: transparent;
  box-shadow: inset 0 calc(-1 * var(--nav-indicator)) 0 0 var(--nav-accent);
}

/* the collapse toggle (M1 .btn icon-button) — hidden until the bar collapses */
.nav__toggle {
  display: none;
  margin-left: auto;
}

/* =============================================================================
   VARIANT B — SIDE / VERTICAL RAIL (.nav--side)
   Supports grouped sections (an eyebrow label) and collapsible disclosure groups
   (a <button> that toggles a sub-list; chevron from the M1 sprite).
   ============================================================================= */
.nav--side .nav__list {
  flex-direction: column;
  align-items: stretch;
  gap: var(--nav-gap);
  padding: var(--sp-3);
}
.nav--side .nav__link { width: 100%; }

/* SIDE current item = a left rail + accent wash pill. */
.nav--side .nav__link[aria-current] {
  background: var(--nav-active-bg);
  box-shadow: inset calc(var(--nav-indicator) + 1px) 0 0 0 var(--nav-accent);
}
.nav--side .nav__link[aria-current]:hover {
  background: var(--nav-active-bg);      /* hold the verified accent/wash pairing */
}

/* section eyebrow (group label) — ink-dim on surface = 5.4 / 5.6 :1 (AA small). */
.nav__group-label {
  display: block;
  padding: var(--sp-4) var(--nav-pad-x) var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* collapsible group: a disclosure button + a sub-list (indented). */
.nav__disclosure { justify-content: flex-start; }
.nav__chevron {
  margin-left: auto;
  /* rotation is STATE (not motion) so it renders under reduced-motion too;
     only the TRANSITION is gated below. */
}
.nav__disclosure[aria-expanded="true"] .nav__chevron { transform: rotate(180deg); }

.nav__sublist {
  list-style: none;
  margin: 0;
  padding: 0;
  display: none;                         /* collapsed by default */
}
.nav__disclosure[aria-expanded="true"] + .nav__sublist { display: block; }
.nav__sublist .nav__link {
  padding-left: calc(var(--nav-pad-x) + var(--sp-6));  /* indent under the parent */
  font-weight: var(--fw-regular);
}
.nav__sublist .nav__link[aria-current] { font-weight: var(--fw-medium); }

/* =============================================================================
   RESPONSIVE COLLAPSE — the top BAR folds its links behind the menu toggle at
   narrow widths. The toggle flips [aria-expanded] (nav.js); CSS :has() reveals
   the dropdown. With JS off, a <details>-free fallback: the toggle is a real
   button that still flips aria via the tiny handler; if the handler never loads,
   the links are simply always-visible on wide and reachable via the dropdown
   markup which stays in the DOM.

   🔴 SEED GAP #1: the `48rem` literal below cannot be a token — @media conditions
   cannot read custom properties. Flagged for a seed breakpoint scale so every
   component collapses at shared widths.
   ============================================================================= */
@media (max-width: 48rem) {
  .nav--bar .nav__toggle { display: inline-flex; }

  .nav--bar .nav__menu {
    display: none;
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    flex-direction: column;
    background: var(--nav-bg);
    border-bottom: 1px solid var(--nav-edge);
    box-shadow: var(--elevation, var(--shadow-md));
    padding: var(--sp-2);
    z-index: 20;
  }
  /* :has() reveals the dropdown when the toggle is expanded (JS flips aria only) */
  .nav--bar:has(.nav__toggle[aria-expanded="true"]) .nav__menu { display: flex; }

  .nav--bar .nav__list { flex-direction: column; align-items: stretch; }
  .nav--bar .nav__link { width: 100%; }

  /* in the dropdown, the current item uses the side-style rail+wash (an underline
     reads wrong in a vertical list) — reuses the already-verified accent/wash. */
  .nav--bar .nav__link[aria-current] {
    background: var(--nav-active-bg);
    box-shadow: inset calc(var(--nav-indicator) + 1px) 0 0 0 var(--nav-accent);
  }
}

/* =============================================================================
   MOTION — authored ONLY inside prefers-reduced-motion:no-preference (the seed's
   opt-in law). Static is the default: a reduced-motion user gets instant color /
   background / indicator changes and an instantly-rotated chevron (state, not
   animation). No transform-on-hover, nothing that moves layout.
   ============================================================================= */
@media (prefers-reduced-motion: no-preference) {
  .nav__link {
    transition: background-color var(--motion-fast) var(--ease-standard),
                color var(--motion-fast) var(--ease-standard),
                box-shadow var(--motion-base) var(--ease-standard);
  }
  .nav__chevron {
    transition: transform var(--motion-base) var(--ease-standard);
  }
}
/* =============================================================================
   AlexVillarroel.com — PAGE LAYER  (page.css)
   The ONLY authored layer on top of the vendored Estate Design System. It adds
   NO palette and NO raw color/radius/spacing/type value the seed already
   tokenizes: every color is var(--ink/--bg/--accent/--surface/--edge/...),
   every space is var(--sp-*), every size var(--fs-*)/var(--size-*)/var(--measure).
   Its whole job is LAYOUT (the reading spine, exhibits, rows, the form sheet)
   plus the one sanctioned consumer override: pointing the family tokens at the
   site's EXISTING self-hosted fonts (Inter / Fraunces) since the DS ships no
   woff2 binaries. Register A, light-first, full theme-aware triad (inherited).
   Load order (PATTERN.md §2): tokens -> preset-sites -> components -> THIS FILE.
   ============================================================================= */

/* -----------------------------------------------------------------------------
   0 · SELF-HOSTED FONTS — keep the repo's existing Inter + Fraunces (the DS
   "Estate Sans/Serif" ship no binaries). Same files the site already preloads.
   Paths are relative to this stylesheet at assets/css/ -> ../fonts/.
   --------------------------------------------------------------------------- */
@font-face {
  font-family: "Fraunces";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/fraunces-latin-var.woff2") format("woff2");
}
@font-face {
  font-family: "Fraunces";
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/fraunces-italic-latin-var.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("../fonts/inter-latin-var.woff2") format("woff2");
}

/* Point the DS family tokens at the self-hosted faces. This is the documented
   consumer override (the seed's "Estate Sans/Serif" fall back to a system stack
   when no binary is present; here we redirect to the shipped Inter/Fraunces).
   Scoped to the register root so it rides the same cascade as the preset. */
.register-sites {
  --font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica,
    Arial, sans-serif;
  --font-serif: "Fraunces", Georgia, "Times New Roman", serif;
  --font-display: var(--font-serif); /* Register-A accent already sets this; kept explicit */
}

/* -----------------------------------------------------------------------------
   0b · ESTATE WARM PALETTE - the second sanctioned consumer override. The DS
   default palette is cool (white ground + blue accent). The estate identity
   (Shop Marshal + Streamline) is cream/ink with a warm industrial-clay accent,
   so we RE-POINT the seed's palette tokens (never hardcode a color into a
   component rule) scoped to the register root. Mirrors the DS's own three-state
   theme triad: light on the bare register root, then only the changed tokens
   redefined under the two dark selectors. preset-sites.css §58 blesses exactly
   this ".register-sites { --accent: ... }" brand override.
   AA notes: --accent #B4471F on --bg #FDF8F4 = 5.15:1 (AA normal text + links);
   --ink on cream is ~16:1; dark --accent #D2743C on #0e1117 = 5.72:1.
   --------------------------------------------------------------------------- */
.register-sites {
  /* surfaces */
  --bg: #FDF8F4;          /* estate cream, page ground */
  --surface: #F3ECE1;     /* cream-deep, raised panel / card / alt band */
  --surface-2: #ECE3D6;   /* deeper panel / header band / hover */
  --edge: #E4DACB;        /* hairline separator */
  --edge-strong: #C7B99E; /* heavier divider */
  /* ink */
  --ink: #1E1F20;         /* primary text */
  --ink-2: #55504A;       /* secondary text */
  --ink-dim: #6D6556;     /* muted / tertiary */
  /* accent: warm industrial clay, used sparingly */
  --accent: #B4471F;
  --accent-hover: #983917;
}

/* dark state 2: system-dark, unless an explicit light theme is set. Keep the DS
   dark grounds; warm only the accent so it reads on dark. */
@media (prefers-color-scheme: dark) {
  .register-sites:not([data-theme="light"]) {
    --accent: #D2743C;
    --accent-hover: #E08951;
  }
}
/* dark state 3: explicit [data-theme="dark"] toggle (wins both ways). */
.register-sites[data-theme="dark"] {
  --accent: #D2743C;
  --accent-hover: #E08951;
}

/* =============================================================================
   1 · BASE / RESET (minimal — tokens.css already paints body bg + ink + sans)
   ============================================================================= */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }

body { font-size: var(--font-size-body, var(--fs-md)); line-height: var(--lh-loose); }

img, svg, picture { max-width: 100%; height: auto; display: block; }

h1, h2, h3, h4 {
  font-family: var(--font-serif);
  color: var(--ink);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
  margin: 0;
  font-weight: var(--fw-semibold);
}
p { margin: 0; }

a { color: var(--accent); text-underline-offset: 3px; }
a:hover { color: var(--accent-hover); }

/* the seed already defines :focus-visible on a/button/[tabindex]; reassert for
   any element and keep the ring visible */
:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* --- skip link ------------------------------------------------------------- */
.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: calc(-1 * var(--sp-9));
  z-index: 100;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--edge-strong);
  border-radius: var(--r-default);
  padding: var(--sp-3) var(--sp-5);
  text-decoration: none;
}
.skip-link:focus { top: var(--sp-4); }

.icon-sprite { display: none; }
.u-serif { font-family: var(--font-serif); }
.u-clay { color: var(--accent); } /* THE single load-bearing accent word */

/* =============================================================================
   2 · LAYOUT — the reading spine

   SEED GAP (flagged, not worked around in a token): the seed's width ramp
   (--size-xs 20rem .. --size-xl, + --measure 65ch) sizes CONTENT boxes and prose
   measure, but the estate has no token for the small STRUCTURAL track widths an
   editorial spine needs. These three literals are the only rem widths this file
   introduces, each a layout track (not a color/space/type value the seed owns):
     - the left rail column .......... 12rem   (narrower than --size-xs 20rem)
     - the Background portrait column . 16rem
     - the evidence field-label column  8.5rem
   Recommend the seed mint a small "structural track" scale (e.g. --track-rail /
   --track-aside) so these stop being page-local literals. Left as literals here,
   flagged for the DS owner, rather than mis-using a content-width token.
   ============================================================================= */
.wrap {
  width: 100%;
  max-width: var(--size-xl);           /* 1024px content frame */
  margin-inline: auto;
  padding-inline: var(--sp-6);
}

.section { padding-block: clamp(var(--sp-8), 10vh, var(--sp-9)); }
.section + .section .wrap { border-top: 1px solid var(--edge); padding-top: clamp(var(--sp-7), 6vh, var(--sp-8)); }

/* the spine: a persistent left rail (section number + eyebrow) beside a capped
   reading column. On narrow it collapses to an inline eyebrow above the column. */
.spine { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-5); }
.spine__rail { display: flex; flex-direction: column; gap: var(--sp-2); }
.spine__num {
  font-family: var(--font-sans);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.spine__col { min-width: 0; max-width: var(--measure); }
.spine__col > * + * { margin-top: var(--sp-5); }

/* wide variant — proof / ecosystem / gallery break out past the prose measure */
.spine--wide .spine__col { max-width: none; }

@media (min-width: 60rem) {
  .spine {
    grid-template-columns: 12rem minmax(0, var(--measure));
    gap: var(--sp-8);
    align-items: start;
  }
  .spine--wide { grid-template-columns: 12rem minmax(0, 1fr); }
  .spine__rail { position: sticky; top: calc(var(--sp-9) + var(--sp-4)); }
}

/* section headline + lead rhythm */
.headline { font-size: var(--fs-xl); }
.lead {
  font-family: var(--font-serif);
  font-size: var(--fs-lg);
  line-height: var(--lh-base);
  color: var(--ink-2);
}
.prose { color: var(--ink-2); font-size: var(--font-size-body); line-height: var(--lh-loose); }
.prose + .prose { margin-top: var(--sp-5); }

/* the punctuation line — an isolated serif pull-quote closing a section */
.punch {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--fs-xl);
  line-height: var(--lh-tight);
  color: var(--ink);
  margin: 0;
  padding-top: var(--sp-5);
}
.punch__src {
  display: block;
  margin-top: var(--sp-3);
  font-family: var(--font-sans);
  font-style: normal;
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* a quiet cluster of text-link actions (not buttons) */
.actions { display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-6); align-items: baseline; }
.actionlink {
  font-family: var(--font-sans);
  font-weight: var(--fw-semibold);
  font-size: var(--font-size-body);
  color: var(--accent);
  text-decoration: none;
  border-bottom: 2px solid color-mix(in srgb, var(--accent) 42%, transparent);
  padding-bottom: 1px;
}
.actionlink:hover { border-bottom-color: var(--accent); }

/* =============================================================================
   3 · HEADER / NAV — DS nav tokens, no collapse toggle (links wrap on narrow),
   so it is fully functional with zero JS.
   ============================================================================= */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg);
  border-bottom: 1px solid var(--edge);
}
.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  min-height: var(--sp-9);
  flex-wrap: wrap;
}
.brandmark {
  font-family: var(--font-serif);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-lg);
  color: var(--ink);
  text-decoration: none;
  letter-spacing: var(--tracking-tight);
}
.site-header .nav { background: transparent; }
.site-header .nav__list { flex-wrap: wrap; justify-content: flex-end; }

/* =============================================================================
   4 · HERO (section 01)
   ============================================================================= */
.hero__eyebrow { margin-bottom: var(--sp-4); }
.hero__thesis {
  font-size: var(--fs-2xl);
  line-height: var(--lh-tight);
  max-width: 20ch;
}
.hero__lead { max-width: var(--measure); }
.identity-line {
  font-family: var(--font-sans);
  font-weight: var(--fw-semibold);
  color: var(--ink);
  font-size: var(--font-size-body);
}

/* proof strip — meta type, hairline-ruled, NO logos */
.proof-strip {
  list-style: none;
  margin: 0;
  padding: var(--sp-5) 0 0;
  border-top: 1px solid var(--edge);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
}
.proof-strip li { font-size: var(--fs-sm); color: var(--ink-dim); }

/* =============================================================================
   5 · THREE FORMS (section 02) — three stacked movements, not a card grid
   ============================================================================= */
.forms-list { display: flex; flex-direction: column; gap: var(--sp-6); }
.form-move { border-top: 1px solid var(--edge); padding-top: var(--sp-5); }
.form-move__title { font-size: var(--fs-lg); margin-bottom: var(--sp-2); }

/* field->digital equivalence exhibit (section 03) */
.equiv {
  list-style: none;
  margin: var(--sp-5) 0 0;
  padding: var(--sp-5);
  border: 1px solid var(--edge);
  border-radius: var(--r-default);
  background: var(--surface);
  display: grid;
  gap: var(--sp-2);
}
.equiv li {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: var(--sp-3);
  align-items: baseline;
  font-size: var(--fs-sm);
}
.equiv__field { color: var(--ink); font-weight: var(--fw-medium); }
.equiv__arrow { color: var(--ink-dim); }
.equiv__digital { color: var(--ink-2); text-align: right; }

/* =============================================================================
   6 · SELECTED PROOF (section 04) — evidence EXHIBITS, six-field anatomy
   ============================================================================= */
.exhibits { display: flex; flex-direction: column; gap: var(--sp-6); }

.evidence { gap: var(--sp-5); }         /* .card supplies box/hairline/pad */
.evidence__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
}
.evidence__title { font-size: var(--fs-lg); }
.evidence__kicker { margin-top: var(--sp-1); color: var(--ink-2); font-size: var(--font-size-body); }

.ev-fields { margin: 0; display: grid; gap: var(--sp-3) var(--sp-5); }
.ev-fields > div { display: grid; gap: var(--sp-1); }
.ev-fields dt {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--ink-dim);
}
.ev-fields dd { margin: 0; color: var(--ink-2); font-size: var(--font-size-body); line-height: var(--lh-base); }

@media (min-width: 40rem) {
  .ev-fields > div { grid-template-columns: 8.5rem 1fr; align-items: baseline; }
  .ev-fields dt { padding-top: 0.15em; }
}

/* honesty-grammar "illustrative" tag reuses the seed prov-label (assumption =
   hollow ring): a value we have NOT confirmed never renders as a hard fact. */
.illustrative { font-size: var(--fs-xs); }

/* =============================================================================
   7 · NOW (section 05)
   ============================================================================= */
.now-meta { color: var(--ink-dim); font-size: var(--fs-sm); }

/* =============================================================================
   8 · FIELD NOTES (section 06) + the notes index — a RULED LIST, newest first
   ============================================================================= */
.notes-list { list-style: none; margin: var(--sp-5) 0 0; padding: 0; }
.note-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
  border-top: 1px solid var(--edge);
  padding-block: var(--sp-5);
}
.note-row:last-child { border-bottom: 1px solid var(--edge); }
.note-row__title { font-family: var(--font-serif); font-size: var(--fs-lg); color: var(--ink); text-decoration: none; }
.note-row__title:hover { color: var(--accent); }
.note-row__meta {
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--ink-dim);
  white-space: nowrap;
}
.reassure { color: var(--ink-dim); font-size: var(--fs-sm); }

/* -----------------------------------------------------------------------------
   8b · SYSTEM PACKS (systems page) — the FEATURED give-away card. The packs are
   the page's conversion funnel, so they lead the library and read as featured
   next to the plain tool cards below them. The featured signal is TWO restrained
   seed-token touches, no new palette: a warm-clay top edge + a faint accent wash
   ground. (The DS .card--accent-top modifier cannot be used here: the register
   preset stub `.register-sites .card` sets the full border at 0,2,0 specificity
   and defeats it, so the edge is authored at register specificity to win.) The
   DOWNLOAD action itself is the DS .btn--solid (warm-clay fill) so it is the one
   solid button on the page and the clear primary action.
   --------------------------------------------------------------------------- */
.register-sites .pack {
  border-top: 3px solid var(--accent);
  background: var(--wash-accent);
}

/* =============================================================================
   9 · COMPANY ECOSYSTEM (section 07) — ruled entries, not equal-weight cards
   ============================================================================= */
.eco { margin-top: var(--sp-5); }
.eco-row { border-top: 1px solid var(--edge); padding-block: var(--sp-6); }
.eco-row:last-child { border-bottom: 1px solid var(--edge); }
.eco-row__head { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-3); }
.eco-row__name { font-size: var(--fs-lg); }
.eco-row__desc { color: var(--ink-2); font-size: var(--font-size-body); max-width: var(--measure); }
.eco-row__desc + .eco-row__desc { margin-top: var(--sp-3); }
.eco-row__route { margin-top: var(--sp-4); display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-6); }

/* =============================================================================
   10 · BACKGROUND (section 08) — portrait at human scale, hairline-framed
   ============================================================================= */
.bg-grid { display: grid; grid-template-columns: 1fr; gap: var(--sp-6); align-items: start; }
@media (min-width: 48rem) { .bg-grid { grid-template-columns: 1fr minmax(0, 16rem); } }
.portrait {
  border: 1px solid var(--edge);
  border-radius: var(--r-default);
  padding: var(--sp-2);
  background: var(--surface);
}
.portrait img { border-radius: var(--r-1); aspect-ratio: 4 / 5; object-fit: cover; width: 100%; }
.portrait figcaption { margin-top: var(--sp-2); color: var(--ink-dim); font-size: var(--fs-xs); }

/* =============================================================================
   11 · LIFE OUTSIDE WORK (section 09) — restrained, at most two photos
   ============================================================================= */
.life-photos { list-style: none; margin: var(--sp-5) 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-4); }
.life-photos li { flex: 0 1 16rem; }
.life-photos img {
  border: 1px solid var(--edge);
  border-radius: var(--r-default);
  aspect-ratio: 1 / 1;
  object-fit: cover;
  width: 100%;
}

/* =============================================================================
   12 · CONTACT (section 10) — the routed intake sheet, DS .field controls
   ============================================================================= */
.contact-routes { margin-bottom: var(--sp-6); }
.contact-form { max-width: var(--size-md); display: flex; flex-direction: column; gap: var(--sp-5); margin-top: var(--sp-5); }
.contact-form .field--emphasis .textarea { min-height: calc(var(--fs-md) * 6); }
.form-footnote { font-size: var(--fs-sm); color: var(--ink-dim); }
.field__optional { font-weight: var(--fw-regular); color: var(--ink-dim); font-size: var(--fs-xs); }
.mailto-fallback { font-size: var(--fs-sm); color: var(--ink-dim); margin-top: var(--sp-3); }
.mailto-fallback a { font-weight: var(--fw-semibold); }
.turnstile-slot {
  border: 1px dashed var(--edge-strong);
  border-radius: var(--r-default);
  padding: var(--sp-3) var(--sp-4);
  color: var(--ink-dim);
  font-size: var(--fs-xs);
  background: var(--surface);
}

/* =============================================================================
   13 · FOOTER
   ============================================================================= */
.site-footer {
  border-top: 1px solid var(--edge);
  padding-block: var(--sp-8);
  text-align: center;
}
.social-row { display: flex; flex-wrap: wrap; gap: var(--sp-3); justify-content: center; margin-bottom: var(--sp-5); }
.social-link {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--edge);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink);
  text-decoration: none;
  font-size: var(--fs-sm);
}
.social-link:hover { border-color: var(--edge-strong); color: var(--accent); }
.social-icon { width: 1.1em; height: 1.1em; fill: currentColor; flex: none; }
.footer-meta { color: var(--ink-dim); font-size: var(--fs-sm); }
.footer-sign {
  font-family: var(--font-serif);
  font-style: italic;
  color: var(--ink-2);
  margin-top: var(--sp-2);
}

/* =============================================================================
   14 · MOTION — one effect, opt-in only (the seed's law). A first-paint rise on
   sections; a reduced-motion reader gets everything visible, no movement.
   ============================================================================= */
@media (prefers-reduced-motion: no-preference) {
  @keyframes av-rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
  .section { animation: av-rise var(--motion-slow) var(--ease-standard) both; }
}

/* =============================================================================
   15 · ARTICLE (field notes) reading layout
   ============================================================================= */
.article { max-width: var(--measure); margin-inline: auto; }
.article > * + * { margin-top: var(--sp-5); }
.article h2 { font-size: var(--fs-xl); margin-top: var(--sp-7); }
.article h3 { font-size: var(--fs-lg); margin-top: var(--sp-6); }
.article p { color: var(--ink-2); font-size: var(--font-size-body); line-height: var(--lh-loose); }
.article__title { font-size: var(--fs-2xl); line-height: var(--lh-tight); }
.article__meta { color: var(--ink-dim); font-size: var(--fs-sm); font-family: var(--font-sans); }
.article__lead { font-family: var(--font-serif); font-size: var(--fs-lg); color: var(--ink-2); }
.article blockquote {
  margin: 0;
  padding-left: var(--sp-5);
  border-left: 3px solid var(--accent);
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--fs-lg);
  color: var(--ink);
}
