/* views.css — view-specific layout only, design tokens only, one block per view.
   The app shell (S26) needs nothing view-specific: the home placeholder and the not-found page use
   design-system classes only. View steps add their blocks below this line. */

/* ---- overlays: click-to-close scrim, "more" menu sheet (S29) --------------
   components.css owns .sheet, .sheet__*, .seg and .toast; this block only adds what the shared
   overlay API needs and the design system does not define: a backdrop that closes the sheet,
   a 44 px link list inside it, and a wider drawer on desktop. */
.sheet-scrim { position: fixed; inset: 0; z-index: 55; background: var(--overlay); }
/* A phone bottom sheet must darken the page behind it; a desktop drawer must not — the reference
   map keeps the map at full brightness next to the open station drawer (reference/design-map-
   desktop.png), and a dimmed map cannot be read while its own card is open. The scrim stays
   click-to-close at every width. */
@media (min-width: 1024px) { .sheet-scrim { background: transparent; } }
.sheet-menu { display: grid; gap: var(--s-1); }
.sheet-menu a {
  display: flex; align-items: center; gap: var(--s-3);
  min-height: 44px; padding: 0 var(--s-3); border-radius: var(--r-md);
  color: var(--text-2); font-size: var(--fs-sm); font-weight: 550;
}
.sheet-menu a:hover { background: var(--surface-2); color: var(--text); }
.sheet-menu .icon { color: var(--text-muted); }
/* align-items: the sheet body is a .stack (stretch), and a stretched .seg leaves an empty pill track */
.sheet-menu__theme { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-2); }
@media (min-width: 1024px) { .sheet--wide { width: min(560px, calc(100vw - 2 * var(--s-4))); } }

/* ---- station (S30): the "Wiecej" pollutant menu ---------------------------
   .popover (components.css) draws the frame; only the tab-list layout is view-specific. */
.station-more-pop { display: grid; gap: var(--s-1); }
.station-more-pop .tab { justify-content: flex-start; text-align: left; }

/* ---- station monthly card (S32): range seg + anomalies switch -------------
   At phone width the action group wraps under the title instead of crushing
   it; components.css owns .card__head, this only re-enables wrapping here. */
#monthly .card__head { flex-wrap: wrap; }
#monthly .card__actions { flex-wrap: wrap; justify-content: flex-end; }

/* ---- station hour x month card (S35): the tall portrait heat -------------
   charts.css sizes a heat card at 180-220px because its rows are 7 weekdays. Here the phone card
   carries 24 hour rows (charts/heat.js transposes the grid under the breakpoint), so it needs the
   height the design system reserves for a vertical grid: ~20px per hour row plus the month axis
   and the class scale under it. Desktop keeps charts.css .chart--lg (420px): 24 columns across. */
#heatmap .chart.chart--portrait { height: 560px; }

/* ---- station extremes card (S38): four columns in 390 px --------------------
   Long dates plus a "Klasa" column: the shared .table (components.css) keeps `white-space: nowrap`
   and 28 px padding per cell, so a 390 px card scrolls the last column out of view. Here the dates
   may wrap onto a second line and the padding tightens - all four columns then fit the phone and
   the table never needs horizontal scrolling (the class colour is the point of the last column). */
#episodes .table th, #episodes .table td { padding: var(--s-2); }
#episodes .table td:nth-child(2) { white-space: normal; min-width: 100px; }
#episodes .table th:last-child, #episodes .table td:last-child { width: 1%; }

/* ---- station data quality card (S39): the download control on a phone -------------
   SPEC Mobile: touch targets >= 40 px. The design-system .btn--sm (32 px) is desktop density; on
   a phone the download pill is the only control in this card, so only it is enlarged here (the
   frame stays in components.css, this is a view-specific delta). */
@media (max-width: 767.98px) {
  #quality .card__actions .btn { --btn-h: 44px; padding: 0 var(--s-4); font-size: var(--fs-sm); }
}

/* ---- home KPI card (S47): a station name is not a truncatable label ----------
   The "cleanest station" card ends in a link to that station. `.ellipsis` is the design system's
   one-line utility; a name cut as "Borsukowizna, Szkółka Le…" is a claim a reader cannot verify,
   and on the phone the KPI grid is two columns wide. Let the foot wrap instead. */
.kpi__foot .link { white-space: normal; overflow-wrap: break-word; }

/* ---- map controls (S41): the phone pill bar is one row ------------------------
   map.css turns .map-panel--controls into a sideways-scrolling pill bar below 1024 px, but the
   view groups its controls inside [data-controls], which is a plain block: its rows stacked and
   the panel became a ~190 px tall card covering the top third of the map (reference/design-map-
   mobile.png shows a bar one control high). The group lays out sideways here; the panel's own
   overflow-x keeps every control reachable by a swipe. Desktop is untouched. */
@media (max-width: 1023.98px) {
  .map-panel--controls [data-controls] { display: flex; align-items: center; gap: var(--s-2); flex: 0 0 auto; }
  .map-panel--controls [data-controls] > * { flex: 0 0 auto; }
}

/* ---- map year player (S42): ticks at their true positions ------------------
   .player, .slider and .player__ticks come from components.css. The prototype placed its ticks
   with a fixed padding that only fit its demo range; the real range differs per pollutant, so
   syncPlayer lays a scale row exactly over the track and each tick sits at its own percentage
   of that scale (SPEC: ticks every 5 years, plus the two range ends). The second line of the
   value label names the year the record only partly covers; the trend measure has no year to
   play, so its controls sit dimmed and disabled. */
.map-panel--bottom [data-player] { display: flex; flex-direction: column; gap: var(--s-1); }
.map-panel--bottom .player__ticks > div { position: relative; height: 1.4em; } /* syncPlayer sets left + width = the slider's box */
.map-panel--bottom .player__ticks span { position: absolute; transform: translateX(-50%); white-space: nowrap; }
.map-panel--bottom .player__part { display: block; white-space: nowrap; font-size: var(--fs-2xs); font-weight: 500; letter-spacing: 0; color: var(--text-faint); }
.map-panel--bottom .player__btn[disabled], .map-panel--bottom .slider[disabled] { opacity: .45; }

/* ---- map station sheet (S43): colour only a proven trend -----------------------
   `.sheet`, `.sheet__handle` / `.sheet__head`, `.stats-inline` and `.stat-inline*` are the design
   system (components.css) and the hover card is in map.css, so the sheet needs nothing here but
   the two claims the reference makes on the trend number itself (reference/design-map-*.png). An
   insignificant trend gets no colour at all — the same rule that draws its dot as a hollow ring. */
.map-sheet-good { color: var(--ok); }
.map-sheet-bad { color: var(--bad); }

/* The sheet's close button is the design system's dense 32 px icon (`.btn--sm`), the same control
   the prototype shows. SPEC Mobile wants >= 40 px for anything a thumb must hit: on a phone the X
   is the only way out of a full-width bottom sheet besides Escape, so it grows here. */
@media (max-width: 767.98px) {
  .sheet__head [data-close] { --btn-h: 44px; }
}

/* ---- map voivodeship layer + station list (S44) ------------------------------
   The regional fill needs no new element: `map/style.js` already draws `voiv-fill` as the land
   colour, and views/map.js recolours it through feature-state (SPEC "View: Map"). What the view
   owns here is the ONE number that keeps the fill a background instead of a second map — a fill
   that opaque would swallow the station dots it is supposed to summarise (ACCEPT: the fill never
   hides the dots) — and the frame of the "Lista stacji" alternative: the controls panel's frame
   (.map-panel, map.css) at the controls' width under the controls on desktop, the design-system
   bottom sheet / drawer (.sheet, components.css) on a phone, where the pill bar has no room for a
   table. `.table-wrap` (components.css) does the scrolling in both. */
/* READ-ONLY custom property (no CSS consumer): views/map.js asks `cssVar` for it, because a fill as
   opaque as the land would swallow the station dots it exists to summarise (ACCEPT: the fill never
   hides the dots) and one number must decide that, not two. */
:root { --voiv-fill-opacity: 0.42; }

.map-panel--list { top: var(--s-4); left: var(--s-4); width: 340px; max-height: calc(100dvh - 32px);
  padding: 0; z-index: 6; display: grid; grid-template-rows: auto minmax(0, 1fr); }
.map-panel--list .map-panel__title { padding: var(--s-3) var(--s-4); }
.map-panel--list .map-panel__title strong { font-size: var(--fs-lg); font-weight: 600; letter-spacing: -0.02em; }
/* the panel itself does not scroll — the table does, so the header keeps its sticky rule */
.map-panel--list .table-wrap { border: 0; border-radius: 0; background: none; max-height: none; }
.map-panel--list .table th, .map-panel--list .table td { padding: var(--s-2) var(--s-4); }
.map-panel--list .table td.num small { color: var(--text-faint); }
/* a row's name is a button, and it must read as the row's text, not as an extra control */
.table__open { all: unset; cursor: pointer; color: inherit; font: inherit; }

@media (max-width: 1023.98px) {
  /* the phone has no room for a floating table over the map: the list becomes the bottom sheet the
     design system already draws (`.sheet`, components.css — the same frame the station sheet uses),
     above the controls bar and below the station sheet it can open. A wide table may not push the
     page sideways, so its columns stop truncating and scroll inside instead. */
  .map-panel--list { top: auto; left: 0; right: 0; bottom: 0; width: auto; max-height: 72dvh;
    border-radius: var(--r-lg) var(--r-lg) 0 0; z-index: 8; }
  .map-panel--list .table th, .map-panel--list .table td { padding: var(--s-2); }
  .map-panel--list .table th:not(.num), .map-panel--list .table td:not(.num) { white-space: normal; }
  .map-panel--list [data-list-count] { display: none; }
}

@media (max-width: 767.98px) {
  .map-panel--list [data-close-list] { --btn-h: 44px; } /* a 32 px X is hard to hit over a sheet */
}

/* --------------------------------------------------------------------------- home: trend + mini map row
   The prototype puts the national trend and the small map in ONE 12-column row (8 + 4): the page reads
   as one argument — the trend, then where it is worst. The markup inside each column is the card
   component's own frame (ui/card.js), and `.span-8` / `.span-4` set a grid SPAN, which must not land on
   `.card` (its own size comes from its parent's grid): the span lives on this wrapper and the card fills
   it. `flex: 1 1 auto` is what makes the short card match the tall one, so the row ends level. */
.home-card { display: flex; flex-direction: column; }
.home-card > .card { flex: 1 1 auto; width: 100%; }


/* --------------------------------------------------------------------------- trends: KPI strip (S48)
   The strip is four tiles (grid--kpi). The fifth one exists only for O3, where the headline metric is
   the count of days over the target: a fifth tile would wrap into the first column of the four-column
   grid and leave a half-empty row, so it gets its OWN full-width row (span-12) that reads as a strip —
   label, number and footnote side by side, in the order the reader needs them. */
.kpi--wide { grid-column: 1 / -1; }
@media (min-width: 768px) {
  .kpi--wide { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; }
  .kpi--wide .kpi__value { justify-content: center; }
  .kpi--wide .kpi__foot { justify-content: flex-end; }
}

/* --------------------------------------------------------------------------- trends: breakdown (S49)
   Categories: the cards are a family of small multiples, and the point of putting them side by side is
   that their slopes are comparable (charts/national.js draws them all on one shared vertical scale).
   Two per row on a phone — at one per row the reader compares cards on two screens and the comparison
   is gone; three from 1024 px where a card is wide enough to hold the axis. `chart--sm` (220 px) is
   the design-system height for a wide card; at ~170 px the phone card would be a tall tower, so only
   there the box is lowered to a height that still separates the categories' levels. */
.trend-cats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 1024px) { .trend-cats { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 767.98px) { .trend-cat .chart { --chart-h: 150px; } }

/* Regions: 16 rows whose job is a column of numbers you can read downward. A `.table` (components.css)
   keeps every cell on one line and pads 28 px, which at 390 px pushes the region name out of view —
   ACCEPT says the rows must not be truncated, so this is a list with its own grid: name and numbers on
   the first line, the sparkline full width under them (a 70 px sparkline across 16 years says nothing).
   From 768 px the same grid puts the sparkline in its own column instead, which is where the row reads
   as a row. No colour on the sparkline: the direction of the change is already stated by the `.delta`
   chip in the same row, and two colour systems for one claim is one too many.
   The list owns the column tracks and every row is a subgrid of them: with a grid per row, each row
   sized its own `auto` columns, so every sparkline started where its region's name happened to end and
   the numbers did not line up into a column. */
.voiv { list-style: none; margin: 0; padding: 0; display: grid; column-gap: var(--s-3);
  grid-template-columns: minmax(0, 1fr) auto auto; }
.voiv__row { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; gap: var(--s-1) var(--s-3);
  align-items: baseline; padding: var(--s-3) 0; border-bottom: 1px solid var(--border);
  grid-template-areas: "id val delta" "spark spark spark"; }
.voiv__row:last-child { border-bottom: 0; }
.voiv__id { grid-area: id; display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-2); min-width: 0; }
.voiv__name { font-weight: 550; }
.voiv__n { font-size: var(--fs-2xs); color: var(--text-faint); }
.voiv__spark { grid-area: spark; width: 100%; height: 26px; }
.voiv__spark path { fill: none; stroke: var(--text-faint); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.voiv__gap { grid-area: spark; font-size: var(--fs-2xs); color: var(--text-faint); }
.voiv__val { grid-area: val; display: flex; align-items: baseline; justify-content: flex-end; gap: var(--s-2); }
.voiv__value { font-size: var(--fs-md); font-weight: 600; }
.voiv__year { font-size: var(--fs-2xs); color: var(--text-faint); }
.voiv__delta { grid-area: delta; display: flex; justify-content: flex-end; }
@media (min-width: 768px) {
  /* From 768 px the sparkline is the widest column and the spare width is split BETWEEN the columns:
     a 160 px spark glued to the numbers would leave 700 px of nothing after the name, and a 16-year
     line that thin says nothing about its shape. */
  .voiv { justify-content: space-between; grid-template-columns: auto minmax(200px, 520px) auto auto; }
  .voiv__row { grid-template-areas: "id spark val delta"; }
}

/* The season card's answer is a computed ratio, not a caption: it is the card's sub, so it must read as
   the claim it is while the sentences around it stay quiet. */
.season-ratio { color: var(--text); font-weight: 600; }

/* ---- Ranking (S51 + S52): a real table, and a card list below 640 px ----------------
   On wide screens this IS the prototype's table component (components.css `.table` /
   `.table-wrap` / `.bar-cell` / `.th-sort`): rank, name, region, category, the value with a
   bar whose width is relative to the maximum of the shown rows, trend, method. Our own work
   is only the controls row and the honesty colours of the trend/method cells. Below 640 px
   the view renders `ol.rank-list` / `li.rank-card` INSTEAD of the table (SPEC "View:
   Ranking": no horizontal scrolling on a phone; the JS picks the DOM, see views/ranking.js),
   and the controls card collapses behind a "Filtry" button. From 640 to 768 px the table is
   real markup but a row stops being a table row and becomes card-like: rank + name + trend on
   the first line, the bar with the value at its end on the next (a narrow bar you cannot
   compare is decoration), region / category / method as small print. */
.ranking-controls { display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-4); align-items: end; }
.ranking-controls .field { flex: 0 1 200px; align-items: flex-start; }
  /* selects fill their field (their own width: 100%). A segment must instead keep its INTRINSIC
     width (GOTCHAS S41): a field clamped to 200 px shows "Średnia roczna · Dni > 50" and hides
     the other two metric buttons behind an invisible horizontal scroll inside the pill - the
     screenshot looks tidy, only `seg.scrollWidth > seg.clientWidth` says the labels are cut.
     On a phone the field is full width anyway and the segment keeps its own scrolling row there. */
@media (min-width: 768px) { .ranking-controls .field:has(.seg) { flex: 0 0 auto; } }
.ranking-controls .field:has([data-q]) { flex: 1 1 220px; } /* the search box takes the spare row */
.ranking-controls .chips { flex-basis: 100%; flex-wrap: wrap; }
/* an unproven trend is printed without colour; a missing one is the dash with its reason */
.ranking-trend-none { color: var(--text-faint); }
.ranking-trend-notsig { color: var(--text-muted); }
.ranking-trend-good { color: var(--ok); font-weight: 600; }
.ranking-trend-bad { color: var(--bad); font-weight: 600; }
.ranking-partial { color: var(--warn); font-weight: 600; }
.ranking-foot { margin: var(--s-3) 2px 0; font-size: var(--fs-2xs); color: var(--text-faint); line-height: 1.5; }
/* the value cell: the bar sits on a track that ENDS at the number instead of running under it.
   The layout lives on inner spans - the td itself stays a plain table cell (components.css).
   Desktop keeps the table's own text weight; only the phone card emphasises the value. */
#rankingTable .bar-cell__inner { display: flex; align-items: center; gap: var(--s-2); }
#rankingTable .bar-cell__track { position: relative; flex: 1 1 auto; min-width: 0; height: 8px; }
#rankingTable .bar-cell__bar { position: absolute; left: 0; top: 0; height: 100%; width: 0;
  border-radius: 4px; background: var(--swatch, var(--accent)); opacity: .85; }
#rankingTable .bar-cell__val { position: static; display: inline-block; text-align: right; }
@media (max-width: 767.98px) {
  .ranking-controls { gap: var(--s-3); }
  .ranking-controls .field { flex: 1 1 100%; }
  #rankingTable .table-wrap { max-height: none; }
  #rankingTable .table thead { display: none; }
  #rankingTable .table tbody tr { display: grid; align-items: baseline; padding: var(--s-3) var(--s-2);
    border-bottom: 1px solid var(--divider); gap: var(--s-1) var(--s-3);
    grid-template-columns: auto 1fr auto; grid-template-areas: "rank name trend" "value value value" "voiv cat method"; }
  #rankingTable .table td { padding: 0; border: 0; white-space: normal; }
  #rankingTable .table td[data-c="rank"] { grid-area: rank; }
  #rankingTable .table td[data-c="name"] { grid-area: name; font-weight: 600; }
  #rankingTable .table td[data-c="trend"] { grid-area: trend; text-align: right; }
  #rankingTable .table td[data-c="value"] { grid-area: value; }
  #rankingTable .table td[data-c="voiv"] { grid-area: voiv; }
  #rankingTable .table td[data-c="cat"] { grid-area: cat; }
  #rankingTable .table td[data-c="method"] { grid-area: method; text-align: right; }
  #rankingTable .table td[data-c="voiv"], #rankingTable .table td[data-c="cat"],
  #rankingTable .table td[data-c="method"] { font-size: var(--fs-2xs); color: var(--text-faint); }
  #rankingTable .bar-cell__val { font-size: var(--fs-md); font-weight: 600; }
  #rankingTable .table tbody tr:last-child td { border-bottom: 0; }
}
/* ---- the phone card list (S52) ------------------------------------------------------
   The same rows as the table, thumb-sized (a card is ~72 px tall: rank + name + trend, the
   value bar full width, region / category as small print). The bar/number layout is inherited
   from the `#rankingTable .bar-cell__*` rules above - the list sits in the very same card,
   so a value reads identically in both layouts and names wrap instead of truncating. */
.rank-list { list-style: none; margin: 0; padding: 0; }
.rank-card { display: grid; align-items: baseline; gap: var(--s-1) var(--s-3);
  padding: var(--s-3) var(--s-2); border-bottom: 1px solid var(--divider);
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas: "rank name trend" "value value value" "meta meta meta"; }
.rank-card:last-child { border-bottom: 0; }
.rank-card__rank { grid-area: rank; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.rank-card__name { grid-area: name; font-weight: 600; overflow-wrap: break-word; }
.rank-card__trend { grid-area: trend; text-align: right; }
.rank-card__value { grid-area: value; }
.rank-card__meta { grid-area: meta; font-size: var(--fs-2xs); color: var(--text-faint); }
/* the filter toggle sits at the top of the controls card; the panel it opens carries [hidden],
   so its collapsed state needs no layout rule of its own. */
.ranking-filters { margin-bottom: var(--s-2); }

/* ---- Episodes (S53): four full-width cards and one table --------------------------
   Each card takes the whole content width on purpose. The long-term chart is the first card of
   the view ("the episodes became rarer" is what the page exists for), and 365 daily bars are not
   readable in a half column. Below 720 px a chart scrolls INSIDE its own .chart-scroll band
   (charts.css gives it a min-width there): the page itself never scrolls sideways and the canvas
   still has a real width - the same trick the station's own calendar uses, so the calendar of a
   year stays a 53-week horizontal grid on a phone. A vertical calendar needs a near-square card;
   in a 390px one its cells become unreadable strips (views/station/calendar.js).
   The episode table is one markup for every width: below 768 px a row stops being a table row and
   becomes two grid lines - the dates with the peak share on the first, the station and its value on
   the second - with the column headers hidden, exactly as the ranking's rows do. The episode's
   length is the one cell a phone drops, because the two dates on the same line already state it;
   the peak share and the station are not repeated anywhere else. */
.episodes-controls { display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-4); align-items: end; }
.episodes-controls .field { flex: 0 1 200px; align-items: flex-start; }

/* The peak bar is measured on the axis of the whole view (0–100 % of the reporting stations), so
   its width needs no normalising, and its colour is the class the legend prints (--swatch is set
   inline from the very bins the calendar and the daily bars use). Same geometry as the ranking's
   value bar, scoped to this card, but the bar ENDS at the number instead of running under it: here
   the percentage is what a reader compares, and a bar behind it would only decorate. */
#episodesTable .bar-cell__inner { display: flex; align-items: center; gap: var(--s-3); }
#episodesTable .bar-cell__track { position: relative; flex: 1 1 110px; min-width: 64px; height: 8px;
  border-radius: 4px; background: var(--surface-3); }
#episodesTable .bar-cell__bar { position: absolute; left: 0; top: 0; height: 100%; width: 0;
  border-radius: 4px; background: var(--swatch, var(--accent)); opacity: .85; }
#episodesTable .bar-cell__val { display: inline-block; white-space: nowrap; text-align: right;
  font-variant-numeric: tabular-nums; }
#episodesTable td[data-c="when"] { white-space: nowrap; }
#episodesTable .episodes-peak { display: block; font-size: var(--fs-2xs); color: var(--text-faint); }
#episodesTable td[data-c="top"] { white-space: normal; min-width: 210px; }
#episodesTable td[data-c="top"] a { display: inline-block; margin-bottom: var(--s-1); }
#episodesTable td[data-c="top"] .bar-cell__track { flex-basis: 80px; }
.episodes-foot { margin: var(--s-3) 2px 0; font-size: var(--fs-2xs); color: var(--text-faint); line-height: 1.5; }

/* Episode day player (S54): the card reuses the map player's controls (.player, .player__btn,
 * .player__ticks, .slider — components.css) and the mini-map frame (.mini-map — map.css); only
 * the type differs — this player names a DATE, not a year. It is not a `.map-panel--bottom`, so
 * the two rules views.css states there are restated for `#epPlayer`: the kind label under the
 * date, and the dimmed disabled controls while the year product loads (views/episodes/player.js). */
#epPlayer .player__part { display: block; white-space: nowrap; font-size: var(--fs-2xs);
  font-weight: 500; letter-spacing: 0; color: var(--text-faint); }
#epPlayer .player__btn[disabled], #epPlayer .slider[disabled] { opacity: .45; }

@media (max-width: 767.98px) {
  .episodes-controls { gap: var(--s-3); }
  .episodes-controls .field { flex: 1 1 100%; }
  #episodesTable .table-wrap { max-height: none; overflow: visible; }
  #episodesTable .table thead { display: none; }
  #episodesTable .table tbody tr { display: grid; align-items: baseline; padding: var(--s-3) var(--s-2);
    border-bottom: 1px solid var(--divider); gap: var(--s-1) var(--s-3);
    grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "when peak" "top top"; }
  #episodesTable .table td { padding: 0; border: 0; white-space: normal; }
  #episodesTable .table td[data-c="when"] { grid-area: when; }
  #episodesTable .table td[data-c="peak"] { grid-area: peak; }
  #episodesTable .table td[data-c="top"] { grid-area: top; min-width: 0; }
  #episodesTable .table td[data-c="top"] a { display: block; margin-bottom: var(--s-2); font-weight: 600; }
  #episodesTable .table td[data-c="days"] { display: none; }
  #episodesTable .table tbody tr:last-child td { border-bottom: 0; }
}


/* ---- runner (S56) ---- */
/* Controls line: station chips left, season / level / window segments right, wrapping freely.
   The groups and their behaviour come from base.css (.row--wrap, .row--between), components.css
   (.chips, .seg) and .reco + .hourstrip; only the gaps are view-specific here so the segments
   keep their intrinsic width (GOTCHAS S41) and on a phone each group takes its own full-width
   row with the segment's own scrolling track. */
.runner-controls { gap: var(--s-3) var(--s-4); }
.runner-controls .chips { flex: 0 1 auto; }
.reco .card__foot { margin: 0 calc(-1 * var(--s-6)) calc(-1 * var(--s-6)); border-radius: 0 0 var(--r-lg) var(--r-lg); }
.reco .card__foot a { color: inherit; }

/* ---- compare (S58) ---------------------------------------------------------------------
   The chip row IS this page's control (it names what is compared), so it wraps instead of
   scrolling sideways the way a filter strip does (.chips, components.css): a station whose colour
   every chart uses, and its ✕, must stay reachable on a phone. Everything else reuses the shared
   parts (.card, .tabs, .table, .chart-legend, .ranking-trend-*); only the cell's second line (the
   year a value comes from) and the table's phone layout are view-specific. */
.cmp-chips { flex-wrap: wrap; overflow-x: visible; }
.cmp-chip { max-width: 100%; }
.cmp-chip .icon { opacity: .55; }
.cmp-chip--add { border-style: dashed; color: var(--text-muted); }
.cmp-notes { display: grid; gap: var(--s-2); }
.cmp-note { font-size: var(--fs-xs); color: var(--text-muted); }
.cmp-cell__meta { display: block; font-size: var(--fs-2xs); color: var(--text-faint); }
#cmp-table .table td[data-c="station"] { white-space: normal; }
#cmp-table .table td[data-c="station"] .swatch { margin-right: 7px; }
#cmp-table .cmp-cell__label { display: none; }
.cmp-foot { margin: var(--s-3) 2px 0; font-size: var(--fs-2xs); color: var(--text-faint); line-height: 1.5; }
@media (max-width: 767.98px) {
  /* the same rows as the ranking's phone cards: the station names its own row, every metric
     becomes a labelled line (the label is the very string the <th> uses, views/compare.js) */
  #cmp-table .table-wrap { max-height: none; }
  #cmp-table .table thead { display: none; }
  #cmp-table .table tbody tr { display: grid; gap: var(--s-2); padding: var(--s-3) var(--s-2);
    border-bottom: 1px solid var(--divider); }
  #cmp-table .table td { padding: 0; border: 0; white-space: normal; }
  #cmp-table .table td[data-c="station"] { display: block; font-weight: 600; }
  #cmp-table .table td:not([data-c="station"]) { display: flex; align-items: baseline;
    justify-content: space-between; gap: var(--s-3); text-align: left; }
  #cmp-table .cmp-cell__label { display: inline; font-size: var(--fs-2xs); color: var(--text-faint); }
  #cmp-table .table tbody tr:last-child td { border-bottom: 0; }
}

/* ---- accessibility (S60): the skip link (index.html) ---------------------------------
   WCAG 2.4.1: keyboard users get one link past the app bar into `main`, which carries
   tabindex="-1" so the fragment link can really move focus there. Parked off-screen until it
   has focus, so the layout the design specifies never changes for a pointer user. */
.skip-link { position: fixed; top: var(--s-3); left: var(--s-3); z-index: 120; padding: var(--s-2) var(--s-4);
  background: var(--text); color: var(--bg); border-radius: var(--r-pill); font-size: var(--fs-sm);
  font-weight: 600; text-decoration: none; box-shadow: var(--shadow-lg); transform: translateY(-250%); }
.skip-link:focus { transform: none; }

/* Focus ring (SPEC Accessibility): base.css draws focus as a box-shadow, but components.css loads
   later and a component's own resting shadow (.btn, .tab, .seg__btn, .player__btn, .map-panel) or an
   explicit `box-shadow: none` (.btn--ghost) has the same specificity and wins - the keyboard user
   would see nothing change. The same ring, a higher specificity, so no component can swallow it.
   [tabindex="0"] is a scroll region (the station list, a table body): landing there must be visible.
   main carries tabindex="-1" and is deliberately not in this list. */
:root :is(a[href], button, input, select, textarea, summary, [tabindex="0"]):focus-visible {
  box-shadow: 0 0 0 3px var(--accent-ring);
}
