/* CFL ELO Dashboard — minimal, dark theme */

/* ── 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: #b5262f; }   /* NCAAF crimson */

/* ── SSA grade ramp — shared token block (DESIGN_SYSTEM.md §1 "The grade
   ramp"). Byte-identical across golf-tournament-predictor, cfl-elo-dashboard,
   ncaaf-dashboard and nfl-elo-dashboard; copied verbatim, do not edit values
   here. Band 1 = best. Nine bands, ONE ramp, TWO ladders — football ends
   `D+ · D` and golf ends `D · F`, which is exactly why the bands are numbered
   and not lettered (`--grade-d` would mean band 8 in one repo and band 9 in
   the other). Every band is a TEXT colour on an UNFILLED badge and clears
   WCAG AA against --bg, --bg-elev and --bg-row; worst band is 4.81. ─────── */
: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   */
}

/* ── Repo-specific data colours ───────────────────────────────────────────
   These encode DATA meaning, so they sit outside the shared block. */
:root {
    --bar-projw: #6b7fd6;     /* Forecast projected-wins fill bar (slate-purple) */
    /* Key Numbers chart marks (2026-09-10): the recessive bar and the
       emphasised key-number bar. One series with emphasis, so a neutral plus
       one hue; the key colour is the crimson-tint variant (this repo's
       --league-tint family), run through the dataviz palette validator on
       --bg-elev against the neutral (CVD separation 10.8 deutan / 27.6
       tritan, both >= 3:1). */
    --kn-bar:     #5f6b7d;
    --kn-bar-key: #e8737a;
}

* { 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. */
    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,
.subsite-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); }
select, input[type=number], input[type=text], 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; }

/* ── 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 PNGs via scripts/fetch_logos.py) ── */
.team-logo {
    display: inline-block; width: 32px; height: 32px;
    object-fit: contain; vertical-align: middle;
    flex-shrink: 0;
}
.team-logo.sm { width: 20px; height: 20px; }
.team-logo.lg { width: 48px; height: 48px; }
/* table rows (rankings, team detail): logo + name inline */
td .team-logo.sm { margin-right: 7px; }
td .team-badge.sm { margin-right: 7px; }

/* ── Schedule (sportsbook-style table) ───────────────────── */
/* ONE control bar: week filter + hide-completed (+ the FCS toggle on the
   internal host) with the odds/model freshness readout on the right. All
   chrome — surface tokens + --accent only. Replaced the old standalone
   freshness bar 2026-08-08. */
.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 {
    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); }
/* The FCS toggle module renders its own boxed bar on other pages; inside the
   control bar it is one more inline control. */
.sched-controls .fcs-toggle { margin: 0; padding: 0; background: none; border: none; }
.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; }

.week-section { margin-bottom: 28px; }
.week-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; }
.schedule-table tbody tr.future { 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; }
.schedule-table .matchup-cell .record { font-size: 11px; color: var(--text-dim); }
.schedule-table .matchup-row { display: flex; flex-direction: column; gap: 8px; }
.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 .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 .edge-cell { font-size: 12px; color: var(--text-dim); }
/* `.pick` = the RECOMMENDED side's row (John, 2026-09-23): the highlight marks
   the side WE like, not the side the book likes — it replaced a green tint on
   the favourite's Pred. Pred + Edge share this one accent rule so they and the
   Grade letter on the same row read as one pick; the other row stays plain,
   and a game with no recommendation highlights nothing. Accent is chrome, not
   a data colour. `.edge-cell.big` (edge >= 2 pts) is the STRONGER form. */
.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; }
/* 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; }
/* The Grade column: SYSTEM 2's letter (see the .sg-tier ramp), on the edge
   team's row beside SYSTEM 1's edge number — separate columns, never summed. */
.schedule-table .grade-cell { text-align: center; }
/* Completed public games: Pred/Edge/Grade collapse into one link out to the
   box score. 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; }
.schedule-table .game-date {
    font-size: 11px; color: var(--text-dim);
    padding: 6px 14px; background: rgba(0,0,0,0.15);
}
.schedule-table .game-date td { padding: 6px 14px; text-align: left; border-bottom: 1px solid var(--border); }

/* ── 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 — this is golf's treatment, adopted portfolio-wide 2026-08-16
   after John: the football columns "are all gray, no matter what the grade".
   The old ramp gave six of nine bands a literal `var(--bg-row)` box, so the
   grey box dominated and the letter's colour read as incidental. A fill would
   also drag `color: var(--bg)` back in per the filled-control rule and undo
   exactly that.

   Every band is measured against --bg, --bg-elev AND --bg-row (the badge sits
   on --bg-elev in a table body and on --bg-row when the row is hovered);
   worst band is 4.81:1. No hex literals — the tokens live in the shared 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 side the
   model chose — a genuine negative on a signed scale, not a warning. The model
   has still chosen that side 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. The single
   letters still render as squares because min-width wins on a one-glyph
   label. */
.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. One slug function on the client, one
   set of rules here; tests/test_sharp_grade_ui.py asserts a rule exists for
   every tier the config declares, using that same slug. */
/* The Grade COLUMN cell (schedule table). Bigger than the old 18px date-row
   chip so the letter reads at a row glance; the ramp classes below apply
   unchanged. */
.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   */

/* ── ATS Performance ─────────────────────────────────────── */
.ats-headlines {
    display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 24px;
}
.ats-headline {
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
    padding: 18px 20px;
}
.ats-headline-label {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em;
    color: var(--text-dim); margin-bottom: 4px;
}
.ats-headline-record {
    font-size: 28px; font-weight: 700; line-height: 1.1;
}
.ats-headline-meta {
    font-size: 13px; color: var(--text-dim); margin-top: 6px;
}

/* ── 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 cheat sheet ──────────────────── */
.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 Win Total block made its neighbours look
   top-pinned. Ported from nfl-elo-dashboard (John, 2026-08-06); keep the two
   in step. */
.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-cfp { color: var(--accent-2); }
.ts-stat-val .dim { color: var(--text-dim); font-weight: 400; }
.ts-grid {
    display: grid; grid-template-columns: 1fr 1.1fr 1.2fr; gap: 14px;
    margin-bottom: 22px; 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-panel {
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 6px;
    padding: 12px 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; }
/* Data age under the metrics header — a timestamp, so --text-muted (DESIGN_SYSTEM §1). */
.ts-asof { display: block; margin-top: 2px; font-weight: 400; font-size: 11px; color: var(--text-muted); text-transform: none; letter-spacing: 0; }
.ts-coach { padding: 3px 0; font-size: 13px; }
.ts-coach-role {
    display: inline-block; width: 30px; font-size: 10px; font-weight: 700;
    color: var(--text-dim);
}
.ts-metrics { width: 100%; border: 0; font-size: 12px; margin-bottom: 10px; }
.ts-metrics th, .ts-metrics td { padding: 4px 6px; border-bottom: 1px solid var(--border); }
.ts-metrics thead th { color: var(--text-dim); font-size: 10px; text-transform: uppercase; }
/* Wraps because the strip grew from 3 stats to 6 — without this it is the
   widest thing in a 327px column on a phone. */
.ts-sp { display: flex; flex-wrap: wrap; gap: 10px 20px; margin: 10px 0; }
.ts-ret-wrap { margin-top: 8px; }
.ts-ret-head { font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); margin-bottom: 3px; }
/* national-rank chip beside each metric (green top-third, amber mid, red bottom) */
.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: rgba(63, 185, 80, 0.18); color: var(--good); }
.ts-rank.r-mid { background: rgba(230, 194, 107, 0.15); color: var(--warn); }
.ts-rank.r-bad { background: rgba(248, 81, 73, 0.06); color: var(--bad); }
/* Identity metrics (run rate, and tempo when it lands) rank without grading —
   green/red would assert a better/worse that does not exist. 5.41:1 on the
   panel background. */
.ts-rank.r-neutral { background: rgba(154, 160, 170, 0.12); color: var(--text-dim); }
/* SSA player-grade chips (internal preview) ride the rank-chip ramp; the
   grade is data, so data colors are correct here. ° = provisional,
   † = graded at another school (transfer). */
.pg-chip { margin-left: 4px; min-width: 22px; }
.dc-band h5 .pg-chip { margin-left: 6px; font-size: 10px; }
.pg-option {
    display: inline-block; font-size: 9px; font-weight: 700; padding: 0 4px;
    margin-left: 6px; border-radius: 3px; letter-spacing: .05em;
    background: rgba(154, 160, 170, 0.12); color: var(--text-dim);
    border: 1px solid var(--border);
}
.ts-ret { display: flex; align-items: center; gap: 8px; padding: 2px 0; font-size: 11px; }
.ts-ret-label { flex: none; width: 90px; color: var(--text-dim); }
.ts-ret-bar { flex: 1; height: 7px; background: var(--bg); border-radius: 3px; overflow: hidden; }
.ts-ret-bar span { display: block; height: 100%; background: var(--good); }
.ts-ret-pct { flex: none; width: 34px; text-align: right; font-variant-numeric: tabular-nums; }
.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 tr.future td { }
.ts-sched-line { color: var(--text-dim); margin-left: 4px; }

/* Conference race card — compact projected standings under the schedule.
   `table-layout: fixed` + an ellipsised team cell is what keeps a long program
   name ("Southern Mississippi") from pushing the panel wider than its column
   once the sheet grid collapses to one column on a phone. */
.ts-race { width: 100%; border: 0; font-size: 12px; table-layout: fixed; }
.ts-race th, .ts-race td { padding: 4px 6px; border-bottom: 1px solid var(--border); }
.ts-race thead th {
    color: var(--text-dim); font-size: 10px; font-weight: 600;
    text-transform: uppercase; letter-spacing: .03em;
}
.ts-race tbody tr:last-child td { border-bottom: none; }
.ts-race th:nth-child(1), .ts-race td:nth-child(1) { width: 22px; }
.ts-race th:nth-child(3), .ts-race td:nth-child(3) { width: 46px; }
.ts-race th:nth-child(4), .ts-race td:nth-child(4) { width: 46px; }
.ts-race th:nth-child(5), .ts-race td:nth-child(5) { width: 48px; }
.ts-race td.num { font-variant-numeric: tabular-nums; }
.ts-race-team { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ts-race-team .team-logo.sm { margin-right: 4px; }
.ts-race .ts-race-me td { background: var(--bg-row); font-weight: 600; }
/* The pinned row sits BELOW the top-N cut at its real position — the accent
   rule is what separates it from the teams actually in the race. */
.ts-race .ts-race-pinned td { border-top: 1px solid var(--accent); }
.ts-race-gap td {
    padding: 0 6px 2px; border-bottom: none; text-align: center;
    color: var(--text-dim); letter-spacing: .25em;
}
.ts-race-note { margin: 8px 0 0; font-size: 11px; line-height: 1.5; color: var(--text-dim); }

.ts-side h5 {
    margin: 10px 0 6px; font-size: 11px; text-transform: uppercase;
    letter-spacing: .05em; color: var(--text-dim);
    border-bottom: 1px solid var(--border); padding-bottom: 3px;
}
.ts-side-groups { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 6px 12px; }
.ts-posgroup { min-width: 0; }
.ts-pos { font-size: 10px; font-weight: 700; color: var(--text-dim); margin-bottom: 2px; }
.ts-player {
    font-size: 11px; padding: 1px 0; white-space: nowrap; overflow: hidden;
    text-overflow: ellipsis;
}
.ts-jersey { display: inline-block; width: 20px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.ts-cls { color: var(--text-dim); font-size: 10px; margin-left: 3px; }
/* injury/unavailability red-flag (John: must be unmissable) */
.ts-player.ts-out { color: var(--bad); }
.ts-player.ts-out .ts-jersey, .ts-player.ts-out .ts-cls { color: var(--bad); opacity: .7; }
.ts-flag {
    font-size: 9px; font-weight: 700; color: var(--bg); background: var(--bad);
    border-radius: 3px; padding: 0 4px; margin-left: 5px; vertical-align: 1px;
}
.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); }

/* ── Formation depth chart (Ourlads order, CFL-sheet style) ── */
.ts-grid-2 { grid-template-columns: 1fr 1.2fr; }
@media (max-width: 1000px) { .ts-grid-2 { grid-template-columns: 1fr; } }
.dc-panel { margin-bottom: 22px; }
.dc-band h5 {
    margin: 8px 0 4px; font-size: 10px; text-transform: uppercase;
    letter-spacing: .08em; color: var(--text-dim);
}
.dc-row { display: flex; justify-content: space-evenly; align-items: flex-start; gap: 10px; margin: 8px 0; flex-wrap: wrap; }
.dc-box { min-width: 150px; max-width: 190px; }
.dc-poslabel {
    display: inline-block; background: #000; color: #fff; font-weight: 700;
    font-size: 11px; letter-spacing: .05em; padding: 2px 10px; margin-bottom: 3px;
    border: 2px solid var(--accent); border-radius: 3px;
}
.dc-player { font-size: 11px; padding: 1px 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dc-player.dc-starter { font-weight: 700; font-size: 12px; }
.dc-tr { font-size: 8px; color: var(--warn); margin-left: 4px; vertical-align: 1px; }
.dc-divider { border-top: 2px solid var(--accent); margin: 14px 0 6px; }
.dc-st .dc-row { justify-content: flex-start; gap: 26px; }
.dc-other { justify-content: flex-start; gap: 26px; }
.dc-inj {
    margin-top: 12px; padding: 8px 10px; border: 1px solid var(--bad);
    border-radius: 5px; font-size: 11px; display: flex; gap: 12px; flex-wrap: wrap;
    align-items: center;
}
.dc-inj-label { font-weight: 700; color: var(--bad); font-size: 10px; letter-spacing: .06em; }
.dc-inj-player { color: var(--bad); }

/* ── 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-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;
}

/* ── 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; }

/* ── Power Rankings — neutral-field matchup tool ─────────── */
.matchup-tool {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 12px 14px; margin-bottom: 16px;
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 6px;
}
.mt-label { font-size: 13px; font-weight: 600; }
.mt-sel { max-width: 220px; }
.mt-result { font-size: 14px; min-width: 140px; }
.mt-result strong { font-weight: 700; }
.mt-result .team-logo { margin-right: 6px; }
/* the global .dim styles are td-scoped; scope one for the tool's spans */
.matchup-tool .dim { color: var(--text-dim); }

/* E14: every rankings row is a link into Team Detail (rankings.js delegates
   the click; rows carry tabindex=0 + role=link). Hover already reads as
   interactive via the global tbody tr:hover — add the pointer and a keyboard
   focus ring. Chrome, so --accent only. */
#page-rankings tbody tr[data-team] { cursor: pointer; }
#page-rankings tbody tr[data-team]:focus-visible {
    outline: 2px solid var(--accent); outline-offset: -2px;
}

/* E24: "Model updated <relative>" freshness pill on the rankings tab — rides
   the right edge of the matchup-tool bar, same voice as the schedule bar's
   odds/model readout. */
.matchup-tool .rankings-fresh {
    margin-left: auto; white-space: nowrap;
    font-size: 12px; color: var(--text-dim);
    background: var(--bg-row); border: 1px solid var(--border);
    border-radius: 999px; padding: 3px 10px;
}
.rankings-fresh .label {
    text-transform: uppercase; letter-spacing: 0.04em;
    font-weight: 600; font-size: 11px; margin-right: 4px;
    color: var(--text-muted);
}

/* ── Forecast — schedule-spot filter chips ───────────────── */
.fc-filters {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    /* bottom margin restores the intro→first-conference gap the bar displaced
       (it replaced p.dim as .fc-head's last child, whose 14px used to collapse
       through) */
    margin: 10px 0 14px; padding: 10px 12px;
    background: var(--bg-elev); border: 1px solid var(--border); border-radius: 6px;
}
.fc-filters-label { font-size: 12px; font-weight: 600; }
.fc-filter {
    font-size: 12px; padding: 4px 10px; border-radius: 12px;
    background: var(--bg); color: var(--text-dim); border: 1px solid var(--border);
}
.fc-filter:hover { color: var(--text); background: var(--bg-row); }
.fc-filter.active { background: var(--accent); border-color: var(--accent); color: var(--bg); }
.fc-filter.active:hover { filter: brightness(1.12); }
.fc-filters-note { font-size: 12px; color: var(--text-dim); }

/* ── Forecast — CFP bracket (collapsible, WC-predictor style) ─────── */
.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-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.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 school 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 rgba(0, 0, 0, 0.55); 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; }

/* ── 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 stamps 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, stacked. */
.fc-conf-list { display: flex; flex-direction: column; gap: 22px; }
.fc-conf { min-width: 0; }

/* Conference/division roll-up row: season-odds sums (Σ wins vs market, expected
   playoff teams, group title/champ probability). The label reuses .fc-team so it
   stays sticky; the week 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; }

.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-x 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) */
.fc-team-head, .fc-team {
    position: sticky; left: 0; z-index: 2;
    text-align: left !important; padding-left: 10px !important;
    background: var(--bg-elev);
}
/* .fc-grid thead th prefix is REQUIRED: bare .fc-team-head (0,1,0) loses the
   specificity fight to `.fc-grid thead th` (0,1,2), computing z-index 3 — equal
   to the week-number ths, whose later siblings then paint OVER the stuck corner
   on horizontal scroll. */
.fc-grid thead th.fc-team-head { z-index: 4; }
.fc-team-head { 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 { margin-right: 7px; vertical-align: middle; }
.fc-team .team-logo { width: 20px; height: 20px; margin-right: 7px; }
/* tiny opponent logo inside heatmap cells */
.fc-opp-logo { width: 11px; height: 11px; margin-right: 2px; vertical-align: -2px; }
.fc-team .team-name { font-weight: 600; vertical-align: middle; }
.fc-grid tbody tr:hover td { background: var(--bg-elev); }

.fc-wk { min-width: 46px; }
.fc-cell { min-width: 46px; line-height: 1.15; }
.fc-cell.fc-byewk { background: var(--bg); }
.fc-opp { font-size: 11px; color: #a6acb6; letter-spacing: .02em; }
.fc-wp { font-weight: 700; font-size: 12px; }
/* predicted spread beside the win% (dimmer + lighter so the % stays the anchor).
   #a6acb6 is deliberately one step lighter than var(--text-dim): these cells sit
   on the strong-fav green tint over --bg-elev (the global `table` rule), where
   --text-dim measures 4.48:1 — under WCAG AA. #a6acb6 clears it at 5.16:1. */
.fc-line { margin-left: 4px; font-weight: 400; font-size: 10px; color: #a6acb6; }
.fc-cell.fc-w .fc-wp { color: var(--good); }
.fc-cell.fc-l .fc-wp { color: var(--bad); }
/* conference-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 rgba(255, 255, 255, 0.6); }
/* spot filters: non-matching cells fade so matches pop; keep hover legibility */
.fc-cell.fc-dimmed { opacity: 0.13; }
.fc-grid tbody tr:hover .fc-cell.fc-dimmed { opacity: 0.45; }
/* standings-matched final week (Pac-12): projected aggregate, no fixed opponent */
.fc-cell.fc-projected .fc-opp { font-style: italic; opacity: 0.85; }
/* A projected flex game (Pac-12 week 13, ADR-0006) has a real opponent: italic marks
   the projection; no opacity, so the label keeps full AA contrast on every heat cell. */
.fc-cell.fc-flex .fc-opp { font-style: italic; }
.fc-confgame-legend {
    color: var(--text); padding: 0 4px; border-radius: 3px;
    box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, 0.6);
}
/* 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); }
/* CFP committee rank (ADR-0008): a published fact, not a model conclusion, so
   chrome colour — the secondary accent badges use (5.93:1 worst case), never
   the green/red data ramp. */
.fc-cfp { color: var(--accent-2); font-weight: 600; }
/* section buffer: empty channel between the three forecast zones (games+wins |
   conference | playoff). Page-bg fill + no borders so it reads as a real gap,
   and it never picks up the row-hover highlight. */
.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-record cell: horizontal fill bar to the win fraction; the record
   text renders on top of the gradient. */
.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%); }
.fc-prob { background: rgba(63, 185, 80, calc(var(--p, 0) * 0.5)); 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: rgba(63, 185, 80, 0.22); }
.wp-light-fav  { background: rgba(63, 185, 80, 0.10); }
.wp-coinflip   { background: transparent; }
.wp-light-dog  { background: rgba(248, 81, 73, 0.05); }
.wp-strong-dog { background: rgba(248, 81, 73, 0.10); }

/* ATS "All seasons" total row (tfoot) — visually separates the
   cross-season aggregate from the per-season rows above. The padding is
   restated here because the `tbody td` rule above doesn't reach the
   tfoot; without it the total row falls back to the 1px UA default and
   its cells sit cramped + horizontally offset from the body columns. */
.ats-total td {
    padding: 8px 12px;
    background: var(--bg-elev);
    font-weight: 700;
    border-top: 2px solid var(--border);
    white-space: nowrap;
}

/* ── ATS conference breakdowns (dual-scope: season | all-time) ──────────── */
.collapsible .collapsible-body > .ats-scope-table { margin-top: 0; }
.ats-scope-table { width: 100%; }
.ats-scope-table th.ats-scope-head {
    font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em;
    border-bottom: 1px solid var(--border);
}
.ats-scope-table .ats-scope-sep { border-left: 2px solid var(--border); }
.ats-scope-table tbody tr:hover { background: var(--bg-row); }
.ats-scope-table tbody tr:last-child td {
    border-top: 1px solid var(--border); font-weight: 700;   /* Total / All row */
}

/* ── Nav-right (Help + Account stub) — used by public.html.
   Previously inlined per-page; promoted here so the public surface can reuse
   it without duplicating styles. ───────────────────────────────────────── */
.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-dim); background: var(--bg-row);
    padding: 2px 8px; border-radius: 4px; font-weight: 500;
}

/* ── Public surface (cfl.SSA) ─────────────────────────────────────────── */
.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 .lock-price { color: var(--text); font-size: 15px; font-weight: 600; }
.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); }

.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;
}

/* Single headline card (public ATS shows All-time only) */
.ats-headlines.single { grid-template-columns: 1fr; max-width: 360px; }
/* Wide data tables (ATS splits, the weekly schedule, the team-sheet
   schedule) scroll horizontally on narrow screens instead of squashing
   their columns. */
.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; }

/* ── Public paywall module view (2026-07-30 pilot; NCAAF port) ─────────── */
/* 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; }

/* ── FBS-vs-FCS exclusion toggle ──────────────────────────────────────────
   One control, rendered by static/js/fcs.js on the Schedule, Team, Forecast
   and ATS pages. Pure chrome: surface + border tokens only, no data colour and
   no hex literal (docs/DESIGN_SYSTEM.md). Rendered only on the internal host —
   on the public host the rows never arrive, so there is nothing to toggle. */
.fcs-toggle {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    margin: 0 0 16px;
    padding: 10px 12px;
    background: var(--bg-elev);
    border: 1px solid var(--border);
    border-radius: 6px;
}
.fcs-toggle label {
    display: flex; align-items: center; gap: 8px;
    margin: 0; cursor: pointer;
}
.fcs-toggle .dim { font-size: 12px; }

/* Footnote naming the basis of a record or rate that the exclusion changed,
   so the number is labelled rather than quietly one win light. */
.ts-log-note { margin: 0 0 10px; font-size: 12px; }

/* ── Key Numbers (2026-09-10) ──────────────────────────────────────────────
   Two inline-SVG bar charts + their tables. Marks take the two 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. */
.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; }
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; }

/* ── 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; }
    .ats-headlines.single { max-width: none; }
    .ats-headline-record { font-size: 24px; }
    /* 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; }
    /* Tab bar scrolls sideways instead of overflowing the viewport. */
    .page-nav { overflow-x: auto; flex-wrap: nowrap; }
    .page-nav-btn { white-space: nowrap; }

    /* ── Schedule: card-per-game layout (both shells) ─────────────────────
       Same DOM as desktop — the date/away/home row triplet regrids into a
       stacked card inside its .week-section; per-cell headings come off the
       renderer's data-label attributes because the thead is hidden. Every
       rule lives behind this media query, so the >600px table is untouched
       (the .ats-table-wrap scroll wrapper stays the fallback there and for
       any table not carded here). Chrome tokens only. */
    .schedule-table { display: block; border: 0; background: transparent; overflow: visible; }
    .schedule-table thead { display: none; }
    .schedule-table tbody { display: block; }
    .schedule-table tbody tr {
        display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px 8px;
        padding: 8px 12px; background: var(--bg-elev);
        border: 1px solid var(--border); border-top: 0;
    }
    .schedule-table tbody tr.game-date {
        display: block; padding: 6px 12px; margin-top: 14px;
        border: 1px solid var(--border); border-radius: 8px 8px 0 0;
    }
    .schedule-table tbody tr.game-date:first-child { margin-top: 0; }
    .schedule-table .game-date td { display: block; padding: 0; border-bottom: 0; }
    .schedule-table tbody td { padding: 0; border-bottom: 0; }
    /* Card header line: matchup + record left, score/status right. */
    .schedule-table .matchup-col { grid-column: 1 / -2; }
    .schedule-table .matchup-cell { min-width: 0; padding-left: 0; }
    .schedule-table .score-cell { grid-column: -2 / -1; align-self: center; justify-self: end; }
    /* Market/model cells reprint their column heading above the value. */
    .schedule-table td[data-label]::before {
        content: attr(data-label); display: block;
        font-size: 9px; font-weight: 600; text-transform: uppercase;
        letter-spacing: 0.05em; color: var(--text-dim); margin-bottom: 2px;
    }
    .schedule-table .odds-cell { min-width: 0; width: 100%; padding: 4px 6px; }
    .schedule-table .boxscore-cell { grid-column: 1 / -1; text-align: left; }
}

/* ── 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; }

/* Forecast team name -> Team Detail. 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. The
   roll-up label cells (.fc-sum-label) carry no anchor, so they are untouched. */
.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; }

/* ── Lock-card teasers (public shell; wave-2 UX pass) ─────────────────────
   SYNTHETIC rows rendered behind the Schedule/Forecast lock cards by
   public.js::teaserHTML. The teaser div also carries .elo-teaser, which
   supplies the blur + pointer-events/user-select inertness; the card
   overlaps the teaser's lower rows and stays fully readable on its own
   opaque background. Chrome tokens only. Wrapper class is .lock-wrap — the
   portfolio-wide name the vendored lockcard.js emits (wave 3). */
.lock-wrap { position: relative; }
.lock-teaser { max-height: 150px; overflow: hidden; }
.lock-teaser table { font-size: 12px; }
.lock-wrap .lock-card { position: relative; margin-top: -56px; }
