/* Shared dashboard components. Every colour, size and radius below is a token
   from css/tokens.css (#38); no literal survives in this file.

   WHAT WAS DELETED HERE, AND WHY IT MATTERED. This file used to define
   `.styled-table`, `.styled-table th, td`, `.styled-table th` and
   `.styled-table td` a second time. d3_visualizations.css loads after it on
   both pages and defines all four, so THIS file's copies were dead -- the
   table has been rendering at d3_visualizations.css's 11px/6px, never at the
   16px/8px written here. Link order, not intent, was choosing the design. The
   dead copies are gone; the table lives in d3_visualizations.css alone.

   `.chart-grid` and `.chart-container` were duplicated the same way and are
   resolved the other direction -- they stay here, because two tests pin
   `.chart-container` to this file by name, and they now carry the values that
   were actually live (grid `1fr`, not the dead `minmax(600px, 1fr)`; the
   container's `overflow-x: auto`). d3_visualizations.css's copies are gone. */
body {
    /* style.css owns the body's font, colour, background and margin. The one
       thing this file still has to say is the page gutter; everything else it
       used to repeat here was either identical or -- in the case of the
       background -- a second opinion that quietly won. See tokens.css. */
    padding: var(--space-8);
}
h1 {
    text-align: center;
    color: var(--color-heading);
}
.filters {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: var(--space-8);
    padding: var(--space-8);
    background-color: var(--color-surface);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-1);
    margin-bottom: var(--space-8);
    flex-wrap: wrap;
}
.filters label {
    font-weight: bold;
}
.filters select, .filters input, .filters button {
    padding: var(--space-4) var(--space-6);
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border-strong);
}
.filters select, .filters input {
    background-color: var(--color-surface);
    color: var(--color-text);
}
.filters button {
    background-color: var(--color-primary);
    color: var(--color-on-primary);
    border: none;
    cursor: pointer;
    transition: background-color 0.2s;
}
.filters button:hover {
    background-color: var(--color-primary-hover);
}
.chart-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
}
.chart-container {
    background-color: var(--color-surface);
    padding: var(--space-8);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-1);
    overflow-x: auto;
}
.loading-overlay, .error-overlay {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100%;
    font-size: 1.2em;
    color: var(--color-text-subtle);
}
/* The panel being replaced must not collapse to one line of text (#40). Six
   panels shrinking at once pulls the page up under the reader's thumb, and on a
   phone that is worse than the wait it is reporting. This is the whole of the
   issue's "loading skeleton": the panel holds its place. */
.loading-overlay {
    min-height: 120px;
}
/* A real failure. Kept visually distinct from the notice below on purpose: the
   two used to share a class, so "the season has not started" and "the bucket is
   unreadable" looked identical (#27). */
.error-overlay {
    color: var(--color-danger);
    /* AND IT HAS TO BE READABLE TO THE END (#142). The messages this carried
       were one short line ("No data available for the selected filters."), so
       `height: 100%` above was never tested against a long one -- and it
       resolves against a stretched grid item, which CLIPS anything taller than
       the panel behind a scrollbar. An init failure names the object that could
       not be read and the reason, which is three lines at 1400px and more on a
       phone: measured at 289px of text in a 210px panel. The panel grows to the
       message instead, and `min-height` keeps it from collapsing on a short one
       -- the same 120px the loading overlay holds (#40). */
    height: auto;
    min-height: 120px;
    text-align: center;
}
/* An EXPECTED empty state -- a season that has not kicked off, a week that has
   not been played, a round these two teams are not in. Calm, informative and
   never red: none of these is a fault, and the site is in one of them for
   months of every year (#27). */
.notice-overlay {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    height: 100%;
    padding: var(--space-7);
    text-align: center;
    border-left: 4px solid var(--color-accent);
    background-color: var(--color-accent-tint);
    color: var(--color-text-on-tint);
}
.notice-headline {
    font-size: 1.1em;
    font-weight: bold;
    margin-bottom: var(--space-3);
}
.notice-detail {
    font-size: 0.9em;
    color: var(--color-text-muted);
    max-width: 46em;
}
/* The one thing the schedule can always answer: when. */
.notice-when {
    margin-top: var(--space-4);
    font-size: 0.9em;
    font-weight: bold;
    color: var(--color-accent-text);
}
/* `.page-loading-overlay` WAS HERE, and #40's mobile stage deleted it. It was
   `position: fixed`, the full viewport, `z-index: 1000` and had no
   `pointer-events: none`, so every update blanked the whole page and swallowed
   clicks on the filter bar it covered -- a reader could not change their mind
   mid-load. Stage 1 made all four controls live, so it fired on every change
   rather than once per Update click, which turned an occasional flash into the
   normal state of the page.
   Nothing replaced it: the per-container `.loading-overlay` above already
   existed and is what both scripts call, so the panel being refetched says so
   and the others stay readable and clickable while it does. The one token this
   rule alone used, `--color-scrim`, went with it. */

/* The season banner and the blended-cell marking (#28). A blended value is an
   estimate carried forward from last season, so it must never look like a
   number a game produced: the cell is italic, muted and carries a marker, and
   the badge above the table says how much of it is this season. */
.season-banner {
    margin: var(--space-4) 0 var(--space-6) 0;
    padding: var(--space-4) var(--space-6);
    border-left: 4px solid var(--color-accent);
    background-color: var(--color-accent-tint);
    color: var(--color-text-on-tint);
    font-size: var(--text-md);
}
.blend-badge {
    display: inline-block;
    margin: var(--space-1) 0 var(--space-3) 0;
    padding: var(--space-1) var(--space-4);
    border-radius: var(--radius-pill);
    background-color: var(--color-surface-muted);
    color: var(--color-text-muted);
    font-size: var(--text-xs);
    font-weight: bold;
}
.blended-value {
    font-style: italic;
    color: var(--color-text-muted);
}
.blended-value::after {
    content: '~';
    font-style: normal;
    color: var(--color-text-subtle);
    margin-left: var(--space-1);
}

/* --- The league-wide page (#64) ------------------------------------------
   Deliberately no new visual language: the table is the same `.styled-table`
   the matchup dashboard uses, the notice/error split is #27's, and the blend
   badge is #28's. What is added here is what a 32-row table needs and a
   two-column one did not -- sortable headers, a direction line under each
   label, and a tab strip. */
.page-links {
    text-align: center;
    color: var(--color-text-muted);
    font-size: var(--text-md);
    margin: -6px 0 var(--space-7) 0;
}
.tab-strip {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin: var(--space-6) 0 var(--space-2) 0;
}
.tab-strip .tab {
    padding: var(--space-3) var(--space-6);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-sm);
    background-color: var(--color-surface);
    color: var(--color-heading);
    font-size: var(--text-sm);
    cursor: pointer;
}
.tab-strip .tab:hover {
    background-color: var(--color-accent-tint);
}
.tab-strip .tab.active {
    background-color: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-on-accent);
    font-weight: bold;
}
/* A sortable header says so, and says which way it is currently pointing. */
.styled-table th.sortable {
    cursor: pointer;
    user-select: none;
}
.styled-table th.sortable:hover {
    background-color: var(--color-accent-hover);
}
/* Since #38 it is also a tab stop, and its ring is the one place in the site
   that is drawn INSIDE the element rather than around it. Two reasons, both
   forced: the header cells abut, so an outward ring is painted over the
   neighbouring header instead of onto a surface of its own; and the first
   column is `position: sticky`, where an outward ring is clipped by the
   scroller that makes it sticky. Inset, it lands on the header's own
   `--color-accent` background -- 6.65:1 in light, 5.80:1 in dark, and that is
   the surface the ring colour was chosen against. The ring itself is
   style.css's; only the offset moves. */
.styled-table th.sortable:focus-visible {
    outline-offset: -3px;
}
.metric-label {
    font-weight: bold;
}
.metric-label.muted {
    font-weight: normal;
    color: var(--color-text-muted);
}
/* The direction line under a column label. `no-direction` is the case that
   matters: `higher_is_better: null` means the payload declined to say which end
   is good, so the column is never ranked, never highlighted and never
   arrow-marked -- see d3_league.js's directionOf(). It has a token of its own,
   `--color-descriptive`, so that "we decline to say" looks the same wherever
   it is said. */
.metric-direction {
    display: block;
    font-weight: normal;
    font-size: var(--text-xxs);
    color: var(--color-text-muted);
}
.metric-direction.no-direction {
    font-style: italic;
    color: var(--color-descriptive);
}
.league-table td.count-cell {
    color: var(--color-text-muted);
}
.league-table .team-abbr {
    font-weight: bold;
}
.league-table .team-cell {
    text-align: left;
    min-width: 9em;
}
/* The postseason record, beside the regular-season one (#85). Deliberately
   quiet: `team_record` is the number people mean by "record" and keeps the
   weight, while this says what the other one is rather than letting a reader
   guess why a playoff team shows two. Blank on every regular-season row, so
   this rule matches nothing for weeks 1-18. */
.postseason-record {
    display: inline-block;
    margin-left: var(--space-3);
    padding: 1px var(--space-3);
    border-radius: var(--radius-pill);
    background-color: var(--color-surface-muted);
    color: var(--color-text-muted);
    font-size: var(--text-xxs);
    font-family: var(--font-sans);
}

.thin-sample-badge {
    display: inline-block;
    margin-top: var(--space-1);
    padding: 1px var(--space-3);
    border-radius: var(--radius-pill);
    background-color: var(--color-caution-surface);
    color: var(--color-caution-text);
    font-size: var(--text-xxs);
    font-weight: bold;
}
/* A caveat the payload made, rendered where the number is.

   THREE OF THE FOUR CALL SITES USED TO RESTATE THIS RULE INLINE (#38) -- the
   same 12px, the same muted colour and the same `4px 0 10px 0` margin, set with
   `.style()` immediately after `.attr('class', 'chart-caption')`. They said
   nothing the class did not, and a caption that had drifted from its class
   would have been impossible to see in either file. */
.chart-caption {
    font-size: var(--text-sm);
    color: var(--color-text-muted);
    margin: var(--space-2) 0 var(--space-5) 0;
}
.chart-caption.warning {
    color: var(--color-caution-text);
}
/* The fourth site, which is a real variant rather than a restatement: the
   injury panel's footnote naming what is deliberately NOT built. Quieter than a
   caveat about the numbers above, and it sits under them rather than over. */
.chart-caption.footnote {
    font-size: var(--text-xs);
    color: var(--color-text-subtle);
    margin: var(--space-5) 0 0 0;
}

/* --- Panel components the renderers used to set inline (#38) -------------- */
/* A heading INSIDE a panel: "Unit vs. unit (EPA/play)", "Projected play count".
   One step under the panel's own `<h2>`, which is what `--text-lg` is for. */
.table-subheading {
    margin: var(--space-7) 0 var(--space-2) 0;
    font-size: var(--text-lg);
}

/* The direction line the metric tables print beside a metric's label. The
   league page's `.metric-direction` is the same information on its own line,
   under a column header; this is the inline form, in a two-column table where
   the label has a row to itself. */
.direction-note {
    color: var(--color-text-subtle);
}

/* The injury report's designation counts: "1 Out · 1 Doubtful · 2 dnp". Built
   from the payload's own metric list, so the number of them is not known here. */
.chip-row {
    margin: var(--space-1) 0 var(--space-4) 0;
    font-size: var(--text-sm);
}
.chip-separator {
    color: var(--color-text-subtle);
}
/* A count the server named a metric, and it is non-zero. */
.chip-strong {
    font-weight: bold;
}
/* A count the server counted but did not name a metric -- the practice-status
   tallies. Present, and deliberately not competing with the designations. */
.chip-muted {
    color: var(--color-text-muted);
}

/* The two-column panel shell. #33's injury report first, and #34's player
   usage panel since -- one table per side of the matchup, in the away-then-home
   order the game_id itself is written in. Shared rather than copied: a second
   set of these three rules is how two panels on one page end up disagreeing
   about what a column is.
   `1 1 320px` and not a grid: below roughly
   680px of panel the two columns become one, which is the same answer the
   600px tier would give without needing to know about it. `min-width: 0` is
   what lets the table inside scroll instead of forcing the column wider. */
.report-columns {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-7);
}
.report-column {
    flex: 1 1 320px;
    min-width: 0;
}
/* One side above the other instead of beside it (#34). Five columns fit a
   half-width card; fourteen do not, and a usage table read one offence at a
   time is the shape the research actually has.
   A FULL-WIDTH BASIS AND NOT `flex-direction: column`, which was tried and
   measured wrong: a WRAPPING column flex container sizes each line to its
   content on the cross axis, so at 390px the two sides came out 758px wide
   and the card scrolled sideways past the caption instead of the table
   scrolling inside it. Wrapping one item per line keeps `min-width: 0`
   doing its job. */
.report-columns.stacked .report-column {
    flex-basis: 100%;
}
/* The team's name over its half of the report. It carries `.team-cell` too, for
   the same reason the table cells do -- but `.styled-table .team-cell` needs a
   table ancestor and this is a bare `<div>`, so the weight has to be said here.
   The two colours are the payload's and stay inline in the renderer. */
.report-team {
    padding: var(--space-2) var(--space-4);
    font-weight: bold;
}

/* The W/L/T/BYE mark beside a week number in the combined summary. It was an
   inline `style="color: ${resultColor}; font-weight: bold"` built by a
   five-branch if/else in the renderer; the branch is now a class name and the
   colours are where every other colour is. */
.result-mark {
    font-weight: bold;
    color: var(--color-text);
}
.result-mark.win {
    color: var(--color-result-win);
}
.result-mark.loss {
    color: var(--color-result-loss);
}
.result-mark.tie {
    color: var(--color-result-tie);
}
.result-mark.bye {
    color: var(--color-result-bye);
}
/* The slate. `caution` is the season banner in a different key: not an error --
   nothing has failed -- but not routine news either, which is what the backtest
   verdict is. */
.season-banner.caution {
    border-left-color: var(--color-caution);
    background-color: var(--color-caution-surface);
    color: var(--color-caution-text);
}
.verdict-headline {
    font-weight: bold;
}
.verdict-detail {
    margin-top: var(--space-2);
    color: var(--color-caution-text);
}
.slate-table td {
    text-align: left;
}
.slate-table td.edge-cell {
    font-weight: bold;
    text-align: right;
}
.slate-table td.crosses-key {
    font-weight: bold;
    color: var(--color-heading);
}
.slate-table .market-source,
.slate-table .venue-badge {
    display: inline-block;
    margin-left: var(--space-3);
    padding: 1px var(--space-3);
    border-radius: var(--radius-pill);
    background-color: var(--color-surface-muted);
    color: var(--color-text-muted);
    font-size: var(--text-xxs);
}
