/* =====================================================================
   BMFStats — Design System
   Ported from docs/design/mockup/assets/tokens.css (the approved clickable
   prototype) with docs/design/mockup/assets/theme-red.css inlined as the
   defaults. Implements the frozen token contract in
   docs/design/DESIGN_TEARDOWN.md §2.
   ---------------------------------------------------------------------
   Load order in base.html:
     <link rel="stylesheet" href="{% static 'css/output.css' %}">        <- Tailwind
     <link rel="stylesheet" href="{% static 'css/design-system.css' %}"> <- this file

   This file loads AFTER Tailwind's build so component classes can beat a
   utility where the two overlap. tailwind.config.js mirrors the colour
   tokens below, so `bg-surface-1` (utility) and `.card` (component)
   resolve to the identical hex. If you change a colour here, change it
   there too — the design-system test suite fails if they drift.

   RED ACCENT ONLY. The prototype shipped a blue variant behind a theme
   switcher; the owner chose red, so the switcher and the blue values do
   not ship. Do not reintroduce them.

   Dark theme only — no `prefers-color-scheme: light` branch, deliberately.
   Mobile-first: base rules are the 390px case, media queries only ever
   enhance upward (sm 640 / md 768 / lg 1024 / xl 1280).
   ===================================================================== */


/* =====================================================================
   0. RESET
   ===================================================================== */

/* Vendored, not linked. Same rule Highcharts had to learn the hard way: a CDN
   that starts refusing requests silently restyles the product, and these are
   pages about minors — no third-party gets to log who is reading them. */
@font-face {
  font-family: "Inter";
  src: url("../fonts/inter-var-latin.65850a373e25.woff2") format("woff2-variations");
  font-weight: 100 900;          /* one variable file covers every weight we use */
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Squada One";
  src: url("../fonts/squada-one-latin.69165ee43c53.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;   /* Inter renders first; the swap is a weight change, not a blank */
}

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

html {
  color-scheme: dark;              /* native controls/scrollbars render dark */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body, h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol, fieldset, legend {
  margin: 0;
  padding: 0;
}

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

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

input, button, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }

a { color: inherit; }

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

fieldset { border: 0; }

[hidden] { display: none !important; }

/* Selection wash tracks the brand hue. The prototype's blue value was a
   leftover from the variant that did not ship. */
::selection { background: rgba(255,59,48,.32); color: #fff; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}


/* =====================================================================
   1. TOKENS
   ===================================================================== */

:root {

  /* -----------------------------------------------------------------
     1.1 COLOUR  (teardown §2.1 + theme-red.css)

     ###################################################################
     #  THE RED RULE — read this before using --brand anywhere.        #
     #                                                                 #
     #  The old UI had red doing eleven jobs, so nothing could be read #
     #  as clickable, important, or dangerous (teardown §1.4). The fix  #
     #  in a red-accented product is a WEIGHT ramp inside one hue,      #
     #  replacing the hue split the blue variant used:                  #
     #                                                                 #
     #    --brand   #FF3B30  FILLED red = the BMF mark, and THE ONE     #
     #                                    primary action on a screen.  #
     #    --accent  #FF7A70  red TEXT   = active tab, selected segment, #
     #                                    links, checked state, sort    #
     #                                    direction. A lighter step:    #
     #                                    #FF3B30 text on near-black is #
     #                                    fine on a button but dim at   #
     #                                    small sizes; this restores    #
     #                                    the contrast the blue had.    #
     #    --accent-dim / --accent-line  = red washes for selected       #
     #                                    surfaces and their hairlines. #
     #                                                                 #
     #  --danger is for destructive actions ONLY (delete, deactivate).  #
     #  It stays in the red family, differentiated by OUTLINE style +   #
     #  confirmation (the Netflix pattern), never by hue.               #
     #                                                                 #
     #  Grade chips NEVER use red — high school is cyan, not red.       #
     #  Section kickers, headings, badges and chart series: NEVER red   #
     #  (chart subject is the single exception, see --chart-subject).   #
     ###################################################################
     ----------------------------------------------------------------- */

  --bg:           #0B0C0E;              /* page — warmer than pure black */
  --surface-1:    #131519;              /* cards */
  --surface-2:    #1A1D22;              /* raised / hover / inputs */
  --surface-3:    #22262D;              /* borders-as-fills, chips */
  --line:         rgba(255,255,255,.07);
  --line-strong:  rgba(255,255,255,.13);

  --ink:          #F2F4F7;              /* primary text */
  --ink-2:        #A8B0BB;              /* secondary */
  /* Was #6B7480, which measures 4.13:1 on --bg — below the 4.5:1 WCAG AA floor for
     normal text, and this is the colour of EVERY timestamp, count, caption and hint in
     the product at 11-13px. #79828E is 5.03:1 and reads the same. */
  --ink-3:        #79828E;              /* tertiary / meta */

  --brand:        #FF3B30;              /* BMF red — mark + ONE primary action */
  --brand-ink:    #FFFFFF;              /* text on brand */
  --brand-dim:    rgba(255,59,48,.12);

  --accent:       #FF7A70;              /* interactive / selected / focus */
  --good:         #34D399;
  --warn:         #FBBF24;
  --danger:       #F87171;              /* destructive only */

  /* derived accent tints — selection fills, focus glows */
  --accent-dim:   rgba(255,59,48,.13);
  --accent-line:  rgba(255,122,112,.40);
  --good-dim:     rgba(52,211,153,.12);
  --warn-dim:     rgba(251,191,36,.12);
  --danger-dim:   rgba(248,113,113,.12);

  /* Grade hues — a cue, never a status. Never brand red.
     Deliberately left COOL. With red no longer meaning "interactive",
     the chip/accent collision dissolves: cool hues are pure DATA
     colours (grades), red is interaction, green/amber are status. */
  --grade-elementary: #4C8DFF;
  --grade-middle:     #A78BFA;
  --grade-high:       #22D3EE;

  /* Rank medals — styled numerals, NOT emoji (fix for §1.8) */
  --gold:         #E3B341;
  --silver:       #B6C0CE;
  --bronze:       #C4794A;

  /* Chart palette (fix for §1.11): the subject is the only colour on
     the canvas; every comparison series is greyscale. */
  --chart-subject: var(--brand);
  --chart-compare: var(--ink-3);
  --chart-grid:    var(--line);

  /* -----------------------------------------------------------------
     1.2 TYPE  (teardown §2.2)
     Inter, with a system stack fallback.
     Uppercase is reserved for .t-kicker and NOTHING else (fix §1.3).
     ----------------------------------------------------------------- */

  --font-sans: "Inter", "Inter var", -apple-system, BlinkMacSystemFont,
               "Segoe UI", Roboto, "Helvetica Neue", Arial,
               "Apple Color Emoji", sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
               "Liberation Mono", monospace;
  --font:      var(--font-sans);   /* alias — same reason as --r-* below */

  /* B1 (8-5 review). Squada One is bmf25.com's display face — the condensed
     athletic block used for every section header on Coach's site. Bringing it
     here is the single highest-leverage thing that makes this app and that site
     read as one brand.

     DISPLAY ROLES ONLY: page titles, kickers, hero numerals, rank badges. It is
     a jersey font — at 13px in a table it is unreadable, and any number a coach
     has to read precisely stays on Inter. A test enforces that it never reaches
     `body`. */
  --font-display: "Squada One", "Inter", var(--font-sans);

  /* display  32/38  700  -0.02em  — page hero, public board only */
  --fs-display: 32px;  --lh-display: 38px;  --fw-display: 700;  --ls-display: -0.02em;
  /* h1       24/30  700  -0.01em  — page title */
  --fs-h1:      24px;  --lh-h1:      30px;  --fw-h1:      700;  --ls-h1:      -0.01em;
  /* h2       18/24  600           — card title */
  --fs-h2:      18px;  --lh-h2:      24px;  --fw-h2:      600;  --ls-h2:      0;
  /* body     15/22  400           — default */
  --fs-body:    15px;  --lh-body:    22px;  --fw-body:    400;  --ls-body:    0;
  /* body-sm  13/18  400           — secondary */
  --fs-body-sm: 13px;  --lh-body-sm: 18px;  --fw-body-sm: 400;  --ls-body-sm: 0;
  /* label    12/16  600  +0.02em  — form labels, SENTENCE case */
  --fs-label:   12px;  --lh-label:   16px;  --fw-label:   600;  --ls-label:   0.02em;
  /* meta     11/14  500           — timestamps, counts */
  --fs-meta:    11px;  --lh-meta:    14px;  --fw-meta:    500;  --ls-meta:    0;
  /* kicker   11/14  700  +0.08em  — UPPERCASE, max ONE per card */
  --fs-kicker:  11px;  --lh-kicker:  14px;  --fw-kicker:  700;  --ls-kicker:  0.08em;

  /* -----------------------------------------------------------------
     1.3 SPACE & SHAPE  (teardown §2.3)
     4px base: 4 8 12 16 24 32 48 64.
     Exposed twice — value-named (--space-16) and index-named
     (--space-s4) — so either convention resolves to the same number.
     ----------------------------------------------------------------- */

  --space-4:  4px;
  --space-8:  8px;
  --space-12: 12px;
  --space-16: 16px;
  --space-24: 24px;
  --space-32: 32px;
  --space-48: 48px;
  --space-64: 64px;

  /* index aliases: --space-s1 .. --space-s8 walk the same scale */
  --space-s1: 4px;
  --space-s2: 8px;
  --space-s3: 12px;
  --space-s4: 16px;
  --space-s5: 24px;
  --space-s6: 32px;
  --space-s7: 48px;
  --space-s8: 64px;

  --radius-sm:   8px;
  --radius-md:   12px;
  --radius-lg:   16px;
  --radius-pill: 999px;

  /* Short aliases. §2.3 fixes the VALUES (sm 8 · md 12 · lg 16 · pill 999)
     but not the property names, and both page builders independently
     reached for --r-*. Both spellings resolve to the same number. */
  --r-sm:   var(--radius-sm);
  --r-md:   var(--radius-md);
  --r-lg:   var(--radius-lg);
  --r-pill: var(--radius-pill);

  /* No shadows on dark — elevation is a surface step (§2.3). */
  --elev-1-bg:     var(--surface-1);
  --elev-1-border: var(--line);
  --elev-2-bg:     var(--surface-2);
  --elev-2-border: var(--line-strong);

  /* -----------------------------------------------------------------
     1.4 LAYOUT  (teardown §2.4)
     Breakpoints (for reference — CSS custom props cannot be used in
     media queries, so these numbers are hard-coded below):
       sm 640 · md 768 · lg 1024 · xl 1280
     ----------------------------------------------------------------- */

  --page-max:    1200px;   /* one value, every page */
  --content-max: 760px;    /* reading / forms column */
  --gutter:      16px;     /* 16 mobile · 24 ≥768 · 32 ≥1200 */

  --grid-min:    320px;    /* repeat(auto-fill, minmax(320px, 1fr)) */
  --tap:         44px;     /* minimum interactive target */
  --header-h:    56px;

  /* WHITE focus ring, not red. Keyboard focus must be visible on
     red-filled, red-selected and neutral surfaces alike — a red ring on
     a red button is invisible exactly where focus matters most. */
  --ring: 0 0 0 2px var(--bg), 0 0 0 4px rgba(242, 244, 247, .9);

  /* Same decision, expressed as a plain colour for `outline:` rules. The port
     applied the white ring to --ring but left every focus-visible OUTLINE on
     --accent, so focus still painted red. That is not merely inconsistent: on
     Log Shots the player combo takes focus on load, and a red ring sitting
     under a "(required)" label reads unmistakably as a validation error on a
     form the coach has not touched yet. */
  --focus-ring: rgba(242, 244, 247, .9);
  --dur:  140ms;

  /* Content cross-fade. --dur is a micro-interaction (hover, border colour) and
     is far too quick to swap a whole sentence under. These are the standard
     content-transition durations: out fast, in slower. */
  --dur-fade-out: 200ms;
  --dur-fade-in:  300ms;
  --ease-accelerate: cubic-bezier(.4, 0, 1, 1);
  --ease-decelerate: cubic-bezier(0, 0, .2, 1);
  --ease: cubic-bezier(.2,.6,.3,1);
}

@media (min-width: 768px) {
  :root { --gutter: 24px; --header-h: 64px; }
}
@media (min-width: 1200px) {
  :root { --gutter: 32px; }
}


/* =====================================================================
   2. BASE ELEMENTS
   ===================================================================== */

html { background: var(--bg); }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: var(--fw-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  min-height: 100%;
}

h1 { font: var(--fw-h1) var(--fs-h1)/var(--lh-h1) var(--font-sans); letter-spacing: var(--ls-h1); }
h2 { font: var(--fw-h2) var(--fs-h2)/var(--lh-h2) var(--font-sans); letter-spacing: var(--ls-h2); }
h3 { font: 600 var(--fs-body)/var(--lh-body) var(--font-sans); }

p { max-width: 68ch; }

small { font-size: var(--fs-body-sm); line-height: var(--lh-body-sm); }

a {
  color: var(--accent);              /* links are accent red, never brand red */
  text-decoration: none;
  text-underline-offset: 3px;
}
a:hover { text-decoration: underline; }

hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: var(--space-24) 0;
}

/* Focus: real, visible, accent-coloured, never removed. */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}


/* =====================================================================
   3. TYPE UTILITIES
   ===================================================================== */

.t-display {
  font-family: var(--font-display);
  font-size: calc(var(--fs-display) * 1.18);   /* Squada runs small at a given px */
  line-height: 1.05;
  font-weight: 400;                            /* the face has one weight */
  letter-spacing: .02em;
  text-transform: uppercase;
}
.t-h1 {
  font-family: var(--font-display);
  font-size: calc(var(--fs-h1) * 1.2);
  line-height: 1.1;
  font-weight: 400;
  letter-spacing: .02em;
  text-transform: uppercase;
}
.t-h2 {
  font-size: var(--fs-h2); line-height: var(--lh-h2);
  font-weight: var(--fw-h2); letter-spacing: var(--ls-h2);
}
.t-body {
  font-size: var(--fs-body); line-height: var(--lh-body); font-weight: var(--fw-body);
}
.t-body-sm {
  font-size: var(--fs-body-sm); line-height: var(--lh-body-sm); font-weight: var(--fw-body-sm);
}
.t-label {
  font-size: var(--fs-label); line-height: var(--lh-label);
  font-weight: var(--fw-label); letter-spacing: var(--ls-label);
  /* sentence case — do NOT add text-transform here (§1.3) */
}
.t-meta {
  font-size: var(--fs-meta); line-height: var(--lh-meta);
  font-weight: var(--fw-meta); color: var(--ink-3);
}
/* The ONLY uppercase style in the system. Max one per card. */
.t-kicker {
  font-family: var(--font-display);
  font-size: calc(var(--fs-kicker) * 1.18); line-height: var(--lh-kicker);
  font-weight: 400; letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: block;
}

.t-ink   { color: var(--ink); }
.t-ink-2 { color: var(--ink-2); }
.t-ink-3 { color: var(--ink-3); }
.t-good  { color: var(--good); }
.t-warn  { color: var(--warn); }
.t-danger{ color: var(--danger); }
.t-brand { color: var(--brand); }   /* brand mark only */

/* Every numeric column, stat and score. */
.num, .tabular {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

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

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap; border: 0;
}


/* =====================================================================
   4. LAYOUT  (§2.4 — the fix for §1.1 "mobile app stretched to 1440px"
   and §1.2 "no spatial system")
   ===================================================================== */

/* Full-width page container. ONE max-width, every page. */
.page {
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Reading / forms column. Nest inside .page or use standalone. */
.content {
  width: 100%;
  max-width: var(--content-max);
  margin-inline: auto;
}

/* Vertical rhythm for a page body. */
.stack       { display: flex; flex-direction: column; gap: var(--space-16); }
.stack--sm   { gap: var(--space-8); }
.stack--lg   { gap: var(--space-24); }
.stack--xl   { gap: var(--space-32); }

.row {
  display: flex; align-items: center; gap: var(--space-12); flex-wrap: wrap;
}
.row--between { justify-content: space-between; }
.row--end     { justify-content: flex-end; }
.row--tight   { gap: var(--space-8); }
.row--nowrap  { flex-wrap: nowrap; }

.spacer { flex: 1 1 auto; }

/* THE responsive grid: Grades and Roster lists.
   7 players => 1 column at 390px, 2-up ≥768, 3-up ≥1024. */
.grid-auto {
  display: grid;
  gap: var(--space-16);
  grid-template-columns: repeat(auto-fill, minmax(var(--grid-min), 1fr));
}
/* Narrower cells where cards are small (e.g. stat tiles). */
.grid-auto--sm { --grid-min: 220px; }
.grid-auto--lg { --grid-min: 380px; }

/* Log Shots: form left (--content-max), today's entries right, ≥1024. */
.split {
  display: grid;
  gap: var(--space-24);
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 1024px) {
  .split { grid-template-columns: minmax(0, var(--content-max)) minmax(320px, 1fr); }
}

/* Player page: the existing 2:1 split, widened to --page-max. */
.split-2-1 {
  display: grid;
  gap: var(--space-24);
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 1024px) {
  .split-2-1 { grid-template-columns: 2fr 1fr; }
}

.page-header {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: var(--space-12);
  padding-block: var(--space-24) var(--space-16);
}

/* Show / hide by breakpoint.
   Written as max-width/min-width guards rather than a `display: none`
   base plus a `revert`, so the element keeps its own display value
   (inline-flex for .btn, grid for .grid-auto, …) at the visible size. */
@media (max-width: 767px) { .hide-mobile { display: none !important; } }
@media (min-width: 768px) { .only-mobile { display: none !important; } }


/* =====================================================================
   5. COMPONENTS  (§2.5)
   ===================================================================== */

/* ---------------------------------------------------------------------
   5.1 .btn — 44px minimum target, sentence case (never uppercase)
   Variants: --primary (ONE per screen) · --secondary · --ghost · --danger
   Modifiers: --sm · --lg · --block · --icon
   ------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-8);
  min-height: var(--tap);
  min-width: var(--tap);
  padding: 0 var(--space-16);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  font-size: var(--fs-body);
  line-height: 1;
  font-weight: 600;
  letter-spacing: 0;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.btn:hover { text-decoration: none; }
.btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* B3 (8-5 review). bmf25.com's calls to action are 48px-radius pills, so the
   public pages match. Scoped to a modifier, NOT applied to .btn globally: the
   Clipboard is a dense data-entry surface where rectangles pack better and a
   row of pills reads as decoration. */
.btn--pill { border-radius: var(--radius-pill); padding: 0 var(--space-24); }
.btn:active { transform: translateY(1px); }
.btn[disabled], .btn[aria-disabled="true"] {
  opacity: .45; pointer-events: none; cursor: not-allowed;
}
.btn svg { width: 20px; height: 20px; flex: none; }

/* The one primary action per screen. Filled red lives here and nowhere else. */
.btn--primary {
  background: var(--brand);
  color: var(--brand-ink);
  border-color: var(--brand);
}
.btn--primary:hover { background: #FF544A; border-color: #FF544A; }

.btn--secondary {
  background: var(--surface-2);
  color: var(--ink);
  border-color: var(--line-strong);
}
.btn--secondary:hover { background: var(--surface-3); border-color: rgba(255,255,255,.20); }

.btn--ghost {
  background: transparent;
  color: var(--ink-2);
  border-color: transparent;
}
.btn--ghost:hover { background: var(--surface-2); color: var(--ink); }

.btn--danger {
  background: transparent;
  color: var(--danger);
  border-color: rgba(248,113,113,.38);
}
.btn--danger:hover { background: var(--danger-dim); }

/* 36px was below the 44px minimum every other control in this system honours,
   which made every .btn--sm in the product a target a thumb can miss. It now
   keeps the SMALL LOOK — smaller text, tighter padding — at a full-size target. */
.btn--sm    { min-height: var(--tap); padding: 0 var(--space-12); font-size: var(--fs-body-sm); }
.btn--lg    { min-height: 52px; padding: 0 var(--space-24); }
.btn--block { display: flex; width: 100%; }
.btn--icon  { padding: 0; width: var(--tap); height: var(--tap); }
.btn--sm.btn--icon { width: var(--tap); height: var(--tap); min-width: var(--tap); }

/* Button strip that stacks on mobile, inlines on desktop. */
.btn-row { display: flex; flex-direction: column; gap: var(--space-8); }
@media (min-width: 640px) {
  .btn-row { flex-direction: row; align-items: center; }
  .btn-row .btn--block { width: auto; }
}


/* ---------------------------------------------------------------------
   5.2 .card — elevation via surface step, no shadows (§1.5)
   .card (surface-1 + line) · .card--raised (surface-2 + line-strong)
   ------------------------------------------------------------------- */

.card {
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-16);
}
@media (min-width: 768px) { .card { padding: var(--space-24); } }

.card--raised   { background: var(--surface-2); border-color: var(--line-strong); }
.card--flush    { padding: 0; overflow: hidden; }
.card--tight    { padding: var(--space-12); }
@media (min-width: 768px) { .card--tight { padding: var(--space-16); } }

/* Interactive card (roster/grade tiles). Selection = accent. */
.card--interactive {
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.card--interactive:hover { background: var(--surface-2); border-color: var(--line-strong); }
.card--interactive:focus-within { border-color: var(--accent-line); }
.card.is-selected { border-color: var(--accent); background: var(--accent-dim); }

.card__header {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--space-12); margin-bottom: var(--space-12);
}
.card__title  { font-size: var(--fs-h2); line-height: var(--lh-h2); font-weight: var(--fw-h2); }
.card__body   { display: flex; flex-direction: column; gap: var(--space-12); }
.card__footer {
  margin-top: var(--space-16);
  padding-top: var(--space-12);
  border-top: 1px solid var(--line);
  color: var(--ink-3);
  font-size: var(--fs-body-sm);
}
/* Section divider inside a .card--flush */
.card__section { padding: var(--space-16); border-top: 1px solid var(--line); }
.card__section:first-child { border-top: 0; }


/* ---------------------------------------------------------------------
   5.3 .chip — small neutral label. Grade variants are a hue cue only.
   ------------------------------------------------------------------- */

.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px var(--space-8);
  min-height: 22px;
  border-radius: var(--radius-pill);
  background: var(--surface-3);
  border: 1px solid var(--line);
  color: var(--ink-2);
  font-size: var(--fs-meta);
  line-height: var(--lh-meta);
  font-weight: 600;
  letter-spacing: 0;      /* sentence case — not a kicker */
  white-space: nowrap;
}
.chip svg { width: 14px; height: 14px; }

.chip__dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; flex: none;
}

/* Grade variants — colour is identity, never status. Never red. */
.chip--elementary {
  color: var(--grade-elementary);
  background: rgba(76,141,255,.12);
  border-color: rgba(76,141,255,.28);
}
.chip--middle {
  color: var(--grade-middle);
  background: rgba(167,139,250,.12);
  border-color: rgba(167,139,250,.28);
}
.chip--high {
  color: var(--grade-high);
  background: rgba(34,211,238,.12);
  border-color: rgba(34,211,238,.28);
}

/* Status variants */
.chip--good   { color: var(--good);   background: var(--good-dim);   border-color: rgba(52,211,153,.28); }
.chip--warn   { color: var(--warn);   background: var(--warn-dim);   border-color: rgba(251,191,36,.28); }
.chip--danger { color: var(--danger); background: var(--danger-dim); border-color: rgba(248,113,113,.28); }
.chip--accent { color: var(--accent); background: var(--accent-dim); border-color: var(--accent-line); }
.chip--quiet  { background: transparent; color: var(--ink-3); }
.chip--lg     { min-height: 28px; padding: 5px var(--space-12); font-size: var(--fs-body-sm); }


/* ---------------------------------------------------------------------
   5.4 .field — label + control + hint + error
   Markup:
     <div class="field">
       <label class="field__label" for="x">Shots made</label>
       <input class="field__control" id="x">
       <p class="field__hint">Counted toward this week.</p>
       <p class="field__error">Enter a number.</p>
     </div>
   Add .field--invalid on the wrapper to surface the error.
   ------------------------------------------------------------------- */

.field { display: flex; flex-direction: column; gap: 6px; }

.field__label {
  font-size: var(--fs-label);
  line-height: var(--lh-label);
  font-weight: var(--fw-label);
  letter-spacing: var(--ls-label);
  color: var(--ink-2);
  /* SENTENCE case. No text-transform. (§1.3) */
}
.field__req { color: var(--ink-3); font-weight: 500; }  /* not red */

.field__control,
.input, .select, .textarea {
  width: 100%;
  min-height: var(--tap);
  padding: 0 var(--space-12);
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
  appearance: none;
  -webkit-appearance: none;
}
.textarea { padding: var(--space-12); min-height: 96px; resize: vertical; line-height: var(--lh-body); }

.field__control::placeholder,
.input::placeholder, .textarea::placeholder { color: var(--ink-3); }

.field__control:hover,
.input:hover, .select:hover, .textarea:hover { border-color: rgba(255,255,255,.20); }

.field__control:focus-visible,
.input:focus-visible, .select:focus-visible, .textarea:focus-visible {
  /* WHITE, not accent. The red theme moved the focus ring to white deliberately, and a
     red ring on an untouched field reads as a validation error — the login page autofocuses
     its username input, so a visitor lands on what looks like a rejected form. The same
     bug was fixed on the .combo button earlier; these two rules were missed because they
     style focus with border-color + box-shadow rather than `outline`, which is all the
     original test looked at. */
  outline: none;
  border-color: var(--focus-ring);
  box-shadow: 0 0 0 3px rgba(242, 244, 247, .18);
}

.field__hint  { font-size: var(--fs-body-sm); line-height: var(--lh-body-sm); color: var(--ink-3); margin: 0; }
.field__error { font-size: var(--fs-body-sm); line-height: var(--lh-body-sm); color: var(--danger); margin: 0; display: none; }

.field--invalid .field__error { display: block; }
.field--invalid .field__control,
.field--invalid .input,
.field--invalid .select { border-color: var(--danger); }

/* Numeric inputs get tabular figures and lose the spinner. */
.field__control[inputmode="numeric"],
.input--num {
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-h2);
  font-weight: 600;
}
.input--num::-webkit-outer-spin-button,
.input--num::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.input--num { -moz-appearance: textfield; }

.field-row { display: grid; gap: var(--space-12); grid-template-columns: 1fr; }
@media (min-width: 640px) { .field-row--2 { grid-template-columns: 1fr 1fr; } }


/* ---------------------------------------------------------------------
   5.5 .select — themed chevron (fix for §1.7, the worst offender)
   The chevron is an inline SVG data URI so there is no external asset
   and no native browser arrow. Wrap dates in .input--date for the same
   treatment on <input type="date">.

   Note: the dropdown LIST of a native <select> is still OS chrome. Where
   zero native chrome is required, use .combo (§5.13) instead.
   ------------------------------------------------------------------- */

.select {
  padding-right: 40px;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='none' stroke='%23A8B0BB' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 8l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-12) center;
  background-size: 20px 20px;
  cursor: pointer;
  text-overflow: ellipsis;
}
.select::-ms-expand { display: none; }
/* The dropdown list itself is OS chrome — force at least a dark palette. */
.select option { background: var(--surface-2); color: var(--ink); }

/* <input type="date"> — kill the native light-mode calendar chrome. */
.input--date { color-scheme: dark; }
.input--date::-webkit-calendar-picker-indicator {
  filter: invert(72%) sepia(8%) saturate(300%) hue-rotate(180deg);
  cursor: pointer;
  opacity: .8;
}
.input--date::-webkit-calendar-picker-indicator:hover { opacity: 1; }


/* ---------------------------------------------------------------------
   5.6 .segmented — 3 options visible, no dropdown (fix for §1.9)
   Radio-driven so it works with zero JS.
     <div class="segmented" role="group">
       <label class="segmented__opt">
         <input type="radio" name="grade-1" checked><span>Elementary</span>
       </label> …
     </div>
   ------------------------------------------------------------------- */

.segmented {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 2px;
  padding: 3px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}
.segmented--wrap { grid-auto-flow: row; grid-auto-columns: auto; }
@media (min-width: 480px) { .segmented--wrap { grid-auto-flow: column; grid-auto-columns: 1fr; } }

.segmented__opt { position: relative; display: block; }
.segmented__opt input {
  position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; width: 100%; height: 100%;
}
.segmented__opt span {
  display: flex; align-items: center; justify-content: center;
  min-height: 38px; padding: 0 var(--space-12);
  border-radius: var(--radius-sm);
  font-size: var(--fs-body-sm); font-weight: 600; color: var(--ink-2);
  text-align: center; white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.segmented__opt:hover span { color: var(--ink); background: var(--surface-3); }
/* Selected = accent (red TEXT on a red wash), never a filled red block. */
.segmented__opt input:checked + span {
  background: var(--accent-dim);
  color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent-line);
}
/* B0.2 — a GRADE is never selected in the brand hue.

   Red is the mark and the one primary action, and this file's own header says grade chips
   are never red. But the Grades screen selects a grade through the shared .segmented
   rule, so picking "High school" painted it red — the one screen where the thing being
   selected IS a grade. The general accent-selection rule stays correct for tabs and
   periods; .segmented--grade opts that screen out and uses each grade's own hue. */
.segmented--grade .segmented__opt input:checked + span {
  background: color-mix(in srgb, var(--grade-tint, var(--ink-2)) 16%, transparent);
  color: var(--grade-tint, var(--ink));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--grade-tint, var(--ink-2)) 45%, transparent);
}
.segmented--grade .segmented__opt[data-grade="elementary"]   { --grade-tint: var(--grade-elementary); }
.segmented--grade .segmented__opt[data-grade="middle_school"] { --grade-tint: var(--grade-middle); }
.segmented--grade .segmented__opt[data-grade="high_school"]   { --grade-tint: var(--grade-high); }

.segmented__opt input:focus-visible + span { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* Taller variant when the segmented control is the primary control on a row. */
.segmented--tap .segmented__opt span { min-height: var(--tap); }


/* ---------------------------------------------------------------------
   5.7 .tabs — horizontally scrollable on mobile, NEVER cramped.
   The old 4 tabs squeeze at 390px; here they keep their natural width
   and the strip scrolls with snap points + an edge fade.
     <nav class="tabs" data-tabs>
       <a class="tab is-active" data-tab href="#panel-log">Log shots</a> …
     </nav>
   ------------------------------------------------------------------- */

.tabs {
  display: flex;
  gap: var(--space-4);
  padding: 4px;
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}
.tabs::-webkit-scrollbar { display: none; }

/* JS adds .is-overflowing when the strip actually scrolls and keeps
   --fade-l / --fade-r in sync with the scroll position, so the user can
   see there are more tabs. Purely additive — no JS, no fade, no harm. */
.tabs.is-overflowing {
  --fade-l: 0px;
  --fade-r: 28px;
  -webkit-mask-image: linear-gradient(to right,
      transparent 0, #000 var(--fade-l),
      #000 calc(100% - var(--fade-r)), transparent 100%);
  mask-image: linear-gradient(to right,
      transparent 0, #000 var(--fade-l),
      #000 calc(100% - var(--fade-r)), transparent 100%);
}

.tab {
  flex: 0 0 auto;                 /* never squeeze — this is the fix */
  scroll-snap-align: start;
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--space-8);
  /* Was 40px, which quietly undercut this system's own --tap token. The prototype
     never had to be tapped; the app does, and the three period tabs were the only
     controls in the whole product falling short of the 44px Apple HIG minimum. */
  min-height: var(--tap);
  padding: 0 var(--space-16);
  border-radius: var(--radius-sm);
  color: var(--ink-2);
  font-size: var(--fs-body);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}

/* On a 390px phone the three-period strip (This Week / Last Week / This Month)
   misses fitting by ~23px, so the last tab sat half off the card edge. Scrolling
   is right for a long strip, but for three short labels it is worse than simply
   fitting them. Tightening the horizontal padding — not the 40px tap height, not
   the font size — buys the room without cramping anything. A genuinely longer
   strip still scrolls, now with the edge fade base.html wires up. */
@media (max-width: 430px) {
  .tab { padding: 0 var(--space-12); }
}
.tab:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.tab svg { width: 20px; height: 20px; }
/* Active tab = accent red TEXT, not a filled red block. */
.tab.is-active, .tab[aria-selected="true"] {
  background: var(--accent-dim);
  color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent-line);
}
.tab:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }

/* On wide screens the strip has room — let tabs share it evenly. */
@media (min-width: 768px) {
  .tabs { overflow-x: visible; }
  .tabs--fill .tab { flex: 1 1 0; }
}

/* Underline variant for sub-navigation inside a card. */
.tabs--underline {
  background: transparent; border: 0; border-bottom: 1px solid var(--line);
  border-radius: 0; padding: 0; gap: var(--space-16);
}
.tabs--underline .tab {
  border-radius: 0; padding: 0 2px; min-height: var(--tap);
  box-shadow: none; background: transparent;
}
.tabs--underline .tab.is-active {
  background: transparent; color: var(--ink);
  box-shadow: inset 0 -2px 0 0 var(--accent);
}

.tab-panel { padding-top: var(--space-16); }


/* ---------------------------------------------------------------------
   5.8 .table — tabular numerals + sortable header affordance
     <table class="table" data-sortable>
       <thead><tr>
         <th class="table__num" data-sort="num" aria-sort="descending">
           <button class="table__sort" type="button">Makes<svg …/></button>
         </th>
       </tr></thead>
   Sorting reorders whole <tr>s, so rank/medal cells travel with the
   player and are never renumbered by row position (§1.8).
   ------------------------------------------------------------------- */

.table { width: 100%; font-size: var(--fs-body); }

.table th, .table td {
  padding: var(--space-12);
  text-align: left;
  vertical-align: middle;
}
.table thead th {
  font-size: var(--fs-label);
  line-height: var(--lh-label);
  font-weight: var(--fw-label);
  letter-spacing: var(--ls-label);
  color: var(--ink-3);
  text-transform: none;            /* sentence case (§1.3) */
  border-bottom: 1px solid var(--line-strong);
  padding-top: var(--space-8);
  padding-bottom: var(--space-8);
  white-space: nowrap;
}
.table tbody tr { border-bottom: 1px solid var(--line); }
.table tbody tr:last-child { border-bottom: 0; }
.table tbody tr:hover { background: rgba(255,255,255,.025); }
.table tbody tr.is-selected { background: var(--accent-dim); }

/* Numeric columns — always tabular, always right-aligned. */
.table__num, .table td.table__num, .table th.table__num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
.table__num--strong { font-weight: 700; font-size: var(--fs-h2); }

/* Sortable header affordance */
.table__sort {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 32px;
  color: inherit; font: inherit; letter-spacing: inherit;
  padding: 0; border-radius: var(--radius-sm);
  cursor: pointer;
  transition: color var(--dur) var(--ease);
}
.table__sort:hover { color: var(--ink); }
.table__sort:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; }
.table__num .table__sort { flex-direction: row-reverse; }

.table__sort svg { width: 14px; height: 14px; opacity: .45; transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease); }
.table__sort:hover svg { opacity: .8; }
th[aria-sort="ascending"]  .table__sort,
th[aria-sort="descending"] .table__sort { color: var(--accent); }
th[aria-sort="ascending"]  .table__sort svg { opacity: 1; transform: rotate(180deg); }
th[aria-sort="descending"] .table__sort svg { opacity: 1; }

/* Mobile: the whole table scrolls inside its own container, so the
   page body never scrolls sideways. */
.table-wrap {
  width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}
.table-wrap .table { min-width: 480px; }
@media (min-width: 768px) { .table-wrap .table { min-width: 0; } }

.table--compact th, .table--compact td { padding: var(--space-8) var(--space-12); }

/* Row-level secondary line (e.g. "last entry 2d ago") */
.table__sub { display: block; font-size: var(--fs-meta); line-height: var(--lh-meta); color: var(--ink-3); margin-top: 2px; }


/* ---------------------------------------------------------------------
   5.9 .rank-badge — styled numerals, NOT emoji (fix for §1.8)
   <span class="rank-badge rank-badge--gold">1</span>
   Ranks 4+ use the plain .rank-badge (no metal fill).
   ------------------------------------------------------------------- */

.rank-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--surface-3);
  border: 1px solid var(--line);
  color: var(--ink-3);
  font-family: var(--font-display);          /* B1 — jersey numerals */
  font-size: calc(var(--fs-body-sm) * 1.25);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  flex: none;
}
.rank-badge--gold {
  background: linear-gradient(160deg, rgba(227,179,65,.28), rgba(227,179,65,.10));
  border-color: rgba(227,179,65,.55);
  color: var(--gold);
}
.rank-badge--silver {
  background: linear-gradient(160deg, rgba(182,192,206,.24), rgba(182,192,206,.08));
  border-color: rgba(182,192,206,.50);
  color: var(--silver);
}
.rank-badge--bronze {
  background: linear-gradient(160deg, rgba(196,121,74,.26), rgba(196,121,74,.09));
  border-color: rgba(196,121,74,.52);
  color: var(--bronze);
}
.rank-badge--lg { width: 34px; height: 34px; font-size: var(--fs-body); }


/* ---------------------------------------------------------------------
   5.10 .stat — a labelled number
     <div class="stat">
       <span class="stat__label">Makes</span>
       <span class="stat__value">227</span>
       <span class="stat__delta stat__delta--up">+12 vs last week</span>
     </div>
   ------------------------------------------------------------------- */

.stat { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.stat__label {
  font-size: var(--fs-label); line-height: var(--lh-label);
  font-weight: var(--fw-label); letter-spacing: var(--ls-label);
  color: var(--ink-3);
  /* sentence case — the old UI shouted these (§1.3) */
}
.stat__value {
  font-family: var(--font-display);          /* B1 — scoreboard numerals */
  font-size: calc(var(--fs-h1) * 1.25); line-height: 1.1; font-weight: 400;
  letter-spacing: .01em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.stat__unit  { font-size: var(--fs-body-sm); font-weight: 500; color: var(--ink-3); margin-left: 2px; }
.stat__delta { font-size: var(--fs-meta); line-height: var(--lh-meta); font-weight: 500; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.stat__delta--up   { color: var(--good); }
.stat__delta--down { color: var(--ink-2); }

.stat--lg .stat__value { font-size: var(--fs-display); line-height: var(--lh-display); letter-spacing: var(--ls-display); }
.stat--sm .stat__value { font-size: var(--fs-h2); line-height: var(--lh-h2); }

/* A row of stats that wraps rather than squeezing. */
.stat-row {
  display: grid;
  gap: var(--space-16);
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
}
.stat-tile {
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: var(--space-12) var(--space-16);
}


/* ---------------------------------------------------------------------
   5.11 .empty — empty state
     <div class="empty">
       <svg class="empty__icon" …/>
       <p class="empty__title">No entries yet</p>
       <p class="empty__body">Log a session and it shows up here.</p>
       <a class="btn btn--secondary" href="#">Log shots</a>
     </div>
   ------------------------------------------------------------------- */

.empty {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--space-8);
  text-align: center;
  padding: var(--space-32) var(--space-16);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-lg);
  color: var(--ink-2);
}
.empty__icon { width: 28px; height: 28px; color: var(--ink-3); margin-bottom: var(--space-4); }
.empty__title { font-size: var(--fs-h2); line-height: var(--lh-h2); font-weight: var(--fw-h2); color: var(--ink); }
.empty__body  { font-size: var(--fs-body-sm); line-height: var(--lh-body-sm); color: var(--ink-3); max-width: 46ch; }
.empty .btn   { margin-top: var(--space-8); }
.empty--inline { padding: var(--space-24) var(--space-16); border-style: solid; border-color: var(--line); }


/* ---------------------------------------------------------------------
   5.12 .toast — transient confirmation.
   ------------------------------------------------------------------- */

.toast-region {
  position: fixed;
  left: var(--gutter); right: var(--gutter);
  bottom: calc(var(--gutter) + env(safe-area-inset-bottom, 0px));
  z-index: 60;
  display: flex; flex-direction: column; gap: var(--space-8);
  align-items: center;
  pointer-events: none;
}
@media (min-width: 640px) {
  .toast-region { left: auto; right: var(--gutter); align-items: flex-end; max-width: 380px; }
}

.toast {
  display: flex; align-items: flex-start; gap: var(--space-12);
  width: 100%; max-width: 380px;
  padding: var(--space-12) var(--space-16);
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  color: var(--ink);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body-sm);
  pointer-events: auto;
  animation: toast-in 200ms var(--ease) both;
}
.toast.is-leaving { animation: toast-out 160ms var(--ease) both; }
.toast svg { width: 20px; height: 20px; flex: none; margin-top: -1px; }
.toast__body { flex: 1 1 auto; }
.toast__title { font-weight: 600; display: block; }
.toast__close { color: var(--ink-3); width: 20px; height: 20px; flex: none; }
.toast__close:hover { color: var(--ink); }

.toast--success { border-left: 3px solid var(--good); }
.toast--success svg { color: var(--good); }
.toast--error   { border-left: 3px solid var(--danger); }
.toast--error svg { color: var(--danger); }
.toast--info    { border-left: 3px solid var(--accent); }
.toast--info svg { color: var(--accent); }

@keyframes toast-in  { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateY(4px); } }


/* ---------------------------------------------------------------------
   5.13 .combo — a real listbox, hoisted out of the Clipboard mockups.
   .select (§5.5) themes a native <select>, whose dropdown LIST is still
   OS chrome. The brief called for zero native chrome, so the panel is
   ours. Styled to match .field__control exactly, so a .combo and an
   .input sitting side by side line up to the pixel.

   All four Clipboard prototype pages carried a byte-identical copy of
   this block; this is the one shared definition.
   ------------------------------------------------------------------- */

.combo{position:relative}
.combo__btn{display:flex;align-items:center;justify-content:space-between;gap:var(--space-8);
  width:100%;min-height:var(--tap);padding:0 var(--space-12);
  background:var(--surface-2);border:1px solid var(--line-strong);border-radius:var(--radius-md);
  color:var(--ink);font-size:var(--fs-body);line-height:var(--lh-body);font-weight:400;
  text-align:left;cursor:pointer;transition:border-color var(--dur) var(--ease)}
.combo__btn:hover{border-color:rgba(255,255,255,.20)}
.combo[data-empty="true"] .combo__value{color:var(--ink-3)}
.combo__btn>svg{color:var(--ink-3);transition:transform var(--dur) var(--ease)}
.combo[data-open="true"] .combo__btn{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-dim)}
.combo[data-open="true"] .combo__btn>svg{transform:rotate(180deg)}
.combo__panel{position:absolute;z-index:30;top:calc(100% + 6px);left:0;right:0;display:none;
  padding:var(--space-4);max-height:300px;overflow-y:auto;
  background:var(--surface-2);border:1px solid var(--line-strong);border-radius:var(--radius-md)}
.combo[data-open="true"] .combo__panel{display:block}
.combo__opt{display:flex;align-items:center;justify-content:space-between;gap:var(--space-8);
  width:100%;min-height:var(--tap);padding:0 var(--space-12);border-radius:var(--radius-sm);
  background:transparent;border:0;color:var(--ink);font-size:var(--fs-body);text-align:left;cursor:pointer}
.combo__opt:hover{background:var(--surface-3)}
.combo__opt[aria-selected="true"]{color:var(--accent);background:var(--accent-dim)}

/* Group heading inside the listbox. role="presentation" in the markup so it is
   a visual divider only and never announced as an option. */
.combo__group{
  padding:var(--space-8) var(--space-12) var(--space-4);
  font-size:var(--fs-meta);line-height:var(--lh-meta);font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);
}
.combo__group + .combo__opt{margin-top:0}

/* Already entered for this date. Dimmed, ticked, and showing the total — but
   fully selectable, because a second gun session in one day is legal and must
   not require deleting the first entry. */
.combo__opt--done{color:var(--ink-2)}
.combo__opt--done:hover{color:var(--ink)}
.combo__opt--done .combo__opt-name{display:inline-flex;align-items:center;gap:var(--space-8)}
.combo__opt--done .combo__opt-name svg{color:var(--good);flex:none}
.combo__opt-meta{font-size:var(--fs-meta);color:var(--ink-3);font-variant-numeric:tabular-nums;white-space:nowrap}

/* "5 of 7 logged · 2 to go: Erin W., Ryan W." — answers the mid-session question
   without making Coach open the picker to count. */
/* Deliberately NOT flex: this is a sentence, and flex `gap` treats the comma
   between two names as its own item, printing "Haylee Stone , Peyton Sharp". */
.log-progress{margin-top:var(--space-4)}
.log-progress__count{color:var(--ink-2);font-weight:600}
.log-progress__left{color:var(--ink-3)}
.log-progress__who{color:var(--ink-2)}
.log-progress__done{color:var(--good);font-weight:600}


/* ---------------------------------------------------------------------
   5.14 .datepick — prev / date / next. Not <input type="date">: the
   native picker is OS chrome, and stepping days is what batch entry
   needs. Hoisted out of the Clipboard mockups alongside .combo.
   ------------------------------------------------------------------- */

.datepick{position:relative;display:flex;align-items:center;gap:var(--space-4);padding:4px;
  background:var(--surface-2);border:1px solid var(--line-strong);border-radius:var(--radius-md)}
.datepick__step{display:grid;place-items:center;width:var(--tap);height:var(--tap);flex:none;
  border-radius:var(--radius-sm);background:transparent;border:0;color:var(--ink-3);cursor:pointer}
.datepick__step:hover{background:var(--surface-3);color:var(--ink)}
.datepick__main{flex:1;min-height:var(--tap);display:flex;align-items:center;justify-content:center;
  gap:var(--space-8);border-radius:var(--radius-sm);background:transparent;border:0;color:var(--ink);
  font-size:var(--fs-body);font-weight:600;cursor:pointer}
.datepick__main:hover{background:var(--surface-3)}
.datepick__main>svg{color:var(--ink-3)}
.datepick__pop{position:absolute;z-index:30;top:calc(100% + 6px);left:0;right:0;display:none;
  flex-direction:column;gap:var(--space-8);padding:var(--space-12);
  background:var(--surface-2);border:1px solid var(--line-strong);border-radius:var(--radius-md)}
.datepick[data-open="true"] .datepick__pop{display:flex}
.daychips{display:flex;flex-wrap:wrap;gap:var(--space-8);margin-top:var(--space-8)}

/* Section switcher that sits under a Clipboard page header. */
.hub-tabs{margin-bottom:var(--space-24)}


/* =====================================================================
   6. SHELL — header, nav, drawer, footer
   Copy-paste markup lives in docs/design/mockup/assets/shell.html.

   NOTE: the prototype called these `.header` and `.footer`. Renamed to
   `.site-header` / `.site-footer` here — a bare `.header` is far too
   generic to introduce into a 60-template app that already ships plenty
   of hand-written markup.
   ===================================================================== */

.site-header {
  position: sticky; top: 0; z-index: 40;
  /* The e2 TIER, not the .e2 class: that class is authored as a card (18px radius plus a
     border on all four sides), so a full-bleed bar wearing it renders a rounded rectangle
     floating in the top strip. Same recipe, no card geometry. The ground drops .88 -> .62
     so the court-light wash is actually visible through the bar. */
  background: rgba(11,12,14,.62);
  -webkit-backdrop-filter: saturate(150%) blur(22px);
  backdrop-filter: saturate(150%) blur(22px);
  border-bottom: 1px solid var(--line);
}
.site-header__inner {
  display: flex; align-items: center; gap: var(--space-12);
  min-height: var(--header-h);
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* The BMF mark — one of only two legitimate uses of filled red. */
.brand { display: inline-flex; align-items: center; gap: var(--space-8); text-decoration: none; flex: none; }
.brand:hover { text-decoration: none; }
.brand__mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px;
  border-radius: var(--radius-sm);
  background: var(--brand);
  color: var(--brand-ink);
  font-size: 12px; font-weight: 800; letter-spacing: -0.02em;
}
.brand__name { font-size: var(--fs-body); font-weight: 700; color: var(--ink); letter-spacing: -0.01em; }
.brand__sub  { font-size: var(--fs-meta); color: var(--ink-3); display: block; font-weight: 500; }

/* Desktop nav — hidden on mobile, where the drawer takes over. */
.nav { display: none; }
@media (min-width: 768px) {
  .nav { display: flex; align-items: center; gap: var(--space-4); margin-left: var(--space-16); }
}
.nav__link {
  display: inline-flex; align-items: center; gap: var(--space-8);
  min-height: 36px; padding: 0 var(--space-12);
  border-radius: var(--radius-sm);
  color: var(--ink-2);
  font-size: var(--fs-body-sm); font-weight: 600;
  text-decoration: none;
}
.nav__link:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.nav__link svg { width: 20px; height: 20px; }
/* Current page = accent red TEXT, not a filled red block. */
.nav__link.is-active, .nav__link[aria-current="page"] {
  background: var(--accent-dim); color: var(--accent);
}

.site-header__actions { margin-left: auto; display: flex; align-items: center; gap: var(--space-8); }

.nav-toggle { display: inline-flex; }
@media (min-width: 768px) { .nav-toggle { display: none; } }

/* Mobile drawer. Works with :target (no JS) and with .is-open (JS). */
.drawer {
  position: fixed; inset: 0; z-index: 50;
  display: none;
}
.drawer:target, .drawer.is-open { display: block; }
@media (min-width: 768px) { .drawer:target, .drawer.is-open { display: none; } }

.drawer__scrim {
  position: absolute; inset: 0;
  background: rgba(0,0,0,.6);
  /* No blur here. It made a third blurred surface the moment the drawer opened — over
     override 3's budget — and it is fixed-and-blurred over a scrolling list, the exact
     pattern plan §2 calls expensive. Under 60% black almost none of it was visible. */
}
.drawer__panel {
  position: absolute; inset: 0 0 0 auto;
  width: min(320px, 86vw);
  background: var(--surface-1);
  border-left: 1px solid var(--line-strong);
  padding: var(--space-16);
  display: flex; flex-direction: column; gap: var(--space-8);
  overflow-y: auto;
  animation: drawer-in 180ms var(--ease) both;
}
@keyframes drawer-in { from { transform: translateX(16px); opacity: 0; } to { transform: none; opacity: 1; } }

.drawer__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-8); }
.drawer__link {
  display: flex; align-items: center; gap: var(--space-12);
  min-height: var(--tap);
  padding: 0 var(--space-12);
  border-radius: var(--radius-md);
  color: var(--ink-2); font-weight: 600; text-decoration: none;
}
.drawer__link:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.drawer__link.is-active, .drawer__link[aria-current="page"] { background: var(--accent-dim); color: var(--accent); }
.drawer__link svg { width: 20px; height: 20px; }
.drawer__sep { height: 1px; background: var(--line); margin: var(--space-8) 0; }

/* Scroll lock while a drawer is open. `.mobile-open` is the legacy shell's
   spelling, kept so the current mobile menu keeps its scroll lock until
   D2 replaces that markup. Drop it when the legacy shell goes. */
body.is-locked, body.mobile-open { overflow: hidden; }

/* ONE footer. The old public page shipped two (§1.10). */
.site-footer {
  border-top: 1px solid var(--line);
  margin-top: var(--space-48);
  padding-block: var(--space-24);
  color: var(--ink-3);
  font-size: var(--fs-body-sm);
}
.site-footer__inner {
  max-width: var(--page-max); margin-inline: auto; padding-inline: var(--gutter);
  display: flex; flex-direction: column; gap: var(--space-12); align-items: center; text-align: center;
}
@media (min-width: 768px) {
  .site-footer__inner { flex-direction: row; justify-content: space-between; text-align: left; }
}
.site-footer__social { display: flex; gap: var(--space-4); }
.site-footer__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--tap); height: var(--tap);
  border-radius: var(--radius-md);
  color: var(--ink-3);
}
.site-footer__icon:hover { color: var(--ink); background: var(--surface-2); }
.site-footer__quote {
  color: var(--ink-3); font-size: var(--fs-body-sm); font-style: italic;
}

/* Quote strip — dismissible, one line, and BELOW content, never above it
   (fix for §1.6). */
.quote-strip {
  display: flex; align-items: center; gap: var(--space-12);
  padding: var(--space-8) var(--space-12);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface-1);
  color: var(--ink-3);
  font-size: var(--fs-body-sm);
}
.quote-strip__text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }


/* =====================================================================
   7. SMALL SHARED PIECES
   ===================================================================== */

.icon { width: 20px; height: 20px; flex: none; }
.icon--sm { width: 16px; height: 16px; }
.icon--lg { width: 24px; height: 24px; }

/* Live/status dot */
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-3); flex: none; display: inline-block; }
.dot--good { background: var(--good); }
.dot--warn { background: var(--warn); }

/* Avatar / initials */
.avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--surface-3); border: 1px solid var(--line);
  color: var(--ink-2); font-size: var(--fs-body-sm); font-weight: 700;
  flex: none;
}

/* Divider with a label */
.rule { display: flex; align-items: center; gap: var(--space-12); color: var(--ink-3); }
.rule::before, .rule::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--line); }

/* Meter — % bars in leaderboards / player pages */
.meter { height: 6px; border-radius: var(--radius-pill); background: var(--surface-3); overflow: hidden; }
.meter__fill { height: 100%; border-radius: inherit; background: var(--ink-2); }
.meter__fill--subject { background: var(--brand); }   /* chart subject only */
.meter__fill--accent  { background: var(--accent); }

/* Skip link */
.skip-link {
  position: absolute; left: var(--space-8); top: var(--space-8);
  transform: translateY(-200%);
  background: var(--surface-2); color: var(--ink);
  padding: var(--space-8) var(--space-12); border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  z-index: 100;
}
.skip-link:focus { transform: none; }

/* Utilities */
.mt-0 { margin-top: 0; }
.mt-8 { margin-top: var(--space-8); }
.mt-16{ margin-top: var(--space-16); }
.mt-24{ margin-top: var(--space-24); }
.mt-32{ margin-top: var(--space-32); }
.mb-8 { margin-bottom: var(--space-8); }
.mb-16{ margin-bottom: var(--space-16); }
.mb-24{ margin-bottom: var(--space-24); }
.pb-48{ padding-bottom: var(--space-48); }
.w-full { width: 100%; }
.text-right { text-align: right; }
.text-center { text-align: center; }
.no-wrap { white-space: nowrap; }


/* =====================================================================
   8. PROGRESSIVE ENHANCEMENT
   With JS off, every tab panel stays visible and the drawer works via
   :target. The page script adds .js to <html> and only then hides panels.
   ===================================================================== */

.js .tab-panel[hidden] { display: none; }


/* =====================================================================
   9. HIDING WINS — the cascade guard

   design-system.css is linked AFTER Tailwind's output.css so component
   classes can beat utilities. That ordering has one nasty consequence:
   every component here that sets `display` also beats Tailwind's
   `.hidden { display: none }` at equal specificity.

   It was found the expensive way. `.empty { display: flex }` outranked
   `.hidden`, so every empty state painted ON TOP of populated content —
   "Season starts soon" under a full leaderboard, "No makes logged yet"
   under a drawn chart. Twenty-nine component rules set `display`, so
   patching them one at a time is a losing game.

   `!important` is right here: the entire job of these two selectors is
   "do not render", and nothing may outrank that. Inline `style.display`
   is not a conflict — every toggle in the app removes the class before
   setting the style.
   ===================================================================== */

.hidden,
[hidden] { display: none !important; }


/* =====================================================================
   10. TICKER — the public quote crawl (8-5 review, option Q1)

   Appended as its own component block; nothing above is touched.

   Scoped to the PUBLIC pages. It never appears on the Clipboard: Coach
   is working there, and moving text beside a data-entry form is the
   same mistake the quote strip made above the page title (§1.6).

   Colour follows the red rule: the quotation mark and the attribution
   are red, the words themselves stay --ink-2 so they read as text and
   not as a highlight.
   ===================================================================== */

.ticker {
  display: flex; align-items: center; gap: var(--space-8);
  margin-top: var(--space-8);
  min-width: 0;
  /* `100%` is not belt-and-braces next to 64ch — it is the whole fix. The rail is
     several thousand pixels of quotes, and that intrinsic width propagates up through
     every ancestor whose min-width is still `auto`, which put 233px of horizontal
     overflow on the landing page at 390px. Measured, not guessed. */
  max-width: min(64ch, 100%);
}

.ticker__mark {
  flex: none;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 24px; line-height: .7;
  color: var(--brand);
  transform: translateY(3px);
  -webkit-user-select: none; user-select: none;
}

/* The window. The mask fades both ends so quotes enter and leave instead
   of being guillotined at a hard edge. */
.ticker__view {
  flex: 1 1 auto; min-width: 0;
  /* No mask: nothing enters or leaves the edges now, and the old left fade
     dimmed the first character of a resting quote. */
}

/* The rail carries the day's quotes TWICE and travels exactly -50%, so
   copy two lands where copy one began — a seamless loop with no reset
   jump. The spacing is padding on each item rather than `gap` for the
   same reason: a flex gap would add one extra space between the two
   copies and put the loop half a gap out of register. */
/* Quotes CROSS-FADE, they do not scroll.

   This scrolled: first the whole list at once (so you always saw two), then one
   quote crossing at 55px/s. Both were wrong for the content. A crawl is built
   for headlines you skim; a quote has to be read, and moving text is measurably
   harder to read than still text — you cannot glance back at a word you missed.

   This is Material's "fade through", the standard transition for replacing one
   piece of content with an UNRELATED one, which is exactly what two quotes are:
   the outgoing item accelerates away, the incoming one decelerates in, and they
   never overlap. Asymmetric on purpose — content should leave faster than it
   arrives, or the swap feels sluggish.

   Nothing moves any more, so the rail's scroll geometry and the edge masks that
   hid items entering and leaving are gone with it. */
.ticker__item {
  display: block;
  font-size: var(--fs-body-sm); line-height: var(--lh-body-sm);
  color: var(--ink-2); font-style: italic;
  opacity: 1;
  transition: opacity var(--dur-fade-in) var(--ease-decelerate);
}
.ticker__item.is-out {
  opacity: 0;
  transition-duration: var(--dur-fade-out);
  transition-timing-function: var(--ease-accelerate);
}
.ticker__who { color: var(--accent); font-weight: 600; font-style: normal; }

/* WCAG 2.2.2: moving text lasting more than five seconds needs a stop
   control. Hover is not one on a phone, so this button is not optional. */
.ticker__pause {
  flex: none;
  display: grid; place-items: center;
  /* Drawn at 26px so it does not tower over a 26px ticker row, but the TOUCH
     TARGET is the full 44px minimum — the box is 44px and pulled back with a
     negative margin so the row height is unchanged. A pseudo-element would have
     been invisible to the measurement that caught this, and to a fingertip. */
  width: var(--tap); height: var(--tap);
  margin: calc((26px - var(--tap)) / 2) 0;
  padding: 0;
  background-clip: content-box;
  border-width: 1px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--ink-2);
}
.ticker__pause:hover { background: var(--surface-3); color: var(--ink); }
.ticker__pause:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.ticker__pause svg { width: 11px; height: 11px; }

/* Stands genuinely still — one quote, no animation at all. The global
   reduced-motion rule near the top only collapses the DURATION, which
   for a looping crawl would still snap it to its end position. */
@media (prefers-reduced-motion: reduce) {
  .ticker__item, .ticker__item.is-out { transition: none; }
  .ticker__pause { display: none; }
}


/* =====================================================================
   10. DIRECTION B — "The Arena": the elevation ladder
   Plan: docs/design/DIRECTION_B_BUILD_PLAN.md · Mockup: 8-5-direction-b.html

   Depth encodes IMPORTANCE. It is not decoration applied evenly — applying
   glass to everything destroys the hierarchy it was meant to create, which
   is the classic way this style fails. Exactly one thing per screen reaches
   the top of the ladder.

     e0   flat, hairline only          every repeated row: 20 board rows,
                                       20 grade rows, 20 roster rows,
                                       explainers, footer. NO blur, ever —
                                       this is the performance rule.
     e1   solid card + one shadow      supporting content
     e2   glass                        the one card the screen is ABOUT
     e3   + brand bloom (with e2)      the single hero number

   Why the blur is real: glass over flat dark refracts nothing, so you pay
   the GPU cost and see almost nothing. .b-atmo below paints a court-light
   wash for it to work against.
   ===================================================================== */

.e1 {
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: 0 8px 20px -14px rgba(0, 0, 0, .9);
}

.e2 {
  position: relative;
  border-radius: 18px;
  border: 1px solid rgba(255, 255, 255, .10);
  background: linear-gradient(180deg, rgba(255,255,255,.075), rgba(255,255,255,.028));
  -webkit-backdrop-filter: blur(22px) saturate(150%);
          backdrop-filter: blur(22px) saturate(150%);
  box-shadow:
    0 1px 0 rgba(255,255,255,.07) inset,
    0 22px 46px -22px rgba(0,0,0,.92),
    0 5px 14px -8px rgba(0,0,0,.6);
}

/* Worn WITH .e2, never instead of it — a second blur on one element is pure
   cost for no visible gain. This adds the bloom and nothing else. */
.e3 {
  box-shadow:
    0 1px 0 rgba(255,255,255,.09) inset,
    0 30px 60px -24px rgba(0,0,0,.95),
    0 0 60px -20px rgba(255,59,48,.30);
}

.glow-num { text-shadow: 0 0 34px rgba(255,59,48,.42), 0 0 12px rgba(255,59,48,.24); }

/* Without blur support the panel would be a translucent sheet over live text.
   Note this buys CORRECTNESS, not speed: slow devices DO support backdrop-filter,
   so this never fires for them. Bounding the number of blurred layers is the
   actual performance mitigation, and it is asserted in test_direction_b.py. */
@supports not ((-webkit-backdrop-filter: blur(4px)) or (backdrop-filter: blur(4px))) {
  .e2 { background: #191C21; }
}

/* ---------------------------------------------------------------------
   The court-light backdrop. Declared ONCE and mounted once, behind
   everything: brand wash overhead, a red-pink key upper-right, a cool
   bounce from the left, a faint floor reflection. Decoration only —
   aria-hidden in the markup, and it must never take pointer events or the
   page stops responding.
   ------------------------------------------------------------------- */
.b-atmo {
  position: absolute;
  inset: 0 0 auto 0;
  height: 760px;
  pointer-events: none;
  z-index: 0;
  overflow: hidden;
  background:
    radial-gradient(125% 78% at 50% -10%, rgba(255,59,48,.17),  rgba(255,59,48,0)  62%),
    radial-gradient(95%  55% at 88%   4%, rgba(255,122,112,.10), rgba(255,122,112,0) 60%),
    radial-gradient(105% 62% at 6%   34%, rgba(76,141,255,.075), rgba(76,141,255,0) 66%),
    radial-gradient(140% 60% at 50%  96%, rgba(255,255,255,.030), rgba(255,255,255,0) 70%);
}
.b-atmo > svg { position: absolute; left: 50%; top: 38px; transform: translateX(-50%); opacity: .5; }

/* Content sits above the wash. Deliberately NOT a new stacking context on a
   wrapper — the mobile drawer was trapped by one once already. */
/* NOT .site-header. It is declared `sticky; z-index: 40` above, and this rule sits later
   at equal specificity, so including it here silently demoted the app bar to
   `relative; z-index: 1` and the header scrolled away with the page. Caught by B2 in the
   browser, not by a test — there is one now. */
main, .site-footer { position: relative; z-index: 1; }

/* ---------------------------------------------------------------------
   The accessible — and practical — escape hatch. Not in the mockup; added
   deliberately (plan §2, override 1). Someone on a slow phone can reach
   this setting; they cannot reach a device-sniffing heuristic, and
   `deviceMemory` guesses age badly.
   ------------------------------------------------------------------- */
@media (prefers-reduced-transparency: reduce) {
  .e2 {
    -webkit-backdrop-filter: none;
            backdrop-filter: none;
    background: #191C21;
  }
  .b-atmo { display: none; }
}
