/* tokens.css — the whole design system as custom properties.
 *
 * NO SELECTORS OTHER THAN :root. If you find yourself writing a component
 * selector here, it belongs in one of the sheets after this one. The map of
 * those sheets, and the classes a page is allowed to build from, is
 * css/README.md — read it before adding a page.
 *
 * ---------------------------------------------------------------------------
 * THE LANGUAGE: "BABEL", A BROADSHEET
 *
 * The site reads as a newspaper about the practice floor: a nameplate, a giant
 * numeral that is the day's headline, ruled columns, small-caps kickers over
 * every section. Three faces, three voices:
 *
 *   --font-display  Fraunces — the publication speaking. Headlines, section
 *                   titles, group topics. Variable: opsz 9-144, wght 300-900,
 *                   italic. Optical sizing is automatic, so the same face is
 *                   a hairline-serif at 120px and a sturdy text face at 17px.
 *   --font-text     Bricolage Grotesque — claims made in words, every caveat,
 *                   every control. Variable width (75-100) and weight.
 *   --font-mono     anything we MEASURED, and the small-caps kickers. A number
 *                   that is not a measurement (a page number, a tab count) is
 *                   not mono; a kicker is mono but always set in caps, so the
 *                   two can never be confused.
 *
 * Both webfonts come from ONE Google Fonts <link> in index.html (preconnect +
 * display=swap). Each has a metric-matched local fallback in base.css so the
 * swap reflows as little as possible.
 *
 * ---------------------------------------------------------------------------
 * THE THEME CONTRACT
 *
 * An inline script in index.html stamps a resolved `data-theme="light"` or
 * `data-theme="dark"` on <html> BEFORE first paint, and app.js keeps it in step
 * with the stored preference and the OS query. That is why the dark values
 * are written exactly once below instead of twice (once under @media, once
 * under [data-theme]).
 *
 * If that stamp ever stops happening, the page falls back to the LIGHT theme,
 * which is a legible, complete, intentional design — not a broken one.
 *
 * The two themes are NOT inversions of each other:
 *   light is paper — the morning edition. Structure comes from RULES drawn on
 *          one warm newsprint ground with a faint grain; surfaces sit ~1.7
 *          points off --bg; the accent is dark prussian ink that ABSORBS.
 *   dark  is ink   — the night edition. A hairline vanishes on a dark ground,
 *          so structure comes from SURFACE STEPS (~4.5 points, three times the
 *          light step) plus a brighter rule; the accent is light, higher-chroma
 *          and EMITS (--glow-*). Lifted surfaces get a 1px specular top line
 *          (--halo); paper has no such edge, so in light --halo is transparent.
 *
 * That asymmetry is expressed entirely here, so no component knows about it.
 * ---------------------------------------------------------------------------
 */

:root, :root[data-theme='light'] {
  color-scheme: light;

  /* ---- ground: warm newsprint. The warm cast is identity — it is why a
   * screenshot of this does not look like every other blue-grey dashboard. */
  --bg:          oklch(97.5% 0.006 85);
  --bg-sunk:     oklch(94.8% 0.008 85);   /* insets, drawers, sticky axis rows */
  --surface:     oklch(99.2% 0.003 85);   /* lifted: popover, readout, drawer  */
  --surface-2:   oklch(95.6% 0.008 85);   /* filled chip, meter track          */
  --halo:        transparent;             /* paper has no specular edge        */

  /* The grain. A 180px tile of fractal noise at 7% ink, so the page reads as
   * printed stock rather than a flat screen. Night edition has none. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .32 0 0 0 0 .27 0 0 0 0 .2 0 0 0 .07 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  /* The hero's sky: nothing on paper; a low lamp in the night edition. */
  --sky: none;

  /* ---- rules: the structural device in this theme */
  --rule:        oklch(76% 0.012 85);     /* 1px section rule                  */
  --rule-soft:   oklch(86% 0.010 85);     /* 1px row rule                      */
  --rule-hair:   oklch(91% 0.008 85);     /* grid lines, dotted leaders        */
  --rule-strong: oklch(40% 0.012 70);     /* 2px emphasis rule / active tab    */

  /* ---- ink */
  --ink:         oklch(21% 0.014 65);     /* 16.5:1 on --bg                    */
  --ink-2:       oklch(44% 0.012 65);     /*  7.2:1 — secondary prose          */
  --ink-3:       oklch(52% 0.010 65);     /*  5.1:1 — muted. AA for body text. */
  --ink-wash:    oklch(21% 0.014 65 / 0.055);  /* hover fill                   */
  --ink-wash-2:  oklch(21% 0.014 65 / 0.10);   /* pressed / selected row       */

  /* ---- semantics. THE MEANINGS ARE FROZEN; only the values move.
   *   --live  observed and current
   *   --warn  stale or approximate
   *   --void  truncated / never measured
   * Nothing else on this site encodes meaning in colour. A group property is
   * not a measurement, so it may never take one of these — see --attr-* below. */
  --live:        oklch(48% 0.14 158);     /* 6.1:1  observed & current         */
  --live-quiet:  oklch(80% 0.07 158);     /* fills and strokes, never text     */
  --warn:        oklch(47% 0.13 62);      /* 6.4:1  stale / approximate        */
  --warn-quiet:  oklch(92% 0.05 62);
  --void:        oklch(52% 0.004 70);     /* 5.1:1  not measured. The only
                                           * near-zero-chroma grey on a warm
                                           * page — that IS the signal.        */
  --danger:      oklch(48% 0.18 27);

  /* ---- accent: prussian ink. Interactive affordance and the headline
   * numeral only, never a large fill behind text other than --on-accent. */
  --accent:      oklch(38% 0.11 258);     /* 9.3:1 as a link on --bg           */
  --accent-soft: oklch(94% 0.022 258);
  --accent-line: oklch(72% 0.070 258);
  --on-accent:   oklch(99% 0.003 85);

  /* Emission. Paper does not glow, so both are none here. */
  --glow:     none;                       /* a figure or a live word           */
  --glow-hot: none;                       /* the ONE headline numeral          */

  /* ---- data ink: sequential magnitude, DERIVED so it can never drift from
   * --accent. Used by the year grid and the week grid only. */
  --data-1: color-mix(in oklab, var(--accent) 18%, var(--bg));
  --data-2: color-mix(in oklab, var(--accent) 36%, var(--bg));
  --data-3: color-mix(in oklab, var(--accent) 56%, var(--bg));
  --data-4: color-mix(in oklab, var(--accent) 78%, var(--bg));
  --data-5: var(--accent);

  --shadow-lift: 0 1px 0 var(--rule), 0 10px 24px -18px oklch(30% 0.02 70 / 0.35);
  /* The hover lift on a card: a deeper, softer throw than a popover. */
  --shadow-hover: 0 1px 0 var(--rule), 0 18px 30px -22px oklch(30% 0.03 70 / 0.55);
}

:root[data-theme='dark'] {
  color-scheme: dark;

  /* Warm ink, hue 75-80. Deliberately NOT the light palette inverted: the
   * surface STEPS are ~3x larger, because a hairline cannot carry structure on
   * a dark ground and the eye loses a low-contrast line faster here. */
  --bg:          oklch(15.5% 0.008 78);
  --bg-sunk:     oklch(12.5% 0.008 78);
  --surface:     oklch(20.0% 0.010 78);   /* +4.5 pts, vs +1.7 in light        */
  --surface-2:   oklch(25.0% 0.011 78);
  --halo:        oklch(100% 0 0 / 0.055); /* 1px inset top line on lifts       */

  --grain: none;
  --sky:
    radial-gradient(60% 55% at 72% 42%, color-mix(in oklab, var(--accent) 10%, transparent), transparent 72%),
    radial-gradient(120% 70% at 50% 120%, var(--bg-sunk), transparent 60%);

  --rule:        oklch(40% 0.012 78);
  --rule-soft:   oklch(30% 0.010 78);
  --rule-hair:   oklch(25% 0.009 78);
  --rule-strong: oklch(80% 0.010 80);

  --ink:         oklch(93% 0.006 80);     /* 15.9:1 */
  --ink-2:       oklch(75% 0.010 80);     /*  8.8:1 */
  --ink-3:       oklch(62% 0.011 80);     /*  5.4:1 */
  --ink-wash:    oklch(100% 0 0 / 0.050);
  --ink-wash-2:  oklch(100% 0 0 / 0.090);

  --live:        oklch(76% 0.16 158);     /* 9.1:1  */
  --live-quiet:  oklch(46% 0.10 158);
  --warn:        oklch(79% 0.14 72);      /* 10.1:1 */
  --warn-quiet:  oklch(34% 0.07 62);
  --void:        oklch(62% 0.004 80);     /*  5.4:1 */
  --danger:      oklch(72% 0.17 25);

  --accent:      oklch(76% 0.10 250);     /* 9.1:1 — light and emitting        */
  --accent-soft: oklch(27% 0.038 250);
  --accent-line: oklch(46% 0.075 250);
  --on-accent:   oklch(15.5% 0.008 78);

  --glow:     0 0 0.55em color-mix(in oklab, var(--accent) 34%, transparent);
  --glow-hot: 0 0 0.4em color-mix(in oklab, var(--accent) 60%, transparent),
              0 0 1.6em color-mix(in oklab, var(--accent) 38%, transparent);

  --data-1: color-mix(in oklab, var(--accent) 16%, var(--bg));
  --data-2: color-mix(in oklab, var(--accent) 32%, var(--bg));
  --data-3: color-mix(in oklab, var(--accent) 52%, var(--bg));
  --data-4: color-mix(in oklab, var(--accent) 74%, var(--bg));
  --data-5: var(--accent);

  --shadow-lift: 0 0 0 1px oklch(0% 0 0 / 0.5), 0 14px 30px -20px oklch(0% 0 0 / 0.8);
  --shadow-hover: 0 0 0 1px oklch(0% 0 0 / 0.5), 0 20px 36px -22px oklch(0% 0 0 / 0.9),
                  inset 0 1px 0 var(--halo);
}

/* ===========================================================================
 * Everything below is theme-independent.
 * ======================================================================== */

:root {
  /* ---- typography: three faces doing three different jobs (see header). */
  --font-display:
    "Fraunces", "Fraunces Fallback", ui-serif, "Iowan Old Style", Charter,
    Cambria, "Noto Serif", Georgia, serif;
  --font-text:
    "Bricolage Grotesque", "Bricolage Fallback", ui-sans-serif, system-ui,
    -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono:
    ui-monospace, "SF Mono", "Cascadia Mono", "JetBrains Mono", "Roboto Mono",
    Menlo, Consolas, monospace;

  /* Fluid between a 360px and a 1600px viewport. BODY IS NEVER UNDER 16px.
   * Every text-ladder clamp keeps a rem intercept >= 60% of its floor, so a
   * reader who raises their browser font size still moves the text (WCAG
   * 1.4.4). The two display-only tokens at the end relax that to ~50%: they
   * are single short lines whose floor is already 42px. */
  --t-3xs:    clamp(0.6875rem, 0.679rem + 0.04vw, 0.719rem);   /* 11  -> 11.5 */
  --t-2xs:    clamp(0.750rem,  0.741rem + 0.04vw, 0.781rem);   /* 12  -> 12.5 */
  --t-xs:     clamp(0.8125rem, 0.804rem + 0.04vw, 0.844rem);   /* 13  -> 13.5 */
  --t-sm:     clamp(0.906rem,  0.897rem + 0.04vw, 0.9375rem);  /* 14.5 -> 15  */
  --t-base:   clamp(1.000rem,  0.973rem + 0.12vw, 1.094rem);   /* 16  -> 17.5 */
  --t-lg:     clamp(1.125rem,  1.089rem + 0.16vw, 1.250rem);   /* 18  -> 20   */
  --t-xl:     clamp(1.250rem,  1.159rem + 0.40vw, 1.5625rem);  /* 20  -> 25   */
  --t-2xl:    clamp(1.500rem,  1.319rem + 0.81vw, 2.125rem);   /* 24  -> 34   */
  --t-3xl:    clamp(1.875rem,  1.476rem + 1.77vw, 3.250rem);   /* 30  -> 52   */
  --t-display:clamp(2.500rem,  1.556rem + 4.19vw, 5.750rem);   /* 40  -> 92   */
  --t-headline: clamp(2.625rem, 1.356rem + 5.65vw, 7.000rem);  /* 42  -> 112  */

  /* The FIGURE ladder is separate from the text ladder on purpose. A
   * measurement at 30px and a heading at 30px are not the same object and must
   * not share a token — sharing one is how `31h together` ended up louder than
   * the number its list was actually sorted by. */
  --t-fig-giant: clamp(2.750rem, 1.371rem + 6.13vw, 7.500rem); /* 44 -> 120 */
  --t-fig-xl: clamp(2.000rem, 1.492rem + 2.26vw, 3.750rem);    /* 32 -> 60  */
  --t-fig-lg: clamp(1.375rem, 1.194rem + 0.81vw, 2.000rem);    /* 22 -> 32  */
  --t-fig:    clamp(1.0625rem, 1.008rem + 0.24vw, 1.250rem);   /* 17 -> 20  */
  --t-fig-sm: var(--t-sm);

  --lh-tight:  1.02;   /* display and h1 — Fraunces at 44px+ sets tight      */
  --lh-snug:   1.22;   /* h2, h3, ranked-row titles                          */
  --lh-normal: 1.55;   /* body prose                                         */
  --lh-relax:  1.65;   /* long caveat passages and the method aside          */
  --lh-flat:   0.9;    /* figures — a figure's box is its digits             */

  --tr-display: -0.028em;
  --tr-title:   -0.014em;
  --tr-body:     0;
  --tr-fig:     -0.04em;   /* mono lining figures are wide; pull them together */
  --tr-label:    0.12em;   /* kicker caps. THE ONLY letterspaced uppercase
                            * micro-type on the site. Units are never set this
                            * way. */

  /* ---- spacing: one 4px-rooted scale plus three fluid tokens.
   * No component invents a value. */
  --s-1:  0.25rem;
  --s-2:  0.5rem;
  --s-3:  0.75rem;
  --s-4:  1rem;
  --s-5:  1.5rem;
  --s-6:  2rem;
  --s-7:  3rem;
  --s-8:  4rem;
  --s-9:  6rem;

  --s-gutter:  clamp(1rem, 0.710rem + 1.29vw, 2rem);   /* 16 -> 32 page edge   */
  --s-section: clamp(3rem, 2.129rem + 3.87vw, 6rem);   /* 48 -> 96 band rhythm */
  --s-lede:    clamp(2rem, 1.290rem + 3.16vw, 5rem);   /* 32 -> 80 around the
                                                        *          display     */

  /* ---- radii. Editorial print has no rounded rectangles; radius is reserved
   * for things that are physically round or physically a pill. */
  --r-0:    0;
  --r-1:    2px;
  --r-2:    4px;
  --r-3:    8px;
  --r-full: 999px;

  --bw-1: 1px;
  --bw-2: 2px;
  --bw-4: 4px;

  /* Named rules, so a component never composes colour + width itself. */
  --edge-hair:   var(--bw-1) solid var(--rule-hair);
  --edge-soft:   var(--bw-1) solid var(--rule-soft);
  --edge:        var(--bw-1) solid var(--rule);
  --edge-strong: var(--bw-2) solid var(--rule-strong);
  /* The broadsheet section head: a 4px double rule, as over a newspaper page. */
  --edge-double: var(--bw-4) double var(--rule-strong);

  /* ---- motion. Nothing on this site eases linearly EXCEPT a continuous
   * turntable (the home helix), where any curve would stutter once a lap. */
  --dur-1: 90ms;    /* pointer feedback: hover wash, press          */
  --dur-2: 160ms;   /* state change: tab, segment, popover          */
  --dur-3: 260ms;   /* entrance: route settle, band reveal          */
  --dur-4: 440ms;   /* data reveal: bar grow, line draw, meter fill */
  --dur-5: 900ms;   /* the one-off: headline letters, helix arrive  */

  --ease-out:  cubic-bezier(0.16, 1, 0.30, 1);   /* arrive: decelerate hard  */
  --ease-snap: cubic-bezier(0.20, 0, 0, 1);      /* commit: no overshoot     */
  --ease-both: cubic-bezier(0.65, 0, 0.35, 1);   /* reversible: tab underline */
  /* A damped spring for lifts. Where linear() is unsupported the transition
   * falls back to `ease`, which is a fine lift too. */
  --ease-spring: linear(0, 0.009, 0.035 2.1%, 0.141 4.4%, 0.723 12.9%, 0.938 16.7%,
    1.017 19.4%, 1.067, 1.089 25.2%, 1.079 28.4%, 1.049 32.4%, 0.988 43.4%,
    0.991 51.4%, 1.001 66.2%, 1);

  /* The lift: how far a card rises under the pointer. Hover only — records
   * never shrink when pressed. */
  --lift: -3px;

  /* ---- data-viz. The site's job is numbers, so the chart system gets tokens
   * rather than per-module values.
   *
   * RULE: a chart may use --ink at varying opacity, the --data-1..5 ramp, and
   * the three semantic colours. It MAY NOT introduce a hue. Category is
   * encoded by position, by label, by TYPE (weight, width, slant), or by the
   * deterministic per-person hash — never by a palette. */
  --viz-grid:    var(--rule-hair);      --viz-grid-w:   0.5;
  --viz-axis:    var(--rule);           --viz-axis-w:   1;
  --viz-series:  var(--ink);            --viz-series-w: 1.75;
  --viz-series-2: var(--ink-3);         /* second series: dashed, never a hue */
  --viz-emph:    var(--accent);         /* the one highlighted mark           */
  --viz-tick-size: var(--t-3xs);
  --viz-font:    var(--font-mono);
  --viz-cell-r:  1;
  --viz-bar-h:   9px;
  --viz-lane-h:  28px;
  --viz-now:     var(--live);           --viz-now-w:    1.5;
  --viz-hatch:   var(--void);

  /* ---- the home helix (css/home.css). Size encodes people, sqrt of the
   * head count so a glyph's AREA grows with it; nothing else is encoded. */
  --helix-r:      clamp(8.5rem, 4rem + 11vw, 15rem);  /* radius of the turn    */
  --helix-pitch:  1.2rem;                             /* rise per group        */
  --helix-per-turn: 9;                                /* groups per lap: 40deg */
  --helix-ink:    var(--ink);
  --helix-far:    0.1;                                /* opacity at the back   */

  /* ---- group attribute marks. ACHROMATIC BY RULE.
   *
   * A group property is not a warning, not a measurement and not a liveness
   * state, so it may not borrow --warn, --live or --void. It earns attention
   * through weight, a drawn glyph and position — never colour. */
  --attr-ink:        var(--ink-2);
  --attr-ink-strong: var(--ink);
  --attr-line:       var(--rule);
  --attr-fill:       var(--ink-2);
  --attr-gap:        var(--s-2);
  --attr-glyph:      12px;
  --attr-glyph-w:    1.4;

  /* ---- freshness: the "as of" ladder.
   *
   * Five states, and only ONE of them is coloured green and one amber. The
   * whole point is that the age of a figure is ALWAYS on the page, so its mere
   * presence carries no alarm — the wording escalates, the colour does not,
   * until genuine staleness. */
  --fresh-live:    var(--live);
  --fresh-quiet:   var(--ink-3);
  --fresh-stale:   var(--warn);
  --fresh-unknown: var(--void);
  --fresh-track:   var(--rule-soft);
  --fresh-fill:    var(--ink-3);
  --fresh-bar-w:   3rem;
  --fresh-bar-h:   2px;

  /* ---- layout tracks. Content at maximum is
   *      152 + 720 + 320 + (2 x 32 gap) = 1256px.
   * Every pixel between the margins belongs to a named track. */
  --w-rail:  9.5rem;   /* 152px — section label, rank, date, legend key */
  --w-main: 45rem;     /* 720px — the primary column                    */
  --w-aside:20rem;     /* 320px — the method column                     */
  --w-text: 36rem;     /* 576px — prose measure, ~64 characters at 17px  */
  --w-page: 78.5rem;   /* 1256px — rail + main + aside + two gaps       */
  --col-gap: var(--s-gutter);

  /* px, NOT ch. `ch` resolves against the element's own font, so a rank column
   * sized in ch is a different width in the mono row and the sans header and
   * every figure slides out of alignment. */
  --w-rank: 2.25rem;
  --w-fig:  9rem;

  --topbar-h: 3.75rem;

  /* Breakpoints. Media queries cannot read custom properties: the literals in
   * the sheets ARE the source of truth and these exist only as documentation.
   *   >= 1100    full three-track: rail label, content, method note
   *   720-1099   two tracks; the aside drops below its band as a ruled note
   *   480-719    one track; the rail label becomes an eyebrow; no helix
   *   < 480      as above, gutter at its 16px floor, narrower rank column */
  --bp-sm: 30rem;      /* 480  */
  --bp-md: 45rem;      /* 720  */
  --bp-lg: 68.75rem;   /* 1100 */
}
