/* johnbrodish.com — design tokens.
 * Binding spec: the design language. §2.4 type, §2.5 colour, §2.6 rules.
 *
 * Light is the default polarity: :root carries paper, the dark block is the
 * override. That is the reason the page reads as a printed document rather
 * than a product console.
 *
 * Base-4 (§2.6): every size, line height, pad and gap is a 4px multiple,
 * stepped at 40rem/60rem. No fluid clamps, no vh paddings. The single
 * exception is the 1px hairline, which is a drawing device rather than a
 * measurement — --rule below is where 1px is used.
 *
 * Kept in step by hand with the fun site's tokens.css: the @property block, the
 * palette, the rule token and the motion values are identical in both files.
 * fun/ carries only the type roles it uses.
 *
 * THE SPACE SCALE IS THE ONE EXCEPTION. --space-lg is 2rem here and 1.5rem
 * there, deliberately: fun/ runs a tighter register, and every consumer it
 * has for this rung is real spacing rather than an icon size. Every other
 * rung is identical. The reasoning in full sits in the fun site's tokens.css's
 * own header.
 */

/* ---------------------------------------------------------------------------
 * Motion system, Layer 0 — polarity colours registered as <color> so they
 * interpolate under a transition instead of hard-cutting. styles.css does the
 * transitioning; this file only declares the types.
 *
 * EVERY polarity-dependent colour must be registered, or it hard-cuts while
 * the rest of the palette fades. Eight in all — --ink-3 and --pen-soft
 * included.
 *
 * The --lb-* tokens below are deliberately NOT registered — they are fixed in
 * both polarities by design and must never transition.
 * initial-value = the light polarity, the default.
 * ------------------------------------------------------------------------- */
@property --ground        { syntax: '<color>'; inherits: true; initial-value: #FCFCFB; }
@property --ink           { syntax: '<color>'; inherits: true; initial-value: #15171A; }
@property --ink-2         { syntax: '<color>'; inherits: true; initial-value: #4A4F55; }
@property --ink-3         { syntax: '<color>'; inherits: true; initial-value: #6E747B; }
@property --pen           { syntax: '<color>'; inherits: true; initial-value: #C0341E; }
@property --pen-soft      { syntax: '<color>'; inherits: true; initial-value: #F7E7E3; }
@property --pen-ink       { syntax: '<color>'; inherits: true; initial-value: #75423B; }
@property --hairline      { syntax: '<color>'; inherits: true; initial-value: #C4C9CF; }
@property --exhibit-ground{ syntax: '<color>'; inherits: true; initial-value: #F2F2F0; }

:root {
  /* -------- colour · light polarity (default) · spec §2.5 --------
   * Contrast against --ground, computed with the WCAG relative-luminance
   * formula, not estimated:
   *   --ink    17.49:1   --ink-2   8.05:1   --ink-3  4.60:1
   *   --pen     5.45:1   --hairline 1.62:1  --exhibit-ground 1.09:1
   *
   * Three declared constraints, each from a failing pair:
   *   1. --ink-3 is authored against --ground ONLY. On --exhibit-ground it is
   *      4.21:1 and on --pen-soft 3.93:1 — both under 4.5. Apparatus sitting
   *      on either of those grounds steps up to --ink-2 (7.37:1 / 6.89:1).
   *      No ground exists on which --ink-3 clears 4.5 except --ground itself,
   *      so this is a rule, not a value to tune.
   *   2. --pen-soft carries --ink or --pen-ink, never --pen and never --ink-3. Pen on
   *      pen-soft is 4.66:1 here but only 4.02:1 in the dark polarity; ink on
   *      pen-soft is ~14.9:1 in both. One rule that holds in both polarities
   *      beats two that disagree.
   *   3. The focus ring must have --ground on at least one side (outline plus
   *      outline-offset, never flush against a rule). Pen against --hairline
   *      is 3.36:1 light / 2.89:1 dark — the dark case misses WCAG 1.4.11.
   */
  --ground: #FCFCFB;            /* near-white paper */
  --ink: #15171A;               /* all primary text, including body copy — never greyed (§2.4) */
  --ink-2: #4A4F55;             /* apparatus, secondary */
  --ink-3: #6E747B;             /* micro-labels — 4.60:1, do not lighten (§2.5) */
  --pen: #C0341E;               /* the designer's hand: annotation markers — nothing else. Not a state
                                   colour; the focus ring is --ink (styles.css, :focus-visible) */
  --pen-soft: #F7E7E3;          /* the pen's wash: paired/active state, the `not measured` chip's fill */
  --pen-ink: #75423B;           /* the pen at reading strength: text ON --pen-soft, nowhere else.
                                   Same hue as --pen (8°), saturation 0.50 against the pen's 0.84,
                                   so it sits with the ink ramp's weight instead of shouting.
                                   6.73:1 on --pen-soft; --pen itself is 4.66:1 there and 4.04:1
                                   in the dark polarity, which is why this token exists. */
  --hairline: #C4C9CF;          /* the single --rule token's colour; see below */
  --exhibit-ground: #F2F2F0;    /* one step off paper, behind artifacts */

  /* The annotated-proof marker's keyline (§2.2).
     KEYED TO THE ARTIFACT, NOT TO THE ROOM — which is why this is here rather
     than being read off --ground.
     The outline sits on the marker, and the marker sits on the ARTIFACT. Every
     artifact this component annotates is a light-UI capture: the pixels under
     all four markers of lift-summary-shipped.png are #FFFFFF or #FAFAFA
     (decoded, opaque RGB, colortype 2). That is true in the page and in the
     lightbox, in light and in dark — the artifact does not change when the room
     does. Painting the keyline in the ROOM's colour would therefore invert it
     in three of the four rooms: against the artifact's own white,
     `outline: 2px solid var(--ground)` is 1.03:1 in a light page (correct,
     quiet) and 17.96:1 in a dark one — a black halo at rest. This is §2.5's
     named error class: transposing a light-polarity number where the
     light-polarity JOB is what needs carrying, the same reasoning the
     --exhibit-ground mount follows.
     So the value is FIXED in both polarities, like the --lb-* set below and
     for the same reason, and is deliberately NOT registered with @property:
     there is nothing to crossfade when the page polarity changes, because the
     value never does. Against the artifact's #FFFFFF: 1.03:1, in all four
     rooms. The hex is the light polarity's paper.
     No sharpened companion token for the paired state, on purpose: an ink
     keyline (17.96:1 against the artifact) around a hovered marker is a black
     outline the markers should not carry. The paired treatment lives at
     .annot-mark[data-paired] in styles.css. */
  --keyline-quiet: #FCFCFB;     /* at rest — the artifact's own paper, 1.03:1 */

  /* THE MARKER DISC IS KEYED TO THE ARTIFACT, THE SAME WAY ITS OUTLINE IS.
     --pen is solved twice, once per page ground, and the dark value is right
     for a mark sitting ON the dark page — the notes list, for one. The marker
     disc does not sit there: it sits on the exhibit, and the exhibit is light
     in every room. Held to the room's pen, the dark-polarity disc reads 3.80:1
     against that light artifact and its numeral has to invert to dark to stay
     legible. Pinned to the light value it is 5.60:1 against the artifact and
     carries a paper-coloured numeral at 5.45:1, in both polarities, which is
     also what keeps the numerals matching the notes list beside them. */
  --pen-artifact: #C0341E;

  /* Lightbox — its own dark room in BOTH page polarities, so the reader
     looking closest is always in the same place. Near-opaque rather than
     opaque: the 0.96 scrim costs nothing. Composited
     against the page beneath, the ratios hold in both directions —
       over a light page  → #13181E : ink 17.38 · ink-2 8.02 · ink-3 4.58 · pen 4.69
       over a dark page   → #090E15 : ink 18.85 · ink-2 8.70 · ink-3 4.97 · pen 5.09
     The light-page composite is the worst case and every pair clears 4.5:1
     there, micro-labels included.
     The four foreground values are the dark polarity's, NOT --ink/--pen: the
     room is dark whichever polarity the page is in, and --pen (#C0341E) would
     be 3.19:1 against #13181E. Fixed, not registered — they must not
     transition when the page polarity changes underneath them. */
  --lb-ground: rgba(9, 14, 21, 0.96);
  --lb-ink: #FCFCFB;                /* 17.38:1 worst case */
  --lb-ink-2: #A4AFBC;              /* 8.02:1 worst case */
  --lb-ink-3: #7B8289;              /* 4.58:1 worst case */
  --lb-pen: #F14126;                /* 4.69:1 worst case — see the note above on --pen */

  /* -------- type · role split (§2.4) --------
   * Named by ROLE, not by classification. "The role split is the rule, and it
   * must not leak" — a token called --apparatus cannot be reached for to set
   * running prose without the mistake being visible in the diff.
   * Apparatus: wordmark, headings, kickers, captions, chips, marker numerals,
   * table headers, keys. Prose: lede, body, notes, table cells. */
  --apparatus: 'Schibsted Grotesk', system-ui, -apple-system, sans-serif;
  --prose: 'Literata', Georgia, 'Times New Roman', serif;

  /* -------- type scale · base-4, mobile-first (stepped below) --------
   * Desktop ladder, for the ratios that matter at scroll speed:
   *   display 64 → title 48 → h2 36 → eyebrow/fine 12
   *   ratios      1.33      1.33     — (see below)
   *
   * --text-h3 / --lh-h3 have exactly one consumer: the resume's <h3> entry
   * titles. The resume's structure is name → section label → entry → prose, a
   * genuine third level and the only one on this site. Its eight <h3>s are
   * EMPLOYER NAMES, not component labels: they are the loudest thing inside
   * their entry because they are the thing being scanned for; they sit in the
   * reading column; and there is exactly one treatment, applied to all eight.
   *
   * Neither the homepage (thesis → problem → three evidence units →
   * about/contact) nor the case pages (h1 → h2 sections) has a third
   * structural level, so on those pages the ladder is the one above: three
   * apparatus display steps and a 12px micro-label rank, with nothing between
   * 36 and 12. A middle rung is only ever earned by a real mid-level heading
   * — it must not be manufactured by promoting a label. The two candidates
   * are wrong for it:
   *   .claims-ledger-head ("What I claim") and .annot-notes-head ("What
   *   changed, and why") are component LABELS, not section heads. Setting
   *   either at 24px puts it second-loudest inside its evidence unit, above
   *   the claims and the notes it introduces — the label louder than the
   *   evidence. Treating one and not the other would split a treatment across
   *   the two signature components.
   *
   * Seven distinct roles live at 12px (the design language §5). This rung does
   * nothing about that: a middle rung was never the fix for it, and the resume
   * adds no new 12px role — its section labels and date kickers are the
   * existing .eyebrow and .facts treatments, reused.
   *
   * THE VALUES ARE the fun site's tokens.css's, TO THE DECLARATION (there it is
   * the entry-title role, .entry h2). Both files read 20/24 → 24/28. The
   * resume's own step below 24 is --text-body at 16. */
  --text-display: 2.25rem;  --lh-display: 2.5rem;   /* 36 / 40 — homepage hero, one per site */
  --text-title: 2rem;       --lh-title: 2.25rem;    /* 32 / 36 — interior page <h1>, and the display numeral in a stat line */
  --text-h2: 1.5rem;        --lh-h2: 1.75rem;       /* 24 / 28 — top-level section head */
  --text-h3: 1.25rem;       --lh-h3: 1.5rem;        /* 20 / 24 — resume entry titles (employer names) */
  --text-lede: 1.25rem;     --lh-lede: 1.75rem;     /* 20 / 28 — prose */
  --text-body: 1rem;        --lh-body: 1.5rem;      /* 16 / 24 — prose; leading opens at 40rem as the measure widens */
  --text-eyebrow: 0.75rem;  --lh-eyebrow: 1rem;     /* 12 / 16 — uppercase micro-label, tight */
  --text-fine: 0.75rem;     --lh-fine: 1.25rem;     /* 12 / 20 — running fine print, loose */

  /* -------- rule (§2.6) --------
   * ONE rule, not three ranks: a conventional divider/underline system rather
   * than a ranked one. Three ranks (4px/1px ink, 1px ink, 1px hairline) would
   * exist to separate a top-level boundary from a section head from an
   * attachment line at a glance; a single divider has nothing left to
   * separate itself from, so no such distinction is drawn.
   *
   * 1px, not 4px. A heavier weight only earns its place by contrast with
   * thinner ranks — it has no job of its own once nothing beside it is
   * thinner. Set next to running body copy (16px/24px) it reads as six-plus
   * lines of type stacked on a single bar: the loudest mark on the page,
   * which is the opposite of what "conventional" is asking for.
   *
   * --hairline, not --ink. Against --ground in both polarities: --ink at 1px
   * is 17.49:1 light and dark — the same contrast body text gets, so a
   * full-ink hairline still reads as something to notice, not as ambient
   * structure. --hairline is 1.62:1 light / 1.64:1 dark: visible, not
   * decorative-invisible, and quiet enough that the reader organises by it
   * without being told to look at it. That is the whole point of one
   * conventional rule instead of three ranked ones — used identically at a
   * top-level page boundary and at a caption's attachment line, so the reader
   * never has to learn what a given line's weight means, because there is
   * only one kind of line.
   *
   * Accepted cost, stated rather than routed around: a 4px full-ink rule at
   * the top-level boundaries (between evidence units, above the footer) does
   * real structural signalling — deliberately the loudest thing on the page.
   * At 1px hairline that signal is weaker. That is the honest consequence of
   * "conventional," not a defect to compensate for by quietly reintroducing
   * a second weight under a different name. If that signal is ever wanted
   * back, it comes from spacing — not from a second rule. */
  --rule: 1px solid var(--hairline);

  /* -------- space · 4pt scale --------
   * THERE IS NO 24 RUNG. The step from --space-md to --space-lg is 16 → 32,
   * and that is a real hole, not an oversight to fill casually. Anything that
   * wants 24 is either an icon — --icon below, which is where the four glyph
   * consumers live — or it needs a considered new rung, not a second meaning
   * bolted onto an existing one.
   * the fun site's tokens.css carries 24 at --space-lg. That divergence is
   * deliberate and is described in this file's header. */
  --space-2xs: 0.25rem;   /* 4 */
  --space-xs: 0.5rem;     /* 8 */
  --space-sm: 0.75rem;    /* 12 */
  --space-md: 1rem;       /* 16 */
  --space-lg: 2rem;       /* 32 */
  --space-xl: 2.5rem;     /* 40 */
  --space-2xl: 4rem;      /* 64 */
  --space-3xl: 6rem;      /* 96 */

  /* -------- icon size --------
   * NOT a rung of the scale above, which is the whole reason it exists.
   * The zoom badge, the lightbox close glyph, the annotation markers and the
   * note numerals are all 24px, and they take it from here rather than from
   * --space-lg: a size chosen for a glyph does not belong on a scale that
   * gets tuned for layout. Riding a spacing rung, all four would grow a third
   * larger the day that rung moved, with nobody asking them to — and the
   * badge would keep its 16px glyph inside the bigger box, doubling its
   * optical padding from 4 to 8 a side. The badge sits cleanly among the
   * markers only while both are 24px.
   * NOT this token: the masthead toggle's 20px, which is an authored
   * exception with its own note, sized against the nav links beside it. */
  --icon: 1.5rem;         /* 24 */

  /* -------- layout --------
   * The declared right-edge set. §2.6 asks for a single left axis; left
   * unmanaged, the right side accumulates near-misses (a dozen-odd distinct
   * edges across six pages, 40px and 3px apart). Four edges are legal and
   * nothing in styles.css may set a max-width outside them:
   *   --page               the content column
   *   --page-exhibit       the annotated proof, on the page and in the
   *                        lightbox — the ONLY block allowed past --page
   *   --measure-apparatus  apparatus fine print and section/entry headings
   *   --measure            all prose
   * --measure-apparatus is the one edge for .facts / .fine / figcaption,
   * which otherwise tend to cap themselves by hand at 36rem, 40rem and
   * 44rem. */
  --measure: 34rem;
  --measure-apparatus: 40rem;
  --page: 64rem;
  /* A FOURTH edge, and the reason it is not a fifth right edge in disguise.
   * --page (64rem) is a READING column, sized for the lede and the case body.
   * The annotated proof is not read, it is examined, and confined to --page
   * it is taxed by the 3rem page gutter and by any notes column beside it —
   * with a 20rem rail it would measure 895px at a 959 viewport and 568px at
   * 1600: the signature component at its largest on a small tablet and
   * smallest on a designer's monitor.
   *
   * 72rem, NOT WIDER. A breakout far past the reading column is jarringly
   * wide and breaks the flow of the page. Real-world convention for an
   * artifact call-out wider than an article's column — Rachel Andrew's
   * named-grid-line breakout technique
   * (rachelandrew.co.uk/archives/2017/06/01/breaking-out-with-css-grid-explained/)
   * and Ryan Mulligan's breakout grid system (ryanmulligan.dev/blog/layout-breakouts/)
   * — is one shape: a SYMMETRIC, CENTERED breakout offset from the content
   * column by a SMALL FIXED AMOUNT per side, not "as wide as the arithmetic
   * allows." Mulligan's own numbers are 2rem (32px) per side for a modest
   * "popout" and 5rem (80px) per side for his most dramatic "feature" tier —
   * both collapsing to zero on small screens. A 96rem ceiling would put the
   * exhibit's left edge 208–304px off the content axis, 2.6×–3.8× past even
   * Mulligan's most extreme documented tier. The value is --page + 2 ×
   * --space-2xl = 64rem + 2×4rem = 72rem (1152px) — base-4 (1152 ÷ 4 = 288),
   * and a small, fixed, named offset in the same spirit as the sources above,
   * rather than a number chosen to maximize what a 1600px source capture
   * could carry.
   *
   * The centering mechanism is `.annot`'s
   * `--annot-room: min(var(--page-exhibit), 100vw - 2*var(--pad))` and
   * `margin-inline: calc((100% - var(--annot-room)) / 2)` — the symmetric,
   * centered mechanism the sources above describe. This token is only the
   * ceiling they resolve against.
   *
   * WHY THERE IS NO NOTES RAIL beside the artifact. A 20rem rail plus a
   * 2.5rem gap taxes the room 360px. At a 1152px ceiling the room available
   * to a two-column layout maxes out at 1152 (the token IS the binding
   * constraint for every viewport ≥1248px), so room − 360 tops out at 792px —
   * permanently below the 960px the stacked layout already reaches at 1024px
   * and holds to 1439px. There is no arrival breakpoint that fixes this: room
   * never exceeds 1152 regardless of viewport, so 792 < 960 at every width a
   * rail could ever fire at. A narrower rail fails the same way at a tighter
   * budget (§2.2's own "rejected alternatives," "A narrower rail. There is no
   * rail width that works"). `.annot` and `.lb-annot-inner` both stay
   * single-column at every width; the exhibit grows once past 90rem, from
   * 960px to the 1152px ceiling — a real breakout, arriving all at once at
   * 1440 (see `.annot`, styles.css) rather than growing gradually, because
   * 1152 is already the token's asymptote by the time 90rem is reached.
   *
   * Consumers, TWO: `.annot` (the page) and `.lb-annot-inner` (the lightbox
   * — matched to the page deliberately, so a reader who opens the exhibit
   * does not land in a differently-laid-out room). The case pages' running
   * margin marker (`.when`) is NOT a consumer of this token — see the note at
   * --margin-note below and at `.when` in styles.css for why: at 72rem the
   * token caps out well short of what the marker's own column needs, and the
   * marker is not the thing asking for a width cap in the first place.
   *
   * the fun site's tokens.css does not carry this token — that site has no
   * annotated proof; it declares its own --page-cover instead, reasoned out
   * at the token there (right-anchored rather than centered — see
   * the design language §2.6 and §5). */
  --page-exhibit: 72rem;

  /* The margin note's column width — a WIDTH, not a right edge, and NOT
   * routed through --page-exhibit. That token's 72rem ceiling (1152px) binds
   * from 1248px up — well inside the marker's own operating range — and
   * leaves only 112px of push-out past the content column, where the marker's
   * column (176px) plus its gutter (32px) needs 208px; anchored to it, the
   * marker's column would start 64px inside the reading column it is meant
   * to sit beside at 1440px. The marker is not a component asking for an
   * artifact-width cap — it is a fixed 176px column of text that does not
   * get jarringly wide no matter how much margin exists around it — so it
   * computes its own right edge directly from the raw page margin
   * (100vw − 2×--pad), the same arithmetic that picks 176px below. See
   * `.when`, styles.css.
   * 11rem = 176px. Derived from the space it has to fit into: at 1440, the
   * first width where the marker appears, the clear page margin right of the
   * content column is 208px, and 208 = 32 (gutter off the content edge) + 176.
   * A wider column would land the marker on the reading column's edge at
   * 1440; a narrower one runs the 41-character strings past four lines. At
   * 176px the five live strings set 2–3 lines, which is what
   * --margin-note-min below is sized against. */
  --margin-note: 11rem;
  /* The marker's painted floor, and it is an occlusion value rather than a
   * size. Consecutive sticky markers pin at the same `top`, so the arriving
   * one has to cover the outgoing one completely; equal minimum heights plus
   * an opaque --ground do that. 3rem = 48px = the tallest of the five strings
   * at --margin-note (3 lines × --lh-eyebrow). It costs no layout — the rule
   * pulls the same amount back out with a negative margin-bottom. */
  --margin-note-min: 3rem;

  --pad: 1.25rem;               /* 20 → 32 → 48 */
  --hero-pad-top: 3rem;         /* 48 → 56 */
  --hero-pad-bottom: 4rem;      /* 64 → 80 */

  /* -------- motion -------- */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-cross: cubic-bezier(0.4, 0, 0.2, 1);  /* near-symmetric — crossfades only */
  --dur-micro: 120ms;
  --dur-short: 220ms;
  --dur-long: 420ms;
  --dur-theme: 320ms;                          /* polarity change */
  --dur-page:  280ms;                          /* navigation */

  /* -------- z scale -------- */
  --z-raised: 10;
  --z-sticky: 200;
  --z-modal: 400;

  color-scheme: light;
}

@media (min-width: 40rem) {
  :root {
    --text-display: 3rem;    --lh-display: 3.25rem;  /* 48 / 52 */
    --text-title: 2.5rem;    --lh-title: 2.75rem;    /* 40 / 44 */
    --text-h2: 2rem;         --lh-h2: 2.25rem;       /* 32 / 36 */
    --text-h3: 1.5rem;       --lh-h3: 1.75rem;       /* 24 / 28 */
    --text-lede: 1.5rem;     --lh-lede: 2.25rem;     /* 24 / 36 */
                             --lh-body: 1.75rem;     /* 16 / 28 — more leading once the measure widens */
    --pad: 2rem;
    --hero-pad-top: 3.5rem;
    --hero-pad-bottom: 5rem;
  }
}

@media (min-width: 60rem) {
  :root {
    --text-display: 4rem;    --lh-display: 4.25rem;  /* 64 / 68 */
    --text-title: 3rem;      --lh-title: 3.25rem;    /* 48 / 52 — caps below the homepage display */
    --text-h2: 2.25rem;      --lh-h2: 2.5rem;        /* 36 / 40 */
    /* --text-lede holds at 24 from the 40rem step and only the leading opens,
       36 → 38: deliberately off the +4/+8 arithmetic the other rungs follow
       (which would give 28/44), to keep the lede's footprint down at this
       tier. */
    --text-lede: 1.5rem;      --lh-lede: 2.375rem;    /* 24 / 38 */
    --pad: 3rem;
  }
}

/* -------- colour · dark polarity (override) --------
 * Not a filter and not a new palette: the two polarities swap the same two
 * pigments. The ground is the light polarity's own ink, and the ink is its
 * own paper. That is why this is #15171A and not a navy such as #111A27 — a
 * navy is justified only by a finding about light-sensitive migraine
 * patients (Juva), an audience-specific corrective the spec records as
 * rejected for this audience (§3, §4.3).
 *
 * The greys hold the light polarity's contrast RANKS, not its hex values:
 * each was solved for its target ratio with the hue and saturation of its
 * light counterpart preserved (H≈212°, saturation falling as value rises,
 * the same ramp).
 *
 * Contrast against --ground:
 *   --ink 17.49:1  --ink-2 8.07:1  --ink-3 4.61:1
 *   --pen  4.72:1  --hairline 1.64:1  --exhibit-ground 1.35:1
 * Light ranks, for comparison: 17.49 / 8.05 / 4.60 / 5.45 / 1.62 / 1.09.
 *
 * TWO VALUES — --pen-soft and --exhibit-ground — are solved differently from
 * the ramp, because neither carries text and the paragraph above does not
 * cover them. Each holds a light-polarity JOB rather than transposing a
 * light-polarity NUMBER; the notes at the tokens say how. The ink ramp, the
 * pen and the hairline hold their light ranks to two decimals.
 */
:root[data-theme="dark"] {
  --ground: #15171A;
  --ink: #FCFCFB;
  --ink-2: #A4AFBC;
  --ink-3: #7B8289;             /* micro-labels — 4.61:1, do not darken */
  /* --pen: #C0341E holds hue 8.15° and saturation 0.844; only value is raised,
     0.753 → 0.945. #C0341E on this ground is 3.21:1 and unusable; this is
     4.72:1 both as a mark on the ground and as the ground-coloured numeral
     inside a filled marker disc. */
  --pen: #F14126;

  /* --pen-soft is solved the way the ink ramp is: hold the JOB, not the hex.
     A value that only holds the surface step — #33211C, 1.18:1, saturation
     0.451 against the pen's 0.842 — loses the pigment. That is the difference
     between a dark red and a brown: a coffee stain, the only warm brown on an
     otherwise consistently cool blue-grey ramp, and both halves of that are
     the same defect. At that saturation it does not read as the pen, so its
     warmth has no licence.
     This value carries the pen's own hue and saturation (8.00° / 0.845
     against #F14126's 7.98° / 0.842) and only the value drops, 0.945 → 0.278
     — the mirror of what the light polarity does by tinting toward paper. The
     two polarities measure the same to two decimals:
       step off --ground        1.17:1 dark · 1.17:1 light
       --ink on --pen-soft     14.98:1 dark · 14.96:1 light
     The forbidden pairs stay forbidden: --pen on --pen-soft is 4.04:1 and
     --ink-3 on it is 3.95:1. Constraint 2 above holds — --pen-soft carries
     --ink or --pen-ink, never --pen. */
  --pen-soft: #47130B;
  /* --pen-ink is solved like --pen-soft: hold the hue (8°), drop the
     saturation to 0.35 and lift the value to 0.84, so it reads as the wash's
     own pigment brought up to text strength. 6.25:1 on --pen-soft. */
  --pen-ink: #D6958B;

  --hairline: #383D42;

  /* --exhibit-ground is the one place the dark polarity deliberately does
     NOT match the light polarity's ratio.
     The mount's job is to separate the artifact from the page. In light that
     is nearly free: a screenshot of a light UI and the paper are both near
     L*100, so a 1.09:1 mount (ΔL* 3.5) is a legible surface between two
     things that already sit together. In dark the same artifact is still
     L*100 and the page is L*7.7 — ΔL* 92 — and next to that a mount at the
     light ratio (#1D2024: L*12.1, ΔL* 4.5 off the page) is indistinguishable
     from the page: light-UI screenshots punch through a near-black page,
     mounted on a ground that reads as a mis-set border rather than a mount.
     Equal RATIOS across the polarities would preserve that defect, so this
     holds the mount's job instead: 1.35:1, L*19.7, ΔL* 12.0 off the page,
     which is a surface the eye lands on between the page and a white
     artifact. Hue and saturation stay on the dark ramp exactly (216° / 0.185
     against --ground's 216° / 0.192) — it is a lighter step of the same
     pigment, not a new one.
     Every pair that lands on it:
       --ink       12.92:1   --ink-2  5.96:1
       --pen        3.49:1  — the :focus-visible ring inside an exhibit;
                    WCAG 1.4.11 wants 3:1 for a non-text indicator
       --hairline   1.21:1  — the artifact keyline reads against the PAGE
                    (1.64:1), which is the side that carries it; against its
                    own mount it is quieter and the mount's edge does the
                    drawing.
       --ink-3      3.41:1  — under 4.5, so constraint 1 above holds and its
                    main consumer, .exhibit figcaption, steps up to --ink-2. */
  --exhibit-ground: #2C3036;
  color-scheme: dark;
}

/* No-JS fallback: honour the OS preference. theme.js applies the same rule on
 * the JS path, so both paths agree — absent any signal the visitor gets paper.
 * The data-default-theme exemption is the second half of that agreement: a
 * page that pins its opening polarity (the gate pins light) must not be flipped
 * here when scripting is off. An explicit data-theme, set by the toggle, is
 * matched by its own block above and is unaffected either way. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]):not([data-default-theme]) {
    --ground: #15171A;
    --ink: #FCFCFB;
    --ink-2: #A4AFBC;
    --ink-3: #7B8289;
    --pen: #F14126;
    --pen-soft: #47130B;
    --pen-ink: #D6958B;
    --hairline: #383D42;
    --exhibit-ground: #2C3036;
    color-scheme: dark;
  }
}
