/* ── tokens/fonts.css ─────────────────────────────────────────── */
/* Fonts — IBM Plex superfamily, served from Google Fonts.
   SUBSTITUTION NOTE: no font binaries were supplied with the source material.
   Plex was chosen for its industrial/technical provenance and true humanist
   sans. Swap the @import for self-hosted @font-face rules when licensed files
   land; the --font-* tokens below are the only thing consumers reference. */
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,600;1,400&family=IBM+Plex+Sans+Condensed:wght@400;500;600;700&family=IBM+Plex+Sans:ital,wght@0,400;0,450;0,500;0,600;0,700;1,400&display=swap");

:root {
  --font-sans: "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-condensed: "IBM Plex Sans Condensed", "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

/* ── tokens/colors.css ─────────────────────────────────────────── */
/* Base palette — TUNED JEWEL.
 *
 * One palette, two modes. Nine tokens cover both: the chromatic trio is
 * mode-invariant as fills, the neutrals rotate one position, and dark mode
 * alone borrows lightened text variants of the accents.
 *
 * Modes are set on a container:
 *     <body>                     light (default)
 *     <body data-theme="dark">   dark
 *
 * Plus VERDICT (seven states, owned by the verifier) and JAB (the symbol's own
 * module colours — data, not brand).
 *
 * COUNTING, because two numbers are both right and get confused. The verifier
 * owns SEVEN verdicts. The palette carries EIGHT NAMED STATES across SIX INKS,
 * the eighth being PROVISIONING — a non-verdict indication, not a member of
 * the enum. Two of the eight are deliberate borrows: UNPROVEN takes STALE's
 * amber, PROVISIONING takes UNSIGNED's achromatic, and both stay
 * distinguishable by word and glyph. The inks are sapphire, amber, rust,
 * bold grey, wine and achromatic.
 *
 * A ninth indication, the FAILED READ, has no ink of its own: it renders in
 * the achromatic tier and is a progress meter rather than a state with a
 * colour. It is named in the reader's HUD and nowhere in this file, on
 * purpose. (Reconciles DESIGN-BRIEF-STUDIO-2026-09-21 §5, which reports the
 * token file as documenting six and out of date — it documented seven, and
 * now documents the eighth.)
 */
:root {
  /* ── NEUTRALS ───────────────────────────────────────────────────────────
     Four values that rotate roles between modes. White and Black trade places
     as canvas and ink; Silver and Bold Grey exchange text and border duty. */
  --black:  #141414;  /* Sport Black */
  --grey:   #66626C;  /* Bold Grey */
  --silver: #C1BDC6;  /* Silver */
  /* Two tuned steps between Bold Grey and Silver, so the faint text tier is a
     TEXT value and not a border value reused. Silver is a border at 1.85:1 on
     white; anything carrying words needs its own step. */
  --grey-soft: #78737F;  /* faint text, light mode — 4.62:1 on white */
  --grey-lift: #918C98;  /* faint text, dark and band — 5.59:1 / 4.74:1 */
  --white:  #FFFFFF;  /* Pure White */

  /* Surface tints named by the scheme. */
  --surface-tint-light: #F6F5F8;  /* Pure White nudged one step toward Silver */
  --surface-tint-dark:  #1E1B22;
  --band-dark:          #262230;  /* the dark-mode field, lifted off canvas */
  --sunken-dark:        #17151A;
  --inset-dark:         #2A262F;

  /* ── CHROMATICS ─────────────────────────────────────────────────────────
     Mode-invariant as fills and as light-mode text. All three were cut at a
     depth that makes them legible on white without adjustment. */
  --violet:   #502785;  /* primary action — never a verdict */
  --wine:     #8C1D3F;
  --sapphire: #24549C;

  /* Two additions. The scheme names two verdict roles; the verifier enum has
     six, and violet is brand. These are cut at the violet's depth so they
     inherit the same property: born legible on white, lifted for dark text. */
  --amber: #8A5A12;
  --rust:  #A33116;

  /* Lifted variants. Saturated darks that pass as fills fail as 12px text on
     black — same hue, adjusted lightness. Dark mode only. */
  --violet-lift:   #8968B3;
  --wine-lift:     #C94F72;
  --sapphire-lift: #5B85C9;
  --amber-lift:    #C9922F;
  --rust-lift:     #D4694A;

  /* Violet ramp, for hover and soft fills. */
  --violet-deep:  #3C1D64;
  --violet-hover: #613098;
  --violet-soft:  #EFEAF5;
  --violet-soft-dark: #2A1C3D;

  /* ── VERDICT ────────────────────────────────────────────────────────────
     Seven states, mirroring VerdictStatus (jabauth-verify-android:
     verify/TrustVerdict.kt). This enum is EXHAUSTIVE and owned by the
     verifier, not by design: it grows or shrinks only when the verifier's
     own enum does. Design does not get a vote on the membership, and must
     not be the reason one is missing. (This used to read "do not add a
     seventh"; the verifier added BOUND_UNPROVEN when it learned to check
     holder binding, and the rule is restated as what it always meant.)

       TRUSTED    sapphire  signature valid, issuer anchored, not revoked
       STALE      amber     valid, but the trust-list is past its window
       UNTRUSTED  rust      fail-closed default — absence of positive proof
       REVOKED    grey      on the revocation list. Withdrawn, not forged
       TAMPERED   wine      CRC framing broke — the bytes were altered
       UNSIGNED   neutral   sealed layers, no authorship claimed
       UNPROVEN   amber     issuer signature good; holder binding claimed, not proven

     Colour is NEVER the sole signal — every verdict carries its word and its
     glyph. Values here are the LIGHT mode set; semantic.css re-points them
     under [data-theme="dark"]. */
  --trusted-ink:     var(--sapphire);
  --trusted-deep:    #17386B;
  --trusted-surface: #E7EDF6;
  --trusted-border:  #A8BEDD;

  --stale-ink:     var(--amber);
  --stale-deep:    #5E3C0B;
  --stale-surface: #F7EFDF;
  --stale-border:  #DCC08A;

  --untrusted-ink:     var(--rust);
  --untrusted-deep:    #6E2010;
  --untrusted-surface: #F8E3DD;
  --untrusted-border:  #DFAC9B;

  --revoked-ink:     var(--grey);
  --revoked-deep:    #3B3841;
  --revoked-surface: #EEEDF0;
  --revoked-border:  var(--silver);

  --tampered-ink:     var(--wine);
  --tampered-deep:    #5C1229;
  --tampered-surface: #F6E1E7;
  --tampered-border:  #D9A3B4;

  /* UNSIGNED is deliberately ACHROMATIC. The artifact carries no authenticity
     signal, so it is given no colour signal either. Green would imply a pass;
     red would accuse a working artifact of being a forgery. Nothing was
     claimed, so nothing is asserted. Reasoning preserved from Theme.kt. */
  --unsigned-ink:     var(--grey);
  --unsigned-deep:    var(--black);
  --unsigned-surface: var(--surface-tint-light);
  --unsigned-border:  var(--silver);

  /* UNPROVEN shares AMBER with STALE, and for the same reason: both mean
     "a check could not be completed here", not "this is a forgery". The
     issuer signature really is good; what is missing is proof of whose
     hands the mark is in. Never a pass. Aliases, so the dark re-point of
     --stale-* carries it. Reasoning preserved from Theme.kt. */
  --unproven-ink:     var(--stale-ink);
  --unproven-deep:    var(--stale-deep);
  --unproven-surface: var(--stale-surface);
  --unproven-border:  var(--stale-border);

  /* PROVISIONING is the eighth named state and the second deliberate borrow.
     It is NOT a verdict — the verifier never returns it, and it is absent from
     VerdictStatus. It is a non-verdict HUD indication
     (HudIndication.Provisioning(installable), ui/HudIndicator.kt): an issuer's
     own credential being handed over, read by the verify camera.

     It borrows UNSIGNED's achromatic ink, and the reason is the same reason
     UNSIGNED has no colour. A provisioning symbol is not making an
     authenticity claim, so it must not be given a signal that reads as one —
     and critically, VerifyViewModel routes it deliberately AROUND the verdict
     path so that an issuer's key delivery can never be accused of being
     tampered with. Colouring it would undo that in the one place the user
     looks. Word and glyph keep it apart from UNSIGNED; the ink does not.

     Aliases, so the dark and band re-points carry it. */
  --provisioning-ink:     var(--unsigned-ink);
  --provisioning-deep:    var(--unsigned-deep);
  --provisioning-surface: var(--unsigned-surface);
  --provisioning-border:  var(--unsigned-border);

  /* ── JAB MODULE PALETTE ─────────────────────────────────────────────────
     The symbol's own colours. These are DATA, not brand. Use them for the
     symbol and the Nc ladder. Never for UI chrome, buttons, charts,
     illustration or texture. */
  --jab-k: #000000;
  --jab-w: #FFFFFF;
  --jab-r: #FF0000;
  --jab-g: #00FF00;
  --jab-b: #0000FF;
  --jab-c: #00FFFF;
  --jab-m: #FF00FF;
  --jab-y: #FFFF00;
}

/* ── tokens/typography.css ─────────────────────────────────────────── */
/* Type scale. Humanist sans for prose and UI, condensed for labels and
   packaging-panel density, mono for every machine-legible value: serials,
   hashes, GTINs, Nc counts, byte capacities, timestamps. */
:root {
  --text-2xs: 10px;
  --text-xs:  11px;
  --text-sm:  13px;
  --text-md:  15px;
  --text-lg:  17px;
  --text-xl:  21px;
  --text-2xl: 27px;
  --text-3xl: 34px;
  --text-4xl: 44px;
  --text-5xl: 58px;
  --text-6xl: 76px;

  --leading-flush: 1;
  --leading-tight: 1.14;
  --leading-snug:  1.3;
  --leading-body:  1.55;
  --leading-loose: 1.7;

  --weight-regular:  400;
  --weight-text:     450;
  --weight-medium:   500;
  --weight-semibold: 600;
  --weight-bold:     700;

  /* Tracking. Display tightens; small condensed caps open right up — the
     ingredient-panel move. */
  --track-display: -0.022em;
  --track-tight:   -0.011em;
  --track-normal:  0em;
  --track-label:   0.06em;
  --track-panel:   0.14em;
  --track-stamp:   0.24em;
}

/* ── tokens/roles.css ─────────────────────────────────────────── */
/* ROLE-NAMED TYPE SCALE — the estate's source of truth.
 *
 * Adopted from DESIGN-BRIEF-TYPOGRAPHY-2026-09-21 §3. The size-named scale in
 * typography.css stays for the large marketing sizes the public site needs;
 * every product surface should reach for a ROLE instead, because a role names
 * the job and a size does not. Both codebases generate from this block — CSS
 * custom properties here, a Compose Typography object on the reader, which
 * today references MaterialTheme.typography nowhere and carries ~200 literal
 * sizes across thirteen values (10 · 11 · 11.5 · 12 · 12.5 · 13 · 14 · 15 ·
 * 17 · 18 · 20 · 24 · 26 · 32sp). 11.5, 12.5, 13 and 17 are drift and have no
 * role; they collapse onto their nearest neighbour.
 *
 * FAMILY: IBM Plex, HELD. Brief §8. §7.1 returned a null result on letterform
 * glance-legibility under three-vote adversarial verification, so no candidate
 * can be ranked on measured glance performance and there is no evidence to
 * move on. Re-evaluate only against the incumbent on equal terms.
 *
 * WEIGHT CEILING — a correction to the brief. §2 credits Plex Sans with "a
 * true Black weight". It has none. The family runs Thin 100 through Bold 700
 * and stops; fonts.css loads 400 · 450 · 500 · 600 · 700. So `display` and
 * `verdict` top out at 700, and because §7.2 asks for a LARGER verdict word,
 * SIZE is the only lever left. That is the first reason `verdict` earns its
 * own role rather than borrowing `title`.
 */
:root {
  /* stamp — registration footers, micro-labels, the reader's
     "the camera never left" line. */
  --role-stamp-size:    10px;
  --role-stamp-face:    var(--font-condensed);
  --role-stamp-weight:  var(--weight-semibold);
  --role-stamp-track:   var(--track-stamp);
  --role-stamp-leading: var(--leading-snug);
  --role-stamp-case:    uppercase; /* @kind other */

  /* label — the all-caps label above a fact, table headers, kickers. */
  --role-label-size:    11px;
  --role-label-face:    var(--font-condensed);
  --role-label-weight:  var(--weight-semibold);
  --role-label-track:   var(--track-panel);
  --role-label-leading: var(--leading-snug);
  --role-label-case:    uppercase; /* @kind other */

  /* meta — secondary lines, help text, captions, HUD evidence rows. Sans by
     default; swap the face to --font-mono for machine values, which is not
     optional (brief §4). */
  --role-meta-size:    12px;
  --role-meta-face:    var(--font-sans);
  --role-meta-weight:  var(--weight-regular);
  --role-meta-track:   var(--track-normal);
  --role-meta-leading: var(--leading-body);
  --role-meta-case:    none; /* @kind other */

  /* body — default reading text. */
  --role-body-size:    14px;
  --role-body-face:    var(--font-sans);
  --role-body-weight:  var(--weight-text);
  --role-body-track:   var(--track-normal);
  --role-body-leading: var(--leading-body);
  --role-body-case:    none; /* @kind other */

  /* lede — the intro paragraph under a title. */
  --role-lede-size:    16px;
  --role-lede-face:    var(--font-sans);
  --role-lede-weight:  var(--weight-regular);
  --role-lede-track:   var(--track-tight);
  --role-lede-leading: var(--leading-body);
  --role-lede-case:    none; /* @kind other */

  /* title — card and section titles. NOT the verdict word; see below. */
  --role-title-size:    20px;
  --role-title-face:    var(--font-sans);
  --role-title-weight:  var(--weight-semibold);
  --role-title-track:   var(--track-tight);
  --role-title-leading: var(--leading-snug);
  --role-title-case:    none; /* @kind other */

  /* screen — screen titles. */
  --role-screen-size:    26px;
  --role-screen-face:    var(--font-sans);
  --role-screen-weight:  var(--weight-bold);
  --role-screen-track:   var(--track-display);
  --role-screen-leading: var(--leading-tight);
  --role-screen-case:    none; /* @kind other */

  /* display — the refusal band's DO NOT MINT, large figures, site hero. */
  --role-display-size:    32px;
  --role-display-face:    var(--font-sans);
  --role-display-weight:  var(--weight-bold);
  --role-display-track:   var(--track-display);
  --role-display-leading: var(--leading-flush);
  --role-display-case:    none; /* @kind other */

  /* verdict — A DEDICATED ROLE. The brief's open question, answered yes.
   *
   * Three reasons, in order of weight:
   *
   * 1. It is the only role whose size is under an external instruction. §7.2
   *    requires the verdict word to grow past 20sp, and the weight ceiling is
   *    already reached at 700, so size carries the whole increase alone. A
   *    role that must move on evidence cannot share a row with one that must
   *    not: every console card title would move with it.
   * 2. The reader brief §6 documents three near-identical frames kept apart
   *    ON PURPOSE, so that a change to onboarding cannot quietly restyle a key
   *    ceremony. Coupling the verdict word to `title` is the same mistake one
   *    layer down.
   * 3. It is the only type in the estate with a contrast test attached (§7.2
   *    item 3). A test needs a row of its own to assert against.
   *
   * 28px: the smallest step that is unambiguously larger than `title` at a
   * glance, with the +0.04em tracking the reader already applies at 20sp
   * (1sp) carried over. Heavy caps at speed need the tracking; tightening it
   * is what makes a heavy word read as a block instead of letters.
   *
   * UNTESTED, DELIBERATELY. §2 says the verdict word is the one case to prove
   * on a physical device outdoors rather than argue. 28px is the proposal to
   * take to that test, not a settled value. */
  --role-verdict-size:    28px;
  --role-verdict-face:    var(--font-sans);
  --role-verdict-weight:  var(--weight-bold);
  --role-verdict-track:   0.04em;
  --role-verdict-leading: var(--leading-flush);
  --role-verdict-case:    uppercase; /* @kind other */

  /* TABULAR FIGURES. Brief §7.9: zero of seventeen confirmed claims addressed
     lining or tabular figures for ANY candidate, incumbent included. It is an
     open empirical question, so this is the switch to test with rather than an
     assumption. Apply to counters, meters, tables and evidence rows — anywhere
     a digit changes in place and must not shift its neighbours. */
  --figures-tabular: "tnum" 1, "lnum" 1; /* @kind other */
}

/* ── tokens/spacing.css ─────────────────────────────────────────── */
/* Spacing. The base unit is 4px, but the layout rhythm is governed by the
   QUIET ZONE: the mandatory clear margin around a 2D symbol, four modules
   wide. Every card, band and symbol frame pads to a quiet-zone token, not an
   arbitrary value. */
:root {
  --space-0:  0px;
  --space-1:  4px;
  --space-2:  8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  20px;
  --space-6:  24px;
  --space-8:  32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;
  --space-20: 80px;
  --space-24: 96px;
  --space-32: 128px;

  /* Quiet zone — module-derived padding. A symbol at --module-md needs
     --quiet-md of clear substrate on all four sides. */
  --module-xs: 2px;
  --module-sm: 3px;
  --module-md: 4px;
  --module-lg: 6px;
  --module-xl: 8px;
  --quiet-xs: 8px;
  --quiet-sm: 12px;
  --quiet-md: 16px;
  --quiet-lg: 24px;
  --quiet-xl: 32px;

  --measure-prose: 62ch;
  --measure-lede:  46ch;
  --page-max:      1200px;
  --gutter:        var(--space-8);
}

/* ── tokens/surface.css ─────────────────────────────────────────── */
/* Borders, radii, shadows and the two print motifs the brand actually uses:
   REGISTRATION MARKS and OVERPRINT. */
:root {
  /* Radii. Print does not have rounded corners. Almost everything is square;
     radius is reserved for chips and controls a finger touches. */
  --radius-none: 0px;
  --radius-xs:   2px;
  --radius-sm:   3px;
  --radius-md:   4px;
  --radius-pill: 999px;

  /* Borders. Hairlines are the primary separator; the system leans on rules
     rather than shadows. */
  --border-hair:   1px;
  --border-rule:   1.5px;
  --border-heavy:  2px;
  --border-frame:  3px;

  --line-hair:  var(--border-hair) solid var(--border-subtle);
  --line-rule:  var(--border-rule) solid var(--border-default);
  --line-ink:   var(--border-heavy) solid var(--text-primary);
  --line-dash:  var(--border-hair) dashed var(--border-default);

  /* Shadow. Ink on paper does not float. Elevation is expressed as a hard
     offset block — an impression, not a blur — with one soft shadow reserved
     for genuinely floating layers (dialogs, menus). */
  --shadow-none:  none;
  --shadow-press: 2px 2px 0 0 var(--shadow-color);
  --shadow-block: 4px 4px 0 0 var(--shadow-color);
  --shadow-accent: 4px 4px 0 0 var(--surface-accent);
  --shadow-float: 0 12px 32px -8px rgb(20 20 20 / 0.34), 0 2px 6px -2px rgb(20 20 20 / 0.22);

  /* SCRIM — the ground under a modal layer. Mode-invariant on purpose: a scrim
     that lightened in dark mode would stop reading as "the page behind is out
     of reach", which is the only job it has. Was a literal inside Modal.jsx;
     the ceremony overlays needed the same value, so it is a token now. */
  --scrim: rgb(20 20 20 / 0.5);

  /* REGISTRATION MARKS — crosshair targets at card and section corners.
     Drawn as gradients so any element can carry them with no extra DOM. */
  --reg-color: var(--silver);
  --reg-size: 9px;
  --reg-hair: 1px;
  --reg-inset: 6px;

  /* OVERPRINT — accent laid over neutral so overlaps darken like real ink.
     Apply as mix-blend-mode with these paired values. */
  --overprint-mode: multiply; /* @kind other */
  --overprint-accent: rgb(80 39 133 / 0.86);
  --overprint-ink:    rgb(20 20 20 / 0.9);
}

/* ── tokens/motion.css ─────────────────────────────────────────── */
/* Motion. Printing presses do not bounce. Everything is short, linear-ish and
   physical: things register into position, they do not spring. No easing
   curve overshoots. */
:root {
  --dur-instant: 80ms; /* @kind other */
  --dur-fast:    140ms; /* @kind other */
  --dur-base:    200ms; /* @kind other */
  --dur-slow:    320ms; /* @kind other */
  --dur-band:    520ms; /* @kind other */

  --ease-register: cubic-bezier(0.22, 0.61, 0.36, 1); /* @kind other */
  --ease-out:      cubic-bezier(0.16, 0.84, 0.44, 1); /* @kind other */
  --ease-in:       cubic-bezier(0.55, 0.06, 0.68, 0.19); /* @kind other */
  --ease-linear:   linear; /* @kind other */

  --hover-lift: translate(-1px, -1px); /* @kind other */
  --press-sink: translate(1px, 1px); /* @kind other */
}

/* ── tokens/semantic.css ─────────────────────────────────────────── */
/* Semantic aliases. Build against these, not the base palette.
 *
 * Light is the default. Set data-theme="dark" on any container to invert.
 * The roles rotate; the chromatics stay put.
 */
:root {
  /* Text */
  --text-primary:    var(--black);
  --text-secondary:  var(--grey);
  --text-tertiary:   var(--grey);
  --text-faint:      var(--grey-soft);
  --text-inverse:    var(--white);
  --text-accent:     var(--violet);
  --text-link:       var(--violet);
  --text-link-hover: var(--violet-deep);
  --text-code:       var(--black);

  /* Surfaces */
  --surface-page:    var(--white);
  --surface-card:    var(--white);
  --surface-sunken:  var(--surface-tint-light);
  --surface-raised:  var(--white);
  --surface-inset:   var(--surface-tint-light);
  --surface-band:    var(--black);

  /* Accent — violet, mode-invariant. Interactive only, never evaluative. */
  --surface-accent:        var(--violet);
  --surface-accent-soft:   var(--violet-soft);
  --surface-accent-hover:  var(--violet-hover);
  --surface-accent-border: var(--violet-deep);
  --on-accent:             var(--white);

  /* Shadow colour. Every hard offset composes from this so a theme can
     re-point it — a black impression is invisible on a black canvas. */
  --shadow-color: var(--black);

  /* Borders */
  --border-subtle:  var(--silver);
  --border-default: var(--silver);
  --border-strong:  var(--grey);
  --border-accent:  var(--violet);
  --border-focus:   var(--violet);

  --reg-color: var(--silver);

  /* Controls */
  --control-height-sm: 28px;
  --control-height-md: 36px;
  --control-height-lg: 44px;
  --control-height-touch: 48px;
  --focus-ring: 0 0 0 2px var(--surface-page), 0 0 0 4px var(--violet);

  /* Verdict aliases — seven states mirroring VerdictStatus, plus
     PROVISIONING, the eighth named state, which is an indication and not a
     verdict. See colors.css for why it is achromatic. */
  --verdict-trusted:   var(--trusted-ink);
  --verdict-stale:     var(--stale-ink);
  --verdict-untrusted: var(--untrusted-ink);
  --verdict-revoked:   var(--revoked-ink);
  --verdict-tampered:  var(--tampered-ink);
  --verdict-unsigned:  var(--unsigned-ink);
  --verdict-unproven:  var(--unproven-ink);
  --verdict-provisioning: var(--provisioning-ink);
}

/* ── DARK ────────────────────────────────────────────────────────────────
   Perfect inversion. Canvas and ink trade places, the greys exchange duties,
   the chromatics stay put as fills and lift as text. */
[data-theme="dark"] {
  --text-primary:    var(--white);
  --text-secondary:  var(--silver);
  --text-tertiary:   var(--silver);
  --text-faint:      var(--grey-lift);
  --text-inverse:    var(--black);
  --text-accent:     var(--violet-lift);
  --text-link:       var(--violet-lift);
  --text-link-hover: #A98FCB;
  --text-code:       var(--silver);

  --surface-page:   var(--black);
  --surface-card:   var(--surface-tint-dark);
  --surface-sunken: var(--sunken-dark);
  --surface-raised: var(--surface-tint-dark);
  --surface-inset:  var(--inset-dark);
  /* Lifted off the canvas so trust moments still reverse out of a distinct
     field. Dark mode cannot go darker than the canvas, so the band steps up
     and takes a violet cast — above the card surface, unmistakably a field. */
  --surface-band:   var(--band-dark);

  --surface-accent:        var(--violet);
  --surface-accent-soft:   var(--violet-soft-dark);
  --surface-accent-hover:  var(--violet-hover);
  --surface-accent-border: var(--violet-lift);
  --on-accent:             var(--white);

  /* Lifted, not black — a black impression is invisible on a black canvas. */
  --shadow-color: var(--grey);

  --border-subtle:  #2E2A34;
  --border-default: var(--grey);
  --border-strong:  var(--silver);
  --border-accent:  var(--violet);
  --border-focus:   var(--violet-lift);

  --reg-color: var(--grey);
  --focus-ring: 0 0 0 2px var(--black), 0 0 0 4px var(--violet-lift);

  /* Verdicts — lifted variants carry text; the base hexes hold the borders,
     which is where the true values live in dark mode. */
  --trusted-ink:     var(--sapphire-lift);
  --trusted-deep:    #CFDDF2;
  --trusted-surface: #16233A;
  --trusted-border:  var(--sapphire);

  --stale-ink:     var(--amber-lift);
  --stale-deep:    #F3E3C6;
  --stale-surface: #2E230F;
  --stale-border:  var(--amber);

  --untrusted-ink:     var(--rust-lift);
  --untrusted-deep:    #F5D9D0;
  --untrusted-surface: #331509;
  --untrusted-border:  var(--rust);

  --revoked-ink:     var(--silver);
  --revoked-deep:    #E4E2E7;
  --revoked-surface: #26232B;
  --revoked-border:  var(--grey);

  --tampered-ink:     var(--wine-lift);
  --tampered-deep:    #F5D5DE;
  --tampered-surface: #331020;
  --tampered-border:  var(--wine);

  --unsigned-ink:     var(--silver);
  --unsigned-deep:    var(--white);
  --unsigned-surface: #24212A;
  --unsigned-border:  var(--grey);
}

/* ── VERDICT SCOPE ───────────────────────────────────────────────────────
   `data-verdict="<state>"` on any container exposes --verdict-ink for
   everything inside it, so a surface that changes verdict at runtime keeps
   literal styles and swaps one attribute. Unknown or absent state falls back
   to silver — the achromatic "nothing claimed" ink, never a verdict colour.
   Pair with data-theme="dark" on surfaces that are always dark (the HUD). */
[data-verdict]            { --verdict-ink: var(--silver); }
[data-verdict="trusted"]  { --verdict-ink: var(--verdict-trusted); }
[data-verdict="stale"]    { --verdict-ink: var(--verdict-stale); }
[data-verdict="untrusted"]{ --verdict-ink: var(--verdict-untrusted); }
[data-verdict="revoked"]  { --verdict-ink: var(--verdict-revoked); }
[data-verdict="tampered"] { --verdict-ink: var(--verdict-tampered); }
[data-verdict="unsigned"] { --verdict-ink: var(--verdict-unsigned); }
[data-verdict="unproven"] { --verdict-ink: var(--verdict-unproven); }
[data-verdict="provisioning"] { --verdict-ink: var(--verdict-provisioning); }

/* ── BAND ────────────────────────────────────────────────────────────────
   The inverted field. Trust moments live here. Works in both modes: in light
   it reverses out of the page, in dark it lifts off it. */
[data-band] {
  --surface-page:   var(--surface-band);
  --surface-card:   var(--surface-band);
  --surface-sunken: var(--black);
  --surface-inset:  var(--inset-dark);
  --text-primary:   var(--white);
  --text-secondary: var(--silver);
  --text-tertiary:  var(--silver);
  --text-faint:     var(--grey-lift);
  --text-inverse:   var(--black);
  --text-link:      var(--violet-lift);
  --text-link-hover:#A98FCB;
  --text-accent:    var(--violet-lift);
  --border-subtle:  #3A3640;
  --border-default: var(--grey);
  --border-strong:  var(--silver);
  --reg-color:      var(--grey);
  --shadow-color:   var(--grey);
  --focus-ring: 0 0 0 2px var(--surface-band), 0 0 0 4px var(--violet-lift);

  --trusted-ink:  var(--sapphire-lift);
  --stale-ink:    var(--amber-lift);
  --untrusted-ink:var(--rust-lift);
  --revoked-ink:  var(--silver);
  --tampered-ink: var(--wine-lift);
  --unsigned-ink: var(--silver);
}


/* ── tokens/hud.css ─────────────────────────────────────────── */
/* THE HUD PLATE — the over-camera readout's own surface.
 *
 * From DESIGN-BRIEF-TYPOGRAPHY-2026-09-21 §7.2, which is the highest-yield
 * item in the brief because it is family-independent: it improves the product
 * under any typeface, and §7.1's null result means typeface is the one lever
 * with no evidence behind it.
 *
 * THE PROBLEM. The HUD's regions carry a gradient scrim fading 0.85 → 0.55 →
 * 0.0 alpha. Where it reaches 0 the ink meets whatever the lens is pointed at,
 * so the contrast ratio behind the verdict word is undetermined at runtime —
 * and the two variables that DO have evidence behind them (§7.2) are letter
 * size and ground contrast. The HUD currently leaves both undetermined.
 *
 * THE FIX. A deterministic plate: one opaque surface, sized to the readout
 * block, so the ink always meets a known ground. The gradient stays everywhere
 * else — this is a plate under the verdict block, not a bar across the screen,
 * and the HUD's central discipline of an empty middle is untouched.
 *
 * WHAT IT COSTS. Theme.kt's reasoning for the over-camera palette not flipping
 * with light and dark is that "the surface underneath is not the app's; it is
 * whatever the lens is pointed at." Under the plate, it is the app's again.
 * The palette still must not flip — the plate is dark in both modes, because
 * the camera feed around it is not a theme — but the REASON narrows to the
 * scrimmed regions. Worth stating plainly rather than leaving the comment to
 * quietly stop being true.
 */
:root {
  /* Deeper than --black, which is the app's canvas. The plate should not read
     as the app's page floated over the feed; it reads as its own object. */
  --hud-plate: #0A0A0C;
  --hud-plate-radius: var(--radius-xs);
  /* The scrim stays for the regions the plate does not cover. */
  --hud-scrim: linear-gradient(to bottom, rgb(10 10 12 / 0.85), rgb(10 10 12 / 0.55), rgb(10 10 12 / 0)); /* @kind color */
}

/* MEASURED CONTRAST — every verdict ink against --hud-plate, WCAG 2.1.
 * Computed, not estimated. The over-camera inks are the band/dark cuts, which
 * is what the HUD already uses.
 *
 *   state          ink        hex       ratio   at 28px   at 12px
 *   ─────────────────────────────────────────────────────────────
 *   revoked        silver     #C1BDC6   10.70     pass      pass
 *   unsigned       silver     #C1BDC6   10.70     pass      pass
 *   provisioning   silver     #C1BDC6   10.70     pass      pass
 *   stale          amber-lift #C9922F    7.19     pass      pass
 *   unproven       amber-lift #C9922F    7.19     pass      pass
 *   untrusted      rust-lift  #D4694A    5.58     pass      pass
 *   trusted        sapph-lift #5B85C9    5.32     pass      pass
 *   tampered       wine-lift  #C94F72    4.56     pass      pass
 *
 * FLOOR: tampered at 4.56:1. It is the binding constraint on the plate, and
 * the reason the plate is #0A0A0C rather than --black #141414 — on --black
 * tampered lands at 4.25:1, which clears the 3:1 a 28px headline needs but
 * misses 4.5:1, and the evidence rows beneath the word are 12px meta where
 * 4.5:1 is not negotiable. Deepening the plate by one step buys the whole
 * ladder a single enforceable minimum instead of two.
 *
 * ENFORCE AT 4.5:1 for all eight states, at any size. Every ink holds it with
 * margin except tampered, which holds it by 0.06. Any new ink, any change to
 * the lift cuts, or any lightening of the plate must be re-measured — this is
 * the assertion §7.2 item 3 asks to add alongside the palette and triad tests.
 *
 * White #FFFFFF is 19.78:1 on the plate and silver #C1BDC6 is 10.70:1, so the
 * headline, detail and evidence rows have room to spare. --grey-lift #918C98
 * at 6.04:1 is the faint tier's floor and still clears body text.
 */

/* ── tokens/base.css ─────────────────────────────────────────── */
/* Minimal base layer. Link colours are defined here so no link ever renders
   browser-default blue. */
:root { color-scheme: light; }

body {
  background: var(--surface-page);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-md);
  font-weight: var(--weight-text);
  line-height: var(--leading-body);
  text-wrap: pretty;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--text-link); text-decoration-thickness: 1px; text-underline-offset: 2px; transition: color var(--dur-fast) var(--ease-out); }
a:hover { color: var(--text-link-hover); }
a:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--radius-xs); }

code, kbd, samp, pre { font-family: var(--font-mono); font-size: 0.92em; }

::selection { background: var(--surface-accent-soft); color: var(--text-primary); }

/* Utility: registration marks on any container. */
.reg-marks { position: relative; }
.reg-marks::before, .reg-marks::after {
  content: ""; position: absolute; pointer-events: none;
  width: var(--reg-size); height: var(--reg-size);
  background:
    linear-gradient(var(--reg-color), var(--reg-color)) center / 100% var(--reg-hair) no-repeat,
    linear-gradient(var(--reg-color), var(--reg-color)) center / var(--reg-hair) 100% no-repeat;
}
.reg-marks::before { top: var(--reg-inset); left: var(--reg-inset); }
.reg-marks::after  { bottom: var(--reg-inset); right: var(--reg-inset); }

