/* NBA ELO Dashboard — minimal, dark theme.
   Base = nhl-dashboard/static/css/style.css (the clone-and-adapt source,
   BUILD_PLAN_2026-10-12 §G), itself the NFL sheet minus the L2 .subsite-nav
   (single-page domain), the Survivor planner, the QB matrix and the
   depth-chart chrome. The hockey-only blocks did NOT port: no goalie panel /
   starter chips (no goalie term), no puck-line marks (the NBA market is the
   spread, in points), no totals projection (ADR-0005 is hockey's); the NHL's
   Market Performance / ROI-simulator block did not port as-is — the NBA tab
   (2026-10-15, internal only) carries its own `mkt-*` section at the foot of
   this sheet. The situational-grade ramp +
   badge DID port with the opener build (2026-10-15, src/grade_nba.py): the
   --grade-1..9 block below is byte-identical with the design doc's (the
   workspace scripts/sync-tokens.sh generates it; registering nba-dashboard
   as the sixth grade repo in token-drift-check.sh / sync-tokens.sh /
   vendored-parity-check.sh is the opener build's phase-4 workspace step —
   until it lands the drift check reports this block as a leak) and the
   .grade-cell / .sg-tier rules
   are vendored verbatim from the NHL sheet. Basketball blocks are marked
   "NBA:" in their section headers. */

/* ── SSA design system — shared token block (docs/DESIGN_SYSTEM.md §1).
   Copied verbatim; do not edit values here. ──────────────────────────────── */
:root {
  /* ── Surfaces ────────────────────────────────────────────────────────── */
  --bg:            #0b0f16;  /* page background                            */
  --bg-elev:       #161b22;  /* cards, nav bar, table headers              */
  --bg-row:        #1d2129;  /* table rows, inset panels                   */
  --border:        #2a2f3a;
  --border-subtle: #22262f;

  /* ── Text ────────────────────────────────────────────────────────────── */
  --text:          #e8eaef;  /* headings + body                            */
  --text-dim:      #9aa0aa;  /* secondary                                  */
  --text-muted:    #8b929d;  /* captions, timestamps, footnotes            */

  /* ── Brand / chrome ──────────────────────────────────────────────────── */
  --accent:        #58a6ff;  /* THE brand accent. All chrome.              */
  --accent-dim:    #2f6fd6;  /* hover + pressed FILLS ONLY — never text    */
  --text-disabled: #6b727d;  /* recessive but legible; NOT --text-muted */
  --accent-wash:   rgba(88, 166, 255, 0.14);  /* translucent accent fills */
  --accent-2:      #a78bfa;  /* secondary highlight (All-Access, badges)   */

  /* ── Data semantics — NEVER used for chrome ──────────────────────────── */
  --good:          #3fb950;  /* win, cover, +EV, positive delta            */
  --bad:           #f85149;  /* loss, no-cover, -EV, negative delta        */
  --warn:          #e6c26b;  /* push, stale data, caution                  */

  /* ── Type ────────────────────────────────────────────────────────────── */
  --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
                 Roboto, sans-serif;
}

/* League identity — legal in charts, team badges and hero art only; never in
   nav, buttons or focus rules (DESIGN_SYSTEM.md §1 "Per-league tint"). */
:root { --league-tint: #5b8def; }

/* ── Repo-specific data colours ───────────────────────────────────────────
   These encode DATA meaning, so they sit outside the shared block (additive,
   never in §1). */
:root {
    --bar-projw: #6b7fd6;     /* Forecast projected-wins fill bar (slate-purple) */
    /* Margins & Totals chart marks — the NFL pair (recessive bar + emphasised
       key-number bar, validated on --bg-elev: 19 deutan / 19 tritan, both
       >= 3:1). The hockey OT/SO and empty-net slices did not port: an NBA
       margin is one integer with no regulation/extra-time split. The tab is
       the "not built" stub in v1; the tokens stay declared for the page. */
    --kn-bar:     #5f6b7d;
    --kn-bar-key: #6f9cf0;
    /* The ELO trajectory line on Team Detail — derived from the league tint
       so it tracks a retune, nudged toward --text so it clears 3:1 on
       --bg-elev as a 2px stroke. */
    --chart-line: color-mix(in srgb, var(--league-tint) 85%, var(--text));
}

/* ── SSA design system — the grade ramp (docs/DESIGN_SYSTEM.md §1 "The grade
   ramp"). An ADDITIVE second :root block, byte-identical across the grade
   repos (golf / cfl / ncaaf / nfl-elo / nhl / nba — nba joined 2026-10-15
   with its situational grade); band N == --grade-N, band 1 best, and the
   football ladder S A+ A B+ B C+ C D+ D maps onto bands 1-9.
   GENERATED: written by the workspace scripts/sync-tokens.sh from the doc —
   never hand-edit the values here; token-drift-check.sh diffs it. ─────────── */
:root {
    --grade-1: #c084fc;  /* purple     */
    --grade-2: #60a5fa;  /* blue       */
    --grade-3: #38bdf8;  /* sky        */
    --grade-4: #4ade80;  /* green      */
    --grade-5: #86efac;  /* pale green */
    --grade-6: #eab308;  /* yellow     */
    --grade-7: #f97316;  /* orange     */
    --grade-8: #f85149;  /* red        */
    --grade-9: #fca5a5;  /* pale red   */
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text);
    font-family: var(--font-family);
    font-size: 14px; line-height: 1.5;
}
a { color: var(--text); text-decoration: none; }

/* ── Site nav (standardized across SSA portfolio) ────────────────────── */
.site-nav {
    background: var(--bg-elev);
    border-bottom: 1px solid var(--border);
    padding: 8px 20px;
    display: flex;
    align-items: center;
    gap: 24px;
    flex-wrap: wrap;
    /* E17 (wave 3): the L1 row sticks to the viewport top. z-index 30 clears
       every in-page sticky cell (the data grids use 3/4); the account
       dropdown (z 100) lives INSIDE this stacking context so it still
       paints above. No fixed min-height: that constant exists only for the
       two L2 domains, which pin a .subsite-nav beneath it. */
    position: sticky;
    top: 0;
    z-index: 30;
}
.site-nav-brand {
    text-decoration: none;
    font-size: 14px;
    font-weight: 600;
    color: var(--text);
    letter-spacing: -0.3px;
}
.site-nav-divider {
    width: 1px;
    height: 18px;
    background: var(--border);
}
.site-nav-link {
    text-decoration: none;
    font-size: 13px;
    color: var(--text-dim);
    font-weight: 500;
}
.site-nav-link:hover { color: var(--text); }
.site-nav-link.active {
    color: var(--accent);
    font-size: 15px;
    font-weight: 600;
}
.site-nav-link.disabled { color: var(--text-muted); cursor: not-allowed; }

/* ── Skip link (E18, wave 3) — first element of <body>, visually parked
   off-screen until keyboard focus lands on it. Filled control → dark label
   (white on --accent fails AA; DESIGN_SYSTEM.md §1). ─────────────────────── */
.skip-link {
    position: absolute;
    top: -9999px;
    left: 16px;
    z-index: 40;               /* above the sticky .site-nav (30) */
    background: var(--accent);
    color: var(--bg);
    padding: 8px 14px;
    border-radius: 0 0 8px 8px;
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
}
.skip-link:focus { top: 0; }

/* E18: keyboard focus ring on the chrome links (mouse clicks stay clean —
   :focus-visible). */
.site-nav-link:focus-visible,
.nav-right-link:focus-visible,
.site-footer-links a:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
/* ── Header / season picker ──────────────────────────────── */
.hdr {
    display: flex; align-items: center; justify-content: space-between;
    padding: 16px 24px; border-bottom: 1px solid var(--border);
}
.hdr-title { font-size: 18px; font-weight: 600; }
.hdr-meta { display: flex; align-items: center; gap: 12px; }
.hdr-meta label { font-size: 13px; color: var(--text-dim); }
/* Static season value on the public shell — replaces the season <select> when
   the manifest lists a single season (public season lockdown, 2026-08-08). */
.season-static { color: var(--text); font-weight: 600; }
select, input[type=number], input[type=text], input[type=date], button {
    background: var(--bg-elev); color: var(--text); border: 1px solid var(--border);
    padding: 6px 10px; border-radius: 4px; font-size: 13px; font-family: inherit;
}
select:focus, input:focus, button:focus { outline: 1px solid var(--accent); }
button { cursor: pointer; }
button:hover { background: var(--bg-row); }
button.primary { background: var(--accent); border-color: var(--accent); color: var(--bg); }
button.primary:hover { filter: brightness(1.12); }
.pill {
    font-size: 11px; padding: 2px 8px; border-radius: 10px;
    background: var(--bg-row); color: var(--text-dim);
}

/* ── Page nav ────────────────────────────────────────────── */
.page-nav {
    display: flex; gap: 4px; padding: 12px 24px;
    border-bottom: 1px solid var(--border);
}
.page-nav-btn {
    background: transparent; border: 1px solid transparent;
    padding: 6px 14px; color: var(--text-dim);
}
.page-nav-btn:hover { border-color: var(--text-muted); }
/* L3 active = the filled-accent form (design contract §3; golf/mock were the
   reference). Filled controls take color: var(--bg) — white fails AA. */
.page-nav-btn.active {
    background: var(--accent); border-color: var(--accent); color: var(--bg);
}

/* ── Pages ───────────────────────────────────────────────── */
main { padding: 24px; max-width: 1280px; margin: 0 auto; }
.page { display: none; }
.page.active { display: block; }
.section-head {
    margin: 24px 0 12px; font-size: 13px; font-weight: 600;
    color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em;
}
.section-head:first-child { margin-top: 0; }

/* ── Tables ──────────────────────────────────────────────── */
table {
    width: 100%; border-collapse: collapse; font-size: 12px;
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 6px;
    overflow: hidden;
}
thead th {
    background: var(--bg-row); color: var(--text-dim);
    text-align: left; padding: 10px 12px; font-weight: 600;
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
}
tbody td {
    padding: 8px 12px; border-bottom: 1px solid var(--border);
    white-space: nowrap;
}
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--bg-row); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td.center, th.center { text-align: center; }
td.dim { color: var(--text-dim); }
td.good { color: var(--good); }
td.bad { color: var(--bad); }
td.tag {
    font-size: 11px; padding: 2px 6px; border-radius: 3px;
    background: var(--bg-row); display: inline-block;
}

/* ── Team logo (self-hosted ESPN dark-variant PNGs, static/logos/) ── */
.team-logo {
    display: inline-block; width: 22px; height: 22px;
    object-fit: contain; vertical-align: middle;
    flex-shrink: 0;
}
.team-logo.sm { width: 18px; height: 18px; }
.team-logo.lg { width: 32px; height: 32px; }
td .team-logo { margin-right: 7px; }

/* ── Team badge (abbreviation chip; fallback when a logo is missing) ── */
.team-badge {
    display: inline-block; min-width: 34px; text-align: center;
    background: var(--bg-row); border: 1px solid var(--border); border-radius: 4px;
    padding: 2px 6px; font-size: 11px; font-weight: 700; letter-spacing: 0.02em;
    color: var(--text); vertical-align: middle;
}

/* ── Division standings ──────────────────────────────────── */
.standings-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 16px; margin-bottom: 24px;
}
.standings-div h3 {
    margin: 0 0 8px; font-size: 12px; font-weight: 600; color: var(--text-dim);
    text-transform: uppercase; letter-spacing: 0.04em;
}
.standings-table { font-size: 12px; }

/* ── NBA: standings cut lines ──────────────────────────────────────────────
   The cut line inside a conference table: after the six automatic seeds
   (top 6) and again after seed 10 (the play-in band, 7-10). Chrome, so a
   text/surface token — but NOT --border alone: every row already carries a
   1px --border rule, so a 1px --border top would be invisible beside it.
   --text-disabled (a chrome token, 3.32:1 on --bg-row) reads as a distinct
   rule at 2px. The title-text on the row states the meaning in words. */
.playoff-line td { border-top: 2px solid var(--text-disabled); }
/* The play-in band itself (seeds 7-10, `tr.playin` from rankings.js): a
   translucent accent wash on the row — chrome, so the accent token; the
   row's text stays the table's own (--text / --text-dim on --bg-row +
   14 % accent ≈ the same composite the hover wash already uses), and the
   PI tag on the team cell says it in words, never colour alone. */
.standings-table tr.playin td { background: var(--accent-wash); }
/* The conference's non-automatic table (play-in 7-10 and out) in
   conference_rank order, under the division tables. Full width of the
   conference grid. `.standings-wc` is the hockey name the shared rankings
   renderer was written against; `.standings-playin` is the basketball one —
   one rule, either class. */
.standings-wc, .standings-playin { grid-column: 1 / -1; }
.standings-wc h3, .standings-playin h3 {
    margin: 0 0 8px; font-size: 12px; font-weight: 600; color: var(--text-dim);
    text-transform: uppercase; letter-spacing: 0.04em;
}
.standings-wc table, .standings-playin table { max-width: 720px; }
/* Clinch/elimination glyphs (x / y / z / e) ride the team cell as TEXT —
   dim, never colour-coded. */
.clinch { color: var(--text-muted); font-size: 10px; font-weight: 700; margin-left: 4px; }

/* ── NBA: result tag ───────────────────────────────────────────────────────
   "OT" / "2OT" beside a final score (regulation carries no tag). --text-dim
   on no fill: it is a WORD, never a colour — an overtime loss is a loss. */
.res-tag {
    display: inline-block; margin-left: 4px; vertical-align: 1px;
    font-size: 10px; font-weight: 700; letter-spacing: 0.06em;
    color: var(--text-dim);
}

/* ── NBA: rankings pace column ─────────────────────────────────────────────
   82-game wins pace is noise for the first ~10 games; the cell goes dim
   with a title saying so rather than hiding a number the public page also
   shows. */
.rank-pace.dim { color: var(--text-muted); }
/* "vs Oct 25" under the Rank Δ header — the previous daily capture the
   movement is measured against; caption tone, text token only. */
.rank-delta-vs { display: block; font-weight: 400; font-size: 11px; color: var(--text-muted); }

/* "Model updated <relative>" freshness pill on the rankings tab (wave-3
   E24) — reads the SAME manifest stamp as the schedule control bar's Model
   item (meta.model_updated_at). Chrome: surface tokens only, no data
   colours. Sits between the section head and the table in both shells. */
.rankings-fresh {
    display: inline-flex; align-items: center; gap: 5px;
    margin: 0 0 12px; padding: 3px 10px;
    background: var(--bg-elev); border: 1px solid var(--border);
    border-radius: 999px;
    font-size: 12px; color: var(--text-dim);
}
.rankings-fresh .label {
    text-transform: uppercase; letter-spacing: 0.04em;
    font-weight: 600; font-size: 11px; color: var(--text-muted);
}

/* ── Schedule (sportsbook-style table) ───────────────────── */
/* ONE control bar: date window + hide-completed with the odds/model freshness
   readout on the right. All chrome — surface tokens + --accent only. */
.sched-blurb {
    color: var(--text-muted);
    font-size: 12.5px;
    line-height: 1.5;
    margin: 0 0 10px;
    max-width: 720px;
}

.sched-controls {
    display: flex; align-items: center; flex-wrap: wrap; gap: 8px 18px;
    margin: 0 0 18px; padding: 8px 12px;
    max-width: 1100px;
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 4px;
    font-size: 13px; color: var(--text-dim);
}
.sched-controls label { display: inline-flex; align-items: center; gap: 6px; margin: 0; cursor: pointer; }
.sched-controls select, .sched-controls input[type="date"] {
    background: var(--bg-row); color: var(--text);
    border: 1px solid var(--border); border-radius: 4px;
    padding: 4px 8px; font-size: 13px;
}
.sched-controls input[type="checkbox"] { accent-color: var(--accent); }
.sched-controls .sched-fresh { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; }
.sched-controls .label {
    text-transform: uppercase; letter-spacing: 0.04em;
    font-weight: 600; font-size: 11px; margin-right: 4px;
}
.sched-controls .freshness-sep { opacity: 0.5; }

/* ── NBA: daily navigation (replaces the NFL week <select>) ────────────────
   .sched-daybar = ‹ [seven ISO-week day chips] › above the control bar. A
   chip is a button carrying the weekday+day and a game-count badge. The
   selected chip is the filled-accent form with color: var(--bg) (the
   filled-control rule — white on --accent fails AA); hover lifts the fill
   with brightness, never --accent-dim (the label would drop to 3.99:1).
   Today's chip carries an accent ring so "today" and "selected" stay
   distinguishable when they differ. A chip with no games is .dim and
   disabled — text-disabled clears the 3:1 non-text floor on --bg-elev. */
.sched-daybar {
    display: flex; align-items: center; gap: 8px;
    margin: 0 0 10px; max-width: 1100px;
}
.day-nav {
    flex: none; min-width: 34px; height: 34px; padding: 0 10px;
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 6px;
    color: var(--accent); font-size: 16px; font-weight: 700; line-height: 1;
}
.day-nav:hover { border-color: var(--accent); background: var(--bg-elev); }
.day-nav:disabled { color: var(--text-disabled); cursor: not-allowed; border-color: var(--border-subtle); }
.day-strip {
    display: flex; gap: 6px; flex: 1 1 auto; min-width: 0;
}
.day-chip {
    flex: 1 1 0; min-width: 64px; height: 44px; padding: 4px 6px;
    display: inline-flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 1px; line-height: 1.15;
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 6px;
    color: var(--text); font-size: 12px; font-weight: 600;
}
.day-chip:hover { border-color: var(--accent); background: var(--bg-elev); }
.day-chip .n { font-size: 10px; font-weight: 500; color: var(--text-dim); }
.day-chip.today { box-shadow: inset 0 0 0 1px var(--accent); }
.day-chip.active { background: var(--accent); border-color: var(--accent); color: var(--bg); }
.day-chip.active .n { color: var(--bg); }
.day-chip.active:hover { filter: brightness(1.12); background: var(--accent); }
.day-chip.dim { color: var(--text-disabled); cursor: not-allowed; border-color: var(--border-subtle); }
.day-chip.dim .n { color: var(--text-disabled); }
.day-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── Schedule table — three rows per game (date / away / home) ───────────
   One .day-section per ET date (the NFL's .week-section). */
.day-section { margin-bottom: 28px; }
.day-section h3 {
    margin: 0 0 10px; font-size: 13px; font-weight: 600;
    color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em;
}
table.schedule-table {
    width: 100%; max-width: 1100px; border-collapse: separate; border-spacing: 0;
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
    overflow: hidden; font-size: 13px;
}
.schedule-table thead th {
    background: var(--bg-row); color: var(--text-dim);
    text-align: center; padding: 10px 8px; font-weight: 600; font-size: 11px;
    text-transform: uppercase; letter-spacing: 0.04em;
    border-bottom: 1px solid var(--border);
}
.schedule-table thead th.matchup-col { text-align: left; padding-left: 14px; }
.schedule-table tbody td { padding: 10px 8px; border-bottom: 1px solid var(--border); text-align: center; }
.schedule-table tbody tr:last-child td { border-bottom: none; }
/* Unplayed rows dim slightly — every cell EXCEPT the Grade cell (2026-10-15
   review): a child cannot undo a parent's opacity, and at 0.92 on the row the
   D+ letter composited to 4.53:1 at rest and 4.25:1 hovered, under the 4.5
   floor on the only rows a subscriber sees a grade on. The letter is data;
   tests/test_shell.py measures it on the future row's own surfaces. */
.schedule-table tbody tr.future > td:not(.grade-cell) { opacity: 0.92; }
.schedule-table .matchup-cell { display: flex; align-items: center; gap: 10px; padding-left: 6px; min-width: 200px; }
.schedule-table .matchup-cell .team-block { display: flex; flex-direction: column; line-height: 1.2; }
.schedule-table .matchup-cell .team-name { font-weight: 600; white-space: nowrap; }
/* The line under the team name: the club's record. Caption tone. */
.schedule-table .matchup-cell .record { font-size: 11px; color: var(--text-muted); }
.schedule-table .score-cell { font-weight: 700; font-size: 16px; min-width: 26px; text-align: center; }
.schedule-table .score-cell.dim { color: var(--text-dim); font-weight: 400; font-size: 13px; }
.schedule-table .score-cell .res-tag { font-size: 9px; vertical-align: 3px; }
.schedule-table .odds-cell { background: var(--bg-row); border-radius: 6px; padding: 6px 10px; display: inline-block; min-width: 64px; }
.schedule-table .odds-cell .price { display: block; font-size: 10px; color: var(--text-dim); margin-top: 2px; }
.schedule-table .pred-cell { font-weight: 600; }
.schedule-table .pred-cell .p { display: block; font-size: 10px; font-weight: 500; color: var(--text-dim); margin-top: 1px; }
.schedule-table .edge-cell { font-size: 12px; color: var(--text-dim); }
/* The pick highlight marks the side WE like, not the side the book likes
   (John, 2026-09-23; same day on NFL / NCAAF / CFL, carried through the NHL):
   Model line and Edge on the RECOMMENDED row share one accent rule, so the
   two read as one row. Before that, Pred painted the favourite green
   (`.pred-cell.fav`), pulling the eye to the wrong side on every dog pick —
   and green is data-only (an outcome), never a "look here". `.edge-cell.big`
   (edge >= 4 pp, schedule.js) is the STRONGER form of the same colour, never
   a second one. */
.schedule-table .pred-cell.pick,
.schedule-table .edge-cell.pick { color: var(--accent); font-weight: 600; }
.schedule-table .edge-cell.big { color: var(--accent); font-weight: 700; }
.schedule-table .edge-cell .mkt { font-size: 9px; font-weight: 600; letter-spacing: 0.05em; margin-left: 3px; color: var(--text-muted); }
/* A lineup adjustment on either side (an admin `lineup_adj` entry for the
   team and date — star rest, injury; the title names the points). Chrome
   chip. */
.schedule-table .adj-chip {
    display: inline-block; margin-left: 6px; padding: 0 5px; border-radius: 3px;
    font-size: 9px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
    color: var(--accent); background: var(--accent-wash); vertical-align: 1px;
}
/* The engine's back-to-back flag (schedule_features.b2b_flags — the fatigue
   term's input), stacked on .adj-chip by schedule.js. An engine FACT, not an
   admin entry, so it takes the muted border-chip form rather than the accent
   wash: the two chips read as different kinds of thing. Chrome only — a
   tired team is not an outcome. */
.schedule-table .adj-chip.b2b-chip { color: var(--text-muted); background: transparent; border: 1px solid var(--border); }
/* Final-game market marks: which side of each book number the score landed
   on, computed client-side from the final score. GREEN/AMBER ARE DATA here —
   an outcome, not chrome — and each mark's title-text states the meaning in
   words so it is never colour-only. */
/* Final-score marks hang OUTSIDE the centering: .val-marked centers as the
   value alone and the mark is absolutely positioned off its right edge, so a
   winning side's number stays column-aligned with the losing side's. */
.schedule-table .val-marked { position: relative; display: inline-block; }
.schedule-table .val-marked .mark-win,
.schedule-table .val-marked .mark-push { position: absolute; left: 100%; margin-left: 5px; top: 50%; transform: translateY(-50%); white-space: nowrap; }
.schedule-table .mark-win  { color: var(--good); font-weight: 700; }
.schedule-table .mark-push { color: var(--warn); font-weight: 700; font-size: 11px; }
/* Completed public games: Model line / Fair ML / Edge collapse into one link
   out to the game page. Chrome — accent, like every other link. */
.schedule-table .boxscore-cell { text-align: center; }
.schedule-table .boxscore-link { color: var(--accent); font-weight: 600; font-size: 12px; white-space: nowrap; }
.schedule-table .result-cell.win  { color: var(--good); font-weight: 700; font-size: 16px; }
.schedule-table .result-cell.loss { color: var(--bad);  font-weight: 700; font-size: 16px; }
.schedule-table .result-cell.push { color: var(--warn); font-weight: 700; font-size: 13px; }
/* The date row's darkening wash derives from --bg (the NFL's rgba black
   literal, re-expressed as a token mix so nothing in a rule is a literal). */
.schedule-table .game-date {
    font-size: 11px; color: var(--text-dim);
    padding: 6px 14px; background: color-mix(in srgb, var(--bg) 45%, transparent);
}
.schedule-table .game-date td { padding: 6px 14px; text-align: left; border-bottom: 1px solid var(--border); }
/* A game in progress: the WORD "LIVE" in the date row, plain text tone —
   odds are frozen at tip-off and nothing here updates while it runs (the
   scores stay blank until the final). */
.schedule-table .game-date .live { font-weight: 700; letter-spacing: 0.06em; color: var(--text); }

/* ── VENDORED: the situational grade badge — the RULES are copied VERBATIM
   from nhl-dashboard/static/css/style.css (.grade-cell + the .sg-tier block,
   itself the NFL sheet's; 2026-10-15). The letter is SYSTEM 2
   (src/grade_nba.py, 0-100 situational), rendered by the vendored
   static/js/sharpgrade.js into the schedule's Grade column. Edit in the NFL
   sheet first, copy here in the same commit; tests/test_shell.py measures
   every band against every surface the badge sits on. ─────────────────── */
/* The Grade column: SYSTEM 2's letter (see the .sg-tier ramp), on the graded
   side's row beside SYSTEM 1's edge number — separate columns, never summed. */
.schedule-table .grade-cell { text-align: center; }

/* ── The situational grade badge (SYSTEM 2's letter) ──────────────────────────
   THE LETTER IS DATA, so it takes the full grade ramp — DESIGN_SYSTEM.md §1
   "Letter grades ARE data" and "The grade ramp". Nine bands, band 1 best,
   shared byte-for-byte with golf's D-S badge so one letter means one colour
   across the whole portfolio.

   THE BADGE IS NEVER FILLED. `background: transparent` and the LETTER carries
   the colour — golf's treatment, adopted portfolio-wide 2026-08-16 after
   John: the football columns "are all gray, no matter what the grade". A
   fill would also drag `color: var(--bg)` back in per the filled-control rule
   and collapse the ramp back to a box.

   Every band is measured against --bg, --bg-elev AND --bg-row; worst band is
   4.81:1. No hex literals — the tokens live in the ramp block at the top of
   this file, so a retune moves every grade at once.

   THE RAMP REACHES RED AT THE BOTTOM, AND THAT IS DELIBERATE. This ladder's
   floor means the scheduling and situational factors cut AGAINST the graded
   side — a genuine negative on a signed scale, not a warning, and none of the
   bottom labels says "avoid". The plain-English label rides in the cell's
   `title`, so colour is never the only carrier of meaning. ─────────────────── */
/* min-width + padding rather than a fixed width: four of the nine letters are
   two glyphs ("A+", "B+", "C+", "D+") and a hard 26px clipped them. */
.sg-tier {
    flex: none; display: inline-flex; align-items: center; justify-content: center;
    min-width: 26px; height: 26px; padding: 0 5px; border-radius: 6px;
    font-size: 14px; font-weight: 700; line-height: 1;
}
/* "A+" is not a legal class fragment, so the letter is slugged `+` -> `plus`
   by static/js/sharpgrade.js::tierClass; tests/test_shell.py asserts a rule
   exists for every tier the config declares, using that same slug. */
/* The Grade COLUMN cell (schedule table). */
.sg-tier-row {
    min-width: 26px; height: 26px; padding: 0 6px;
    border-radius: 6px; font-size: 15px;
}
/* THE RAMP — nine bands, best first. Band N == var(--grade-N); the football
   ladder's letters map onto the bands in declaration order (see the table in
   DESIGN_SYSTEM.md, which also carries golf's differing bottom two). */
.sg-tier.t-s     { background: transparent; color: var(--grade-1); }  /* purple     */
.sg-tier.t-aplus { background: transparent; color: var(--grade-2); }  /* blue       */
.sg-tier.t-a     { background: transparent; color: var(--grade-3); }  /* sky        */
.sg-tier.t-bplus { background: transparent; color: var(--grade-4); }  /* green      */
.sg-tier.t-b     { background: transparent; color: var(--grade-5); }  /* pale green */
.sg-tier.t-cplus { background: transparent; color: var(--grade-6); }  /* yellow     */
.sg-tier.t-c     { background: transparent; color: var(--grade-7); }  /* orange     */
.sg-tier.t-dplus { background: transparent; color: var(--grade-8); }  /* red        */
.sg-tier.t-d     { background: transparent; color: var(--grade-9); }  /* pale red   */

/* ── NBA: the injury-report line + the availability chip (ADR-0003, 2026-10-15)
   INTERNAL ONLY — the public row carries no availability field. Under each
   team name: "Out: Curry, Butler · Q: Green ↑ · 3:15 PM" in caption tone.
   An injury status is NOT an outcome, so no green / red / amber anywhere
   here: text tokens and text glyphs (↑ less severe than his previous status,
   ↓ more severe, "new" a late addition) only, and every name's title carries
   the status sequence, the reason and the arithmetic. An unmatched name reads
   "?" (dotted underline), an exempt one (G League, not yet established) is
   italic — shape, never colour. The applied automatic term rides the
   ordinary accent-wash .adj-chip with the word "inj" (an admin entry reads
   "adj"); a term computed but NOT on the line (switch off, or an admin 0
   for the date) is the dashed muted chip — visibly not an adjustment. */
.schedule-table .matchup-cell .avail-line {
    display: block; margin-top: 3px; max-width: 300px; white-space: normal;
    font-size: 10px; font-weight: 400; line-height: 1.35; color: var(--text-muted);
}
.schedule-table .avail-line .avail-p { white-space: nowrap; }
.schedule-table .avail-line .avail-trend { font-weight: 700; color: var(--text); }
.schedule-table .avail-line .avail-exempt { font-style: italic; }
.schedule-table .avail-line .avail-unres { text-decoration: underline dotted; }
.schedule-table .avail-line .avail-time { white-space: nowrap; color: var(--text-dim); }
.schedule-table .adj-chip.avail-off {
    color: var(--text-muted); background: transparent; border: 1px dashed var(--border);
}

/* ── NBA: the injury-report detail (John, 2026-10-16) — BOTH hosts ─────────
   An "Injury report" disclosure button on the game's date row opens a detail
   ROW under the home row: each club's listed players (name, status, the
   report's reason), the report time and the empty states. Names and statuses
   only — no number, no arrow, no chip, no value order (no player value
   leaves the internal host). The button is an OUTLINED chrome control (accent
   text, border → accent on hover / open, accent focus ring; never filled).
   An injury status is not an outcome, so the detail is text tokens only: the
   name in --text, the status --text-dim, the reason / empty state / report
   time --text-muted / --text-dim on --bg-row (5.14:1 worst case, DESIGN_SYSTEM
   §1). The chevron is drawn with borders in currentColor — no glyph for a
   screen reader to announce. `hidden` must win over the phone layout's
   display: block, so the [hidden] rule is the more specific one. */
.schedule-table .game-date .inj-toggle {
    float: right; margin: -4px 0 -4px 12px; min-height: 24px; padding: 0 10px;
    background: transparent; color: var(--accent);
    border: 1px solid var(--border); border-radius: 4px;
    font-size: 11px; font-weight: 600; line-height: 22px; letter-spacing: 0; cursor: pointer;
}
.schedule-table .game-date .inj-toggle::after {
    content: ""; display: inline-block; width: 5px; height: 5px; margin: 0 1px 3px 8px;
    border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg); vertical-align: middle;
}
.schedule-table .game-date .inj-toggle[aria-expanded="true"]::after { transform: rotate(-135deg); margin-bottom: 0; }
.schedule-table .game-date .inj-toggle:hover,
.schedule-table .game-date .inj-toggle[aria-expanded="true"] { border-color: var(--accent); }
.schedule-table .game-date .inj-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.schedule-table tbody tr.inj-detail[hidden] { display: none; }
/* white-space: the global `tbody td` rule is nowrap; without this no name or
   reason in the detail could wrap (the .avail-line restates it too). */
.schedule-table tbody tr.inj-detail > td { padding: 12px 14px 14px; text-align: left; white-space: normal; background: var(--bg-row); }
/* The last game's closed detail is the tbody's last row: drop the home row's
   rule line there exactly as `tr:last-child td` does without a detail. */
.schedule-table tbody tr:has(+ tr.inj-detail[hidden]:last-child) > td { border-bottom: none; }
.schedule-table .inj-panel {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 28px; max-width: 900px;
}
.schedule-table .inj-side { min-width: 0; }
.schedule-table .inj-team {
    display: flex; align-items: center; margin: 0 0 6px;
    font-size: 12px; font-weight: 600; color: var(--text);
}
.schedule-table .inj-list { list-style: none; margin: 0; padding: 0; }
.schedule-table .inj-list li { padding: 3px 0; font-size: 12px; line-height: 1.35; color: var(--text); }
.schedule-table .inj-name { font-weight: 600; overflow-wrap: anywhere; }
.schedule-table .inj-status { margin-left: 4px; color: var(--text-dim); }
.schedule-table .inj-reason { display: block; font-size: 11px; color: var(--text-muted); overflow-wrap: anywhere; }
.schedule-table .inj-empty { margin: 0; font-size: 12px; color: var(--text-muted); }
.schedule-table .inj-asof { margin: 6px 0 0; font-size: 11px; color: var(--text-dim); }
.schedule-table .inj-asof-all { grid-column: 1 / -1; margin: 0; }
@media (max-width: 600px) {
    /* The phone card: the open detail continues the game's card (the home
       row gives up its bottom corners), one club per row, a 28px target. */
    .schedule-table tbody tr.inj-detail {
        display: block; background: var(--bg-row);
        border: 1px solid var(--border); border-top: 0; border-radius: 0 0 6px 6px;
    }
    .schedule-table tbody tr.inj-detail > td { display: block; border-bottom: 0; padding: 10px 12px 12px; }
    .schedule-table tbody tr.game-date + tr + tr:has(+ tr.inj-detail:not([hidden])) { border-radius: 0; }
    .schedule-table .inj-panel { grid-template-columns: minmax(0, 1fr); gap: 10px; }
    /* The date cell is a flow-root on phones (the card block below), so it
       CONTAINS the floated button. Its line box matches the 28px target: the
       button sits level with the date text when both fit, and drops onto its
       own line — below the text, never over it, never beside the away row —
       when they do not (a negative margin there pulled the dropped button
       up into the text's descenders). */
    .schedule-table tbody tr.game-date td:has(> .inj-toggle) { padding-top: 4px; padding-bottom: 4px; line-height: 28px; }
    .schedule-table .game-date .inj-toggle { min-height: 28px; line-height: 26px; margin: 0 0 0 10px; }
}

/* ── Team detail ─────────────────────────────────────────── */
.team-summary {
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 6px;
    padding: 16px; margin-bottom: 16px;
    display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 16px;
}
.team-summary .stat-label { font-size: 11px; color: var(--text-dim); text-transform: uppercase; }
.team-summary .stat-val { font-size: 22px; font-weight: 700; margin-top: 2px; }

/* ── Team Detail — one-page sheet (NCAAF ts-* port via the NFL) ── */
.ts-header {
    display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
    padding: 14px 16px; margin-bottom: 14px;
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 6px;
}
.ts-title h2 { margin: 0; font-size: 22px; }
/* The stats own the full right side of the header row (flex: 1) with even
   spacing, and every block centers VERTICALLY on the team name — without
   align-items the taller Proj Record block made its neighbors look
   top-pinned (John, 2026-08-06). */
.ts-stats { display: flex; flex: 1 1 260px; justify-content: space-evenly;
    align-items: center; gap: 14px; flex-wrap: wrap; }
.ts-stat { text-align: center; }
.ts-stat-val { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; }
.ts-stat-val .ts-big { font-size: 22px; }
.ts-stat-label { font-size: 10px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .05em; }
.ts-panel {
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 6px;
    padding: 12px 14px; margin-bottom: 14px;
}
.ts-panel h4 { margin: 0 0 10px; font-size: 13px; text-transform: uppercase; letter-spacing: .04em; }
.ts-season-tag { font-weight: 400; font-size: 11px; color: var(--text-dim); text-transform: none; letter-spacing: 0; }
/* league-rank chip beside a stat — tiered by thirds of the 30-team league
   (1–10 green / 11–20 amber / 21–30 red); the number is the message. */
.ts-rank {
    display: inline-block; min-width: 26px; text-align: center;
    font-size: 10px; font-weight: 700; padding: 0 4px; margin-left: 6px;
    border-radius: 3px; font-variant-numeric: tabular-nums;
}
.ts-rank.r-good { background: color-mix(in srgb, var(--good) 18%, transparent); color: var(--good); }
.ts-rank.r-mid { background: color-mix(in srgb, var(--warn) 15%, transparent); color: var(--warn); }
.ts-rank.r-bad { background: color-mix(in srgb, var(--bad) 6%, transparent); color: var(--bad); }
.ts-log-title { margin: 20px 0 10px; font-size: 13px; text-transform: uppercase; letter-spacing: .04em; }
/* .good/.bad are td-scoped globally; the sheet uses them on spans too */
.ts-panel .good, .ts-header .good { color: var(--good); }
.ts-panel .bad, .ts-header .bad { color: var(--bad); }

/* ── Sheet 2-col grid: charts + log left, race + next games right. Collapses
   to one column on narrow screens. ── */
.ts-grid {
    display: grid; grid-template-columns: 1fr 1.2fr; gap: 14px;
    margin-bottom: 14px; align-items: start;
}
@media (max-width: 1000px) { .ts-grid { grid-template-columns: 1fr; } }
.ts-col { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.ts-col .ts-panel { margin-bottom: 0; }   /* flex gap owns the spacing */

/* schedule column (NCAAF ts-sched port) */
.ts-sched { width: 100%; border: 0; font-size: 12px; }
.ts-sched td { padding: 4px 6px; border-bottom: 1px solid var(--border); white-space: nowrap; }
.ts-sched-line { color: var(--text-dim); margin-left: 4px; }

/* ── NBA: Team Detail charts — inline SVG, no library. Axes, gridlines and
   labels are text/surface tokens; the marks are the declared data tokens
   (--chart-line for the ELO series, --bar-projw for the wins-pace series).
   Reference lines (1500, the seed, the win-total line, the league-average
   pace) are dashed/dotted --border so they never compete with the series. ── */
.ts-elo-chart, .ts-pace-chart {
    display: block; width: 100%; height: auto; max-width: none;
    font-family: var(--font-family);
}
.ts-chart-grid { stroke: var(--border-subtle); stroke-width: 1; }
.ts-chart-axis { stroke: var(--border); stroke-width: 1; }
.ts-chart-ref { stroke: var(--border); stroke-width: 1; stroke-dasharray: 5 4; }
.ts-chart-ref.seed { stroke-dasharray: 2 3; }
.ts-chart-ref.avg { stroke: var(--text-muted); stroke-dasharray: 2 3; }
.ts-chart-tick { fill: var(--text-muted); font-size: 10px; }
.ts-chart-label { fill: var(--text-dim); font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; }
.ts-elo-chart .ts-chart-line { fill: none; stroke: var(--chart-line); stroke-width: 2; stroke-linejoin: round; }
.ts-elo-chart .ts-chart-dot { fill: var(--chart-line); }
.ts-pace-chart .ts-chart-line { fill: none; stroke: var(--bar-projw); stroke-width: 2; stroke-linejoin: round; }
.ts-pace-chart .ts-chart-dot { fill: var(--bar-projw); }
.ts-chart-note { font-size: 11px; color: var(--text-muted); margin: 6px 0 0; }

/* ── NBA: month-grouped game log (82 rows). details/summary per month; the
   summary line carries the month record, point differential and ELO swing;
   a sticky thead inside each table. Chrome — surface tokens; the W/L
   letters in the rows carry their own data classes. ── */
.ts-month {
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 6px;
    margin-bottom: 10px; overflow: hidden;
}
.ts-month > summary {
    list-style: none; cursor: pointer; user-select: none;
    display: flex; align-items: center; gap: 12px;
    padding: 9px 14px; font-size: 12px; font-weight: 600;
}
.ts-month > summary::-webkit-details-marker { display: none; }
.ts-month > summary::after {
    content: "▾"; margin-left: auto; color: var(--text-dim);
    font-size: 12px; transition: transform 0.15s ease;
}
.ts-month[open] > summary::after { transform: rotate(180deg); }
.ts-month > summary:hover { background: var(--bg-row); }
.ts-month-meta { font-weight: 400; color: var(--text-dim); }
.ts-month table { border: 0; border-radius: 0; }
.ts-month thead th { position: sticky; top: 0; z-index: 2; }
.ts-month-tools { display: flex; gap: 8px; margin: 0 0 10px; }
.ts-month-tools button { font-size: 12px; padding: 4px 10px; }

/* ── Admin ───────────────────────────────────────────────── */
.admin-section {
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 6px;
    padding: 16px; margin-bottom: 16px;
}
.admin-section h3 { margin: 0 0 12px; font-size: 14px; color: var(--text); }
.admin-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 12px;
}
.admin-field { display: flex; flex-direction: column; gap: 4px; }
.admin-field label { font-size: 11px; color: var(--text-dim); text-transform: uppercase; }
.admin-field input { width: 100%; }
.admin-field select { width: 100%; }
.admin-actions { display: flex; gap: 8px; margin-top: 16px; flex-wrap: wrap; }
.admin-locked {
    background: var(--bg-row); padding: 24px; border-radius: 6px;
    border: 1px dashed var(--border); text-align: center;
}
/* ── NBA: admin read-only panels + the freeze banners + the lineup editor ──
   Fit-owned constants render as VALUES, not inputs (an admin edit to the
   calibration block would silently regrade every past game). The two
   preseason win-total boards (market / VORP) each carry a freeze banner;
   the seed-source select sits in the same panel. */
.admin-ro { display: flex; flex-direction: column; gap: 2px; }
.admin-ro .k { font-size: 11px; color: var(--text-dim); text-transform: uppercase; }
.admin-ro .v { font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums; }
.admin-ro .v .u { font-size: 11px; font-weight: 400; color: var(--text-muted); margin-left: 4px; }
.admin-note { font-size: 11px; color: var(--text-dim); margin: 8px 0 0; }
.admin-table-wrap { max-height: 520px; overflow: auto; border: 1px solid var(--border); border-radius: 8px; }
.admin-table-wrap table { border: 0; border-radius: 0; }
.admin-table-wrap thead th { position: sticky; top: 0; z-index: 2; }
.admin-table-wrap input[type=number] { padding: 4px 6px; }
/* The freeze banner: --warn is the stale-data/caution token and this IS a
   caution (a frozen board must not be edited by accident). The text states
   it; the bar reinforces. */
.freeze-banner {
    margin: 0 0 12px; padding: 8px 12px;
    background: var(--bg-row); border: 1px solid var(--border);
    border-left: 3px solid var(--warn); border-radius: 4px;
    font-size: 12px; color: var(--text-dim);
    display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
}
.freeze-banner strong { color: var(--text); }
.freeze-banner label { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; cursor: pointer; }
.freeze-banner input[type=checkbox] { accent-color: var(--accent); }
/* The live board (the one `seed_source` runs the ladder on) is marked by an
   accent rule on its panel head; the other board is the comparison. */
.seed-live { border-left: 3px solid var(--accent); padding-left: 10px; }
.seed-live-tag {
    display: inline-block; margin-left: 8px; padding: 0 6px; border-radius: 3px;
    font-size: 10px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
    color: var(--accent); background: var(--accent-wash); vertical-align: 2px;
}
.adj-editor { width: auto; min-width: 60%; }
.adj-editor th, .adj-editor td { padding: 6px 10px; }
.adj-editor td.num input { width: 80px; }
.adj-editor .adj-del {
    background: transparent; border: 1px solid var(--border); color: var(--text-dim);
    padding: 2px 8px; font-size: 12px; border-radius: 4px;
}
.adj-editor .adj-del:hover { border-color: var(--accent); color: var(--accent); background: transparent; }
.adj-add { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; margin: 12px 0 0; }
.adj-add .admin-field { min-width: 140px; }
.adj-add .admin-field.wide { min-width: 220px; }
.adj-filter { display: flex; align-items: center; gap: 8px; margin: 0 0 10px; font-size: 12px; color: var(--text-dim); }
/* The opener build's two admin switches (2026-10-15): the availability term
   ("apply to unplayed games") and the situational grade. Plain checkbox rows
   in text tone with the accent as the native control's tint — chrome, never
   a data colour; the state is spelled out in the label, never colour-only. */
.admin-switches { display: flex; flex-direction: column; gap: 10px; margin: 0 0 12px; }
.admin-check { display: inline-flex; align-items: flex-start; gap: 8px; font-size: 13px; color: var(--text); cursor: pointer; }
.admin-check input[type=checkbox] { accent-color: var(--accent); margin-top: 3px; }
.admin-check input[type=checkbox]:disabled + span { color: var(--text-dim); }
.admin-check .admin-check-sub { display: block; font-size: 11px; color: var(--text-dim); }

/* ── Toast / status ──────────────────────────────────────── */
.toast {
    position: fixed; bottom: 24px; right: 24px;
    background: var(--bg-elev); border: 1px solid var(--border);
    padding: 10px 14px; border-radius: 6px; font-size: 13px;
    transform: translateY(8px); opacity: 0; pointer-events: none;
    transition: transform 0.18s, opacity 0.18s;
    z-index: 10;
}
.toast.visible { transform: translateY(0); opacity: 1; }
.toast.err { border-color: var(--bad); color: var(--bad); }
.toast.ok { border-color: var(--good); color: var(--good); }

/* ── Misc ────────────────────────────────────────────────── */
.empty { padding: 48px; text-align: center; color: var(--text-dim); }
.spinner {
    display: inline-block; width: 14px; height: 14px;
    border: 2px solid var(--border); border-top-color: var(--accent);
    border-radius: 50%; animation: spin 0.8s linear infinite; vertical-align: middle;
}
@keyframes spin { to { transform: rotate(360deg); } }
.rank-1 { color: var(--accent); font-weight: 700; }
.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ── Forecast — "Dynamic Futures" grid ──────────────────── */
/* Full-bleed: the forecast page escapes main's max-width cap so the (wide) grids
   use the whole viewport instead of overflowing a centred 1280px column. */
/* While the full-bleed forecast tab is ACTIVE the shell's centered cap comes
   off, so the header (hero/season/tab bar) shares the grids' viewport-edge
   alignment instead of floating in a narrower centered column (John,
   2026-08-31). app.js/public.js stamp body[data-page]; the second selector
   exists to outrank .public-main.wide's own cap on the public shell. */
body[data-page="forecast"] main,
body[data-page="forecast"] main.public-main.wide { max-width: none; }
#page-forecast.active {
    width: 100vw;
    margin-left: calc(50% - 50vw);
    padding: 0 24px;
    box-sizing: border-box;
}
.fc-head h3 { margin: 0 0 4px; }
.fc-head .dim { line-height: 1.5; max-width: 760px; margin: 0 0 14px; }
/* One grid per conference (East / West), stacked. */
.fc-conf-list { display: flex; flex-direction: column; gap: 22px; }
.fc-conf { min-width: 0; }
.fc-conf-title {
    margin: 0 0 8px; font-size: 13px; font-weight: 700; letter-spacing: 0.05em;
    text-transform: uppercase; color: var(--accent);
}
/* Each conference scrolls independently on BOTH axes. A bounded height makes the
   container itself the vertical scrollport, so sticky column headers (top:0) work
   on vertical scroll — on a tall conference/short screen you keep the headers —
   while overflow keeps the horizontal scrollbar per-grid (not pinned to the page
   bottom) with the sticky Team column (left:0). */
.fc-scroll {
    overflow: auto; max-height: calc(100vh - 90px);
    border: 1px solid var(--border); border-radius: 8px;
}
.fc-grid {
    border-collapse: separate; border-spacing: 0; font-size: 12px; min-width: 100%;
    /* Override the global `table { overflow: hidden }` (used to clip other tables'
       border-radius). On a table it makes the TABLE the sticky cells' scroll
       ancestor, which silently defeats the sticky Team column + column headers —
       they must stick to .fc-scroll instead. Corners are clipped by .fc-scroll. */
    overflow: visible; border: 0; border-radius: 0;
}
.fc-grid th, .fc-grid td {
    border-bottom: 1px solid var(--border); padding: 5px 6px;
    text-align: center; white-space: nowrap;
}
.fc-grid thead th {
    position: sticky; top: 0; z-index: 3;
    background: var(--bg-elev); color: var(--text-dim);
    font-weight: 600; text-transform: uppercase; font-size: 10px;
}
/* sticky Team column (header + body + division band rows). */
.fc-team-head, .fc-team, .fc-div-cell {
    position: sticky; left: 0; z-index: 2;
    text-align: left !important; padding-left: 10px !important;
    background: var(--bg-elev);
}
.fc-team-head { z-index: 4; min-width: 160px; }
.fc-team { min-width: 160px; background: var(--bg); }
.fc-grid tbody tr:hover .fc-team { background: var(--bg-elev); }
.fc-team .team-badge, .fc-team .team-logo { margin-right: 7px; vertical-align: middle; }
.fc-team .team-name { font-weight: 600; vertical-align: middle; }
.fc-grid tbody tr:hover td { background: var(--bg-elev); }


/* Conference/division roll-up row: season-odds sums (Σ wins vs the seed
   board, expected playoff teams, group title probability). The label reuses
   .fc-team so it stays sticky; the day region is one empty spanning cell. */
.fc-sum-row td { border-top: 2px solid var(--border); background: var(--bg-elev); }
.fc-sum-row td.fc-team { background: var(--bg-elev); }
.fc-sum-row .fc-sum-label { font-weight: 600; font-size: 11px; text-transform: uppercase;
    letter-spacing: 0.05em; color: var(--text-dim); }
.fc-sum-row td.fc-num { font-weight: 600; }

/* Exacta expander: division finish-order pairs + the finish-position heat
   strip. The panel is sticky-left so it stays readable while the wide grid
   scrolls; the caret is chrome (accent), the numbers inherit data styling. */
.fc-sum-row.fc-expandable { cursor: pointer; }
.fc-sum-row.fc-expandable:hover td { background: var(--bg-row); }
.fc-perm-caret { color: var(--accent); display: inline-block; font-size: 9px;
    transition: transform 0.15s ease; }
.fc-sum-row.fc-perm-open .fc-perm-caret { transform: rotate(90deg); }
.fc-perm-row td.fc-perm-cell { background: var(--bg); padding: 0; }
.fc-perm-panel { position: sticky; left: 0;
    max-width: min(1240px, calc(100vw - 64px));
    padding: 12px 16px 14px 22px; }
.fc-perm-title { font-size: 11px; font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.05em; color: var(--text-dim); margin-bottom: 8px; }
.fc-perm-cols { display: flex; flex-wrap: wrap; gap: 6px 32px; }
.fc-perm-col { flex: 1 1 300px; min-width: 280px; }
.fc-perm-col-head { font-size: 10px; font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 4px;
    border-bottom: 1px solid var(--border-subtle, var(--border));
    padding-bottom: 3px; }
.fc-perm-item { display: flex; align-items: baseline; gap: 8px;
    font-size: 12px; line-height: 1.7; font-variant-numeric: tabular-nums; }
.fc-perm-rank { color: var(--text-muted); font-size: 10px; min-width: 16px;
    text-align: right; }
.fc-perm-seq { flex: 1; white-space: nowrap; }
.fc-perm-num { min-width: 44px; text-align: right; }
.fc-perm-odds { min-width: 52px; text-align: right; color: var(--text-dim); }
.fc-perm-note { margin-top: 8px; font-size: 10px; color: var(--text-muted); }
/* Finish-position heat strip: 15 cells P(1st)…P(15th) per team — the
   conference finish distribution (DATA_CONTRACT §6 finish_dist[15]), washed
   by p (a --p wash over --good like .fc-prob; the number sits in the cell). */
.fc-finish { display: grid; grid-template-columns: 160px repeat(15, 40px); gap: 2px 4px; align-items: center; font-size: 11px; }
.fc-finish .fc-finish-head { color: var(--text-muted); font-size: 10px; text-transform: uppercase; text-align: center; }
.fc-finish .fc-finish-team { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fc-finish .fc-finish-cell { text-align: center; border-radius: 3px; padding: 1px 0; font-variant-numeric: tabular-nums;
    background: color-mix(in srgb, var(--good) calc(var(--p, 0) * 50%), transparent); }

/* Division sub-header: a lighter, indented band nested under the conference. */
.fc-div-row td.fc-div-cell {
    font-size: 10px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--text-dim); background: var(--bg);
    padding: 4px 10px 4px 22px;
}
/* ── NBA: next-7-days strip — seven date columns (the bounded replacement
   for the NFL's 18-week grid); a cell is "vs BOS 58%" / "@ BOS 42%", a
   played game W / L (OT tagged), an empty day .fc-off. ── */
.fc-next7 th.fc-wk { min-width: 52px; }
.fc-wk { min-width: 46px; }
.fc-wk .fc-wk-day { display: block; font-size: 9px; color: var(--text-muted); font-weight: 500; }
.fc-cell { min-width: 46px; line-height: 1.15; }
.fc-cell.fc-off { background: var(--bg); }
.fc-opp { font-size: 9px; color: var(--text-dim); letter-spacing: .02em; }
.fc-wp { font-weight: 700; font-size: 12px; }
.fc-cell.fc-w .fc-wp { color: var(--good); }
.fc-cell.fc-l .fc-wp { color: var(--bad); }
/* division-game marker: a white inner ring (inset box-shadow doesn't shift
   layout the way a border would, so the heatmap grid stays aligned). */
.fc-cell.fc-confgame { box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--text) 60%, transparent); }
.fc-confgame-legend {
    color: var(--text); padding: 0 4px; border-radius: 3px;
    box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--text) 60%, transparent);
}
/* numeric/outcome columns */
.fc-num { text-align: right; font-variant-numeric: tabular-nums; min-width: 50px; }
.fc-natty { font-weight: 700; }
.fc-sep { border-left: 2px solid var(--border); }
/* implied-odds column: dim, board-style, sits right of its probability */
.fc-odds {
    text-align: right; font-variant-numeric: tabular-nums; min-width: 52px;
    color: var(--text-dim); font-size: 11px;
}
.fc-odds-head { color: var(--text-dim); }
/* section buffer: empty channel between the forecast zones. */
.fc-grid th.fc-gap, .fc-grid td.fc-gap {
    min-width: 18px; width: 18px; padding: 0;
    background: var(--bg); border-bottom: none;
}
.fc-grid tbody tr:hover td.fc-gap { background: var(--bg); }
/* Projected-wins cell: horizontal fill bar to the wins fraction (of 82). */
.fc-rec {
    min-width: 64px; font-weight: 700;
    background: linear-gradient(
        90deg,
        var(--bar-projw) 0%,
        var(--bar-projw) var(--rec-fill, 0%),
        var(--bg-elev)   var(--rec-fill, 0%),
        var(--bg-elev)   100%
    );
}
.fc-grid tbody tr:hover .fc-rec { background: linear-gradient(
    90deg, var(--bar-projw) 0%, var(--bar-projw) var(--rec-fill, 0%),
    var(--bg-elev) var(--rec-fill, 0%), var(--bg-elev) 100%); }
/* Probability wash: derived from the token (a literal rgba would keep the
   OLD --good after a retune — DESIGN_SYSTEM.md §1 "Translucent washes"),
   capped at 50% so --text on the composite stays above AA. */
.fc-prob { background: color-mix(in srgb, var(--good) calc(var(--p, 0) * 50%), transparent); padding: 1px 5px; border-radius: 4px; }

/* per-game win-probability heatmap. The dog alphas are lower than the fav ones
   on purpose: a played loss renders its % in var(--bad) ON this wash, and --bad
   has far less headroom than --good, so 22% measured 3.91:1. */
.wp-strong-fav { background: color-mix(in srgb, var(--good) 22%, transparent); }
.wp-light-fav  { background: color-mix(in srgb, var(--good) 10%, transparent); }
.wp-coinflip   { background: transparent; }
.wp-light-dog  { background: color-mix(in srgb, var(--bad) 5%, transparent); }
.wp-strong-dog { background: color-mix(in srgb, var(--bad) 10%, transparent); }

/* ── Nav-right (Help + Account stub) — used by the public shells. */
.nav-right { display: flex; align-items: center; gap: 16px; margin-left: auto; }
.nav-right-link { color: var(--text-dim); text-decoration: none; font-size: 13px; font-weight: 500; }
.nav-right-link:hover { color: var(--text); }
.account-stub { position: relative; }
/* Signed-out state: account.js replaces the stub button with a bare <a>,
   which otherwise inherits UA link colors on pages without a global a rule. */
.account-stub > a { color: var(--text-dim); text-decoration: none; font-size: 13px; font-weight: 500; }
.account-stub > a:hover { color: var(--text); }
.account-stub button {
    background: transparent; border: 1px solid var(--border); color: var(--text);
    font: inherit; font-size: 13px; padding: 6px 12px; border-radius: 6px;
    cursor: pointer; display: flex; align-items: center; gap: 6px;
}
.account-stub button:hover { border-color: var(--accent); }
.account-stub-menu {
    display: none; position: absolute; right: 0; top: calc(100% + 6px);
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
    min-width: 240px; padding: 8px 0; box-shadow: 0 8px 30px rgba(0,0,0,0.4); z-index: 100;
}
.account-stub-menu.open { display: block; }
.account-stub-menu .item {
    padding: 8px 16px; font-size: 13px; color: var(--text-dim);
    display: flex; justify-content: space-between; gap: 12px;
}
.account-stub-menu .item.header {
    color: var(--text); font-weight: 600;
    border-bottom: 1px solid var(--border); padding-bottom: 12px; margin-bottom: 4px;
}
.account-stub-menu .item .badge { font-size: 11px; color: var(--text-muted); background: var(--border-subtle); padding: 2px 8px; border-radius: 4px; font-weight: 500; }

/* ── Public surface (sportsbookscienceanalytics.com/nba) ────────────────── */
.public-main { max-width: 920px; margin: 0 auto; padding: 32px 20px 64px; }
.public-hero { margin-bottom: 28px; }
.public-hero .kicker {
    font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase;
    color: var(--text-dim); margin: 0 0 10px;
}
.public-hero h1 { font-size: 30px; line-height: 1.15; margin: 0 0 12px; }
.public-lede { color: var(--text-dim); line-height: 1.6; font-size: 15px; max-width: 660px; margin: 0; }

/* Collapsible sections (native <details>) */
.collapsible {
    background: var(--bg-elev); border: 1px solid var(--border);
    border-radius: 10px; margin-bottom: 16px; overflow: hidden;
}
.collapsible > summary {
    list-style: none; cursor: pointer; user-select: none;
    display: flex; align-items: center; gap: 12px;
    padding: 16px 18px; font-size: 16px; font-weight: 600;
}
.collapsible > summary::-webkit-details-marker { display: none; }
.collapsible > summary::after {
    content: "▾"; margin-left: auto; color: var(--text-dim);
    font-size: 13px; transition: transform 0.15s ease;
}
.collapsible[open] > summary::after { transform: rotate(180deg); }
.collapsible > summary:hover { background: var(--bg-row); }
.collapsible-tag {
    font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em;
    padding: 3px 8px; border-radius: 10px;
}
.collapsible-tag.free { color: var(--text-muted); background: var(--bg-row); }
.collapsible-tag.locked { color: var(--accent); background: var(--accent-wash); }
/* Inside an accent-filled active tab the translucent wash composites to
   accent-on-accent (1.00:1) and the pill disappears — force it opaque.
   (The FREE tag needs nothing: its --bg-row fill is opaque, and --text-muted
   on --bg-row measures 5.14:1 inside the filled tab.) */
.page-nav-btn.active .collapsible-tag.locked { background: var(--bg); }
.collapsible-body { padding: 4px 18px 20px; }
.section-note { color: var(--text-dim); font-size: 13px; line-height: 1.55; margin: 4px 0 16px; }

/* Lock cards (gated content placeholders) */
.lock-card {
    text-align: center; padding: 36px 24px;
    border: 1px dashed var(--border); border-radius: 10px; background: var(--bg-row);
}
.lock-card.wide { padding: 48px 24px; }
.lock-card .lock-icon { color: var(--accent); margin-bottom: 12px; }
.lock-card h3 { margin: 0 0 8px; font-size: 16px; color: var(--text); }
.lock-card p { margin: 0 auto 18px; max-width: 460px; color: var(--text-dim); font-size: 14px; line-height: 1.55; }
.lock-card .cta {
    display: inline-block; background: var(--accent); color: var(--bg);
    border: 1px solid var(--accent); border-radius: 6px;
    padding: 9px 20px; font-size: 14px; font-weight: 600;
}
.lock-card .cta:hover { filter: brightness(1.12); }
/* Catalog-sourced price line (the vendored lockcard.js) — only rendered when
   the apex catalog answered and its show_prices gate is open. */
.lock-card .lock-price { color: var(--text); font-weight: 600; }

/* Blurred SYNTHETIC rows behind the gated lock cards (public.js::teaserHTML,
   mounted by the vendored lockcard.js) — same treatment as .elo-teaser:
   fabricated numbers only, hidden from AT and inert to input. The card
   overlaps the bottom rows so the content reads as continuing beneath it;
   the card itself stays fully readable on top. Wrapper class is .lock-wrap —
   the portfolio-wide name lockcard.js emits (wave 3). */
.lock-wrap { position: relative; }
.lock-teaser {
    filter: blur(5px);
    opacity: 0.6;
    pointer-events: none;
    user-select: none;
    margin-bottom: -52px;
}
.lock-wrap .lock-card { position: relative; }

.schedule-public { margin-top: 24px; }
.public-section-h {
    font-size: 13px; font-weight: 600; color: var(--text-dim);
    text-transform: uppercase; letter-spacing: 0.04em; margin: 0 0 12px;
}

/* Wide data tables (schedule days, the rankings table) scroll horizontally
   on narrow screens instead of overflowing the viewport. The class name is
   the portfolio's (the football ATS tab named it); it is a plain wrapper. */
.ats-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.public-footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid var(--border); }
.public-footer p { color: var(--text-dim); font-size: 12px; line-height: 1.6; margin: 0; }

/* ── Season Overview ─────────────────────────────────────────────────────
   Reuses the forecast sticky-grid mechanics: a bounded, scrollable container
   is the sticky ancestor (so `.so-grid` must NOT be the global overflow:hidden
   table), the Team column sticks left, the header row sticks top. */
.so-head h3 { margin: 0 0 4px; }
.so-head p { margin: 0 0 14px; max-width: 900px; }
.so-note {
    margin: 0 0 14px; padding: 8px 12px; max-width: 900px;
    background: var(--bg-elev); border: 1px solid var(--border);
    border-left: 3px solid var(--warn); border-radius: 4px;
    font-size: 12px; color: var(--text-dim);
}
.so-controls { display: flex; align-items: center; gap: 16px; margin: 0 0 12px; }
.so-controls label { font-size: 12px; color: var(--text-dim); }
.so-controls select { margin-left: 6px; }
.so-hint { font-size: 11px; color: var(--text-dim); }

.so-scroll {
    overflow: auto; max-height: calc(100vh - 190px);
    -webkit-overflow-scrolling: touch;
    border: 1px solid var(--border); border-radius: 8px;
}
.so-grid {
    border-collapse: separate; border-spacing: 0; font-size: 12px; min-width: 100%;
    overflow: visible; border: 0; border-radius: 0;
}
.so-grid th, .so-grid td {
    border-bottom: 1px solid var(--border); padding: 6px 10px;
    text-align: right; white-space: nowrap;
}
.so-grid thead th {
    position: sticky; top: 0; z-index: 3;
    background: var(--bg-elev); color: var(--text-dim);
    font-weight: 600; text-transform: uppercase; font-size: 10px;
    cursor: pointer; user-select: none;
}
.so-grid thead th:hover { color: var(--text); }
.so-col.so-sort-desc::after { content: " ▾"; opacity: 0.8; }
.so-col.so-sort-asc::after  { content: " ▴"; opacity: 0.8; }
/* Sortable headers are real <button>s (keyboard operable), styled to be
   visually identical to the bare header text they replaced. Negative margins
   reclaim the th padding so the hit target stays the whole cell. */
.so-sort-btn {
    background: transparent; border: 0; border-radius: 0;
    font: inherit; color: inherit; letter-spacing: inherit;
    margin: -6px -10px; padding: 6px 10px;
    cursor: pointer; user-select: none;
}
.so-sort-btn:hover { background: transparent; }
/* Zone separators (rest block, quirks block). */
.so-sep { border-left: 1px solid var(--border); }
/* Sticky Team column (header + body + group band). */
.so-team-head, .so-team, .so-group-cell {
    position: sticky; left: 0; z-index: 2;
    text-align: left !important; padding-left: 10px !important;
    background: var(--bg-elev);
}
.so-team-head { z-index: 4; min-width: 170px; }
.so-team { min-width: 170px; background: var(--bg); }
.so-grid tbody tr:hover .so-team { background: var(--bg-elev); }
.so-grid tbody tr:hover td { background: var(--bg-elev); }
.so-team .team-badge, .so-team .team-logo { margin-right: 7px; vertical-align: middle; }
.so-team .team-name { font-weight: 600; vertical-align: middle; }
.so-group-row td.so-group-cell {
    font-size: 10px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--text-dim); background: var(--bg); padding: 5px 10px 5px 12px;
}
.so-num { font-variant-numeric: tabular-nums; }

/* ── Public paywall module view ─────────────────────────────────────────── */
/* The tabbed customer surface reuses the internal .page-nav/.page pattern
   plus the internal table styles; these are only the paywall-specific bits. */
.public-main.wide { max-width: 1280px; }
.page-nav .collapsible-tag { margin-left: 6px; font-size: 10px; padding: 2px 6px; }
.lock-card .cta-secondary {
    display: inline-block; margin-left: 14px; color: var(--text-dim);
    font-size: 13px; text-decoration: none;
    border-bottom: 1px dotted var(--text-dim); padding-bottom: 1px;
}
.lock-card .cta-secondary:hover { color: var(--text); }
.account-stub-menu a.item { text-decoration: none; }
.account-stub-menu a.item:hover,
.account-stub-menu .item.action:hover { background: var(--bg-row); color: var(--text); }
.account-stub-menu .item.action { cursor: pointer; color: var(--text); }
.acct-btn-avatar {
    width: 18px; height: 18px; border-radius: 50%;
    background: linear-gradient(135deg, var(--accent), var(--accent-2)); color: var(--bg);
    font-size: 10px; font-weight: 700; display: inline-flex;
    align-items: center; justify-content: center; overflow: hidden;
}
.acct-btn-avatar img { width: 100%; height: 100%; object-fit: cover; }

/* 404 rescue page (E16). */
.notfound { max-width: 640px; margin: 0 auto; padding: 96px 24px 48px; text-align: center; }
.notfound h1 { font-size: 32px; margin: 0 0 12px; }
.notfound p { color: var(--text-dim); font-size: 15px; }
.notfound a.back {
    display: inline-block; margin-top: 24px; padding: 8px 16px;
    border: 1px solid var(--border); border-radius: 6px; color: var(--text);
}
.notfound a.back:hover { background: var(--bg-row); }

/* ── Mobile ───────────────────────────────────────────────────────────── */
@media (max-width: 600px) {
    .public-main { padding: 22px 14px 48px; }
    .public-hero h1 { font-size: 24px; }
    .public-lede { font-size: 14px; }
    .collapsible > summary { padding: 14px; font-size: 15px; }
    .collapsible-body { padding: 4px 14px 16px; }
    /* E23 (wave 3): single-row horizontal scroll instead of wrapping. The
       edge fade is a sticky final flex item pinned to the scrollport's
       right edge (it returns to its flow position — the end of the row —
       at full scroll, so the affordance disappears exactly when there is
       nothing more to reach). Fade colour = --bg-elev, the nav's own
       surface. */
    .site-nav {
        gap: 14px;
        padding: 8px 14px;
        flex-wrap: nowrap;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
    }
    .site-nav::-webkit-scrollbar { display: none; }
    .site-nav::after {
        content: "";
        position: sticky;
        right: 0;
        flex: none;
        align-self: stretch;
        width: 28px;
        margin-left: -28px;
        z-index: 1;
        pointer-events: none;
        background: linear-gradient(to left, var(--bg-elev), transparent);
    }
    .site-nav-brand, .site-nav-link, .nav-right-link { white-space: nowrap; }
    .site-nav-divider, .nav-right { flex: none; }
    /* The scroll container clips absolutely-positioned descendants, so the
       account dropdown escapes it as position: fixed, just below the
       sticky L1 row. */
    .account-stub-menu { position: fixed; top: 54px; right: 10px; }
    .standings-grid { grid-template-columns: 1fr; }
    /* The tab bar scrolls instead of overflowing the viewport. */
    .page-nav {
        flex-wrap: nowrap; overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }
    /* NBA: the day strip scrolls horizontally like the L1 (E23 rule) —
       seven 64px chips do not fit a phone; the prev/next buttons stay put. */
    .day-strip { overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
    .day-strip::-webkit-scrollbar { display: none; }
    .day-chip { flex: 0 0 auto; }

    /* ── Schedule: card-per-game. SAME DOM as the desktop table — the
       renderer emits data-label attributes and this block reflows the rows
       into stacked cards (the .ats-table-wrap scroll wrapper remains the
       fallback for every other wide table). Each game = its date row as the
       card header, then the away and home rows as labeled grids:
       matchup + score line, market row (ML/Spread/Total), model row
       (Model line/Fair ML/Edge, + the internal Result). */
    /* table.schedule-table, not .schedule-table — must outrank the base
       table.schedule-table rule (0,1,1) or bg/border/radius survive here. */
    table.schedule-table {
        display: block; background: transparent;
        border: 0; border-radius: 0; overflow: visible;
    }
    .schedule-table thead { display: none; }   /* data-labels replace the headers */
    .schedule-table tbody { display: block; }
    .schedule-table tbody tr.game-date {
        display: block; margin-top: 14px;
        background: var(--bg-row);
        border: 1px solid var(--border); border-bottom: 0;
        border-radius: 6px 6px 0 0;
    }
    /* flow-root, not block: the date cell must CONTAIN the floated "Injury
       report" button (2026-10-17 review). A plain block cell let the float
       drop out of the date row whenever date + button outgrew one line, and
       the away row's grid then shrank beside it (~60 % of the card at 375). */
    .schedule-table tbody tr.game-date td { display: flow-root; border-bottom: 0; }
    /* Away row = .game-date + tr; home row = .game-date + tr + tr (every game
       renders exactly date/away/home, in that order). */
    .schedule-table tbody tr.game-date + tr,
    .schedule-table tbody tr.game-date + tr + tr {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 8px;
        align-items: center;
        padding: 10px 12px;
        background: var(--bg-elev);
        border: 1px solid var(--border); border-top: 0;
    }
    .schedule-table tbody tr.game-date + tr + tr {
        border-radius: 0 0 6px 6px;
    }
    .schedule-table tbody tr.game-date + tr td,
    .schedule-table tbody tr.game-date + tr + tr td {
        display: block; border-bottom: 0; padding: 0; text-align: center;
    }
    .schedule-table td.matchup-col { grid-column: 1 / 4; }
    .schedule-table .matchup-cell { min-width: 0; padding-left: 0; }
    .schedule-table td.score-cell { grid-column: 4 / 5; }
    /* Alignment needs the full row-context selector to outrank the
       center-everything rule above (0,2,4). */
    .schedule-table tbody tr.game-date + tr td.matchup-col,
    .schedule-table tbody tr.game-date + tr + tr td.matchup-col { text-align: left; }
    .schedule-table tbody tr.game-date + tr td.score-cell,
    .schedule-table tbody tr.game-date + tr + tr td.score-cell { text-align: right; }
    /* The data-label becomes a caption above the value. */
    .schedule-table td[data-label]::before {
        content: attr(data-label);
        display: block;
        font-size: 9px; font-weight: 600; letter-spacing: 0.05em;
        text-transform: uppercase; color: var(--text-dim);
        margin-bottom: 2px;
    }
    .schedule-table td.boxscore-cell { grid-column: 1 / -1; }
    .schedule-table .odds-cell { min-width: 0; width: 100%; padding: 6px 4px; }
}

/* Right-column panels on Team Detail (division race, next games). */
.ts-divrace td { padding: 5px 8px; font-size: 12px; border-bottom: 1px solid var(--border); }
.ts-divrace tr:last-child td { border-bottom: none; }
.ts-divrace .ts-div-me td { background: var(--bg-row); font-weight: 600; }
.ts-divrace td .team-logo.sm { margin-right: 5px; }

/* ── Site footer — the legal footer block (DESIGN_SYSTEM.md §3, 2026-08-13).
   Byte-identical markup on every SSA page; CSS copied verbatim from apex
   tokens.css. Chrome tokens only — no data colours here. */
.site-footer {
  text-align: center;
  padding: 40px 20px 28px;
  margin-top: auto;
  border-top: 1px solid var(--border-subtle);
}
.site-footer p { margin: 0 auto 8px; max-width: 760px; }
.site-footer-legal {
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1.6;
}
.site-footer-links {
  color: var(--text-muted);
  font-size: 12.5px;
  padding-top: 4px;
}
.site-footer a { color: var(--accent); text-decoration: none; }
.site-footer a:hover { color: var(--text); }
.site-footer-copy { color: var(--text-muted); font-size: 12px; padding-top: 2px; }

/* Team name -> Team Detail (rankings, forecast, standings). Chrome, so the
   hover/focus affordance resolves through var(--accent) and carries no hex
   literal. Needed because the global `a { color: var(--text); text-decoration:
   none }` would otherwise render the link pixel-identically to the plain text
   it replaced. The anchor stays INLINE: .fc-team is `position: sticky; left:
   0` with !important alignment and a min-width, and a block/flex anchor would
   fight both. */
.fc-team-link { color: inherit; text-decoration: none; cursor: pointer; }
.fc-team-link:hover .team-name,
.fc-team-link:focus-visible .team-name { color: var(--accent); text-decoration: underline; }

/* ── Rankings ELO teaser (free public view; John 2026-08-26) ──────────────
   Blurs SYNTHETIC digits — the server never sends the real rating to an
   unentitled session, so this is a preview of the column's shape, not a
   style over real data. */
.elo-teaser {
    filter: blur(5px);
    user-select: none;
    pointer-events: none;
    opacity: 0.85;
}
.elo-teaser-unlock {
    color: var(--accent);
    text-decoration: none;
    white-space: nowrap;
}
.elo-teaser-unlock:hover { text-decoration: underline; }

/* ── Margins & Totals (the Key Numbers block; NFL block 2026-09-10) ──────
   Inline-SVG bar charts + their tables. Marks take the repo-local data
   tokens declared beside --bar-projw; axes, gridlines, labels and the key
   marker are chrome/text and resolve through surface and text tokens. A key
   number is never colour-only: it is direct-labelled on the chart and carries
   a "key" word in its table row. NBA v1: the tab renders the "not built"
   stub; the block stays so the page lands against a finished sheet. */
.kn-head .dim { max-width: 820px; }
.kn-cards {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 14px; margin: 0 0 16px;
}
.kn-card {
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
    padding: 14px 16px;
}
.kn-card-label {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em;
    color: var(--text-dim); margin-bottom: 4px;
}
.kn-card-val { font-size: 26px; font-weight: 700; line-height: 1.1; }
.kn-card-meta { font-size: 12px; color: var(--text-dim); margin-top: 6px; line-height: 1.45; }
.kn-section { margin-top: 22px; }
.kn-intro { line-height: 1.5; max-width: 820px; margin: 0 0 10px; font-size: 12.5px; color: var(--text-dim); }
.kn-intro strong { color: var(--text); font-weight: 600; }
.kn-scroll {
    overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 0 0 12px;
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
    padding: 8px 6px 4px;
}
.kn-chart { display: block; max-width: none; font-family: var(--font-family); }
.kn-grid { stroke: var(--border-subtle); stroke-width: 1; }
.kn-baseline { stroke: var(--border); stroke-width: 1; }
.kn-axis-y, .kn-axis-x, .kn-axis-name { fill: var(--text-muted); font-size: 10px; }
.kn-axis-x.key { fill: var(--text); font-weight: 700; }
.kn-axis-name { font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; }
.kn-bar path { fill: var(--kn-bar); }
.kn-bar.key path { fill: var(--kn-bar-key); }
.kn-bar:hover path { filter: brightness(1.2); }
.kn-label { fill: var(--text); font-size: 10px; font-weight: 700; }
/* Legend swatches — the same tokens as the marks. */
.kn-legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 11px; color: var(--text-dim); margin: 0 0 8px; }
.kn-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 2px; vertical-align: -1px; margin-right: 4px; background: var(--kn-bar); }
.kn-swatch.key { background: var(--kn-bar-key); }
table.kn-table { font-size: 12px; width: auto; min-width: 60%; }
.kn-table th, .kn-table td { text-align: right; padding: 6px 10px; font-variant-numeric: tabular-nums; }
.kn-table thead th { text-align: right; font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; }
.kn-table th:first-child, .kn-table td.kn-num { text-align: left; white-space: nowrap; }
.kn-table .kn-sep { border-left: 2px solid var(--border); }
.kn-key-row td { background: var(--bg-row); }
.kn-key-row td.kn-num { font-weight: 700; }
.kn-key-mark {
    display: inline-block; margin-left: 6px; padding: 0 5px; border-radius: 3px;
    font-size: 9px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
    color: var(--accent); background: var(--accent-wash);
}
.kn-tail-row td { color: var(--text-dim); font-style: italic; }
.kn-table-note { font-size: 11px; margin: 6px 0 0; }
.kn-note { margin-top: 18px; }

/* ── NBA: forecast / season overview page additions ───────────────────────
   Carried from the NHL pages build (2026-09-23) AFTER the shell's sheet; the
   rules above are the shell's and are not edited here. Chrome only — every
   colour resolves through a token; the W/L letters and ▲/▼ glyphs carry the
   data colour on their own classes. The Market Performance rules are their
   own `mkt-*` section at the foot of the sheet (2026-10-15). */
/* The shell sheet scopes `.dim` to td.dim and a few heads; the three grids
   emit <span class="dim">—</span> for a null cell and the buttons/footnotes
   need the same tone. */
.fc-grid .dim, .so-grid .dim,
.admin-section table .dim { color: var(--text-dim); }
/* Next-7 strip: a game in progress shows the WORD, plain text tone (odds
   froze at tip-off); the OT tag rides the W / L letter. */
.fc-cell.fc-live .fc-wp { color: var(--text-dim); font-weight: 600; letter-spacing: 0.04em; font-size: 10px; }
.fc-cell .fc-wp .res-tag { margin-left: 1px; }
/* Finish-position strip inside the expander scrolls on its own so the
   sticky panel never widens the grid. */
.fc-finish-wrap { overflow-x: auto; margin: 0 0 14px; }
/* The conference finish distribution (DATA_CONTRACT §6 finish_dist[15]):
   forecast.js renders it as a COLLAPSED <details> per conference (summary
   chrome like .ts-month) wrapping a compact table — Team + fifteen numeric
   columns P(1st) … P(15th), each cell a --p wash (.fc-prob). Surface and
   text tokens only; the wash is the data mark. */
.fc-finish-details {
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
    margin: 10px 0 14px; overflow: hidden;
}
.fc-finish-details > summary {
    list-style: none; cursor: pointer; user-select: none;
    display: flex; align-items: center; gap: 12px;
    padding: 9px 14px; font-size: 12px; font-weight: 600; color: var(--text-dim);
}
.fc-finish-details > summary::-webkit-details-marker { display: none; }
.fc-finish-details > summary::after {
    content: "▾"; margin-left: auto; color: var(--text-dim);
    font-size: 12px; transition: transform 0.15s ease;
}
.fc-finish-details[open] > summary::after { transform: rotate(180deg); }
.fc-finish-details > summary:hover { background: var(--bg-row); color: var(--text); }
.fc-finish-details .fc-finish-wrap { margin: 0; }
table.fc-finish-table { border: 0; border-radius: 0; font-size: 11px; min-width: 100%; }
.fc-finish-table th, .fc-finish-table td { padding: 4px 6px; white-space: nowrap; border-bottom: 1px solid var(--border); }
.fc-finish-table thead th {
    background: var(--bg-row); color: var(--text-dim);
    font-size: 10px; text-transform: uppercase; text-align: right;
}
.fc-finish-table thead th:first-child { text-align: left; padding-left: 10px; }
.fc-finish-table td.fc-team { min-width: 150px; background: var(--bg-elev); }
.fc-finish-table tbody tr:last-child td { border-bottom: none; }
/* "Show all" toggle on the exacta list — the base button chrome, small. */
.fc-exacta-more { margin: 6px 0 0; padding: 3px 10px; font-size: 11px; }
/* Footnote under the conference grids (sims, tiebreak rule, freshness). */
.fc-foot { margin: 12px 0 0; font-size: 11px; color: var(--text-muted); max-width: 900px; line-height: 1.6; }
/* Season overview: the remaining-SOS column is the sort default and reads
   heavier; the intro's bold terms take the text tone. */
.so-grid td.so-strong { font-weight: 600; }
.so-head p strong { color: var(--text); font-weight: 600; }
/* Playoff bracket (forecast.js `_bracketSection`, the NCAAF CFP-bracket
   port, 2026-10-14): a collapsible <details> holding one scrollable canvas
   per conference plus the Finals strip — absolutely-positioned 2-slot boxes
   with SVG elbow connectors, every slot a click-to-open top-10 popover
   (singleton, fixed-position, role=dialog). Chrome through tokens only;
   the ONE data mark is `.fcb-win .fcb-abbr` — green names the projected
   winner's path (an outcome, so --good is legal); box borders, heads and
   the champion box stay on --border / --accent. The popover shadow derives
   from --bg (no rgba literal). */
.fcb-wrap {
    margin: 0 0 18px; border: 1px solid var(--border); border-radius: 6px;
    background: var(--bg-elev);
}
.fcb-summary {
    cursor: pointer; user-select: none; padding: 10px 14px;
    font-size: 13px; font-weight: 600;
}
.fcb-summary:hover { background: var(--bg-row); }
.fcb-hint { font-weight: 400; font-size: 12px; color: var(--text-dim); margin-left: 8px; }
.fcb-conf {
    margin: 8px 14px 0; font-size: 11px; font-weight: 600; letter-spacing: 0.05em;
    text-transform: uppercase; color: var(--text-dim);
}
.fcb-scroll { overflow-x: auto; padding: 6px 14px 16px; }
.fcb-canvas { position: relative; }
.fcb-links path { fill: none; stroke: var(--border); stroke-width: 1.5; }
.fcb-head {
    position: absolute; top: 0; text-align: center;
    font-size: 10px; font-weight: 600; text-transform: uppercase;
    letter-spacing: .05em; color: var(--text-dim);
}
.fcb-box {
    position: absolute; background: var(--bg); border: 1px solid var(--border);
    border-radius: 5px; overflow: hidden;
}
.fcb-box .fcb-slot + .fcb-slot { border-top: 1px solid var(--border); }
.fcb-champbox { border-color: var(--accent); }
.fcb-slot {
    display: flex; align-items: center; gap: 6px;
    height: 31px; padding: 0 8px; cursor: pointer; font-size: 12px;
}
.fcb-champbox .fcb-slot { height: 36px; }
.fcb-slot:hover, .fcb-slot.open { background: var(--bg-row); }
.fcb-slot:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.fcb-slot.fcb-empty { cursor: default; color: var(--text-dim); }
.fcb-seed {
    flex: none; width: 16px; height: 16px; line-height: 16px; text-align: center;
    font-size: 9px; font-weight: 700; color: var(--text-dim);
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 3px;
}
/* full club name (ellipsized if it outgrows the box) */
.fcb-abbr { font-weight: 700; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fcb-slot .team-logo { width: 16px; height: 16px; flex: none; }
.fcb-win .fcb-abbr { color: var(--good); }
.fcb-p { font-variant-numeric: tabular-nums; color: var(--text-dim); }
.fcb-caret { font-size: 9px; color: var(--text-dim); }
.fcb-pop {
    position: fixed; z-index: 50; width: 320px; max-height: 70vh; overflow-y: auto;
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 6px;
    box-shadow: 0 8px 28px color-mix(in srgb, var(--bg) 70%, transparent);
    padding: 8px 10px; font-size: 12px;
}
.fcb-pop-head { font-weight: 600; margin-bottom: 6px; color: var(--text); }
.fcb-pop-row {
    display: flex; align-items: center; gap: 7px; padding: 2px 0;
    color: var(--text-dim);
}
.fcb-pop-row.lead { color: var(--text); font-weight: 600; }
.fcb-pop-rank { flex: none; width: 16px; text-align: right; font-variant-numeric: tabular-nums; }
.fcb-pop-team {
    flex: none; width: 138px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    display: flex; align-items: center; gap: 5px;
}
.fcb-pop-team .team-logo { width: 14px; height: 14px; }
.fcb-pop-bar { flex: 1; height: 7px; background: var(--bg); border-radius: 3px; overflow: hidden; }
.fcb-pop-bar span { display: block; height: 100%; background: var(--accent); }
.fcb-pop-pct { flex: none; width: 44px; text-align: right; font-variant-numeric: tabular-nums; }
/* Grouping toggle (forecast.js `_groupBar`, John 2026-10-14): a Conference |
   Division chip pair between the bracket and the conference grids, the NCAAF
   filter-chip chrome. Tokens only; the active chip is the filled-accent form
   and takes color: var(--bg) (white on the accent fails AA at 2.53:1), its
   hover lifts brightness rather than swapping to --accent-dim. */
.fc-group {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 10px 0 14px;
    padding: 10px 12px; background: var(--bg-elev); border: 1px solid var(--border); border-radius: 6px;
}
.fc-group-label { font-size: 12px; font-weight: 600; }
.fc-group-btn {
    font-size: 12px; padding: 4px 10px; border-radius: 12px;
    background: var(--bg); color: var(--text-dim); border: 1px solid var(--border); cursor: pointer;
}
.fc-group-btn:hover { color: var(--text); background: var(--bg-row); }
.fc-group-btn.active { background: var(--accent); border-color: var(--accent); color: var(--bg); }
.fc-group-btn.active:hover { filter: brightness(1.12); background: var(--accent); color: var(--bg); }
.fc-group-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.fc-group-note { font-size: 12px; color: var(--text-dim); }
/* The internal Projections toggle (Market | Player-Level, 2026-10-16) sits
   BESIDE the Group bar: the two .fc-group bars share one wrapping row (each
   takes its own line under ~650px). Layout only — no colour of its own. */
.fc-controls { display: flex; flex-wrap: wrap; gap: 0 12px; }
.fc-controls > .fc-group { flex: 1 1 300px; min-width: 0; }

/* ── NBA: Market Performance (internal; pages/market.js, 2026-10-15) ───────
   The NHL page's chrome ported under ONE prefix (`mkt-*`; the football
   `ats-headline` / `ats-scope` / `ats-total` blocks stay retired). Loaded by
   the internal shell only. Chrome and chart marks only — every colour
   resolves through a token. The win/loss pair (--good / --bad) is applied by
   market.js's one `_signCls` helper to a SIGNED units / ROI number (the
   td.good / td.bad rules above and the tile value below) and nowhere else;
   the two chart series wear the declared DATA tokens --chart-line (ROI) and
   --bar-projw (sample size), never green/red, because a threshold is not a
   win or a loss. The "assumed −110" tag is a dashed text-tone outline — a
   label, not a colour. */
.mkt-controls {
    display: flex; align-items: center; flex-wrap: wrap; gap: 8px 18px;
    margin: 0 0 16px; padding: 8px 12px; max-width: 1100px;
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 4px;
    font-size: 13px; color: var(--text-dim);
}
.mkt-controls label { display: inline-flex; align-items: center; gap: 6px; margin: 0; }
.mkt-controls select {
    background: var(--bg-row); color: var(--text);
    border: 1px solid var(--border); border-radius: 4px; padding: 4px 8px; font-size: 13px;
}
.mkt-controls .dim, .mkt-section .dim { color: var(--text-dim); }
.mkt-section { margin-top: 22px; }
.mkt-empty { margin: 16px 0; }
.mkt-intro { line-height: 1.5; max-width: 820px; margin: 0 0 10px; font-size: 12.5px; color: var(--text-dim); }
.mkt-intro strong { color: var(--text); font-weight: 600; }
.mkt-note { margin-top: 14px; font-size: 11px; color: var(--text-muted); max-width: 760px; line-height: 1.6; }
.mkt-intro code, .mkt-note code { font-size: 11px; color: var(--text); background: var(--bg-row); padding: 0 4px; border-radius: 3px; }
/* Headline tiles. */
.mkt-tiles {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 12px; margin-bottom: 12px;
}
.mkt-tile {
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
    padding: 14px 16px; min-width: 0;
}
.mkt-tile-label {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em;
    color: var(--text-dim); margin-bottom: 4px;
}
.mkt-tile-value { font-size: 22px; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; white-space: nowrap; }
.mkt-tile-value.good { color: var(--good); }
.mkt-tile-value.bad { color: var(--bad); }
.mkt-tile-sub { font-size: 11px; color: var(--text-muted); margin-top: 4px; line-height: 1.4; }
/* Tables: compact numeric tables; a Total row is a CLASS (never last-child). */
table.mkt-table { font-size: 12px; width: auto; min-width: 60%; }
.mkt-table th, .mkt-table td { text-align: right; padding: 6px 10px; font-variant-numeric: tabular-nums; }
.mkt-table th:first-child, .mkt-table td:first-child { text-align: left; white-space: nowrap; }
.mkt-total td {
    background: var(--bg-elev); font-weight: 700;
    border-top: 2px solid var(--border); white-space: nowrap;
}
.mkt-product-row td, .mkt-model-row td { background: var(--bg-row); font-weight: 600; }
.mkt-group-row td {
    background: var(--bg-row); color: var(--text-dim); font-size: 10px; font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.05em; padding: 5px 10px;
}
table.mkt-grouped { min-width: 70%; margin: 12px 0 4px; }
.mkt-split-table th[title], .mkt-calib th[title] { cursor: help; }
/* Dual-scope split tables (the selected season beside All-time). */
.mkt-scope-table { width: 100%; }
.mkt-scope-table th.mkt-scope-head {
    font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em;
    border-bottom: 1px solid var(--border);
}
.mkt-scope-table .mkt-scope-sep { border-left: 2px solid var(--border); }
/* The bias glyph is a text mark, not a colour: ▲ / ▼ beside the pick share. */
.mkt-glyph { color: var(--text); font-weight: 700; font-size: 10px; margin-left: 4px; }
.mkt-pop-na { color: var(--text-muted); font-size: 11px; }
.mkt-scope-tag {
    font-size: 10px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase;
    color: var(--text-dim); background: var(--bg-row); border-radius: 3px; padding: 1px 6px; margin-left: 6px; vertical-align: 2px;
}
.mkt-assumed-tag {
    font-size: 10px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase;
    color: var(--text); background: transparent; border: 1px dashed var(--text-muted);
    border-radius: 3px; padding: 1px 6px;
}
.mkt-insample-tag { margin-left: 4px; }
/* The in-sample caption (backfill scopes, All-time while it pools one, the
   assumed −110 section) and the skipped-season caption beside a populated
   logged block (2026-10-15 review): a ruled note, caption text at full
   contrast; the skip's rule takes --warn (stale / incomplete data). */
.mkt-note.mkt-sample, .mkt-note.mkt-skipped { border-left: 3px solid var(--border); padding-left: 8px; color: var(--text-dim); }
.mkt-note.mkt-skipped { border-left-color: var(--warn); }
.mkt-summary-meta { font-weight: 400; font-size: 12px; color: var(--text-dim); margin-left: 10px; }
details.mkt-split > summary { font-size: 14px; }
/* The threshold-curve chart: one SVG, two single-axis panels (ROI, then n)
   stacked over a shared threshold axis — never a dual-axis chart. */
.mkt-chart-wrap {
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
    padding: 10px 14px 4px; margin: 0 0 12px; max-width: 900px;
}
.mkt-chart { display: block; width: 100%; height: auto; }
.mkt-chart text { fill: var(--text-muted); font-size: 10px; }
.mkt-chart .mkt-panel-title { fill: var(--text-dim); font-size: 11px; font-weight: 600; letter-spacing: 0.03em; }
.mkt-chart .mkt-label { fill: var(--text); font-size: 10px; font-weight: 600; }
.mkt-chart .mkt-grid { stroke: var(--border-subtle); stroke-width: 1; }
.mkt-chart .mkt-axis { stroke: var(--border); stroke-width: 1; }
.mkt-chart .mkt-zero { stroke: var(--border); stroke-width: 1; stroke-dasharray: 5 4; }
.mkt-chart .mkt-product { stroke: var(--text-disabled); stroke-width: 1; stroke-dasharray: 2 3; }
.mkt-chart .mkt-roi-line { stroke: var(--chart-line); stroke-width: 2; fill: none; stroke-linejoin: round; stroke-linecap: round; }
.mkt-chart .mkt-roi-dot { fill: var(--chart-line); stroke: var(--bg-elev); stroke-width: 2; }
.mkt-chart .mkt-n-line { stroke: var(--bar-projw); stroke-width: 2; fill: none; stroke-linejoin: round; stroke-linecap: round; }
.mkt-chart .mkt-n-dot { fill: var(--bar-projw); stroke: var(--bg-elev); stroke-width: 2; }
.mkt-chart .mkt-hit { fill: transparent; }
.mkt-legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 11px; color: var(--text-dim); margin: 0 0 6px; }
.mkt-legend .dim { color: var(--text-muted); }
.mkt-swatch { display: inline-block; width: 14px; height: 3px; border-radius: 2px; vertical-align: 2px; margin-right: 5px; background: var(--chart-line); }
.mkt-swatch-n { background: var(--bar-projw); }
/* Sortable team tables, side by side on a wide screen. */
/* Each sortable header holds a real <button> (keyboard / AT reachable; the
   <th> carries aria-sort) styled as the header text — unfilled chrome, the
   accent only on the focus ring and the sort arrow (2026-10-15 review). */
th.mkt-sort { cursor: pointer; user-select: none; white-space: nowrap; }
th.mkt-sort:hover { color: var(--text); }
.mkt-sort-btn {
    background: transparent; border: 0; padding: 0; margin: 0; cursor: pointer;
    font: inherit; color: inherit; text-align: inherit; text-transform: inherit; letter-spacing: inherit;
}
.mkt-sort-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
th.mkt-sort .mkt-sort-arrow { margin-left: 4px; font-size: 9px; color: var(--accent); }
.mkt-team-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.mkt-team-grid > div { min-width: 0; }   /* a grid item's auto min-width lets a wide table spill the panel */
@media (max-width: 1400px) { .mkt-team-grid { grid-template-columns: 1fr; } }   /* eight columns a side need ~700px each */
.mkt-sub { margin: 0 0 8px; font-size: 12px; font-weight: 600; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em; }
table.mkt-team-table { width: 100%; }
/* The phase-2 slot, the spread-lean and the assumed −110 sections are
   collapsible <details> (.mkt-grade / .mkt-lean / .mkt-ats on the element). */
.mkt-grade > summary, .mkt-lean > summary, .mkt-ats > summary { font-size: 15px; }
table.mkt-baselines, table.mkt-ats-baselines, table.mkt-curve-table { margin-bottom: 4px; }
/* The by-grade section (the settled grade LOG, src/log_settle.py; 2026-10-15
   phase 2): golf's band checkboxes + S → X presets choose the tiers the Total
   rows add up. The presets are OUTLINED controls — chrome through the accent
   on hover / focus only, never a fill — and the checkbox takes the accent as
   its tick colour. An unticked tier's row is NOT dimmed (2026-10-15 review:
   opacity put the D+ badge at ~2.1:1 and the green / red numbers under 2.2:1
   — its record is still data on screen): it carries a caption-toned "not in
   Total" tag instead, every cell at full contrast (pinned). The
   tier letter is the vendored `.sg-tier` badge (the grade ramp is data); the
   Ungraded reconciliation row is caption-toned text, never green / red. */
.mkt-bands {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
    margin: 4px 0 10px; font-size: 12px; color: var(--text-dim);
}
.mkt-bands-label { margin-right: 2px; }
.mkt-bands-sel { margin-left: 6px; color: var(--text); font-weight: 600; }
.mkt-preset {
    background: transparent; color: var(--text-dim);
    border: 1px solid var(--border); border-radius: 4px;
    padding: 2px 8px; font-size: 12px; line-height: 1.4; cursor: pointer;
}
.mkt-preset:hover, .mkt-preset:focus-visible { color: var(--text); border-color: var(--accent); }
.mkt-band-check { accent-color: var(--accent); margin: 0 6px 0 0; vertical-align: middle; cursor: pointer; }
.mkt-band-tag { margin-left: 6px; font-size: 10.5px; font-style: italic; color: var(--text-dim); white-space: nowrap; }
tr.mkt-band-off td:first-child .mkt-tier-label { text-decoration: line-through; }
.mkt-logged-table .sg-tier, .mkt-matrix .sg-tier, .mkt-clv-table .sg-tier, .mkt-retro-table .sg-tier {
    height: 22px; min-width: 22px; font-size: 13px; vertical-align: middle;
}
/* The retro-graded history (2026-10-16): its own table under the logged ones; the band rows set apart by weight only. */
table.mkt-retro-table { margin: 8px 0 4px; }
tr.mkt-retro-band td { font-weight: 600; }
.mkt-tier-label { font-size: 11px; }
.mkt-intro.mkt-subhead { margin-top: 20px; }
tr.mkt-ungraded td { color: var(--text-muted); font-style: italic; }
table.mkt-matrix td { vertical-align: top; }
.mkt-cell-sub { display: block; font-size: 10.5px; font-style: normal; color: var(--text-muted); white-space: nowrap; }
@media (max-width: 700px) { .mkt-tier-label { display: none; } }   /* the badge's title still carries it */
