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

/* ── 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 "Proj W" bar fill (cool 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 pair was run through the dataviz palette validator on
       --bg-elev (CVD separation 10.8 deutan / 27.6 tritan vs the neutral,
       both >= 3:1). The key hue is the crimson-tint repos' pick — the NFL
       sibling's blue would read as chrome here. */
    --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); }
/* 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], 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 badge — self-contained abbreviation chip (WPG, SSK, …) ──────────
   Replaced the CFL.ca icon-logo-* data-URL logos 2026-09-02: their 2026-09-01
   site relaunch (the same event as the scraper outage) 404'd that stylesheet
   and every logo rendered as an empty box. Same 32px footprint as the old
   logo so table layouts don't shift. Tokens only — no team-identity colour. */
.team-badge {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 32px; height: 32px; padding: 0 3px;
    background: var(--bg-row); border: 1px solid var(--border); border-radius: 6px;
    font-size: 11px; font-weight: 600; color: var(--text);
    letter-spacing: 0.02em; flex-shrink: 0;
}

/* Self-hosted team logos (static/logos/<slug>.svg, vendored 2026-09-02 after
   CFL.ca's relaunch deleted the sprite this UI used to hot-link). The
   abbreviation badge above remains the unknown-team fallback. */
.team-logo {
    width: 28px; height: 28px; object-fit: contain;
    vertical-align: middle; flex-shrink: 0;
}
/* Rankings team cell: logo sits on the text baseline row, name stays the
   click/read target. Smaller than the schedule's 28px so row height holds. */
.team-cell .team-logo { width: 22px; height: 22px; margin-right: 8px; }
.fc-team .team-logo { width: 18px; height: 18px; margin-right: 7px; }

/* ── Schedule (sportsbook-style table) ───────────────────── */
/* ONE control bar: week filter + hide-completed, with the odds/model freshness
   readout pushed to the right. Ported from the NFL/NCAAF siblings 2026-08-16;
   it REPLACES the old standalone `.freshness-bar`, whose markup this bar
   absorbs. All chrome — surface tokens + --accent only, no data colours.

   Two rules here exist to override things this stylesheet already says, and
   dropping either leaves the bar subtly wrong:
     - `select` is restated to --bg-row/4px-8px because the global control rule
       above paints every select --bg-elev with 6px 10px padding, which would
       make the week picker read as another raised card sitting ON a card
       instead of an inset control in a bar.
     - the checkbox needs an explicit `accent-color`; nothing else in this
       stylesheet sets one, so it would otherwise render the browser-default
       blue rather than the SSA accent.
   The `.label` / `.freshness-sep` rules are RESTATED under .sched-controls
   because the old selectors were scoped `.freshness-bar .label` — moving the
   markup without moving the scope silently drops the ODDS/MODEL treatment. */
.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); }
.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 Pred + Edge cells, one accent rule (John,
   2026-09-23): the highlight marks the side WE like, not the side the book
   likes — it used to paint the favourite's Pred in var(--good), which drew the
   eye to the wrong row and spent a data colour on chrome. Pred + Edge + Grade
   now read as one accent row. `.edge-cell.big` (edge >= 2) is the STRONGER
   form: same colour, heavier weight. */
.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 .rec-cell {
    background: rgba(176, 73, 224, 0.15); color: var(--accent-2);
    border: 1px solid rgba(176, 73, 224, 0.3); border-radius: 4px;
    padding: 4px 8px; font-weight: 600; font-size: 12px; display: inline-block;
    white-space: nowrap;
}
.schedule-table .rec-cell.none { background: transparent; border-color: transparent; color: var(--text-dim); }
.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); }

/* ── Schedule mobile cards (≤600px, 2026-09-02) ──────────────────────────
   The SAME 3-rows-per-game DOM as desktop, relaid as a stacked card per
   game: the date row becomes the card header, then a grid block per team
   (matchup + score up top, labelled stat cells beneath). Column headings
   come from the renderer's data-label attributes because the away/home rows
   carry DIFFERENT cell sets (rowspan Total/Result on the away row,
   placeholder <td>s for the unrecommended side), so positional labelling
   would mislabel. Nothing here renders above 600px — desktop keeps the
   table byte-identical, with .ats-table-wrap as its scroll fallback. */
@media (max-width: 600px) {
    table.schedule-table,
    .schedule-table tbody { display: block; background: none; border: none; }
    .schedule-table thead { display: none; }
    .schedule-table tbody tr { display: block; }
    .schedule-table tbody td {
        display: block; min-width: 0; padding: 0;
        border-bottom: none; text-align: left; white-space: normal;
    }
    /* The unrecommended side's Edge/Grade/Rec placeholders */
    .schedule-table tbody td:empty { display: none; }

    /* Date row = the card's header */
    .schedule-table tr.game-date {
        display: block; margin-top: 14px;
        border: 1px solid var(--border); border-bottom: none;
        border-radius: 10px 10px 0 0; background: var(--bg-elev);
    }

    /* Away + home rows = grid blocks inside the card */
    .schedule-table tr.game-date + tr,
    .schedule-table tr.game-date + tr + tr {
        display: grid; grid-template-columns: repeat(6, 1fr);
        gap: 10px 8px; align-items: start;
        padding: 12px; background: var(--bg-elev);
        border-left: 1px solid var(--border); border-right: 1px solid var(--border);
    }
    .schedule-table tr.game-date + tr { border-bottom: 1px dashed var(--border-subtle); }
    .schedule-table tr.game-date + tr + tr {
        border-bottom: 1px solid var(--border);
        border-radius: 0 0 10px 10px; margin-bottom: 4px;
    }
    .schedule-table tbody td.matchup-col { grid-column: 1 / 5; }
    .schedule-table tbody td.score-cell {
        grid-column: 5 / 7; text-align: right; align-self: center;
    }
    .schedule-table .matchup-cell { min-width: 0; }
    .schedule-table .matchup-cell .team-name { white-space: normal; }
    .schedule-table tbody td[data-label] { grid-column: span 2; }
    .schedule-table tbody td[data-label]::before {
        content: attr(data-label);
        display: block; margin-bottom: 2px;
        font-size: 10px; font-weight: 600; letter-spacing: 0.04em;
        text-transform: uppercase; color: var(--text-muted);
    }
}

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

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

/* ── Forecast grid ──────────────────────────────────────── */
.forecast-scroll { overflow-x: auto; }
.forecast-grid {
    border-collapse: separate; border-spacing: 0;
    font-size: 12px;
}
.forecast-grid th, .forecast-grid td {
    border-bottom: 1px solid var(--border);
    padding: 6px 4px; text-align: center; white-space: nowrap;
}
.forecast-grid thead th {
    background: var(--bg-elev); color: var(--text-dim);
    font-weight: 600; text-transform: uppercase; font-size: 10px;
    position: sticky; top: 0;
}
.forecast-team-head, .forecast-team {
    position: sticky; left: 0; z-index: 2;
    background: var(--bg-elev);
    text-align: left !important; padding-left: 10px !important;
}
.forecast-team {
    display: table-cell; min-width: 70px;
}
.forecast-team .team-logo {
    width: 18px; height: 18px; vertical-align: middle; margin-right: 6px;
}
.forecast-team .team-name { font-weight: 600; vertical-align: middle; }
.forecast-week { min-width: 56px; }
.forecast-cell { min-width: 56px; line-height: 1.15; }
.forecast-cell.bye { background: var(--bg); }
.forecast-cell-opp { font-size: 10px; color: var(--text-dim); }
.forecast-cell-pct { font-weight: 600; font-size: 12px; }
.forecast-played { margin-left: 3px; font-size: 9px; }
.forecast-played.win { color: var(--good); }
.forecast-played.loss { color: var(--bad); }
.forecast-total {
    background: var(--bg-elev); font-weight: 700; min-width: 60px;
}
.forecast-projw {
    /* Horizontal fill bar 0%..100% of the max-possible 18 wins. The cell text
       renders on top of the gradient via z-index. */
    background: linear-gradient(
        90deg,
        var(--bar-projw) 0%,
        var(--bar-projw) var(--projw-fill, 0%),
        var(--bg-elev)   var(--projw-fill, 0%),
        var(--bg-elev)   100%
    );
    position: relative;
}
/* Playoff-odds division band rows (Monte Carlo table on the Forecast tab) */
.forecast-div-row td {
    background: var(--bg-elev); color: var(--text-dim);
    text-align: left !important; padding-left: 10px !important;
    text-transform: uppercase; font-size: 10px; letter-spacing: 0.5px;
}
/* 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 dual-scope breakdown table (season | all-time), ported byte-for-byte
   from the football siblings so the three leagues' ATS pages render a
   breakdown identically. The last body row is the reconciliation Total, hence
   the tr:last-child rule rather than a class. */
.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 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; }
/* One price line under the 402 pitch, sourced live from the apex billing
   catalog — absent entirely when that fetch fails. */
.lock-card .lock-price { margin: -6px auto 18px; color: var(--text); font-size: 14px; 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); }

/* Blurred synthetic teaser behind the public Schedule/Forecast lock cards
   (2026-09-02). Same contract as .elo-teaser: the rows are FABRICATED
   client-side by public.js — no gated fetch, nothing real under the blur —
   and the wrapper is aria-hidden with all interaction disabled. The card
   itself stays sharp on top (opaque --bg-row background, later in paint
   order). overflow:hidden clips the teaser table on narrow screens instead
   of letting it widen the page. */
.lock-wrap { position: relative; overflow: hidden; border-radius: 10px; }
.lock-teaser {
    filter: blur(6px); opacity: 0.55;
    pointer-events: none; user-select: none; -webkit-user-select: none;
}
.lock-teaser table { width: 100%; }
.lock-wrap .lock-card { position: relative; margin-top: -34px; }

.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 ATS table scrolls horizontally on narrow screens instead of
   squashing the splits 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) ────────────────────── */
/* 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; }

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

/* ── Forecast grid (NFL-style layout, ported 2026-07-31) ──────────────────
   One independently-scrolling grid per division, with the season odds inline
   to the right of the weekly cells, separated into zones by empty gap
   columns. Ported from nfl-elo-dashboard/static/css/style.css so CFL and NFL
   read identically; palette comes from this repo's tokens. */

/* Full-bleed: the grid is far wider than the centred content column, so let
   it use the whole viewport instead of overflowing. Applies on the internal
   SPA and the public module view (same section id). */
/* 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: 860px; margin: 0 0 14px; }
.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 division scrolls independently on BOTH axes: a bounded height makes
   this container the vertical scrollport so the sticky column headers work,
   while overflow keeps the horizontal scrollbar per-grid. */
.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 }` — on a table it makes
       the TABLE the sticky cells' scroll ancestor, which silently defeats the
       sticky Team column + headers. 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 */
.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-team-head { z-index: 4; min-width: 190px; }
.fc-team { min-width: 190px; background: var(--bg); }
.fc-grid tbody tr:hover .fc-team { background: var(--bg-elev); }
.fc-team .team-badge { min-width: 18px; height: 18px; padding: 0 3px; font-size: 9px; 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); }

.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: 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); }
/* a played tie: "T" in the push colour (as the schedule's .result-cell.push) */
.fc-cell.fc-t .fc-wp { color: var(--warn); }
/* divisional-game marker: an inner ring (inset shadow doesn't shift layout the
   way a border would, so the heatmap stays aligned). */
.fc-cell.fc-confgame { box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, 0.6); }
.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 / odds 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); }
.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: the empty channel between the three 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 win fraction. */
.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; }

/* ── Weekly-modifier matrix (admin) ───────────────────────────────────────
   Team × week grid of line-point adjustments, ported from the NFL dashboard's
   QB-adjustment matrix. Sticky team column + sticky week headers so the grid
   stays readable while scrolling in both directions. */
.qb-matrix-wrap {
    max-height: 520px; overflow: auto;
    border: 1px solid var(--border); border-radius: 8px;
}
.qb-matrix {
    border-collapse: separate; border-spacing: 0; font-size: 12px;
    /* same override as the forecast grid: the global `table { overflow: hidden }`
       would make the table the sticky cells' scroll ancestor and defeat them. */
    overflow: visible; border: 0; border-radius: 0;
}
.qb-matrix thead th {
    position: sticky; top: 0; z-index: 2; background: var(--bg-row);
    text-align: center; font-size: 10px; white-space: nowrap;
}
.qb-matrix th.qb-team-col, .qb-matrix td.qb-team-col {
    position: sticky; left: 0; z-index: 1; background: var(--bg-elev);
    text-align: left; min-width: 170px; font-weight: 600; white-space: nowrap;
}
.qb-matrix thead th.qb-team-col { z-index: 3; }
.qb-matrix td { padding: 4px 6px; text-align: center; }
.qb-matrix input.wm-cell {
    width: 58px; padding: 4px 6px; text-align: right;
    font-variant-numeric: tabular-nums;
}
/* a cell that already carries an adjustment reads at a glance */
.qb-matrix input.wm-cell.wm-set {
    border-color: var(--accent); color: var(--text); font-weight: 600;
}

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

/* Gametracker links on the schedule (box score when final, live tracker
   when underway). The live pill is informational chrome, accent-tinted —
   green/red stay data-only per the design system. */
/* .boxscore-link is the portfolio-unified class for the settled-game box-score
   link (wave-3 E42b F213); .game-link stays for the live "Game Underway"
   states, which are CFL-only. Same treatment for both. */
.game-link, .boxscore-link { font-size: 11px; white-space: nowrap; color: var(--accent); text-decoration: none; }
.game-link:hover, .boxscore-link:hover { text-decoration: underline; }
.game-link.live { font-weight: 700; }

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

/* 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: clickable rows + freshness pill (wave-3 E14/E24) ───────────
   Every rankings row is a hash-link to #team/<name> (role="link",
   delegated in pages/rankings.js). The base `tbody tr:hover` rule already
   paints the hover surface; this adds the pointer affordance and the
   keyboard focus ring (same accent-outline convention as the nav links).
   The pill sits right-aligned above the table — the table is width:100%,
   so the pill hugs its top-right corner on both shells. Chrome: surface +
   border tokens and --text-dim only. */
.rank-row { cursor: pointer; }
.rank-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.rankings-head { display: flex; justify-content: flex-end; margin: 0 0 10px; }
.rank-fresh {
    display: inline-flex; align-items: center;
    padding: 3px 10px;
    background: var(--bg-elev);
    border: 1px solid var(--border); border-radius: 999px;
    font-size: 12px; color: var(--text-dim);
}

/* ── Rankings ELO teaser (free public view; ADR: 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; }

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