/* The D3 chart language (#38), and the shared table.

   `.chart-grid` and `.chart-container` used to be defined here as well as in
   dashboard.css, and these were the copies that won. They now live in
   dashboard.css alone, carrying these values -- two tests pin
   `.chart-container` to that file by name, so it is the one that had to keep
   them. Nothing on screen moved.

   The table went the other way: dashboard.css's `.styled-table` block was the
   dead one and is gone, so the rules below are now the only definition.

   WHAT THIS FILE WAS BEFORE #38's SECOND HALF, and the reason the chart block
   is a rewrite rather than an addition: FOUR of its six chart rules matched
   nothing on either page. `.axis path`, `.axis line` and `.axis text` are
   written for an axis group carrying `class="axis"`, and no renderer had ever
   set one; `.gridline` was written for a gridline no chart draws; `.chart-title`
   for an SVG `<text>` title, where every panel on both pages titles itself with
   an HTML `<h2>`. So this stylesheet described a chart the site does not draw,
   the charts wore d3-axis's own defaults instead (it sets `font-size: 10` and
   `font-family: sans-serif` as attributes on every axis group it makes), and the
   presentation that WAS live sat in 132 `.style()` calls in d3_dashboard.js --
   three near-identical copies of it, one per chart, which is what the issue
   means by four experiments.

   TWO OF THOSE DEFAULTS WERE A DARK-MODE BUG, measured in a browser rather than
   inferred. SVG `fill` initialises to BLACK, not to `currentColor`, so any
   `<text>` no rule here reached was painted #000 in both themes: the QB charts'
   facet titles and the rush/rec chart's metric titles came out at 1.29:1 on the
   dark card, which is invisible. That is why `.facet-title` and `.facet-metric`
   below name a fill at all.

   THE LANGUAGE, in one line each:
     * the frame is d3's, the type is ours -- one `.axis` rule for every axis in
       every chart, at the token scale, with the numbers in the same numeric face
       the table below gives its own numbers;
     * a bar is `.bar` and is built in exactly one place in the JavaScript
       (`paintBars`), so it cannot ship without a fill, a key, a tab stop and a
       label;
     * everything structural -- the scrolling row of team groups, a team's
       banner, a facet row, the rush/rec label column -- is a class here, and the
       JavaScript sets no size, weight, spacing or structural colour of its own.

   WHAT STAYS INLINE IN THE JAVASCRIPT, deliberately: a colour that arrives on
   the payload (`TeamColor`, `TeamTextColor`, `DefTeamColor`) and a length
   measured from the container at render time. Neither can be written down here.

   NO GRIDLINES, and that is a decision rather than an omission. #38 names
   "gridline" in the shared treatment it asks for, and `.gridline` was one of the
   four dead rules. It is not being switched on: every bar in all three charts
   already prints its own value beside it -- `bar_label` on the two QB charts,
   the metric itself on the rush/rec facets -- and a team's facets share one
   baseline and one synced scale, so heights compare across the row without help.
   A gridline would be a second way to read a number that is already written
   down. `--color-gridline` had no other reader and went with the rule. */

/* --- The frame ---------------------------------------------------------- */
.axis path,
.axis line {
    fill: none;
    stroke: var(--color-text);
    shape-rendering: crispEdges;
}

/* Tick labels are numbers, and this app decided in #38 that numbers are set in
   the mono face with tabular figures -- see `.styled-table td` below, which is
   where that decision already lived. The charts are the half that never got it,
   because these two rules reached nothing. */
.axis text {
    font-size: var(--text-xs);
    fill: var(--color-text);
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* Zero, where a chart has values on both sides of it. Deliberately the text
   colour and not the frame's: the axis says where the scale is, this says which
   side of nothing a bar is on. */
.zero-line {
    stroke: var(--color-text);
    stroke-width: 0.5;
    shape-rendering: crispEdges;
}

/* --- Type inside a chart ------------------------------------------------ */
/* The subject of a facet: a player, on the QB charts. */
.facet-title {
    font-size: var(--text-lg);
    font-weight: bold;
    text-anchor: middle;
    fill: var(--color-text);
}

/* The column of a facet: a metric, on the rush/rec chart. One step quieter than
   `.facet-title`, because the subject there is the row label beside it. */
.facet-metric {
    font-size: var(--text-sm);
    text-anchor: start;
    fill: var(--color-text);
}

/* The rotated axis label, drawn once per chart rather than once per facet. */
.axis-title {
    font-size: var(--text-sm);
    font-weight: bold;
    text-anchor: middle;
    fill: var(--color-text);
}

.bar-label {
    font-size: var(--text-xxs);
    text-anchor: start;
    fill: var(--color-text);
}

/* A bar is a tab stop since #38: the opponent is encoded as the bar's FILL and
   nowhere else, so before this it was the one column of information on the
   page that a reader without a pointer could not get to at all.
   THE RING IS DRAWN TWICE ON PURPOSE. `outline` on an SVG element is honoured
   by current browsers and is the newest of the three properties here (Safari
   only from 16.4), so it is backed by a `stroke`, which every SVG renderer has
   understood since the beginning. Both name the same token, so they cannot
   disagree; the offset is 1px rather than 2 because adjacent bars in these
   charts are a band-gap apart. */
.bar:focus-visible {
    outline-offset: 1px;
    stroke: var(--color-focus-ring);
    stroke-width: 2px;
}

/* --- Chart layout -------------------------------------------------------
   The three charts are small multiples in a horizontally scrolling card, and
   every one of these rules was three copies of the same `.style()` chain in
   d3_dashboard.js before #38's second half. */

/* A row of team groups that scrolls sideways rather than wrapping. The 30px
   gutter is the only thing separating one team's players from the next team's
   and is deliberately larger than any `--space` rung, the same way tokens.css
   leaves the footer's 40px margin a literal where it occurs. */
.chart-scroll-row {
    display: flex;
    flex-wrap: nowrap;
    gap: 30px;
}

.team-group {
    flex-shrink: 0;
}

/* The team's banner over its facets. The two colours are the payload's and stay
   inline in the renderer; everything else is here. */
.team-title {
    margin: var(--space-3) 0;
    padding: var(--space-3);
    border-radius: var(--radius-sm);
    text-align: center;
    font-size: var(--text-xl);
    font-weight: bold;
}

.facet-row {
    display: flex;
    flex-wrap: nowrap;
}

/* Every chart svg. `flex-shrink: 0` because a facet's width is computed from
   its own bar count and must not be renegotiated by the flex row it sits in. */
.facet {
    flex-shrink: 0;
}

/* --- #65's trend sparkline -----------------------------------------------
   A chart inside a table cell. The bars come from the shared painter and the
   zero rule from `.zero-line` above, so all this owns is the cell: no number in
   it, so it is centred rather than set in the table's tabular figures, and the
   row is the history of the numbers above rather than another one of them. */
.styled-table td.trend-cell {
    text-align: center;
    vertical-align: middle;
    padding: var(--space-1) var(--space-2);
}
.styled-table td.trend-cell .facet {
    display: block;
    margin: 0 auto;
}

/* --- The rush/rec chart's two columns ------------------------------------
   A fixed label column that does not scroll, beside a facet column that does,
   so the player's name stays put while their four metrics scroll. Same idea as
   the table's sticky first cell below, and the only chart that measures its own
   container -- which is why the label column's WIDTH is still set in the
   renderer: it is a number the facet arithmetic also reads. */
.player-chart {
    display: flex;
}

.player-labels {
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
}

.player-label {
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: bold;
}

.player-scroller {
    overflow-x: auto;
    flex-grow: 1;
}

.player-rows {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
    min-width: min-content;
}

.metric-row {
    display: flex;
    flex-wrap: nowrap;
    gap: var(--space-3);
}

/* --- The floating tooltip ------------------------------------------------
   It was a 10-declaration `style="..."` attribute on a `<div id="tooltip">`,
   copied into BOTH page templates and kept in step by hand, while the script
   that owns it toggled `display` from JavaScript. One rule, one class, and the
   script now says `.classed('is-visible', ...)` -- which is also what makes the
   hidden state legible in the DOM.
   `pointer-events: none` is load-bearing and not decoration: the box follows the
   pointer, and without it the tooltip is under the cursor and takes the
   `mouseout` that would have closed it. */
.chart-tooltip {
    position: absolute;
    display: none;
    z-index: 1001;
    max-width: min(80vw, 22em);
    padding: var(--space-5);
    border-radius: var(--radius-sm);
    background: var(--color-tooltip-surface);
    color: var(--color-tooltip-text);
    font-size: var(--text-sm);
    pointer-events: none;
}

.chart-tooltip.is-visible {
    display: block;
}
 /* Table styles */
.styled-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: var(--space-8);
    font-size: var(--text-xs);
}
.styled-table th, .styled-table td {
    border: 1px solid var(--color-border);
    padding: var(--space-3);
    text-align: center;
}
.styled-table th {
    background-color: var(--color-accent);
    color: var(--color-on-accent);
    font-size: var(--text-sm);
}
.styled-table td {
    background-color: var(--color-surface-tint);
    /* Numbers that do not line up are not a table. This app had no monospace
       face at all before #38. The body cells are numeric everywhere except the
       identity columns, which opt back out immediately below. */
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}
.styled-table td:first-child,
.styled-table td[colspan],
.styled-table td.matchup-cell,
.styled-table td.result-cell,
.styled-table .team-cell {
    font-family: var(--font-sans);
}
/* --- Cell modifiers (#38) ------------------------------------------------
   The four table renderers each set these inline, in `.style()` calls that were
   identical apart from which cell they were on: a muted row label, a subtle
   separator, a bold total, a section band across the table. Each is `(0,2,1)`
   and so beats `.styled-table td` above wherever they disagree, whatever order
   the stylesheets load in. */
.styled-table td.muted-cell {
    color: var(--color-text-muted);
}

.styled-table td.subtle-cell {
    color: var(--color-text-subtle);
}

.styled-table td.strong-cell {
    font-weight: bold;
}

/* The band that names a section of a metric table -- "Offense", "Defense
   (allowed)", "Tendency", or a position group on the injury report. Always a
   single `td[colspan]`, which is also why it keeps the sans face. */
.styled-table td.section-header {
    background-color: var(--color-surface-muted);
    font-weight: bold;
}

/* --- The player usage table (#34) ----------------------------------------
   Not a second table: this is `.styled-table` with three modifiers and a header
   band, the same way the injury report is `.styled-table` with a section header.
   Nothing below sets a size, a colour or a spacing that is not a token.

   THE BAND ROW IS THE ONE STRUCTURAL IDEA HERE, and it encodes something true
   rather than decorating the header: a band names the DENOMINATOR the columns
   under it divide up. The five columns under "Receiving" share out the team's
   targets, the two under "Rushing" share out its carries, the two under "Red
   zone" share out its touches there. That is also the whole argument for why
   nothing on this table is ranked -- each band sums to one inside a team
   however well the team plays, so "top target share" is a fact about a pie. */
.styled-table.usage-table th.band {
    /* Quieter than the column labels under it: it is a grouping, not a heading.
       STATIC, and that is load-bearing rather than tidy: `.styled-table
       th:first-child` is sticky, and two sticky header cells of different widths
       slide over each other when the table scrolls sideways. The label row keeps
       the sticky cell, which is the row that names the columns. */
    position: static;
    font-family: var(--font-sans);
    font-size: var(--text-xxs);
    font-weight: normal;
}

/* THE THIN SAMPLE, ON THE ROW IT DESCRIBES. A target share over two games and
   one over eight are not the same number and must not read as one, so the row
   is banded and the sticky player cell carries the count in words.
   `--color-caution-surface` is the token this app already uses for "not routine
   news, and not a fault" -- the slate's backtest verdict wears it too. It is
   NOT a ranking and not a warning about the player: a thin sample is a smaller
   denominator, and the direction rule on this endpoint is absolute.
   (0,3,1), so it beats `.styled-table td`'s own background whatever order the
   two stylesheets load in. */
.styled-table.usage-table tr.thin-sample td {
    background-color: var(--color-caution-surface);
}

/* AN UNRESOLVED SNAP SHARE. Never 0%, which would be a claim that the player
   was not on the field, and never a bare dash, which reads as one. It says
   which of the two absences it is -- `unmatched` for a player the
   pfr_player_id crosswalk missed, `no mirror` for a season nobody has captured
   a snap_counts release for -- and the payload's own sentence is the title.
   In the sans face because it is words in a column of numbers. */
.styled-table.usage-table td.unresolved-cell {
    font-family: var(--font-sans);
    font-style: italic;
    color: var(--color-caution-text);
}

/* The per-game opportunity log: targets plus carries, oldest first. The raw
   material a hit rate would be computed FROM -- there is no prop line in this
   repository to grade it against, and the footnote under the panel says so. */
.styled-table.usage-table td.usage-log {
    white-space: nowrap;
    text-align: left;
}

/* Each side scrolls on its own. `.chart-container` scrolls the whole card,
   which is the wrong unit here: it would slide the OTHER team's table out of
   the way to reach this one's last column. It is also what the sticky first
   cell needs -- sticky is relative to the nearest scrolling ancestor, so
   without this the player's name is pinned to a box that never moves. */
.usage-scroller {
    overflow-x: auto;
}

/* The identity column. Left-aligned because it is a name, and wide enough that
   the name and the thin-sample badge under it do not wrap to three lines. */
.styled-table.usage-table th:first-child,
.styled-table.usage-table td:first-child {
    min-width: 9em;
    text-align: left;
}

/* The blank band between two teams' blocks of rows in the combined summary. */
.styled-table tr.team-separator td {
    height: var(--space-5);
    padding: 0;
    background-color: var(--color-surface);
}

/* The combined summary fakes a merged header cell by giving the first of a run
   a `colspan` and hiding the rest. The renderer's own comment calls that a
   visual approximation and it still is; what changed is that the approximation
   is now written down here rather than as a `display: none` from JavaScript. */
.styled-table th.duplicate-header {
    display: none;
}

/* Load-bearing `!important`. `.win-highlight` is (0,1,0) and loses to
   `.styled-table td` (0,1,1) without it -- drop it and winner highlighting
   stops silently while the payload goes on saying which cell should win. */
.win-highlight {
    background-color: var(--color-win) !important; /* the winning cell */
}
/* NO `color` here. It said `white`, and white failed WCAG AA on 7 of the 32
   team colours -- 1.76:1 on Pittsburgh's gold, 1.85:1 on New Orleans'. The
   foreground is now computed from each team colour's own relative luminance in
   views.py and arrives on the payload as `TeamTextColor`, which the two
   renderers set inline beside the background they already set inline (#38). A
   colour here would fight them and win on some cells and not others. */
.styled-table .team-cell {
    font-weight: bold;
}
/* The sticky row label (#40).
   Both wide tables -- the 16-column matchup summary and the 15-column league
   table -- sit in a `.chart-container` that scrolls horizontally, and scrolling
   right took away the only cell that said which row you were reading. The
   numbers were still there and no longer belonged to anybody.
   Anchored on POSITION, not on `.team-cell`. The first column is the identity
   column in every table the site draws -- Team on the summary and the league
   table, the metric name on the efficiency and tendency tables, the matchup on
   the slate -- whereas `.team-cell` is a first-column `td` in two of those and
   a HEADER column in the other two, where making it sticky would pin the wrong
   thing. The separator row opts out: it is one full-width `td[colspan]`, and a
   sticky cell as wide as the table would slide across the whole of it.
   NO BREAKPOINT. These tables overflow at 1400px as well, and there is no width
   at which losing the row label is what a reader wanted.
   The backgrounds are already opaque -- `.styled-table td` and `.styled-table
   th` above, and over them the team's own colour, set inline by both renderers
   -- so the scrolled cells cannot show through. The border does not survive
   `border-collapse: collapse` on a sticky cell in every browser, so the right
   edge is drawn as a shadow that does not depend on it. */
.styled-table th:first-child,
.styled-table td:first-child:not([colspan]) {
    position: sticky;
    left: 0;
    z-index: 1;
    box-shadow: 1px 0 0 0 var(--color-border);
}
.styled-table th:first-child {
    z-index: 2;
}