/* Red Stet — THE design tokens. One definition per concept (CLAUDE.md law 2).
 *
 * This file is the ONLY place a design-token value is written down. Neither
 * landing.css, static.css, style.css, nor any page's inline <style> may
 * declare a custom property that appears here. If you need a new colour,
 * add a step to a scale below — do not invent a local name.
 *
 * ── Loading mechanism: <link>, not @import ─────────────────────────────
 * Every page carries `<link rel="stylesheet" href="/tokens.css">` ahead of
 * its own stylesheet. The alternative — `@import` at the top of landing.css
 * and style.css — costs one line instead of ~50, but it serialises the
 * fetch: the browser cannot discover tokens.css until landing.css has been
 * downloaded AND parsed, adding a full round-trip in front of first paint
 * on a marketing site where LCP is the metric that matters. The <link> is
 * fetched in parallel with the stylesheet that consumes it. The repo
 * already uses exactly this pattern for static.css across the help and
 * science library, and vite.config.js enumerates every page explicitly, so
 * "a new page forgot the link" is a reviewable one-line omission rather
 * than a silent class of bug.
 *
 * ── Surfaces ──────────────────────────────────────────────────────────
 * The app (app/index.html + style.css) and the static site (48 marketing,
 * help, science and policy pages + landing.css/static.css) consume the
 * SAME tokens. The dark theme below is app-only in practice — the static
 * site never sets data-theme — but it lives here because token VALUES
 * live here, without exception.
 *
 * The one sanctioned duplicate is verify/index.html, which is a
 * deliberately single-file offline page (it inlines its ES256 keys and
 * promises to run with no network). Its :root mirrors these values and is
 * marked as a mirror in that file. Same arrangement as extension/content.js.
 */


/* ---------------------------------------------------------------------------
 * Typefaces — declared HERE, once, because every surface links this file.
 *
 * These moved out of style.css (app-only) on 2026-07-30. Before that the
 * marketing and help pages NAMED Tiempos/Söhne in their font stacks but
 * shipped no @font-face, so every real visitor fell through to Georgia —
 * the brand faces only appeared for people who happened to have them
 * installed locally. That is fixed: the static site now serves the same
 * faces the app does.
 *
 * This costs nothing in bytes a page doesn't use. @font-face is LAZY —
 * the browser fetches a file only when text is actually rendered in that
 * family+weight+style. A landing page that sets one display face and one
 * body face downloads those two, not the library.
 *
 * font-display: swap on every face: text paints immediately in the
 * fallback and swaps when the real face arrives. Never invisible text.
 * ------------------------------------------------------------------------- */

@font-face {
  font-family: 'XCharter';
  src: url('/fonts/xcharter/XCharter-Roman.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'XCharter';
  src: url('/fonts/xcharter/XCharter-Italic.woff2') format('woff2');
  font-weight: 400; font-style: italic; font-display: swap;
}

@font-face {
  font-family: 'XCharter';
  src: url('/fonts/xcharter/XCharter-Bold.woff2') format('woff2');
  font-weight: 700; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'XCharter';
  src: url('/fonts/xcharter/XCharter-BoldItalic.woff2') format('woff2');
  font-weight: 700; font-style: italic; font-display: swap;
}

@font-face {
  font-family: 'Söhne';
  src: url('/fonts/sohne/Sohne-Buch.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Söhne';
  src: url('/fonts/sohne/Sohne-BuchKursiv.woff2') format('woff2');
  font-weight: 400; font-style: italic; font-display: swap;
}

@font-face {
  font-family: 'Söhne';
  src: url('/fonts/sohne/Sohne-Kraftig.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Söhne';
  src: url('/fonts/sohne/Sohne-KraftigKursiv.woff2') format('woff2');
  font-weight: 500; font-style: italic; font-display: swap;
}

@font-face {
  font-family: 'Söhne';
  src: url('/fonts/sohne/Sohne-Halbfett.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Söhne';
  src: url('/fonts/sohne/Sohne-HalbfettKursiv.woff2') format('woff2');
  font-weight: 600; font-style: italic; font-display: swap;
}

@font-face {
  font-family: 'Tiempos Headline';
  src: url('/fonts/tiempos/TiemposHeadline-Light.woff2') format('woff2');
  font-weight: 300; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Tiempos Headline';
  src: url('/fonts/tiempos/TiemposHeadline-Regular.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Tiempos Headline';
  src: url('/fonts/tiempos/TiemposHeadline-Medium.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Tiempos Headline';
  src: url('/fonts/tiempos/TiemposHeadline-Semibold.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Tiempos Text';
  src: url('/fonts/tiempos/TiemposText-400-Regular.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Tiempos Text';
  src: url('/fonts/tiempos/TiemposText-400-Regular-Italic.woff2') format('woff2');
  font-weight: 400; font-style: italic; font-display: swap;
}

@font-face {
  font-family: 'Tiempos Text';
  src: url('/fonts/tiempos/TiemposText-500-Medium.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Tiempos Text';
  src: url('/fonts/tiempos/TiemposText-500-Medium-Italic.woff2') format('woff2');
  font-weight: 500; font-style: italic; font-display: swap;
}

@font-face {
  font-family: 'Tiempos Text';
  src: url('/fonts/tiempos/TiemposText-600-Semibold.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Tiempos Text';
  src: url('/fonts/tiempos/TiemposText-600-Semibold-Italic.woff2') format('woff2');
  font-weight: 600; font-style: italic; font-display: swap;
}

@font-face {
  font-family: 'Tiempos Text';
  src: url('/fonts/tiempos/TiemposText-700-Bold.woff2') format('woff2');
  font-weight: 700; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Tiempos Text';
  src: url('/fonts/tiempos/TiemposText-700-Bold-Italic.woff2') format('woff2');
  font-weight: 700; font-style: italic; font-display: swap;
}

@font-face {
  font-family: 'Caveat';
  src: url('/fonts/caveat/Caveat-VariableFont_wght.woff2') format('woff2-variations');
  font-weight: 400 700; font-style: normal; font-display: swap;
}

:root {
  /* ── Type ───────────────────────────────────────────────────────────
     Every surface loads the real faces from the @font-face block above,
     so app and static site render identically. The system names that
     follow are the fallback chain used only while a face is loading, or
     if it fails to fetch. */
  --display: 'Tiempos Headline', 'Tiempos Text', 'Tiempos',
             'Iowan Old Style', 'Charter', Georgia, serif;
  /* ── THE MARKETING SURFACE ────────────────────────────────────────
     v1 ran two design systems whose TOKEN NAMES COLLIDED. The static
     pages defined their own `--ink` / `--ink-2` in landing.css; the app
     defined different values under the same names in style.css. Phase
     6's unification merged them BY NAME, so the marketing pages
     silently inherited the app's darker ink scale — the reason the hero
     sub-heading went from a light warm grey to near-body-black.
     Named explicitly here so the two can never collide again. */
  --ink-site:   #2a2622;   /* marketing headline ink — lighter than the
                              app's --ink (#1A1815) by design */
  --ink-site-2: #6a635a;   /* marketing secondary text. Near-identical
                              to the app's --ink-muted, and nothing like
                              the app's --ink-2 (#3e3a32). */

  /* THE MARKETING SERIF — v1's static-page stack, restored 2026-07-30.
     v1 ran TWO typographic systems and only one of them was deliberate.
     The app loads the licensed faces and sets the manuscript in Tiempos
     Text. The static pages hardcoded this Charter-first stack and
     loaded NO webfonts at all, so the marketing site has always been
     set in whatever serif the visitor happened to have.
     The owner likes that lighter, wider result, so it is now a NAMED
     token rather than an accident — see ARCHITECTURE.md §7.9.
     RESOLVED 2026-07-31 — the platform split is closed. Charter is a
     macOS system face, so Windows and Android visitors were falling to
     Georgia and seeing a materially different page. XCharter (an open,
     actively maintained cut of Bitstream Charter) now SHIPS, so every
     visitor gets the same page. Local 'Charter' stays second: a Mac
     that already has it paints instantly with no network fetch, and
     the two are the same design.

     Only 400 and 700 are declared, which reproduces macOS Charter's
     behaviour exactly rather than approximating it. CSS font matching
     resolves the marketing surface's weights the same way in both:
     500 -> 400 (Roman), 600 -> 700 (Bold). No synthetic bolding. */
  --prose-site: 'XCharter', 'Charter', 'Iowan Old Style', 'Palatino',
             Georgia, 'Times New Roman', serif;

  /* THE MANUSCRIPT SERIF — what a writer's own words are set in.
     Unchanged from v1 and not to be conflated with --prose-site. */
  --prose:   'Tiempos Text', 'Tiempos', 'Charter', 'Iowan Old Style',
             Georgia, 'Times New Roman', serif;
  --sans:    'Söhne', 'Sohne', -apple-system, BlinkMacSystemFont,
             'Helvetica Neue', Arial, system-ui, sans-serif;
  --hand:    'Caveat', 'Bradley Hand', 'Marker Felt', cursive;
  --mono:    'JetBrains Mono', 'SF Mono', ui-monospace, 'Menlo', monospace;

  /* ── Paper scale — light to dark, one naming convention ─────────────
     --paper    page ground
     --paper-2  raised panel / hover fill / chat bubble
     --paper-3  rail, card edge, the darkest cream
     --paper-2 is the exact midpoint of --paper and --paper-3, so the
     scale is derived rather than eyeballed. It replaces the old landing
     value #f6f0e3, which was mixed on a #fcf9f1 base that no longer
     exists. */
  --paper:   #FAF8F2;
  --paper-2: #F3F0E9;
  --paper-3: #ece8df;

  /* ── Ink scale — darkest to faintest ───────────────────────────────
     --ink        primary body text
     --ink-2      secondary heading / strong body
     --ink-soft   tertiary
     --ink-muted  captions, ledes, eyebrows, field labels — THE FAINTEST
                  TEXT COLOUR THERE IS. Nothing below it may hold text.
     --ink-muted is the step the app never had a root name for: it was
     written out as the literal #6a655e 800+ times across the static
     pages and declared once, privately, in style.css's LTI block.

     WHERE --ink-fade WENT (2026-07-31)
     ----------------------------------
     There used to be a fifth step, `--ink-fade: #8a857a`, carrying the
     comment "sub-AA by design — never use it for reading text." It was
     referenced 387 times and 366 of those were `color:` — i.e. text.
     A rule nothing enforces is a rule that loses, and this one lost by
     a factor of thirty.

     The obvious repair — darken it until it passes — does not exist.
     Solving #8a857a down its own hue until it clears 4.5:1 on the
     darkest cream ground (--paper-3) lands on #6c685f. That is
     --ink-muted (#6a655e) to within two units of each channel. Dark
     theme gives the same answer from the other direction: #6a6759
     brightens to #83806e, and --ink-muted there is #888372.

     So the scale has FOUR text steps, not five. There is no room on
     cream for a fifth that a person can read. The value survives for
     the fifteen borders and backgrounds that legitimately used it, as
     --chrome-fade below, where no contrast minimum applies and the
     name no longer invites text.

     scripts/check-contrast.mjs enforces this from the tokens file. */
  --ink:       #1A1815;
  --ink-2:     #3e3a32;
  --ink-soft:  #4a463f;
  --ink-muted: #6a655e;

  /* ── Hairlines ─────────────────────────────────────────────────────
     Solid, not translucent. The old landing --rule/--rule-2 were
     rgba() over cream, which blends to almost exactly these values for a
     single border but DOUBLES where two borders meet (adjacent cells,
     stacked rows). Solid keeps every hairline the same weight, and — the
     reason this matters — it themes: a black-alpha rule is invisible on
     a dark ground, which is what the app's 84 --rule references were
     silently doing in dark mode. */
  --hair:        #e3ddce;
  --hair-strong: #c9c1ad;

  /* Retired --ink-fade, kept for the borders and fills that used it.
     NOT A TEXT COLOUR — it is 3.46:1 on --paper and 3.00:1 on
     --paper-3, and check-contrast.mjs will fail the build if it ever
     appears after `color:`. Borders and backgrounds carry no 4.5:1
     minimum, which is why these fifteen uses were never the problem. */
  --chrome-fade: #8a857a;

  /* ── Editorial red (proofreader's pen — Pilot G-2 red) ──────────────
     --red-soft is a SOLID lighter red. The translucent tint is
     --red-wash. These are two different things and were the single most
     dangerous name collision in the pre-unification palette: landing's
     --red-soft was a 10% wash, the app's is #d96b62.

     --red WAS #B83A2C, v1's pen, and it is 4.67:1 on --paper-3 — under
     five percent of headroom over AA. The app draws red text on a red
     wash in about a dozen hover and active states (`.doc-row.is-active
     .doc-link`, `.pal-btn:hover`, the context menus), and any tint at
     all spends that headroom: the active document title in the sidebar
     measured 4.18:1. Thinning --red-wash does not rescue it — even at
     2% alpha, which is invisible, the pairing only reaches 4.54.

     So the pen is 5% darker: #af372a. That is a ΔE of about 2 against
     the old value — below the threshold most people can see even side
     by side — and it puts every red-on-red-wash pairing over 4.5:1
     without touching 340 call sites. --red-soft is unused as text (it
     styles three dashed borders), so its 3.17:1 is not a violation.
     src/app/io/pdf/render.ts carries the same value for exported
     proofs and was updated with it. */
  --red:      #af372a;
  --red-soft: #d96b62;
  --red-deep: #8c1a13;
  --red-wash: rgba(175, 55, 42, 0.08);

  --accent:    #2b4a6f;   /* deep navy for UI accents */
  --highlight: #fff7b0;

  /* ── Tone palette — badges, pills, status ──────────────────────────
     Also the verifier's pass/warn colours: --tone-good and --tone-warn
     are what verify/ and viewer/ used to call --green and --amber.

     A tone is almost always drawn as ITS OWN COLOUR ON ITS OWN WASH —
     `.conf-moderate { background: --tone-warn-wash; color: --tone-warn }`
     — and a wash tints the ground TOWARD the text, so the pill is the
     hardest ground the tone ever sits on, not the easiest. Measured
     against that pairing on --paper-3 (2026-07-31), good was 4.34:1,
     danger 3.82:1 and warn 3.58:1. All three were AA failures on
     status labels, which are exactly the words that must not be
     guessable-at.

     Corrected values below hold ≥4.5:1 against their own wash over
     every paper ground. Dark theme was already clear (4.94–6.42) and
     is unchanged. check-contrast.mjs computes the pairing rather than
     trusting this comment. */
  --tone-info:        #2b4a6f;   /* 6.37:1 worst-case */
  --tone-info-wash:   rgba(43, 74, 111, 0.10);
  --tone-good:        #38684a;   /* was #3a6b4c — 4.34:1 */
  --tone-good-wash:   rgba(56, 104, 74, 0.12);
  --tone-warn:        #8d4c1a;   /* was #a85a1f — 3.58:1 */
  --tone-warn-wash:   rgba(141, 76, 26, 0.12);
  --tone-danger:      #9f4234;   /* was #b34a3a — 3.82:1 */
  --tone-danger-wash: rgba(159, 66, 52, 0.10);

  --shadow-2: 0 10px 30px rgba(28, 26, 23, 0.10), 0 1px 0 rgba(28, 26, 23, 0.04);

  /* ── App layout + motion ───────────────────────────────────────────
     Consumed only by style.css, but they are token values, so they live
     where token values live. */
  --sidebar-w:  260px;
  --reader-max: 38rem;   /* ~640px → ~65 chars/line, the classic book measure */
  --tb-h:       56px;
  /* Status-bar (footer) height, and the bottom offset every bottom-left
     floating surface uses to sit CLEAR of it. The footer is a real grid
     row at the bottom of the viewport, so a bare `bottom: 24px` put the
     review FAB and panel on top of it. Anything anchored bottom-left
     should use --fab-bottom, not a raw 24px. */
  --status-h:   36px;
  --fab-bottom: calc(var(--status-h) + 20px);

  --t-fast: 120ms cubic-bezier(.2,.7,.3,1);
  --t-med:  220ms cubic-bezier(.2,.7,.3,1);
}

/* ── Dark theme ───────────────────────────────────────────────────────
   App-only in practice: the static site never sets data-theme, so these
   never match there.

   `:not(.lti-iframe-mode)` is how the LMS iframe stays light. style.css
   used to re-declare eight light values with !important to force it;
   excluding the class here means the light :root above simply applies,
   and those eight duplicated values are gone.

   Tuned for long-form reading: ink is warm cream rather than near-white,
   and the red is a softer terracotta that still reads as a proofreader's
   pen but doesn't burn at small sizes. */
body[data-theme="dark"]:not(.lti-iframe-mode) {
  --paper:   #15171a;
  --paper-2: #121417;
  --paper-3: #0f1114;

  --ink:       #d4ccba;   /* warm cream, not white */
  --ink-2:     #b5ad9b;
  --ink-soft:  #95907f;
  --ink-muted: #888372;   /* 4.73:1 on --paper — the faintest text step */

  --hair:        #2a2d33;
  --hair-strong: #3a3e46;
  --chrome-fade: #6a6759;   /* borders/fills only — 3.16:1, not text */

  /* Terracotta, 7% brighter than the #c75d54 this used to be: on a dark
     ground the wash pairing needs MORE luminance, not less, and the old
     value sat at 4.05:1 on its own wash. */
  --red:      #d5645a;
  --red-soft: #d27a72;
  --red-deep: #e29a93;
  --red-wash: rgba(213, 100, 90, 0.08);

  --accent:    #7ea2cf;
  --highlight: #3d3308;

  --tone-info:        #7ea2cf;
  --tone-info-wash:   rgba(126, 162, 207, 0.12);
  --tone-good:        #86b795;
  --tone-good-wash:   rgba(134, 183, 149, 0.12);
  --tone-warn:        #d19a63;
  --tone-warn-wash:   rgba(209, 154, 99, 0.12);
  --tone-danger:      #d27a72;
  --tone-danger-wash: rgba(210, 122, 114, 0.12);

  --shadow-2: 0 10px 30px rgba(0,0,0,0.5), 0 1px 0 rgba(255,255,255,0.04);
}
