/* AUTO-GENERATED by `make css`. Do not edit — edit static/assets/css/claysa/ instead. */
/* ============================================================
   Clay's Almanac — Design Tokens
   Source file 1 of N — see docs/DESIGN.md for the full contract.
   Concatenated by `make css` into static/assets/css/claysa.bundle.css.
   Do not edit the bundle directly; edit files in this directory.
   ============================================================ */

/* ------------------------------------------------------------
   Breakpoint scale — REFERENCE ONLY.
   CSS custom properties cannot be substituted into an `@media`
   condition, so this scale is documented here (and in
   docs/DESIGN.md) and must be reproduced as literal values in
   every `min-width` query across the codebase. Mobile-first:
   base styles are unqualified; every query below adds width.

     sm   30em   ( 480px)  large phones / phones landscape
     md   48em   ( 768px)  tablets
     lg   64em   (1024px)  small laptops, desktop
     xl   90em   (1440px)  large desktop

   Do not write a `max-width` query. Do not invent a fifth step
   without updating this comment and docs/DESIGN.md together.
   ------------------------------------------------------------ */

:root {
  /* ---- Paper palette primitives -------------------------- */
  --paper-ground:       #f2ece0; /* warm newsprint cream — never white */
  --paper-ground-2:     #e9e1d0; /* recessed card / alternating row */
  --paper-ink:          #1a1614; /* warm near-black — never #000 */
  --paper-ink-muted:    #52463d; /* secondary text, captions */
  --paper-rule:         #cbb8a0; /* hairline column rules — decorative, not text */
  --paper-accent:       #c8342f; /* pulp red — from the almanac cover banner */
  --paper-accent-text:  #aa2c27; /* pulp red, darkened for AA text/link use (5.76:1 on ground) */
  --paper-accent-ink:   #fbeee0; /* text/icon colour ON a pulp-red fill */
  --paper-accent-2:     #f5d229; /* cover yellow — sparing use, chip/stamp fills */

  /* ---- Board palette primitives --------------------------- */
  --board-ground:       #12101c; /* deep ink, warm/purple — not sportsbook blue-slate */
  --board-panel:        #1d1a2b; /* panel fill — reads as a lit bezel */
  --board-panel-2:      #262239; /* raised/hover panel variant */
  --board-hairline:     rgba(255, 255, 255, 0.08); /* panel top highlight */
  --board-ink:          #f2ece0; /* light warm ink on dark — mirrors --paper-ground on purpose */
  --board-ink-muted:    #a79db8; /* secondary text on Board */
  --board-correct:      #20e0b0; /* neon mint — status: correct */
  --board-miss:         #ff3d8b; /* hot magenta — status: miss */
  --board-gold:         #f7c948; /* best-model gold, the moneybag colour */

  /* ---- Tolerance-range chart — non-team primitives (Phase D) -------
     Team colour is decoration only in this chart (identity rides on
     position/label/texture — see sports.py chart_identity_colors());
     these are its structural colours: the overlap band is "a neutral
     texture, never a third hue" per the plan, so each is a tint of
     its own surface's neutral family, not a new hue. Aliased into
     --tol-overlap-bg/-ink per surface below, same pattern as
     --surface-*, so one definition works on both Paper and Board —
     validated by scripts/validate_palette.js. */
  --tol-overlap-paper:     #d9c9a8; /* warm tan, distinct from paper-ground/-ground-2/-rule */
  --tol-overlap-board:     #332e47; /* neutral violet-grey, distinct from board-panel/-panel-2 */

  /* ---- Status colours ------------------------------------
     FIXED. Never redefined inside a [data-skin] rule, and never
     consumed by a skin utility. Mint = correct, magenta = miss,
     under every surface and every team skin. See docs/DESIGN.md
     "Skin contract". */
  --status-correct: var(--board-correct);
  --status-miss:    var(--board-miss);
  --status-best:    var(--board-gold);

  /* ---- Type families --------------------------------------
     Loaded via Google Fonts stylesheet link in layouts/base.html
     (same pattern already used by templates/followme.html and
     templates/security/*.html) — a stylesheet link, not a JS
     framework, so it costs one small request, not a render-
     blocking script. Solid system stacks follow every family so
     the page is fully legible before/without the fetch. */
  --font-display: 'Oswald', 'Arial Narrow', Arial, sans-serif;
  --font-body:    'PT Serif', Georgia, 'Times New Roman', serif;
  --font-mono:    'Courier Prime', 'Courier New', Courier, monospace;

  /* ---- Type scale (fluid, mobile-first clamp) -------------- */
  --text-xs:   clamp(0.75rem,  0.72rem + 0.15vw, 0.8rem);
  --text-sm:   clamp(0.875rem, 0.84rem + 0.18vw, 0.95rem);
  --text-base: clamp(1rem,     0.95rem + 0.25vw, 1.125rem);
  --text-md:   clamp(1.125rem, 1.05rem + 0.35vw, 1.3rem);
  --text-lg:   clamp(1.375rem, 1.2rem  + 0.6vw,  1.75rem);
  --text-xl:   clamp(1.75rem,  1.5rem  + 1vw,    2.25rem);
  --text-2xl:  clamp(2.25rem,  1.9rem  + 1.5vw,  3rem);
  --text-3xl:  clamp(3rem,     2.4rem  + 2.5vw,  4.5rem);

  /* ---- Space scale (4px base unit) -------------------------- */
  --space-1: 0.25rem;  /*  4px */
  --space-2: 0.5rem;   /*  8px */
  --space-3: 0.75rem;  /* 12px */
  --space-4: 1rem;     /* 16px */
  --space-5: 1.5rem;   /* 24px */
  --space-6: 2rem;     /* 32px */
  --space-7: 3rem;     /* 48px */
  --space-8: 4rem;     /* 64px */
  --space-9: 6rem;     /* 96px */

  /* ---- Radii --------------------------------------------- */
  --radius-none: 0;
  --radius-sm:   0.25rem;
  --radius-md:   0.5rem;
  --radius-lg:   1rem;
  --radius-pill: 999px;

  /* ---- Motion -----------------------------------------------
     Board is where "all motion lives" per docs/DESIGN.md; Paper
     is deliberately restrained. Rather than branch every
     transition, components scale a shared base duration by
     --surface-motion, which surfaces set below (0 = off). */
  --motion-fast: 120ms;
  --motion-med:  240ms;
  --motion-slow: 480ms;
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --surface-motion: 1;

  /* ---- Skin tokens (chrome only — see docs/DESIGN.md) -----
     Default = the site's own brand colours, so an unskinned
     visitor still gets pulp red / cover yellow chrome. The team
     seeds themselves live in 03-skins.css, generated from the
     sports.py registry — do not hand-author a [data-skin] rule.
     Fills, borders and tints derive from these two via
     color-mix() against --surface-bg, so one definition adapts to
     both Paper and Board automatically. */
  --skin-1: var(--paper-accent);
  --skin-2: var(--paper-accent-2);

  /* Light text/icon colour for use ON a --skin-1 fill. A skin fill
     is a surface-independent block of colour (button fills, nav
     accents), so its foreground doesn't surface-swap the way
     --surface-accent-ink does — but it does have to follow the
     *seed*: this light default is right for pulp red and for most
     team primaries, and unreadable on the pale ones (the Aces'
     #a7a8aa). Each rule in 03-skins.css overrides it with a
     WCAG-checked pick for that seed. */
  --ink-on-skin: #fbeee0;

  /* Seeds used as TEXT (and as focus rings) can't be the raw brand
     colour: a navy primary on Board's near-black ground, or cover
     yellow on Paper's cream, is barely visible. These are the seed
     nudged toward the surface's own ink until it clears AA on that
     surface's ground — computed per team by sports.py's
     contrast_safe() and emitted into 03-skins.css, measured by hand
     here for the brand default. The surface groups below alias the
     right pair into --skin-1-on-surface / --skin-2-on-surface, so a
     component writes one token and gets the surface-correct value. */
  --skin-1-on-paper: var(--paper-accent-text); /* pulp red, already AA-tuned: 5.76:1 on ground */
  --skin-1-on-board: #cf544d;                  /* pulp red lifted for Board: 4.50:1 on ground */
  --skin-2-on-paper: #7c6a1d;                  /* cover yellow darkened for Paper: 4.53:1 (raw is 1.26:1) */
  --skin-2-on-board: var(--paper-accent-2);    /* cover yellow needs no help on Board: 12.66:1 */
}

/* ---- Surface groups -----------------------------------------
   Surface is a component-level default, not a page-level one:
   any element may carry data-surface="paper|board" regardless of
   what its ancestors carry (see docs/DESIGN.md "Composition
   rule"). :root defaults to Paper so an un-annotated page still
   renders sanely. */
:root,
[data-surface='paper'] {
  --surface-bg:        var(--paper-ground);
  --surface-bg-2:       var(--paper-ground-2);
  --surface-ink:        var(--paper-ink);
  --surface-ink-muted:  var(--paper-ink-muted);
  --surface-rule:       var(--paper-rule);
  --surface-accent:     var(--paper-accent);
  --surface-accent-text: var(--paper-accent-text);
  --surface-accent-ink: var(--paper-accent-ink);
  --surface-accent-2:   var(--paper-accent-2);
  --surface-panel-bg:   #ffffff;
  --surface-hairline:   rgba(26, 22, 20, 0.12);
  --surface-motion: 0.25; /* "almost none" — restraint is what makes Board feel alive */
  --tol-overlap-bg:     var(--tol-overlap-paper);
  --tol-overlap-ink:    var(--paper-ink);
  --skin-1-on-surface:  var(--skin-1-on-paper);
  --skin-2-on-surface:  var(--skin-2-on-paper);
}

[data-surface='board'] {
  --surface-bg:         var(--board-ground);
  --surface-bg-2:       var(--board-panel-2);
  --surface-ink:        var(--board-ink);
  --surface-ink-muted:  var(--board-ink-muted);
  --surface-rule:       var(--board-hairline);
  --surface-accent:     var(--board-gold);
  --surface-accent-text: var(--board-gold);
  --surface-accent-ink: var(--board-ground);
  --surface-accent-2:   var(--board-correct);
  --surface-panel-bg:   var(--board-panel);
  --surface-hairline:   var(--board-hairline);
  --surface-motion: 1; /* all motion lives here */
  --tol-overlap-bg:     var(--tol-overlap-board);
  --tol-overlap-ink:    var(--board-ink);
  --skin-1-on-surface:  var(--skin-1-on-board);
  --skin-2-on-surface:  var(--skin-2-on-board);
}

/* ---- Team skins ------------------------------------------------
   The four illustrative rules that used to sit here (LAL/BOS/GSW/
   LVA, bare abbreviations) are gone: every registered NBA and WNBA
   team now has a real one in 03-skins.css, generated from sports.py
   by `make css`. Keys there are sport-qualified — data-skin="nba-LAL",
   not "LAL" — because abbreviations collide across the two leagues.
   Do not hand-author a [data-skin] rule in either file; add the team
   to the registry and regenerate. */

/* ---- Skin utilities — chrome only -----------------------------
   Nav, buttons, links, focus rings, section rules, panel glow,
   masthead accent. NEVER apply these to game-card interiors or
   to anything carrying --status-correct / --status-miss / a
   graded result — those keep the real team colours or the fixed
   status colours, under every skin. */
.skin-tint {
  background: color-mix(in oklab, var(--skin-1) 18%, var(--surface-bg));
}
.skin-tint-strong {
  background: color-mix(in oklab, var(--skin-1) 36%, var(--surface-bg));
}
.skin-solid {
  background: var(--skin-1);
  color: var(--ink-on-skin);
}
/* Text and focus rings read the -on-surface pair, not the raw seed:
   the seed is a jersey colour, and half the registry's primaries are
   navies that vanish on Board's near-black ground. Fills, borders and
   tints above still use the raw seed — a block of colour doesn't have
   a contrast requirement against the surface, only against whatever
   sits on top of it (--ink-on-skin, likewise per-seed). */
.skin-text {
  color: var(--skin-1-on-surface);
}
.skin-accent-2 {
  color: var(--skin-2-on-surface);
}
.skin-border {
  border-color: var(--skin-1);
}
.skin-ring:focus-visible {
  outline: 2px solid var(--skin-2-on-surface);
  outline-offset: 2px;
}
/* ============================================================
   Clay's Almanac — Minimal modern reset
   Source file 2 of N — see docs/DESIGN.md.
   ============================================================ */

*, *::before, *::after {
  box-sizing: border-box;
}

* {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
}

html, body {
  height: 100%;
}

body {
  min-height: 100svh;
  line-height: 1.5;
  font-family: var(--font-body);
  background: var(--surface-bg);
  color: var(--surface-ink);
}

img, picture, svg, video, canvas {
  display: block;
  max-width: 100%;
}

input, button, textarea, select {
  font: inherit;
  color: inherit;
}

button {
  background: none;
  border: none;
  cursor: pointer;
}

a {
  color: inherit;
  text-decoration-thickness: from-font;
}

p, h1, h2, h3, h4, h5, h6 {
  overflow-wrap: break-word;
}

ul[class], ol[class] {
  list-style: none;
  padding: 0;
}

table {
  border-collapse: collapse;
  width: 100%;
}

/* One focus style everywhere, replaced by .skin-ring where a
   component wants the skin colour instead of the surface ink. */
:focus-visible {
  outline: 2px solid var(--surface-ink);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}
/* ============================================================
   Clay's Almanac — Layout primitives
   Source file 3 of N — see docs/DESIGN.md.
   Containers, grid, the deck, and the game rail. Mobile-first:
   base rules are the small-screen layout; min-width queries add
   columns as room appears. See 00-tokens.css for the breakpoint
   scale these literal widths reproduce.
   ============================================================ */

/* ---- Surface root -------------------------------------------
   `[data-surface]` re-declares --surface-* for everything inside
   it (00-tokens.css), but custom properties only cascade to
   descendants — an ancestor `<body>` never sees a value set on a
   child. So the element actually carrying data-surface (see
   layouts/paper.html / board.html) paints its own background and
   ink directly, rather than relying on `body` in 01-reset.css. */
.l-surface {
  min-height: 100svh;
  background: var(--surface-bg);
  color: var(--surface-ink);
  /* Ambient "what's actually behind a button here" token — see
     components/button.css. Custom properties inherit, so this is the
     correct default the moment any descendant is unwrapped page
     content; .panel/.status-card (closer ancestors) override it with
     their own fill wherever a button is nested inside one instead. */
  --btn-fill-ambient: var(--surface-bg);
  /* A block with no border/padding lets its first/last child's
     vertical margin collapse straight through it — here that means
     out past .l-surface into `<body>`, which paints Paper cream
     (data-surface isn't set on body, only on this div; see the
     comment above), showing as a stray coloured strip above any
     page whose first child carries a margin-top (e.g. .wizard-wrap).
     `flow-root` gives this element its own block-formatting context,
     which blocks that collapse — same bug class as the min-width
     note below, fixed at the primitive layer rather than patched
     per page. */
  display: flow-root;
}

/* Grid/flex items default to `min-width: auto`, i.e. they refuse to
   shrink below their content's min-content size. Nest a wide grid
   (e.g. .sg-swatch-grid on /styleguide, a game rail elsewhere) inside
   any of these as a grid/flex item and that min-content size
   propagates straight up into horizontal overflow on `body` — the
   exact bug class the mobile-first checklist in docs/DESIGN.md
   exists to catch. Belongs at the primitive layer, not patched per
   page. */
.l-container,
.l-stack,
.l-cluster,
.l-grid,
.l-deck,
.l-rail,
.l-surface {
  min-width: 0;
}

.l-container {
  width: 100%;
  max-width: 75rem; /* 1200px */
  margin-inline: auto;
  padding-inline: var(--space-4);
}

@media (min-width: 48em) {
  .l-container { padding-inline: var(--space-6); }
}

/* ---- Stack: vertical rhythm ---- */
.l-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.l-stack--tight { gap: var(--space-2); }
.l-stack--loose { gap: var(--space-6); }

/* ---- Cluster: wrapping horizontal group ---- */
.l-cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

/* ---- Grid: intrinsic column count, no breakpoint bookkeeping ---- */
.l-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
}
.l-grid--cards {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
}
.l-grid--2up {
  grid-template-columns: 1fr;
}
@media (min-width: 48em) {
  .l-grid--2up { grid-template-columns: repeat(2, 1fr); }
}

/* ---- The deck: vertical sections, mobile default ----
   Sport chapters, department sections, etc. stack top to bottom
   on narrow screens; widens into a grid once there's room. The
   card/game-rail children decide their own internal layout. */
.l-deck {
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
}

/* ---- Game rail: snap-scroll on mobile, grid on wide ----
   Narrow: a horizontally snapping, peeking row (the next card is
   partially visible as an affordance to swipe). Wide: an
   intrinsic grid, no scroll container at all. */
.l-rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(85%, 20rem);
  gap: var(--space-4);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: var(--space-2);
  scrollbar-width: thin;
}
.l-rail > * {
  scroll-snap-align: start;
}

@media (min-width: 64em) {
  .l-rail {
    grid-auto-flow: row;
    grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
    overflow-x: visible;
    scroll-snap-type: none;
  }
}

/* ---- Wide content (tables, diagrams) — scroll in place, never the body ---- */
.l-table-scroll {
  overflow-x: auto;
}

/* ---- Visually-hidden but reachable by assistive tech ---- */
.u-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
/* ============================================================
   Clay's Almanac — Team skins
   AUTO-GENERATED by `python scripts/gen_skins_css.py` (run for you
   by `make css`). Do not edit — edit the registry in sports.py, or
   the generator, and regenerate.

   One rule per registered team, keyed `data-skin="<sport-slug>-<abbr>"`
   because abbreviations collide across the registry (NBA and WNBA
   both have an ATL, CHI, DAL, IND, MIN, PHX, POR, TOR and WAS).
   Set server-side on <html> by layouts/base.html, so there's no flash
   of the default skin before a stylesheet or a script runs.

   Chrome only — docs/DESIGN.md §7's hard rule. Nothing here touches
   --status-correct / --status-miss, and nothing here is read by a
   game card's interior: those keep the real colours of the teams
   playing under every skin.

   --skin-1 / --skin-2 are the team's own brand seeds. The other three
   properties are computed per skin by sports.skin_css_vars() (WCAG
   contrast, not eyeballing) because one fixed foreground cannot serve
   both a #000000 seed and a #a7a8aa one — see the "Team skins" section
   of sports.py for what each is for.
   ============================================================ */

/* ---- NBA ----------------------------------------------------- */
[data-skin='nba-ATL'] { /* Atlanta Hawks */
  --skin-1: #e03a3e;
  --skin-2: #c1d32f;
  --ink-on-skin: #000000;
  --skin-1-on-paper: #c53538;
  --skin-2-on-paper: #6a7021;
  --skin-1-on-board: #e14144;
  --skin-2-on-board: #c1d32f;
}
[data-skin='nba-BOS'] { /* Boston Celtics */
  --skin-1: #007a33;
  --skin-2: #ba9653;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #007a33;
  --skin-2-on-paper: #80683c;
  --skin-1-on-board: #298e51;
  --skin-2-on-board: #ba9653;
}
[data-skin='nba-BKN'] { /* Brooklyn Nets */
  --skin-1: #000000;
  --skin-2: #ffffff;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #000000;
  --skin-2-on-paper: #6d6b6a;
  --skin-1-on-board: #7f7c75;
  --skin-2-on-board: #ffffff;
}
[data-skin='nba-CHA'] { /* Charlotte Hornets */
  --skin-1: #1d1160;
  --skin-2: #00788c;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #1d1160;
  --skin-2-on-paper: #00768a;
  --skin-1-on-board: #80779b;
  --skin-2-on-board: #208897;
}
[data-skin='nba-CHI'] { /* Chicago Bulls */
  --skin-1: #ce1141;
  --skin-2: #000000;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #ce1141;
  --skin-2-on-paper: #000000;
  --skin-1-on-board: #d74969;
  --skin-2-on-board: #7f7c75;
}
[data-skin='nba-CLE'] { /* Cleveland Cavaliers */
  --skin-1: #6f263d;
  --skin-2: #ffb81c;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #6f263d;
  --skin-2-on-paper: #8a6518;
  --skin-1-on-board: #a06f79;
  --skin-2-on-board: #ffb81c;
}
[data-skin='nba-DAL'] { /* Dallas Mavericks */
  --skin-1: #00538c;
  --skin-2: #b8c4ca;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #00538c;
  --skin-2-on-paper: #686c6e;
  --skin-1-on-board: #4a82a6;
  --skin-2-on-board: #b8c4ca;
}
[data-skin='nba-DEN'] { /* Denver Nuggets */
  --skin-1: #0e2240;
  --skin-2: #fec524;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #0e2240;
  --skin-2-on-paper: #84671b;
  --skin-1-on-board: #747d88;
  --skin-2-on-board: #fec524;
}
[data-skin='nba-DET'] { /* Detroit Pistons */
  --skin-1: #c8102e;
  --skin-2: #1d42ba;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #c8102e;
  --skin-2-on-paper: #1d42ba;
  --skin-1-on-board: #d44d5f;
  --skin-2-on-board: #6179c6;
}
[data-skin='nba-GSW'] { /* Golden State Warriors */
  --skin-1: #1d428a;
  --skin-2: #ffc72c;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #1d428a;
  --skin-2-on-paper: #83681f;
  --skin-1-on-board: #667da8;
  --skin-2-on-board: #ffc72c;
}
[data-skin='nba-HOU'] { /* Houston Rockets */
  --skin-1: #ce1141;
  --skin-2: #000000;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #ce1141;
  --skin-2-on-paper: #000000;
  --skin-1-on-board: #d74969;
  --skin-2-on-board: #7f7c75;
}
[data-skin='nba-IND'] { /* Indiana Pacers */
  --skin-1: #002d62;
  --skin-2: #fdbb30;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #002d62;
  --skin-2-on-paper: #876622;
  --skin-1-on-board: #677f98;
  --skin-2-on-board: #fdbb30;
}
[data-skin='nba-LAC'] { /* Los Angeles Clippers */
  --skin-1: #c8102e;
  --skin-2: #1d428a;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #c8102e;
  --skin-2-on-paper: #1d428a;
  --skin-1-on-board: #d44d5f;
  --skin-2-on-board: #667da8;
}
[data-skin='nba-LAL'] { /* Los Angeles Lakers */
  --skin-1: #552583;
  --skin-2: #f9a01b;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #552583;
  --skin-2-on-paper: #936118;
  --skin-1-on-board: #9070a6;
  --skin-2-on-board: #f9a01b;
}
[data-skin='nba-MEM'] { /* Memphis Grizzlies */
  --skin-1: #5d76a9;
  --skin-2: #12173f;
  --ink-on-skin: #000000;
  --skin-1-on-paper: #566b99;
  --skin-2-on-paper: #12173f;
  --skin-1-on-board: #657cac;
  --skin-2-on-board: #7b7b8a;
}
[data-skin='nba-MIA'] { /* Miami Heat */
  --skin-1: #98002e;
  --skin-2: #f9a01b;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #98002e;
  --skin-2-on-paper: #936118;
  --skin-1-on-board: #bc5f75;
  --skin-2-on-board: #f9a01b;
}
[data-skin='nba-MIL'] { /* Milwaukee Bucks */
  --skin-1: #00471b;
  --skin-2: #eee1c6;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #00471b;
  --skin-2-on-paper: #726a5e;
  --skin-1-on-board: #5c8666;
  --skin-2-on-board: #eee1c6;
}
[data-skin='nba-MIN'] { /* Minnesota Timberwolves */
  --skin-1: #0c2340;
  --skin-2: #236192;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #0c2340;
  --skin-2-on-paper: #236192;
  --skin-1-on-board: #747d88;
  --skin-2-on-board: #5281a4;
}
[data-skin='nba-NOP'] { /* New Orleans Pelicans */
  --skin-1: #0c2340;
  --skin-2: #c8102e;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #0c2340;
  --skin-2-on-paper: #c8102e;
  --skin-1-on-board: #747d88;
  --skin-2-on-board: #d44d5f;
}
[data-skin='nba-NYK'] { /* New York Knicks */
  --skin-1: #006bb6;
  --skin-2: #f58426;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #006bb6;
  --skin-2-on-paper: #a0591f;
  --skin-1-on-board: #2a82bd;
  --skin-2-on-board: #f58426;
}
[data-skin='nba-OKC'] { /* Oklahoma City Thunder */
  --skin-1: #007ac1;
  --skin-2: #ef3b24;
  --ink-on-skin: #ffffff;
  --skin-1-on-paper: #0270b1;
  --skin-2-on-paper: #c73421;
  --skin-1-on-board: #1283c3;
  --skin-2-on-board: #ef3b24;
}
[data-skin='nba-ORL'] { /* Orlando Magic */
  --skin-1: #0077c0;
  --skin-2: #000000;
  --ink-on-skin: #ffffff;
  --skin-1-on-paper: #026fb3;
  --skin-2-on-paper: #000000;
  --skin-1-on-board: #1883c3;
  --skin-2-on-board: #7f7c75;
}
[data-skin='nba-PHI'] { /* Philadelphia 76ers */
  --skin-1: #006bb6;
  --skin-2: #ed174c;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #006bb6;
  --skin-2-on-paper: #d21745;
  --skin-1-on-board: #2a82bd;
  --skin-2-on-board: #ed2757;
}
[data-skin='nba-PHX'] { /* Phoenix Suns */
  --skin-1: #1d1160;
  --skin-2: #e56020;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #1d1160;
  --skin-2-on-paper: #b14d1d;
  --skin-1-on-board: #80779b;
  --skin-2-on-board: #e56020;
}
[data-skin='nba-POR'] { /* Portland Trail Blazers */
  --skin-1: #e03a3e;
  --skin-2: #000000;
  --ink-on-skin: #000000;
  --skin-1-on-paper: #c53538;
  --skin-2-on-paper: #000000;
  --skin-1-on-board: #e14144;
  --skin-2-on-board: #7f7c75;
}
[data-skin='nba-SAC'] { /* Sacramento Kings */
  --skin-1: #5a2d81;
  --skin-2: #63727a;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #5a2d81;
  --skin-2-on-paper: #5f6d75;
  --skin-1-on-board: #9070a2;
  --skin-2-on-board: #727e84;
}
[data-skin='nba-SAS'] { /* San Antonio Spurs */
  --skin-1: #00b2a9;
  --skin-2: #ef426f;
  --ink-on-skin: #12101c;
  --skin-1-on-paper: #0a7872;
  --skin-2-on-paper: #c0385b;
  --skin-1-on-board: #00b2a9;
  --skin-2-on-board: #ef426f;
}
[data-skin='nba-TOR'] { /* Toronto Raptors */
  --skin-1: #753bbd;
  --skin-2: #8a8d8f;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #753bbd;
  --skin-2-on-paper: #6a6b6c;
  --skin-1-on-board: #9468c6;
  --skin-2-on-board: #8a8d8f;
}
[data-skin='nba-UTA'] { /* Utah Jazz */
  --skin-1: #002b5c;
  --skin-2: #00471b;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #002b5c;
  --skin-2-on-paper: #00471b;
  --skin-1-on-board: #697f95;
  --skin-2-on-board: #5c8666;
}
[data-skin='nba-WAS'] { /* Washington Wizards */
  --skin-1: #002b5c;
  --skin-2: #e31837;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #002b5c;
  --skin-2-on-paper: #d31834;
  --skin-1-on-board: #697f95;
  --skin-2-on-board: #e53951;
}

/* ---- WNBA ---------------------------------------------------- */
[data-skin='wnba-ATL'] { /* Atlanta Dream */
  --skin-1: #c8102e;
  --skin-2: #418fde;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #c8102e;
  --skin-2-on-paper: #376ea8;
  --skin-1-on-board: #d44d5f;
  --skin-2-on-board: #418fde;
}
[data-skin='wnba-CHI'] { /* Chicago Sky */
  --skin-1: #418fde;
  --skin-2: #ffcd00;
  --ink-on-skin: #12101c;
  --skin-1-on-paper: #376ea8;
  --skin-2-on-paper: #81690b;
  --skin-1-on-board: #418fde;
  --skin-2-on-board: #ffcd00;
}
[data-skin='wnba-CON'] { /* Connecticut Sun */
  --skin-1: #fc4c02;
  --skin-2: #0c2340;
  --ink-on-skin: #12101c;
  --skin-1-on-paper: #c13e07;
  --skin-2-on-paper: #0c2340;
  --skin-1-on-board: #fc4c02;
  --skin-2-on-board: #747d88;
}
[data-skin='wnba-DAL'] { /* Dallas Wings */
  --skin-1: #0c2340;
  --skin-2: #c4d600;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #0c2340;
  --skin-2-on-paper: #6a700b;
  --skin-1-on-board: #747d88;
  --skin-2-on-board: #c4d600;
}
[data-skin='wnba-GS'] { /* Golden State Valkyries */
  --skin-1: #010101;
  --skin-2: #ad96dc;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #010101;
  --skin-2-on-paper: #74648f;
  --skin-1-on-board: #7f7c75;
  --skin-2-on-board: #ad96dc;
}
[data-skin='wnba-IND'] { /* Indiana Fever */
  --skin-1: #c8102e;
  --skin-2: #041e42;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #c8102e;
  --skin-2-on-paper: #041e42;
  --skin-1-on-board: #d44d5f;
  --skin-2-on-board: #727e8b;
}
[data-skin='wnba-LVA'] { /* Las Vegas Aces */
  --skin-1: #a7a8aa;
  --skin-2: #000000;
  --ink-on-skin: #12101c;
  --skin-1-on-paper: #6c6b6c;
  --skin-2-on-paper: #000000;
  --skin-1-on-board: #a7a8aa;
  --skin-2-on-board: #7f7c75;
}
[data-skin='wnba-LAS'] { /* Los Angeles Sparks */
  --skin-1: #702f8a;
  --skin-2: #ffc72c;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #702f8a;
  --skin-2-on-paper: #83681f;
  --skin-1-on-board: #9a6ba6;
  --skin-2-on-board: #ffc72c;
}
[data-skin='wnba-MIN'] { /* Minnesota Lynx */
  --skin-1: #236192;
  --skin-2: #78be21;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #236192;
  --skin-2-on-paper: #50761b;
  --skin-1-on-board: #5281a4;
  --skin-2-on-board: #78be21;
}
[data-skin='wnba-NY'] { /* New York Liberty */
  --skin-1: #6eceb2;
  --skin-2: #010101;
  --ink-on-skin: #12101c;
  --skin-1-on-paper: #457465;
  --skin-2-on-paper: #010101;
  --skin-1-on-board: #6eceb2;
  --skin-2-on-board: #7f7c75;
}
[data-skin='wnba-PHX'] { /* Phoenix Mercury */
  --skin-1: #582c83;
  --skin-2: #fc4c02;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #582c83;
  --skin-2-on-paper: #c13e07;
  --skin-1-on-board: #8f71a4;
  --skin-2-on-board: #fc4c02;
}
[data-skin='wnba-POR'] { /* Portland Fire */
  --skin-1: #c8102e;
  --skin-2: #e93cac;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #c8102e;
  --skin-2-on-paper: #bb338a;
  --skin-1-on-board: #d44d5f;
  --skin-2-on-board: #e93cac;
}
[data-skin='wnba-SEA'] { /* Seattle Storm */
  --skin-1: #2c5234;
  --skin-2: #fbe122;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #2c5234;
  --skin-2-on-paper: #796b1a;
  --skin-1-on-board: #6a836a;
  --skin-2-on-board: #fbe122;
}
[data-skin='wnba-TOR'] { /* Toronto Tempo */
  --skin-1: #612c51;
  --skin-2: #b8ccea;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #612c51;
  --skin-2-on-paper: #646c79;
  --skin-1-on-board: #967285;
  --skin-2-on-board: #b8ccea;
}
[data-skin='wnba-WAS'] { /* Washington Mystics */
  --skin-1: #c8102e;
  --skin-2: #0c2340;
  --ink-on-skin: #f2ece0;
  --skin-1-on-paper: #c8102e;
  --skin-2-on-paper: #0c2340;
  --skin-1-on-board: #d44d5f;
  --skin-2-on-board: #747d88;
}
/* ============================================================
   Clay's Almanac — Alerts
   Component of docs/DESIGN.md's index. Phase B. Surface-aware
   inline notices: gating messages ("today's picks are for
   subscribers"), empty states ("no predictions available"),
   degraded-data notices. Not a toast/dismissible system — these
   are inline, server-rendered blocks.
   ============================================================ */

.alert {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
}

.alert i {
  margin-top: 0.15em;
  flex-shrink: 0;
}

/* Informational — e.g. a paywall nudge. Uses the current skin accent,
   not a fixed brand colour, so it reads correctly under any skin. */
.alert--info {
  background: color-mix(in oklab, var(--surface-accent) 12%, var(--surface-bg));
  border: 1px solid var(--surface-accent);
}

/* Muted — empty states, "nothing here yet" placeholders. */
.alert--muted {
  background: var(--surface-bg-2);
  border: 1px solid var(--surface-hairline);
  color: var(--surface-ink-muted);
  font-style: italic;
}

/* Warning — degraded data (e.g. a model with no mean+stats file, a
   Tol Warning flag). Amber, not the fixed --status-miss magenta —
   this is a caution, not a graded loss. */
.alert--warning {
  background: color-mix(in oklab, var(--board-gold) 14%, var(--surface-bg));
  border: 1px solid var(--board-gold);
}
/* ============================================================
   Clay's Almanac — Auth (login / register / forgot / reset)
   Component of docs/DESIGN.md's index. Phase B "port existing
   pages": tokenized replacement for the near-identical inline
   <style> blocks that used to live in each of
   templates/security/{login_user,register_user,forgot_password,
   reset_password}.html (Space Grotesk + a purple/pink gradient —
   the generic SaaS look the tenet in docs/DESIGN.md §1 rejects).

   Every selector below is unchanged from the original files, so
   Flask-Security's rendered forms (hidden_tag(), field ids/names)
   and login_user.html's `switchTab()` script — which toggles
   .tab / .tab-content .active — keep working untouched. Paper
   surface: this is the public front door to the product.

   NOTE: the card wrapper is `.auth-panel`, not `.panel` — the
   original inline CSS this was ported from used the generic name
   "panel" for its card, which collides with the sanctioned `.panel`
   component (panel.css) once both ship in the same bundle. That
   collision shipped once (auth.css's width: min(26rem, 100%) leaked
   onto every `.panel` sitewide — /resume, /dashboard, /styleguide)
   and was caught and fixed here. Do not reintroduce `.panel` in this
   file; every other bare-looking name below (.split, .hero, .tabs,
   .tab, .actions, .helper, .home-link, .security-note, .form-check,
   .info-box) was checked against every other components/*.css file
   and is unique to this one.
   ============================================================ */

.auth-shell {
  display: flex;
  flex-direction: column;
  min-height: 100svh;
}
@media (min-width: 64em) {
  .auth-shell { flex-direction: row; align-items: stretch; }
}

.split {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  padding: var(--space-6) var(--space-4);
}

.hero {
  max-width: 26rem;
  text-align: left;
  color: var(--paper-ink);
}
.hero h1 {
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: var(--text-2xl);
  margin: 0 0 var(--space-2);
}
.hero p {
  margin: 0;
  color: var(--paper-ink-muted);
  font-size: var(--text-md);
  font-family: var(--font-body);
}

.auth-panel {
  width: min(26rem, 100%);
  background: var(--paper-ground);
  border: 1px solid var(--paper-rule);
  box-shadow: 0 20px 60px rgba(26, 22, 20, 0.12);
  padding: var(--space-6);
}
.auth-panel h2 {
  font-family: var(--font-display);
  text-transform: uppercase;
  margin-top: 0;
  font-size: var(--text-lg);
  margin-bottom: var(--space-5);
  color: var(--paper-ink);
}
.auth-panel form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.auth-panel label {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--paper-ink-muted);
  font-family: var(--font-mono);
}
.auth-panel input[type="email"],
.auth-panel input[type="password"],
.auth-panel input[type="text"] {
  border: 2px solid var(--paper-rule);
  padding: var(--space-3);
  background: #fff;
  font-size: var(--text-base);
  font-family: var(--font-body);
  width: 100%;
  box-sizing: border-box;
  margin-top: var(--space-1);
}
.auth-panel input:focus {
  outline: 2px solid var(--skin-1-on-surface);
  outline-offset: 1px;
}
.actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: var(--text-sm);
}
.actions a {
  color: var(--paper-accent-text);
  text-decoration: none;
}
.actions a:hover { text-decoration: underline; }

.form-check {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  letter-spacing: 0.05em;
  cursor: pointer;
}
.form-check input {
  width: 1.1rem;
  height: 1.1rem;
  border: 2px solid var(--paper-rule);
  background: #fff;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
}
.form-check input:checked {
  border-color: var(--skin-1);
  background-color: var(--skin-1);
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 18 18' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 9l3 3 7-7' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 70%;
}

.helper {
  text-align: center;
  margin-top: var(--space-4);
  font-size: var(--text-sm);
  color: var(--paper-ink-muted);
}
.helper a {
  color: var(--paper-ink);
  font-weight: 700;
  text-decoration: underline;
}

.home-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--paper-ink-muted);
  font-size: var(--text-sm);
  text-decoration: none;
  margin-bottom: var(--space-5);
}
.home-link:hover { color: var(--paper-accent-text); }

.security-note {
  color: var(--status-miss);
  font-size: var(--text-sm);
}

/* ── Tabs (login_user.html: magic link / password) ─────── */
.tabs {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-5);
  padding: var(--space-1);
  background: var(--paper-ground-2);
}
.tab {
  flex: 1;
  padding: var(--space-3) var(--space-3);
  text-align: center;
  cursor: pointer;
  border: none;
  background: transparent;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.05em;
  color: var(--paper-ink-muted);
  font-family: var(--font-display);
  text-transform: uppercase;
  transition: all calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard);
}
.tab.active {
  background: var(--paper-ground);
  color: var(--paper-accent-text);
  box-shadow: 0 1px 0 var(--paper-rule);
}
.tab:hover:not(.active) { color: var(--paper-ink); }
.tab-content { display: none; }
.tab-content.active { display: block; }
.info-box {
  margin-top: var(--space-3);
  padding: var(--space-3);
  background: var(--paper-ground-2);
  font-size: var(--text-sm);
  color: var(--paper-ink-muted);
  line-height: 1.5;
}
/* ============================================================
   Clay's Almanac — Status badge
   Component of docs/DESIGN.md's index. Phase B "port existing
   pages": a small status pill, first needed by the dashboard's
   Active/Inactive subscription indicator. Uses the fixed
   --status-* tokens, same rule as everywhere else: this is a
   graded/account state, never a skin colour.
   ============================================================ */

.badge {
  display: inline-block;
  padding: 0.32rem var(--space-3);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  font-family: var(--font-mono);
  white-space: nowrap;
}

.badge--active {
  background: color-mix(in oklab, var(--status-correct) 22%, var(--surface-bg));
  color: var(--status-correct);
  border: 1px solid var(--status-correct);
}

.badge--inactive {
  background: color-mix(in oklab, var(--status-miss) 18%, var(--surface-bg));
  color: var(--status-miss);
  border: 1px solid var(--status-miss);
}

/* Live/Soon — a *content* status (sport & department chapters),
   deliberately NOT --status-correct/--status-miss: those two are
   reserved for graded prediction outcomes (docs/DESIGN.md's "semantics
   are never skinnable" rule extends here — reusing mint/magenta for an
   unrelated meaning would blur that signal). Skin-aware chrome instead. */
.badge--live {
  background: var(--skin-1);
  color: var(--ink-on-skin);
  border: 1px solid var(--skin-1);
}

.badge--soon {
  background: transparent;
  color: var(--surface-ink-muted);
  border: 1px solid var(--surface-hairline);
}
/* ============================================================
   Clay's Almanac — Button
   Component of docs/DESIGN.md's index.

   House shape: a clipped top-right corner — a torn ticket stub /
   press-pass notch — rather than the rounded-rectangle every SaaS
   button defaults to (design tenet: subvert the convention
   deliberately). Every variant shares the shape; only fill,
   border and text vary.

   Construction — why this isn't a single `border` + `clip-path`:
   `clip-path` crops the *whole* rendered box, border included, so a
   plain border only ever paints along the box's original straight
   edges. The instant the corner gets clipped off, that new diagonal
   edge is a bare cut with nothing drawn on it — no border segment
   exists there to crop. Two stacked, identically-shaped clip-paths
   fix this: an outer layer filled with the border colour, and an
   inner layer — inset by the border width, same clip-path formula —
   filled with the real button colour. Since clip-path percentages
   resolve against each layer's OWN box, the inner layer's notch is
   automatically a smaller, parallel copy of the outer one; the ring
   of border colour left visible between the two traces the entire
   shape uniformly, diagonal notch included, the same width all the
   way around.

     .btn        — unclipped; just the flex container for the label,
                   so text is never accidentally cropped by the notch.
     .btn::before — the border layer: full size, filled with
                   --btn-border, clipped to the notch shape. Behind.
     .btn::after  — the fill layer: inset by --btn-border-width on
                   every side, filled with --btn-fill, clipped to the
                   same notch shape (auto-scales to the smaller box).
                   On top.
   ============================================================ */

.btn {
  --btn-notch: 0.85rem;
  --btn-border-width: 2px;
  /* Opaque, not transparent — ::after is what masks ::before down to
     a ring in the first place (see the construction note above). A
     bare .btn with no variant class (e.g. /styleguide's skin-switch
     buttons: class="btn btn--sm", no --primary/--secondary/--ghost)
     falls through to these defaults directly, so they need to be
     safe on their own, not just for .btn--secondary. */
  --btn-fill: var(--btn-fill-ambient, var(--surface-bg));
  --btn-border: currentColor;
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-sm);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1;
  border: 0;
  color: var(--surface-ink);
  background: none;
  transition: color calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard),
              transform calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard);
}

.btn::before,
.btn::after {
  content: '';
  position: absolute;
  transition: background-color calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard);
}

.btn::before {
  inset: 0;
  background: var(--btn-border);
  clip-path: polygon(
    0 0,
    calc(100% - var(--btn-notch)) 0,
    100% var(--btn-notch),
    100% 100%,
    0 100%
  );
  z-index: -2;
}

.btn::after {
  inset: var(--btn-border-width);
  background: var(--btn-fill);
  /* Not the same --btn-notch as ::before. Insetting a box by t on
     every side moves its diagonal notch edge t*sqrt(2) away from
     ::before's — the straight edges end up a clean t px apart, but
     the diagonal ends up ~41% thicker (t=2px -> a ~2.83px seam) for
     the exact same reason a mitred picture-frame corner isn't just
     "the same width, rotated". Shrinking the inner notch by
     t*(2-sqrt(2)) compensates so the border reads as one constant
     width all the way around, corner included. */
  clip-path: polygon(
    0 0,
    calc(100% - var(--btn-notch) + var(--btn-border-width) * 0.5858) 0,
    100% calc(var(--btn-notch) - var(--btn-border-width) * 0.5858),
    100% 100%,
    0 100%
  );
  z-index: -1;
}

.btn:hover {
  --btn-fill: color-mix(in oklab, var(--surface-ink) 8%, var(--btn-fill-ambient, var(--surface-bg)));
}

.btn:active {
  transform: translateY(1px);
}

/* Primary — filled with the current skin colour (chrome). This
   is the JOIN button: press-pass, not a generic rounded CTA.

   Fill is the raw brand seed; the border is the -on-surface variant
   of it, so the button still reads as a *shape* when the seed and
   the surface are the same tone — Brooklyn's #000000 fill on Board's
   near-black ground would otherwise be an invisible rectangle with
   floating text. On every other skin the two are close enough that
   the border just reads as a slightly crisper edge. */
.btn--primary {
  --btn-fill: var(--skin-1);
  --btn-border: var(--skin-1-on-surface);
  color: var(--ink-on-skin);
}
.btn--primary:hover {
  --btn-fill: color-mix(in oklab, var(--skin-1) 85%, black);
}

/* Secondary — outline, current surface ink. LOG IN.
   --btn-fill must be opaque here, not transparent: ::after (the fill
   layer) is what MASKS ::before down to a thin ring in the first
   place. A transparent fill doesn't punch a ring-shaped hole in the
   border layer behind it — it just lets that whole solid border-
   coloured shape show through, filling the entire button. --btn-fill-
   ambient (02-layout.css) is "whatever this button is actually
   sitting on" — --surface-bg by default, overridden by .panel /
   .status-card wherever a button is nested inside one instead, so no
   per-button override is normally needed here. */
.btn--secondary {
  --btn-fill: var(--btn-fill-ambient, var(--surface-bg));
  --btn-border: var(--surface-ink);
  color: var(--surface-ink);
}
.btn--secondary:hover {
  --btn-fill: color-mix(in oklab, var(--surface-ink) 10%, var(--btn-fill-ambient, var(--surface-bg)));
}

/* Ghost — no border, for low-emphasis inline actions. */
.btn--ghost {
  /* Genuinely transparent, not ambient-opaque like the base default:
     --btn-border-width: 0 means there's no ring for --btn-fill to
     mask down to (::before and ::after are the same size), so the
     opacity trick .btn--secondary needs doesn't apply here — and a
     real see-through fill is worth keeping so ghost buttons don't
     flatten whatever texture (e.g. Paper's halftone) sits behind them. */
  --btn-fill: transparent;
  --btn-border: transparent;
  --btn-border-width: 0px;
  padding-inline: var(--space-3);
}
.btn--ghost:hover {
  --btn-fill: color-mix(in oklab, var(--surface-ink) 6%, transparent);
}

.btn--sm {
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-xs);
  --btn-notch: 0.6rem;
}

.btn:disabled,
.btn[aria-disabled='true'] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}
/* ============================================================
   Clay's Almanac — Clay's Take
   Component of docs/DESIGN.md's index. Phase C: the editor's-column
   treatment on the homepage. Deliberately undated by default — the
   staleness rule (content.py's TAKE_STALE_DAYS) only shows a date
   when the take is fresh, so an older column reads as timeless
   editorial voice rather than a visible gap. Paper surface: this is
   the newspaper reporting its own opinion, not a data panel.
   ============================================================ */

.clays-take {
  padding: var(--space-6) 0;
  border-top: 3px double var(--paper-rule);
  border-bottom: 1px solid var(--paper-rule);
}

.clays-take__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--paper-accent);
}

.clays-take__title {
  margin: var(--space-2) 0 var(--space-4);
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: var(--text-xl);
}

.clays-take__date {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--paper-ink-muted);
  margin-left: var(--space-3);
  text-transform: none;
  letter-spacing: 0.03em;
}

.clays-take__body {
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: 1.7;
  columns: 1;
}

@media (min-width: 64em) {
  .clays-take__body {
    columns: 2;
    column-gap: var(--space-7);
  }
}

.clays-take__body p:first-child::first-letter {
  float: left;
  font-family: var(--font-display);
  font-size: 3.4em;
  line-height: 0.82;
  padding-right: var(--space-2);
  color: var(--paper-accent);
}
/* ============================================================
   Clay's Almanac — Columnist byline
   Component of docs/DESIGN.md's index. Phase B. Surface-aware
   (reads --surface-*), used on Paper (columnists section, per
   the plan's homepage requirements) but not hardcoded to it.
   ============================================================ */

.col-byline {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  text-decoration: none;
  color: inherit;
}
a.col-byline:hover .col-byline__name {
  text-decoration: underline;
}

.col-byline__headshot {
  width: 3.5rem;
  height: 3.5rem;
  border-radius: var(--radius-pill);
  object-fit: cover;
  filter: grayscale(1) contrast(1.1);
  border: 2px solid var(--surface-rule);
  flex-shrink: 0;
}
.col-byline__headshot--fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-bg-2);
  color: var(--surface-ink-muted);
  font-size: var(--text-lg);
}

.col-byline__name {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-size: var(--text-sm);
}

.col-byline__beat {
  font-style: italic;
  font-size: var(--text-xs);
  color: var(--surface-ink-muted);
}

.col-byline__record {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--surface-ink-muted);
  margin-top: var(--space-1);
}
/* ============================================================
   Clay's Almanac — Contact form
   Component of docs/DESIGN.md's index. Phase B "port existing
   pages": tokenized replacement for the old static/assets/css/
   contact.css. Same selectors as before (templates/contact.html's
   inline `oninput` char counter reads #char-count directly, not a
   class, so it's untouched). Surface-aware (reads --surface-*) — the
   page itself sits on Board, reusing wizard.css's wrap/header/alerts/
   nav scaffold, but this file doesn't hardcode that.
   ============================================================ */

.contact-input, .contact-textarea {
  width: 100%;
  padding: var(--space-3);
  margin-top: var(--space-1);
  background: var(--surface-panel-bg);
  border: 2px solid var(--surface-hairline);
  border-radius: var(--radius-sm);
  color: var(--surface-ink);
  font-size: var(--text-base);
  font-family: var(--font-body);
  box-sizing: border-box;
  transition: border-color calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard);
}
.contact-input:focus, .contact-textarea:focus {
  outline: none;
  border-color: var(--skin-1-on-surface);
}
.contact-input::placeholder, .contact-textarea::placeholder {
  color: var(--surface-ink-muted);
  opacity: 0.6;
}
.contact-textarea {
  height: 150px;
  resize: vertical;
}
.contact-label {
  display: block;
  margin-top: var(--space-4);
  color: var(--surface-ink-muted);
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.contact-char-count {
  text-align: right;
  font-size: var(--text-xs);
  color: var(--surface-ink-muted);
  margin-top: var(--space-1);
}

.contact-home-link {
  text-align: center;
  margin-top: var(--space-5);
}
.contact-home-link a {
  color: var(--surface-accent-text);
  font-size: var(--text-md);
  text-decoration: none;
  border-bottom: 1px dotted var(--surface-accent-text);
}
.contact-home-link a:hover {
  border-bottom-color: transparent;
}
/* ============================================================
   Clay's Almanac — Department card
   Component of docs/DESIGN.md's index. Phase C: the homepage's
   "departments index" section — one card per sections.py Department.
   Deliberately the same shape as sport-card (both are "chapters" of
   the same almanac front page) but its own file per the component
   index, since departments and sports are conceptually distinct and
   may diverge (department cards don't carry a season window).
   ============================================================ */

.dept-card {
  display: block;
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--surface-hairline);
  border-radius: var(--radius-sm);
  background: var(--surface-panel-bg, transparent);
  text-decoration: none;
  color: inherit;
  transition: border-color calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard),
              transform calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard);
}

a.dept-card:hover,
a.dept-card:focus-visible {
  border-color: var(--skin-1-on-surface);
  transform: translateY(-2px);
}

.dept-card--soon {
  opacity: 0.68;
}

.dept-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.dept-card__name {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-size: var(--text-md);
}

.dept-card__blurb {
  margin: var(--space-2) 0 0;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--surface-ink-muted);
}
/* ============================================================
   Clay's Almanac — Error page
   Component of docs/DESIGN.md's index. Phase B "port existing
   pages": tokenized replacement for the old static/assets/css/
   error.css (429.html). Reuses wizard.css's .wizard-summary panel
   for the card itself — same class as before.
   ============================================================ */

.error-wrap {
  display: flex;
  padding: 0 var(--space-4);
  justify-content: center;
  align-items: center;
  min-height: 100svh;
}

.error-card {
  text-align: center;
  padding: var(--space-6) var(--space-5);
}

.error-card h1 {
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: var(--text-2xl);
  margin-bottom: var(--space-2);
  color: var(--board-ink);
}

.error-card p {
  color: var(--board-ink-muted);
  font-size: var(--text-base);
  margin: var(--space-2) 0;
}

.error-card a {
  color: var(--status-best);
  text-decoration: none;
  border-bottom: 1px dotted var(--status-best);
}

.error-card a:hover {
  border-bottom-color: transparent;
}
/* ============================================================
   Clay's Almanac — Footer
   Component of docs/DESIGN.md's index. Phase E.

   Global, site-wide colophon: printed on every page via
   layouts/base.html (not per-layout), so Terms/Privacy links and
   the problem-gambling disclosure are always reachable regardless
   of which surface a page renders on. Deliberately Paper-toned
   even on Board pages — a newspaper's masthead colophon runs the
   same on every section, and the fine-print register reads better
   on newsprint than on the data surface. Sets its own
   data-surface="paper" so it's correct even at the bottom of a
   Board page.
   ============================================================ */

.site-footer {
  background: var(--paper-ground-2);
  border-top: 3px double var(--paper-rule);
  padding: var(--space-6) var(--space-4);
  margin-top: var(--space-8);
}

.site-footer__inner {
  max-width: 64rem;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  text-align: center;
}

.site-footer__disclosure {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--paper-ink-muted);
  line-height: 1.6;
}

.site-footer__disclosure strong {
  color: var(--paper-ink);
}

.site-footer__links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2) var(--space-5);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.site-footer__links a {
  color: var(--paper-ink-muted);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  padding-bottom: 0.15em;
}

.site-footer__links a:hover {
  color: var(--paper-ink);
  border-color: var(--paper-accent);
}

.site-footer__colophon {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--paper-ink-muted);
  opacity: 0.8;
}

@media (min-width: 48em) {
  .site-footer__inner {
    text-align: left;
  }

  .site-footer__links {
    justify-content: flex-start;
  }
}
/* ============================================================
   Clay's Almanac — Form controls
   Component of docs/DESIGN.md's index.
   ============================================================ */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.field__label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--surface-ink-muted);
}

.field__input,
.field__textarea,
.field__select {
  width: 100%;
  padding: var(--space-3);
  background: var(--surface-panel-bg);
  color: var(--surface-ink);
  border: 1px solid var(--surface-hairline);
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: var(--text-base);
}

[data-surface='board'] .field__input,
[data-surface='board'] .field__textarea,
[data-surface='board'] .field__select {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.field__input:focus-visible,
.field__textarea:focus-visible,
.field__select:focus-visible {
  outline: 2px solid var(--skin-1-on-surface);
  outline-offset: 1px;
}

.field__textarea {
  resize: vertical;
  min-height: 6rem;
}

.field__hint {
  font-size: var(--text-xs);
  color: var(--surface-ink-muted);
}

.field__error {
  font-size: var(--text-xs);
  color: var(--status-miss);
}

/* Checkbox / radio — keep native control, restyle the row */
.field-check {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
}
.field-check input {
  accent-color: var(--skin-1);
  width: 1.1rem;
  height: 1.1rem;
}
/* ============================================================
   Clay's Almanac — Front-page banner
   Component of docs/DESIGN.md's index. Phase C: the homepage-only
   nameplate + dateline furniture below the sticky masthead — "VOL.
   II · NO. <n> · <date>", "PRICE: FREE", ★ dividers, double rule
   (docs/DESIGN.md's "Furniture" row). The masthead component (its
   own file) carries the small persistent nameplate; this is the
   full front-page treatment, shown once per page load.

   The almanac cover itself lives in the Sports section below (site
   owner's call — it reads as that chapter's opener rather than the
   whole homepage's hero); this file just carries the centred
   Certified Fresh stamp. Both covers/stamps site-wide are sized
   intrinsically (clamp() width, height: auto) rather than the old
   fixed `min(32rem, 100vh)` cap that caused both known defects this
   replaces: dead space on tall viewports and the cover cropped
   through its own title on mobile.
   ============================================================ */

.front-banner {
  text-align: center;
  padding-block: var(--space-6);
}

.front-banner__rule {
  border: none;
  border-top: 3px double var(--paper-rule);
  margin: 0 0 var(--space-3);
}

.front-banner__meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--paper-ink-muted);
}

.front-banner__meta .star {
  color: var(--paper-accent);
  margin: 0 var(--space-2);
}

.front-banner__nameplate {
  margin: var(--space-3) 0 var(--space-2);
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.01em;
  font-size: var(--text-3xl);
  line-height: 1;
}

.front-banner__nameplate span {
  color: var(--paper-accent);
}

.front-banner__tagline {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--text-md);
  color: var(--paper-ink-muted);
  max-width: 36rem;
  margin-inline: auto;
}

.front-banner__stamp--centered {
  /* Equal top/bottom margin — vertically centers the stamp in the
     gap between the tagline and the rule below it, rather than
     sitting flush against one side. */
  display: block;
  margin: var(--space-6) auto;
}

.front-banner__stamp {
  /* 50% larger than the prior clamp(4rem, 9.2vw, 6.3rem), which was
     itself 15% larger than the original clamp(3.5rem, 8vw, 5.5rem). */
  width: clamp(6rem, 13.8vw, 9.45rem);
  height: auto;
}

/* ---- Sports section opener --------------------------------
   The almanac cover as the Sports chapter's opener — stands alone
   (no "Sports" heading; the cover already says it, and the section
   keeps its accessible name via aria-label instead), centered at
   every width. */
.sports-section__head {
  display: flex;
  justify-content: center;
  margin-bottom: var(--space-5);
}

.sports-section__cover {
  /* Intrinsic: scales with the viewport, never cropped, never
     capped against 100vh — same fix as the old hero placement, just
     at a chapter-opener size rather than a full-hero one. */
  width: clamp(9rem, 22vw, 13rem);
  height: auto;
  display: block;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
}
/* ============================================================
   Clay's Almanac — Game card
   Component of docs/DESIGN.md's index. Phase B: Jinja-macro
   replacement for the shadow-DOM `<game-prediction-summary>` /
   `<game-results-summary>` web components (templates/components/
   game_card.html). Board surface by default — this is the data
   surface — but reads --surface-* so it wouldn't break set into
   a Paper page either (Phase C's "Board panel set into Paper").
   ============================================================ */

.gc {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  background: var(--board-panel);
  color: var(--board-ink);
  border-radius: var(--radius-md);
  box-shadow: inset 0 1px 0 var(--board-hairline), 0 1px 3px rgba(0, 0, 0, 0.4);
  font-family: var(--font-mono);
}

.gc__header {
  display: flex;
  justify-content: space-between;
  font-size: var(--text-xs);
  color: var(--board-ink-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.gc__row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: radial-gradient(var(--team-1, transparent) var(--team-stop, 0%), var(--team-2, transparent));
  color: var(--team-ink, var(--board-ink));
}

/* Winner gets the gold outline — --status-best, never a skin colour. */
.gc__row--winner {
  box-shadow: 0 0 0 2px var(--status-best);
}

.gc__logo-wrap {
  width: 2.5rem;
  height: 2.5rem;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.gc__logo-wrap--circle {
  border-radius: var(--radius-pill);
  background: var(--gc-circle-bg, transparent);
  padding: 0.15em;
}
.gc__logo {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

/* Text-badge fallback for a team with no logo art yet (every WNBA
   team today — see sports.py `has_logo=False`). Never a broken <img>,
   never invented placeholder art. */
.gc__badge {
  width: 100%;
  height: 100%;
  border-radius: var(--radius-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: var(--text-xs);
  background: rgba(0, 0, 0, 0.28);
  letter-spacing: 0.02em;
}

/* The row's background is a radial gradient between a team's own two
   colours (--team-1/--team-2, often one light and one dark — see
   sports.py's `ink_for_background`), so a single flat --team-ink
   reads well at one end of the gradient but not the other. A halo in
   the *opposite* tone (--team-ink-shadow) keeps the text legible
   wherever it lands — the same job `.gc__badge`'s solid scrim above
   does for the logo/abbreviation circle. */
.gc__team,
.gc__score,
.gc__pred {
  text-shadow: 0 0 3px var(--team-ink-shadow, transparent), 0 0 6px var(--team-ink-shadow, transparent);
}

.gc__team {
  flex: 1;
  min-width: 0;
  font-weight: 600;
  font-size: var(--text-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.gc__score {
  font-variant-numeric: tabular-nums;
  font-size: var(--text-lg);
  font-weight: 700;
  min-width: 2.5ch;
  text-align: right;
}
.gc__pred {
  font-variant-numeric: tabular-nums;
  font-size: var(--text-md);
  opacity: 0.85;
  min-width: 2.5ch;
  text-align: right;
}

/* .gc__team/.gc__score/.gc__pred's halo above is a soft blurred glow,
   tuned for running text. A ~16-20px icon needs a tighter, more
   solid-reading outline instead — the fixed status colour (never
   allowed to vary by team, see docs/DESIGN.md §7) can land almost
   exactly on a team's own brand hue (mint --status-correct on New
   York's seafoam primary sits at ~1.1:1, essentially the same
   colour), where a soft glow still reads as "blending in" even
   though it technically passes some contrast at the very edge. Four
   tight, low-blur layers approximate a solid outline instead of a
   halo, which keeps the icon's shape legible regardless of hue.

   The miss (X) icon keeps the per-team --team-ink-shadow pick — it
   already reads well across every game card. The correct-call (money
   bag) icon below instead pins that trim to one fixed dark colour,
   always — a deliberate choice over the per-team variable, on top of
   a gold star backdrop. */
.gc__ml--miss {
  color: var(--status-miss);
  text-shadow:
    0 0 1px var(--team-ink-shadow, transparent),
    0 0 1px var(--team-ink-shadow, transparent),
    0 0 2px var(--team-ink-shadow, transparent),
    0 0 3px var(--team-ink-shadow, transparent);
}

/* Correct-call money-bag icon: a gold star sits behind it, retro
   "you won" badge feel — --board-gold is already the design system's
   "moneybag colour" token (00-tokens.css), so this reuses it rather
   than inventing a new one-off. `isolation` gives the icon its own
   stacking context so the star's negative z-index tucks behind the
   glyph (::before, painted via Font Awesome's `content: var(--fa)`)
   without sinking below the row's own gradient background. */
.gc__ml:not(.gc__ml--miss) {
  color: var(--status-correct);
  position: relative;
  isolation: isolate;
  text-shadow:
    0 0 1px rgba(18, 16, 28, .85),
    0 0 1px rgba(18, 16, 28, .85),
    0 0 2px rgba(18, 16, 28, .85),
    0 0 3px rgba(18, 16, 28, .85);
}
.gc__ml:not(.gc__ml--miss)::after {
  content: "\f005"; /* fa-star, solid */
  font: 900 1.7em "Font Awesome 7 Free";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: var(--board-gold);
  z-index: -1;
  /* Same fixed dark trim as the icon it sits behind — gold isn't
     safe against every team's own gradient either (checked: gold vs
     Indiana's yellow is ~1:1, and several teams' white tertiary sits
     close to gold too), so the star needs its own edge to read as a
     distinct badge shape rather than melting into the row. */
  text-shadow:
    0 0 1px rgba(18, 16, 28, .85),
    0 0 1px rgba(18, 16, 28, .85),
    0 0 2px rgba(18, 16, 28, .85),
    0 0 3px rgba(18, 16, 28, .85);
}

.gc__footer {
  border-top: 1px solid var(--board-hairline);
  padding-top: var(--space-2);
  margin-top: var(--space-1);
}

/* Model-vote split, "today's predictions" cards only (game_card.html's
   game_card_prediction macro). Used to be a lone team icon — nobody
   could tell what it meant without hovering for the tooltip. Each
   segment uses that team's own colour (--vb-away/--vb-home, the same
   hex as the row gradients above), proportioned by --vb-away-pct/
   --vb-home-pct (away_win_votes/home_win_votes over total_models).
   Any gap left over (a model that didn't pick either side) just shows
   the track's own neutral background — never invented as a third
   segment. */
.gc__votebar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--board-ink-muted);
}
.gc__votebar-label {
  flex: 0 0 auto;
  font-weight: 700;
}
.gc__votebar-track {
  flex: 1;
  display: flex;
  gap: 1px;
  height: 0.4rem;
  min-width: 3rem;
  border-radius: var(--radius-pill);
  overflow: hidden;
  background: var(--board-hairline);
}
.gc__votebar-fill {
  height: 100%;
}
.gc__votebar-fill--away {
  width: var(--vb-away-pct, 0%);
  background: var(--vb-away, var(--board-ink-muted));
}
.gc__votebar-fill--home {
  width: var(--vb-home-pct, 0%);
  background: var(--vb-home, var(--board-ink-muted));
}
.gc__votebar-count {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--board-ink);
}

/* Clickable card (has a detail_url — see game_card.html/game_detail
   route) renders as <a class="gc"> instead of <article class="gc">;
   reset link defaults the same way sport-card.css does for its own
   conditional <a>/<div> tag. */
a.gc {
  text-decoration: none;
  color: inherit;
  transition: box-shadow calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard);
}

a.gc:hover,
a.gc:focus-visible {
  box-shadow: inset 0 1px 0 var(--board-hairline), 0 1px 3px rgba(0, 0, 0, 0.4), 0 0 0 2px var(--skin-1-on-surface);
}
/* ============================================================
   Clay's Almanac — Masthead
   Component of docs/DESIGN.md's index.

   Phase A shipped the static chrome: nameplate, nav, and the
   JOIN/LOG IN pair, all consuming the skin tokens so switching
   `data-skin` restyles this and nothing else.

   Phase C adds the sticky, scroll-condensing behaviour, opt-in via
   `.masthead--sticky` (used by the homepage — the page this replaces
   the fixed `#join`/`#login` overlays on; other pages keep the
   static-in-flow masthead unless they opt in too). `.is-condensed`
   is toggled by static/assets/js/masthead.js, a ~20-line vanilla
   scroll listener — no framework JS, and the masthead is fully
   usable (just never condenses) with JS disabled, since
   `position: sticky` alone already satisfies "always on screen,
   never covering content" without any script running.
   ============================================================ */

/* Mobile-first: stacked rows (nameplate, nav, actions) so nothing has
   to shrink to fit — a JOIN button never gets squeezed. From `md` up,
   where a single row actually has room, it becomes one row. This is
   Phase A's static chrome; the sticky/condensing scroll behaviour
   that ultimately replaces the fixed `#join`/`#login` overlays is
   Phase C. */
.masthead {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--surface-bg);
  border-bottom: 3px double var(--surface-hairline);
}

@media (min-width: 48em) {
  .masthead {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
  }
}

.masthead__nameplate {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-lg);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--surface-ink);
  text-decoration: none;
}

.masthead__nameplate span {
  color: var(--skin-1-on-surface);
}

/* Sub-pages (e.g. /nba, /wnba) wrap the nameplate in a link back to
   the homepage — the homepage's own masthead leaves it as plain text
   since it's already there. Reset the anchor defaults and give it the
   same hover/focus affordance as the nav links beside it. */
a.masthead__nameplate:hover {
  opacity: 0.8;
}

a.masthead__nameplate:focus-visible {
  outline: 2px solid var(--skin-2-on-surface);
  outline-offset: 3px;
}

.masthead__dateline {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.05em;
  color: var(--surface-ink-muted);
  text-transform: uppercase;
}

.masthead__nav {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-5);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
}

.masthead__nav a {
  text-decoration: none;
  color: var(--surface-ink-muted);
  border-bottom: 2px solid transparent;
  padding-bottom: 0.15em;
  transition: color calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard),
              border-color calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard);
}

.masthead__nav a:hover,
.masthead__nav a[aria-current='page'] {
  color: var(--surface-ink);
  border-color: var(--skin-1-on-surface);
}

.masthead__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* ---- Sticky / condensing variant (Phase C) --------------------
   Opt-in: replaces the old fixed #join/#login overlays (claysa.css
   19-77, z-index 69, sitting on top of the almanac cover on mobile)
   with a masthead that is always on screen but participates in
   layout — it can never cover content because nothing is stacked
   under it, only above/below it in normal flow. */
.masthead--sticky {
  position: sticky;
  top: 0;
  z-index: 20;
  box-shadow: 0 1px 0 var(--surface-hairline);
}

/* Condensed state: shrinks padding and hides the dateline so the
   sticky bar takes minimal vertical room once scrolled, while
   JOIN/LOG IN stay put — the whole point of this replacing the old
   fixed overlays. Toggled by masthead.js; degrades gracefully (the
   masthead just stays full-size) with JS disabled. */
.masthead--sticky.is-condensed {
  padding-block: var(--space-2);
  transition: padding-block calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard);
}

.masthead--sticky.is-condensed .masthead__dateline {
  display: none;
}
/* ============================================================
   Clay's Almanac — Panel & surface textures
   Component of docs/DESIGN.md's index.

   Two panel treatments (one per surface) plus the two surface
   textures: Paper's halftone grain (reuses static/images/
   overlay.png — no new asset) and Board's scanline/vignette
   (pure CSS, no asset).
   ============================================================ */

.panel {
  padding: var(--space-5);
  border-radius: var(--radius-md);
}

/* Paper panel — a card set onto newsprint: a thin rule, a soft
   shadow, no radius on the corners (newspaper clippings are cut
   square). */
.panel--paper {
  background: var(--surface-panel-bg, #fff);
  border: 1px solid var(--paper-rule);
  border-radius: var(--radius-none);
  box-shadow: 0 1px 0 var(--paper-rule);
  /* A button nested in here (e.g. dashboard.html) needs to know it's
     no longer on --surface-bg — see components/button.css. */
  --btn-fill-ambient: var(--surface-panel-bg, #fff);
}

/* Board panel — "reads as a lit bezel": a 1px highlight along the
   top edge simulating a light source above the panel, matching
   panel fill, soft radius. */
.panel--board {
  background: var(--board-panel);
  color: var(--board-ink);
  border-radius: var(--radius-md);
  box-shadow: inset 0 1px 0 var(--board-hairline), 0 1px 3px rgba(0, 0, 0, 0.4);
  --btn-fill-ambient: var(--board-panel);
}

/* Glow pulse — Board-only motion, used on a fresh graded hit.
   Respects --surface-motion / prefers-reduced-motion via the
   reset's global override. */
.panel--board.is-glowing {
  animation: panel-glow calc(var(--motion-slow) * 4 * var(--surface-motion)) var(--ease-standard) infinite;
}
@keyframes panel-glow {
  0%, 100% { box-shadow: inset 0 1px 0 var(--board-hairline), 0 0 0 rgba(32, 224, 176, 0); }
  50%      { box-shadow: inset 0 1px 0 var(--board-hairline), 0 0 18px rgba(32, 224, 176, 0.45); }
}

/* ---- Surface textures --------------------------------------- */

/* Paper: faint halftone grain, tiled at 128px — the same asset
   and tiling already used by templates/index.html. */
.tex-halftone {
  /* Path is relative to the CONCATENATED bundle's location
     (static/assets/css/claysa.bundle.css), not this source file —
     `make css` does a literal `cat`, so url() resolves against the
     bundle's own path. Matches the existing static/assets/css/claysa.css. */
  background-image: url('../../images/overlay.png');
  background-size: 128px 128px;
  background-repeat: repeat;
  /* multiply, not element opacity — opacity would also fade the
     panel's own text/children, which isn't the point of a grain
     texture on the background */
  background-blend-mode: multiply;
}

/* Board: very low-opacity scanline + CRT vignette. No image
   asset — pure gradients. */
.tex-scanline {
  position: relative;
  isolation: isolate;
}
.tex-scanline::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: repeating-linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0.035) 0,
    rgba(255, 255, 255, 0.035) 1px,
    transparent 1px,
    transparent 3px
  );
}
.tex-scanline::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(ellipse at center, transparent 55%, rgba(0, 0, 0, 0.45) 130%);
}
/* ============================================================
   Clay's Almanac — Pick card
   Component of docs/DESIGN.md's index. Phase B. Sits inside a
   graded game-card's footer: the best model's persona thumb,
   name and record, plus the full-panel vote tally.
   ============================================================ */

.pc {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.pc__thumb {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--radius-pill);
  object-fit: cover;
  border: 1px solid var(--board-hairline);
  flex-shrink: 0;
}

.pc__thumb-fallback {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--radius-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--board-panel-2);
  color: var(--board-gold);
  flex-shrink: 0;
}

.pc__kicker {
  display: flex;
  align-items: center;
  gap: 0.35em;
  margin: 0 0 0.1em;
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--status-best);
}

.pc__name {
  font-weight: 600;
  font-size: var(--text-sm);
}

.pc__record {
  font-size: var(--text-xs);
  color: var(--board-ink-muted);
  font-variant-numeric: tabular-nums;
}
/* ============================================================
   Clay's Almanac — Team skin picker
   Component of docs/DESIGN.md's index.

   Lives in the masthead's action row, beside LOG IN / JOIN. Sets
   `data-skin` on <html>, which repaints chrome only — docs/DESIGN.md
   §7's hard rule. The panel says so out loud, because "pick your
   team" on a predictions site invites exactly the wrong assumption
   (that it filters or favours that team's games).

   Built on <details>/<summary> and a radio group inside a real
   <form>, not a custom listbox: with JS off it is still a working
   disclosure with an Apply button that posts to /skin, and the
   browser gives us open/close, Escape, and roving arrow-key
   selection within the group for free. skin-picker.js adds the two
   things markup can't: instant preview on selection, and saving
   without a page reload.
   ============================================================ */

.skin-picker {
  position: relative;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}

/* The disclosure triangle is replaced by the colour chip — it's a
   better affordance here, since the chip is also the current value. */
.skin-picker__toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  cursor: pointer;
  list-style: none;
  color: var(--surface-ink-muted);
  border: 1px solid var(--surface-hairline);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  transition: color calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard),
              border-color calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard);
}

.skin-picker__toggle::-webkit-details-marker { display: none; }
.skin-picker__toggle::marker { content: ''; }

.skin-picker__toggle:hover {
  color: var(--surface-ink);
  border-color: var(--skin-1-on-surface);
}

.skin-picker__toggle:focus-visible {
  outline: 2px solid var(--skin-2-on-surface);
  outline-offset: 2px;
}

/* Current-skin chip: the two seeds split on the diagonal, so a skin
   whose seeds are close (the Aces' silver/black, say) still reads as
   a deliberate pair rather than one flat colour. */
.skin-picker__chip,
.skin-swatch__chip {
  --sw-1: var(--skin-1);
  --sw-2: var(--skin-2);
  flex: none;
  width: 1.1rem;
  height: 1.1rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--surface-hairline);
  background: linear-gradient(135deg, var(--sw-1) 0 50%, var(--sw-2) 50% 100%);
}

/* Truncate rather than let a long team name stretch the masthead —
   "Portland Trail Blazers" is 22 characters and the row also holds
   JOIN and LOG IN. */
.skin-picker__label {
  max-width: 9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- Panel ---------------------------------------------------
   Absolutely positioned so opening it never reflows the masthead
   (which is sticky on the homepage — a reflow there would shove the
   page content under the reader's cursor), and width-capped so it
   can't push the body wider than the viewport on a phone.

   Mobile-first per docs/DESIGN.md §6 (base styles are the phone
   layout; queries only ever add width). Which edge the panel opens
   from has to follow where the masthead has put the picker, so this
   breakpoint is deliberately the same 48em as the one in
   masthead.css and must move with it: below 48em the masthead is
   stacked and the actions row sits at the left, so the panel opens
   rightward; from 48em up the masthead is a single row with the
   actions at the right, so it opens leftward instead.

   Anchoring to an edge and capping the width beats JS collision
   detection for a panel this size, but only while the anchor matches
   the side the picker is actually on. Anchoring right while the
   picker was still on the left is what put the panel 240px off the
   left edge of a phone in landscape (600px wide: the masthead was
   still stacked, but the panel had already flipped). */
.skin-picker__panel {
  position: absolute;
  z-index: 30;
  top: calc(100% + var(--space-2));
  left: 0;
  width: max(16rem, min(22rem, calc(100vw - var(--space-6))));
  max-height: min(70vh, 30rem);
  overflow-y: auto;
  padding: var(--space-4);
  background: var(--surface-panel-bg);
  border: 1px solid var(--surface-rule);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
}

@media (min-width: 48em) {
  .skin-picker__panel {
    left: auto;
    right: 0;
  }
}

.skin-picker__note {
  margin: 0 0 var(--space-3);
  color: var(--surface-ink-muted);
  line-height: 1.5;
  text-transform: none;
  letter-spacing: 0;
}

.skin-picker__group {
  border: 0;
  padding: 0;
  margin: 0 0 var(--space-3);
}

.skin-picker__legend {
  padding: 0;
  margin-bottom: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--surface-ink);
  border-bottom: 1px solid var(--surface-rule);
  width: 100%;
}

/* ---- One selectable skin -------------------------------------- */
.skin-swatch {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  cursor: pointer;
  color: var(--surface-ink-muted);
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--text-xs);
}

.skin-swatch:hover {
  background: var(--surface-bg-2);
  color: var(--surface-ink);
}

/* The radio stays in the accessibility tree and keeps its keyboard
   behaviour — it's visually replaced by the chip + row highlight,
   not removed. `appearance: none` rather than display:none for
   exactly that reason. */
.skin-swatch input {
  appearance: none;
  -webkit-appearance: none;
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.skin-swatch:has(input:checked) {
  background: var(--surface-bg-2);
  color: var(--surface-ink);
  font-weight: 700;
}

.skin-swatch:has(input:focus-visible) {
  outline: 2px solid var(--skin-2-on-surface);
  outline-offset: 1px;
}

.skin-swatch:has(input:checked) .skin-swatch__chip {
  box-shadow: 0 0 0 2px var(--surface-panel-bg), 0 0 0 3px var(--surface-ink);
}

.skin-swatch__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The no-JS submit. skin-picker.js removes it from the tab order and
   hides it once it has taken over saving — see the file's header for
   why it does that at runtime rather than the template doing it. */
.skin-picker__apply {
  margin-top: var(--space-2);
  width: 100%;
}

.skin-picker[data-enhanced] .skin-picker__apply {
  display: none;
}
/* ============================================================
   Clay's Almanac — Sport card
   Component of docs/DESIGN.md's index. Phase C: the homepage's
   "sports chapters" section — one card per sports.py Sport. Surface-
   aware (reads --surface-*), skin-aware on hover only (chrome, never
   game data — see 00-tokens.css's skin-utility contract).
   ============================================================ */

.sport-card {
  display: block;
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--surface-hairline);
  border-radius: var(--radius-sm);
  background: var(--surface-panel-bg, transparent);
  text-decoration: none;
  color: inherit;
  transition: border-color calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard),
              transform calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard);
}

a.sport-card:hover,
a.sport-card:focus-visible {
  border-color: var(--skin-1-on-surface);
  transform: translateY(-2px);
}

.sport-card--soon {
  opacity: 0.68;
}

.sport-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.sport-card__name {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-size: var(--text-md);
}

.sport-card__window {
  margin-top: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--surface-ink-muted);
}
/* ============================================================
   Clay's Almanac — Status page
   Component of docs/DESIGN.md's index. Phase B "port existing
   pages": tokenized replacement for the old static/assets/css/
   subscription-status.css (post-checkout success/cancel pages).
   Board surface — this is a subscriber-transaction result.
   ============================================================ */

.status-card {
  width: min(680px, 100%);
  background: var(--board-panel);
  border-radius: var(--radius-lg);
  box-shadow: inset 0 1px 0 var(--board-hairline), 0 24px 56px rgba(0, 0, 0, 0.45);
  padding: var(--space-6);
  /* A .btn nested in here needs to know it's not on --surface-bg —
     see components/button.css's "ambient fill" note. */
  --btn-fill-ambient: var(--board-panel);
}

.status-eyebrow {
  display: inline-block;
  margin-bottom: var(--space-3);
  padding: 0.35rem var(--space-3);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.status-success .status-eyebrow {
  color: var(--board-ground);
  background: var(--status-correct);
}

.status-cancel .status-eyebrow {
  color: var(--board-ground);
  background: var(--status-miss);
}

.status-title {
  margin: 0;
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: var(--text-xl);
  color: var(--board-ink);
}

.status-copy {
  margin: var(--space-4) 0 0;
  color: var(--board-ink-muted);
  line-height: 1.6;
}

.status-email {
  color: var(--status-best);
  font-weight: 700;
  text-decoration: none;
}

.status-actions {
  margin-top: var(--space-5);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}
/* ============================================================
   Clay's Almanac — Tolerance-range chart
   Component of docs/DESIGN.md's index. Phase D.

   Horizontal range plot: one shared score axis, one range per team
   (visitor always first — see the macro), the overlap region drawn
   as its own band. Identity rides on position + direct labels + a
   45°/135° hatch texture; --tol-away/--tol-home (set inline per game
   by app.py, via sports.py's chart_identity_colors() OKLab collision
   guard) are DECORATION only — never the only thing distinguishing
   the two sides. --tol-overlap-bg/-ink are the chart's own non-team
   tokens (00-tokens.css), validated on both surfaces by
   scripts/validate_palette.js; every other colour here is an
   existing --surface-*/--skin-* token, surface- and skin-aware like
   every other component in this index.

   Segmented control: CSS-only via :has() (Chrome 105+/Safari
   15.4+/Firefox 121+ — universal by now), so the whole interaction
   works with JavaScript disabled by construction. See the macro's
   own comment for the exact mechanism.
   ============================================================ */

.tol {
  background: var(--surface-panel-bg);
  border: 1px solid var(--surface-hairline);
  border-radius: var(--radius-md);
  padding: var(--space-5);
  --btn-fill-ambient: var(--surface-panel-bg);
}

/* The per-game <details> a sport page nests each .tol inside (see
   pages/sport.html) — keeps the compact game-rail cards from having
   to carry a full chart's width; native disclosure widget, so this
   costs nothing to work with JavaScript disabled. */
.tol-disclosure > summary {
  cursor: pointer;
  list-style: none;
  padding: var(--space-3) var(--space-4);
  background: var(--surface-bg-2);
  border: 1px solid var(--surface-hairline);
  border-radius: var(--radius-sm);
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--surface-ink);
}
.tol-disclosure > summary::-webkit-details-marker {
  display: none;
}
.tol-disclosure > summary::before {
  content: '\25B8';
  display: inline-block;
  margin-right: var(--space-2);
  transition: transform calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard);
}
.tol-disclosure[open] > summary::before {
  transform: rotate(90deg);
}
.tol-disclosure .tol {
  margin-top: var(--space-3);
}
.tol-disclosure + .tol-disclosure {
  margin-top: var(--space-3);
}

.tol__head {
  margin-bottom: var(--space-3);
}
.tol__title {
  display: block;
  font-family: var(--font-display);
  text-transform: uppercase;
  font-weight: 700;
  letter-spacing: 0.03em;
  font-size: var(--text-sm);
  color: var(--surface-ink);
}
.tol__note {
  display: none;
  margin: var(--space-2) 0 0;
  font-size: var(--text-xs);
  color: var(--surface-ink-muted);
  max-width: 46rem;
}
.tol__note[data-level='95'] { display: block; }

/* ---- Segmented control -------------------------------------- */
.tol__control {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  border: 0;
  margin: 0 0 var(--space-4);
  padding: 0;
}
.tol__seg {
  cursor: pointer;
  padding: 0.35rem var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--surface-hairline);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--surface-ink-muted);
  background: transparent;
  transition: background-color calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard),
              color calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard),
              border-color calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard);
}
.tol__seg:hover {
  border-color: var(--skin-1-on-surface);
  color: var(--surface-ink);
}
.tol__radio:checked + .tol__seg {
  background: var(--skin-1);
  border-color: var(--skin-1);
  color: var(--ink-on-skin);
}
.tol__radio:focus-visible + .tol__seg {
  outline: 2px solid var(--skin-2-on-surface);
  outline-offset: 2px;
}

/* ---- Level reveal — pure CSS, no JS ---------------------------
   Baseline (no :has() support): 95% always shown — still a
   correct, functional default. Once :has() is supported, "any
   radio checked" (always true — one is checked from initial markup)
   hides every level again, then the level matching the actually-
   checked radio's `value` is re-revealed. */
.tol__level { display: none; }
.tol__level[data-level='95'] { display: block; }
.tol:has(.tol__radio:checked) .tol__level,
.tol:has(.tol__radio:checked) .tol__note { display: none; }
.tol:has(.tol__radio[value='95']:checked) .tol__level[data-level='95'],
.tol:has(.tol__radio[value='95']:checked) .tol__note[data-level='95'] { display: block; }
.tol:has(.tol__radio[value='90']:checked) .tol__level[data-level='90'],
.tol:has(.tol__radio[value='90']:checked) .tol__note[data-level='90'] { display: block; }
.tol:has(.tol__radio[value='85']:checked) .tol__level[data-level='85'],
.tol:has(.tol__radio[value='85']:checked) .tol__note[data-level='85'] { display: block; }
.tol:has(.tol__radio[value='80']:checked) .tol__level[data-level='80'],
.tol:has(.tol__radio[value='80']:checked) .tol__note[data-level='80'] { display: block; }
.tol:has(.tol__radio[value='75']:checked) .tol__level[data-level='75'],
.tol:has(.tol__radio[value='75']:checked) .tol__note[data-level='75'] { display: block; }

/* ---- Overview strip — every model at once, one shared axis ----
   Level-independent (a model's point prediction doesn't change with
   the confidence level), so this sits above the segmented control's
   effect entirely and never toggles. */
.tol__overview {
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--surface-hairline);
}
.tol__overview-title {
  margin: 0 0 var(--space-2);
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--surface-ink-muted);
}
.tol__overview-axis {
  position: relative;
  height: 1.75rem;
  background: var(--surface-bg-2);
  border: 1px solid var(--surface-hairline);
  border-radius: var(--radius-sm);
}
.tol__overview-dot {
  position: absolute;
  top: 50%;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  border: 1px solid var(--surface-panel-bg);
  z-index: 2;
}
.tol__overview-dot--away { background: var(--tol-away); }
.tol__overview-dot--home { background: var(--tol-home); }

/* ---- Per-model rows (small multiples when several stack) ------ */
.tol__model {
  padding-block: var(--space-4);
  border-top: 1px solid var(--surface-hairline);
}
.tol__model:first-child {
  border-top: 0;
  padding-top: 0;
}
.tol__model-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}
.tol__model-name {
  font-family: var(--font-display);
  text-transform: uppercase;
  font-weight: 700;
  font-size: var(--text-sm);
  letter-spacing: 0.03em;
  color: var(--surface-ink);
}
/* Not a --status-* colour — a caution about the range's validity,
   not a graded correct/miss outcome (docs/DESIGN.md "semantics are
   never skinnable" extends to "never reuse status colours either"). */
.tol__flag {
  font-size: var(--text-xs);
  color: var(--surface-ink-muted);
  border: 1px solid var(--surface-hairline);
  border-radius: var(--radius-pill);
  padding: 0.2rem var(--space-2);
  white-space: nowrap;
}

/* ---- The range plot itself -------------------------------------
   Team colour is DECORATION. The neutral fill under the hatch keeps
   every range legible even when the collision guard falls back to a
   near-black team colour; the hatch angle (45°/135°) and the direct
   labels below are what actually carry identity, and double as the
   colour-blind / print fallback per the plan. */
.tol__axis {
  position: relative;
  height: 2.75rem;
  background: var(--surface-bg-2);
  border: 1px solid var(--surface-hairline);
  border-radius: var(--radius-sm);
}
.tol__range {
  position: absolute;
  top: 0.5rem;
  bottom: 0.5rem;
  border-radius: 2px;
  background-color: color-mix(in oklab, var(--surface-ink) 6%, transparent);
}
.tol__range--away {
  background-image: repeating-linear-gradient(45deg, var(--tol-away) 0 3px, transparent 3px 7px);
  border: 2px solid var(--tol-away);
}
.tol__range--home {
  background-image: repeating-linear-gradient(135deg, var(--tol-home) 0 3px, transparent 3px 7px);
  border: 2px solid var(--tol-home);
}
/* Its own band, drawn on top — "overlap is the story", not an
   incidental blend of the two hatches underneath. Never a third hue:
   a tint of this surface's own neutral family (00-tokens.css). */
.tol__overlap {
  position: absolute;
  top: 0.2rem;
  bottom: 0.2rem;
  z-index: 2;
  background: var(--tol-overlap-bg);
  border: 1px dashed var(--tol-overlap-ink);
  border-radius: 2px;
  opacity: 0.92;
}
/* Mean marker — a diamond, >=8px on a side per the plan. */
.tol__mean {
  position: absolute;
  top: 50%;
  width: 10px;
  height: 10px;
  transform: translate(-50%, -50%) rotate(45deg);
  border: 2px solid var(--surface-panel-bg);
  z-index: 3;
}
.tol__mean--away { background: var(--tol-away); }
.tol__mean--home { background: var(--tol-home); }

/* Actual marker — the real final score, once the game is graded.
   A flagpole (taller than the axis itself, with a small flag on top)
   rather than another diamond, so it never reads as just another
   predicted mean; same left-position convention, same team colour. */
.tol__actual {
  position: absolute;
  top: -0.3rem;
  bottom: -0.3rem;
  width: 2px;
  transform: translateX(-50%);
  z-index: 4;
}
.tol__actual::before {
  content: '';
  position: absolute;
  top: -0.05rem;
  left: 0;
  width: 8px;
  height: 6px;
  clip-path: polygon(0 0, 100% 25%, 0 50%);
}
.tol__actual--away, .tol__actual--away::before { background: var(--tol-away); }
.tol__actual--home, .tol__actual--home::before { background: var(--tol-home); }

/* ---- Direct labels — identity, not decoration ------------------ */
.tol__labels {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--surface-ink-muted);
}
.tol__label {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}
.tol__swatch {
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 2px;
  flex-shrink: 0;
}
.tol__swatch--away {
  background-image: repeating-linear-gradient(45deg, var(--tol-away) 0 2px, transparent 2px 4px);
  border: 1px solid var(--tol-away);
}
.tol__swatch--home {
  background-image: repeating-linear-gradient(135deg, var(--tol-home) 0 2px, transparent 2px 4px);
  border: 1px solid var(--tol-home);
}
.tol__label--overlap {
  color: var(--surface-ink);
  font-weight: 700;
}
.tol__label--noverlap {
  color: var(--surface-ink-muted);
  font-style: italic;
}
.tol__label--actual {
  color: var(--surface-ink);
  font-weight: 700;
}
.tol__label--actual i {
  color: var(--status-best);
}

/* ---- Verdict + disclosures -------------------------------------
   "Layered" presentation: the verdict is for everyone; t_stat/
   p_value/model id/method note sit behind "Show the math" so a
   first-timer never has to see scientific notation to get the point. */
.tol__verdict {
  margin: var(--space-3) 0 0;
  font-size: var(--text-sm);
  color: var(--surface-ink);
}

.tol__math,
.tol__table-toggle {
  margin-top: var(--space-3);
}
.tol__math summary,
.tol__table-toggle summary {
  cursor: pointer;
  list-style: none;
  user-select: none;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--surface-ink-muted);
}
.tol__math summary::-webkit-details-marker,
.tol__table-toggle summary::-webkit-details-marker {
  display: none;
}
.tol__math summary span,
.tol__table-toggle summary span {
  border-bottom: 1px dotted var(--surface-ink-muted);
}
.tol__math summary:hover span,
.tol__table-toggle summary:hover span {
  border-bottom-color: var(--skin-1-on-surface);
  color: var(--surface-ink);
}

.tol__math-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: var(--space-3);
  margin: var(--space-3) 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.tol__math-list div { min-width: 0; }
.tol__math-list dt {
  font-size: var(--text-xs);
  color: var(--surface-ink-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.tol__math-list dd {
  margin: 0;
  font-size: var(--text-base);
  font-weight: 700;
  color: var(--surface-ink);
}
.tol__method {
  font-size: var(--text-xs);
  color: var(--surface-ink-muted);
  max-width: 46rem;
}

/* ---- Table view — required for accessibility, not a grudging add-on ---- */
.tol__table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
  margin-bottom: var(--space-4);
}
.tol__table caption {
  text-align: left;
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: var(--text-xs);
  letter-spacing: 0.03em;
  color: var(--surface-ink-muted);
  margin-bottom: var(--space-2);
}
.tol__table th,
.tol__table td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--surface-hairline);
  text-align: left;
  white-space: nowrap;
}
.tol__table thead th {
  color: var(--surface-ink-muted);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* ============================================================
   Clay's Almanac — Plan wizard
   Component of docs/DESIGN.md's index. Phase B "port existing
   pages": tokenized replacement for the old static/assets/css/
   wizard.css. Every selector below is unchanged from the original
   — static/assets/js/choose_plan.js toggles these same classes
   (.is-selected, .is-visible, .is-active, .is-done) and reads these
   same ids/data-attributes; only the presentation values changed
   (purple/pink SaaS gradients -> design tokens). Board surface —
   this is the subscriber checkout flow, the data/transaction side
   of the product.

   Founder Cheque tier colours (copper/bronze/.../diamond) are a
   self-contained "medal" system, not skin or status colour — they
   stay literal metal-look hex values on purpose, same as before.
   ============================================================ */

.wizard-wrap {
  max-width: 860px;
  margin: var(--space-7) auto var(--space-8);
  padding: 0 var(--space-4);
}

.wizard-footnote strong,
.wizard-cheque-info strong {
  color: var(--board-gold);
  font-weight: 600;
}

/* ── Header ─────────────────────────────────────────────── */
.wizard-header { text-align: center; margin-bottom: var(--space-5); }
.wizard-header h1 {
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: var(--text-2xl);
  margin-bottom: var(--space-2);
  color: var(--board-ink);
}
.wizard-header p { color: var(--board-ink-muted); font-size: var(--text-base); margin: 0; }

/* ── Progress bar ───────────────────────────────────────── */
.wizard-progress {
  display: flex; justify-content: center; gap: var(--space-2); margin-bottom: var(--space-6);
  font-family: var(--font-mono);
}
.wizard-progress-step {
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-xs); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--board-ink-muted);
  transition: color calc(var(--motion-med) * var(--surface-motion)) var(--ease-standard);
}
.wizard-progress-step.is-active { color: var(--board-ink); }
.wizard-progress-step.is-done { color: var(--status-best); }

.wizard-progress-dot {
  width: 1.5rem; height: 1.5rem; border-radius: var(--radius-pill);
  border: 2px solid var(--board-ink-muted);
  display: flex; align-items: center; justify-content: center;
  font-size: 0.68rem; color: var(--board-ink-muted);
  transition: border-color calc(var(--motion-med) * var(--surface-motion)) var(--ease-standard),
              background calc(var(--motion-med) * var(--surface-motion)) var(--ease-standard),
              color calc(var(--motion-med) * var(--surface-motion)) var(--ease-standard);
}
.wizard-progress-step.is-active .wizard-progress-dot {
  border-color: var(--board-ink); background: rgba(242, 236, 224, 0.1); color: var(--board-ink);
}
.wizard-progress-step.is-done .wizard-progress-dot {
  border-color: var(--status-best); background: var(--status-best); color: var(--board-ground);
}

.wizard-progress-line { width: 2.5rem; height: 2px; background: var(--board-hairline); align-self: center; }

/* ── Steps ──────────────────────────────────────────────── */
.wizard-step { display: none; animation: wizardFadeIn calc(var(--motion-slow) * var(--surface-motion)) var(--ease-standard) both; }
.wizard-step.is-visible { display: block; }

@keyframes wizardFadeIn {
  from { opacity: 0; transform: translateY(0.6rem); }
  to   { opacity: 1; transform: translateY(0); }
}

.wizard-step-heading { text-align: center; margin-bottom: var(--space-5); }
.wizard-step-heading h2 { font-family: var(--font-display); text-transform: uppercase; font-size: var(--text-lg); margin-bottom: var(--space-1); color: var(--board-ink); }
.wizard-step-heading p { color: var(--board-ink-muted); font-size: var(--text-sm); margin: 0; }

/* ── Option cards ───────────────────────────────────────── */
.wizard-options { display: flex; justify-content: center; gap: var(--space-4); flex-wrap: wrap; }

.wizard-card {
  position: relative; flex: 1 1 220px; max-width: 280px;
  background: var(--board-panel);
  border: 2px solid var(--board-hairline);
  border-radius: var(--radius-md); padding: var(--space-5) var(--space-4) var(--space-4);
  cursor: pointer; text-align: center;
  transition: border-color calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard),
              background calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard),
              transform calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard);
}
.wizard-card:hover {
  border-color: var(--skin-1-on-surface);
  background: var(--board-panel-2);
  transform: translateY(-3px);
}
.wizard-card.is-selected {
  border-color: var(--skin-1-on-surface);
  background: var(--board-panel-2);
  box-shadow: 0 0 0 1px var(--skin-1-on-surface), 0 8px 32px rgba(0, 0, 0, 0.35);
}

.wizard-card-badge {
  position: absolute; top: -0.6rem; right: var(--space-4);
  background: var(--status-best);
  color: var(--board-ground); font-size: 0.62rem; font-weight: 800;
  letter-spacing: 0.08em; text-transform: uppercase;
  padding: 0.2rem var(--space-3); border-radius: var(--radius-pill);
}

.wizard-card-icon { font-size: 2.2rem; margin-bottom: var(--space-2); display: block; }
.wizard-card h3 { font-family: var(--font-display); font-size: var(--text-md); margin-bottom: var(--space-1); color: var(--board-ink); }
.wizard-card .wizard-card-sub { color: var(--board-ink-muted); font-size: var(--text-sm); line-height: 1.45; margin: 0; }

/* ── Duration cards ─────────────────────────────────────── */
.wizard-options.duration-options { gap: var(--space-3); }
.wizard-options.duration-options .wizard-card { flex: 1 1 240px; max-width: 300px; padding-bottom: var(--space-4); }

.wizard-card .wizard-price {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-lg); font-weight: 700; color: var(--board-ink);
  margin: var(--space-2) 0 0.15rem;
}
.wizard-card .wizard-price-note { font-size: 0.78rem; color: var(--board-ink-muted); margin: 0 0 var(--space-2); }
.wizard-card .wizard-cheque {
  display: inline-block; margin-top: var(--space-2); padding: 0.22rem var(--space-2);
  border-radius: var(--radius-pill); font-size: 0.66rem; font-weight: 800;
  letter-spacing: 0.06em; text-transform: uppercase;
}

/* ── Cheque colours — self-contained medal system, not tokenized ── */
.cheque-copper   { background: #b87333; color: #fff; }
.cheque-bronze   { background: linear-gradient(135deg, #cd7f32, #e8a54b); color: #fff; }
.cheque-silver   { background: linear-gradient(135deg, #8e929a, #c5ccd3); color: #1a1a2e; }
.cheque-gold     { background: linear-gradient(135deg, #d4a843, #f5d779); color: #1a1a2e; }
.cheque-platinum { background: linear-gradient(135deg, #9ba4b0, #dce2e8); color: #1a1a2e; }
.cheque-diamond  { background: linear-gradient(135deg, #7c3aed, #38bdf8); color: #fff;
                   box-shadow: 0 8px 18px rgba(124, 58, 237, 0.62); }

/* ── Perk list ──────────────────────────────────────────── */
.wizard-perks { list-style: none; padding: 0; margin: var(--space-2) 0 0; text-align: left; }
.wizard-perks li { padding: 0.18rem 0 0.18rem 1.2rem; position: relative; font-size: var(--text-sm); color: var(--board-ink-muted); line-height: 1.35; }
.wizard-perks li::before { content: '\2713'; position: absolute; left: 0; color: var(--status-correct); font-weight: 700; }

/* ── Summary ────────────────────────────────────────────── */
.wizard-summary {
  max-width: 520px; margin: 0 auto;
  background: var(--board-panel);
  border: 2px solid var(--board-hairline);
  border-radius: var(--radius-md); padding: var(--space-6) var(--space-5);
}
.wizard-summary-row {
  display: flex; justify-content: space-between;
  padding: 0.55rem 0; border-bottom: 1px solid var(--board-hairline);
  font-size: var(--text-sm);
}
.wizard-summary-row:last-of-type { border-bottom: none; }
.wizard-summary-label { color: var(--board-ink-muted); }
.wizard-summary-value { color: var(--board-ink); font-weight: 700; text-align: right; }
.wizard-summary-value .wizard-cheque { vertical-align: middle; margin-left: var(--space-1); padding: 0.18rem var(--space-2); border-radius: 0.18rem var(--space-2); }

.wizard-summary-total {
  margin-top: var(--space-3); padding-top: var(--space-3);
  border-top: 2px solid var(--skin-1-on-surface);
  display: flex; justify-content: space-between; font-size: var(--text-md);
}
.wizard-summary-total .wizard-summary-value { color: var(--status-correct); font-size: var(--text-lg); }

.wizard-summary-edit { font-size: 0.78rem; color: var(--skin-1-on-surface); cursor: pointer; border-bottom: 1px dotted var(--skin-1-on-surface); margin-left: var(--space-2); }
.wizard-summary-edit:hover { border-bottom-color: var(--skin-2-on-surface); color: var(--skin-2-on-surface); }

/* ── Nav buttons — reuses the site's own .btn shape, torn corner and all ──
   Same two-layer construction as components/button.css's .btn (see the
   comment there for why): .wizard-btn itself carries no clip-path.
   ::before is a full-size layer filled with --btn-border, clipped to the
   notch shape; ::after is the same shape inset by --btn-border-width,
   filled with --btn-fill. clip-path percentages resolve against each
   layer's own (different-sized) box, so ::after's notch is automatically
   a smaller parallel copy of ::before's — the ring of border colour left
   visible between them traces the whole shape uniformly, diagonal notch
   included, which a plain `border` can't reach (clip-path crops a real
   border away right along with the corner it removes). */
.wizard-nav { display: flex; justify-content: center; gap: var(--space-3); margin-top: var(--space-6); }
.wizard-btn {
  --btn-notch: 0.7rem;
  --btn-border-width: 2px;
  /* Opaque, not transparent — see the matching note in
     components/button.css's base .btn rule. Every current .wizard-btn
     usage pairs with -back/-next/-subscribe, but the base default
     needs to be safe on its own regardless, the same way .btn's did. */
  --btn-fill: var(--btn-fill-ambient, var(--surface-bg));
  --btn-border: currentColor;
  position: relative;
  isolation: isolate;
  appearance: none; border: 0;
  font-family: var(--font-display);
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; height: 2.75rem; line-height: 2.6rem;
  padding: 0 var(--space-5); cursor: pointer;
  transition: color calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard),
              transform calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard);
  text-decoration: none; display: inline-block; text-align: center;
  background: none;
}
.wizard-btn::before,
.wizard-btn::after {
  content: '';
  position: absolute;
  transition: background-color calc(var(--motion-fast) * var(--surface-motion)) var(--ease-standard);
}
.wizard-btn::before {
  inset: 0;
  background: var(--btn-border);
  clip-path: polygon(0 0, calc(100% - var(--btn-notch)) 0, 100% var(--btn-notch), 100% 100%, 0 100%);
  z-index: -2;
}
/* ::after's notch is shrunk by border-width*(2-sqrt(2)) to compensate
   for insetting a box by t on every side moving its diagonal edge
   t*sqrt(2) away from ::before's, not just t — see the fuller version
   of this comment in components/button.css's matching rule. */
.wizard-btn::after {
  inset: var(--btn-border-width);
  background: var(--btn-fill);
  clip-path: polygon(0 0, calc(100% - var(--btn-notch) + var(--btn-border-width) * 0.5858) 0, 100% calc(var(--btn-notch) - var(--btn-border-width) * 0.5858), 100% 100%, 0 100%);
  z-index: -1;
}

/* --btn-fill is opaque (--btn-fill-ambient, defaulting to --surface-bg
   — see 02-layout.css), not transparent: ::after is what masks
   ::before down to a ring in the first place; see the construction
   note above. Every current .wizard-btn-back sits on plain page
   ground, not inside a panel, so the default always applies today —
   this still reads it via the ambient token rather than hardcoding
   --surface-bg, so it stays correct if that ever changes. */
.wizard-btn-back { --btn-fill: var(--btn-fill-ambient, var(--surface-bg)); --btn-border: var(--board-ink-muted); color: var(--board-ink); }
.wizard-btn-back:hover { --btn-border: var(--skin-1); --btn-fill: color-mix(in oklab, var(--skin-1) 18%, var(--btn-fill-ambient, var(--surface-bg))); }

.wizard-btn-next { --btn-fill: var(--skin-1); --btn-border: var(--skin-1); color: var(--ink-on-skin); }
.wizard-btn-next:hover { --btn-fill: color-mix(in oklab, var(--skin-1) 85%, black); }
.wizard-btn-next:disabled { opacity: 0.35; cursor: default; pointer-events: none; }

.wizard-btn-subscribe { --btn-fill: var(--status-best); --btn-border: var(--status-best); color: var(--board-ground); padding: 0 var(--space-6); font-size: 0.72rem; }
.wizard-btn-subscribe:hover { --btn-fill: color-mix(in oklab, var(--status-best) 85%, black); }

/* ── Alerts ─────────────────────────────────────────────── */
.wizard-alerts { max-width: 600px; margin: 0 auto var(--space-4); }
.wizard-alert {
  background: color-mix(in oklab, var(--status-miss) 15%, var(--board-panel));
  color: var(--status-miss);
  border: 1px solid var(--status-miss); border-radius: var(--radius-sm);
  padding: var(--space-3); font-size: var(--text-sm); margin-bottom: var(--space-2);
}
/* Success flash override — Phase B, absorbed from the old contact.css */
.wizard-alert-success {
  background: color-mix(in oklab, var(--status-correct) 15%, var(--board-panel)) !important;
  color: var(--status-correct) !important;
  border-color: var(--status-correct) !important;
}

/* ── Cheque explainer ───────────────────────────────────── */
.wizard-cheque-info { max-width: 600px; margin: var(--space-5) auto 0; text-align: center; }
.wizard-cheque-info summary { cursor: pointer; color: var(--board-ink-muted); font-size: var(--text-sm); list-style: none; user-select: none; font-weight: 600; }
.wizard-cheque-info summary::-webkit-details-marker { display: none; }
.wizard-cheque-info summary span { border-bottom: 1px dotted var(--board-ink-muted); }
.wizard-cheque-info summary:hover span { border-bottom-color: var(--status-best); color: var(--board-ink); }
.wizard-cheque-info p { margin-top: var(--space-2); font-size: var(--text-sm); color: var(--board-ink-muted); line-height: 1.55; }

/* ── Feature comparison table (Step 2) ──────────────────── */
.wizard-cheque-info table { color: var(--board-ink); }
.wizard-cheque-info th { color: var(--board-ink-muted) !important; }
.wizard-cheque-info td { color: var(--board-ink-muted) !important; }
.wizard-cheque-info thead tr { border-bottom: 1px solid var(--board-hairline) !important; }
.wizard-cheque-info div[style] { background: var(--board-panel); border-radius: var(--radius-sm); padding: var(--space-3) !important; }

/* ── Footnote ───────────────────────────────────────────── */
.wizard-footnote { text-align: center; margin-top: var(--space-3); font-size: var(--text-xs); color: var(--board-ink-muted); }

/* ── Home link ──────────────────────────────────────────── */
.wizard-home-link { text-align: center; margin-top: var(--space-6); }

/* ── Responsive — mobile-first, min-width only (docs/DESIGN.md §6) ──
   Base/mobile hides the progress-step text label (dots only, like
   the old <=480px rule); it appears once there's room. */
.wizard-progress-step .progress-label { display: none; }
@media (min-width: 30em) {
  .wizard-progress-step .progress-label { display: inline; }
}
