/* =============================================================================
   UltiGameMate — app-screen components
   Used by the app shell: sidebar, tab bar, dashboard, picks, scores.
   Loaded last, after tokens.css + style.css. Mirrors design/ugm.pen.
   ============================================================================= */

/* --- shell : sidebar + content (desktop) / stacked + tab bar (mobile) ------ */
.app-shell {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  min-block-size: 100dvh;
  background: transparent; /* let the body's aurora glow through */
}

@media (min-width: 64rem) {
  .app-shell {
    grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  }
}

.app-main {
  inline-size: 100%;
  max-inline-size: var(--w-app);
  margin-inline: auto;
  padding: clamp(1.25rem, 0.8rem + 2vw, 2.5rem) var(--pad-inline)
    calc(var(--tabbar-h) + var(--space-2xl));
  display: flex;
  flex-direction: column;
  gap: var(--gap-section);
  font-family: var(--font-body);
  font-size: var(--fz-body);
  line-height: var(--lh-body);
  color: var(--text-body);
}

/* Flex items default to min-width:auto, which refuses to shrink below a wide
   descendant's intrinsic width (e.g. .twrap table's min-inline-size) — that
   pushes .app-main, .app-shell and body wider than the viewport instead of
   letting .twrap's own overflow-x:auto scroll the table locally. Real page-
   level horizontal overflow on mobile is known to break position:fixed bars
   (the tab bar), so every screen-level child must be allowed to shrink. */
.app-main > * {
  min-inline-size: 0;
}

@media (min-width: 64rem) {
  .app-main {
    /* anchor content against the sidebar instead of centring it in a void */
    margin-inline: 0;
    padding-inline: clamp(2rem, 1rem + 3vw, 4rem);
    padding-block-end: var(--space-3xl);
  }
}

/* admin screens hold wide data tables — give them more room than a normal
   app screen, but keep sparse tables from stretching edge to edge */
body[class^='admin'] .app-main {
  max-inline-size: 72rem;
}

/* admin team stats: wide For/Against tables, so this screen drops the width cap, and
   the table keeps its natural width and scrolls inside .twrap instead of
   squeezing the match column until the crests collapse */
body.admin-teams .app-main {
  max-inline-size: none;
}

.twrap .team-stats {
  min-inline-size: 64rem;
}

/* admin insights: the same uncapped screen as team stats, but its tables run
   from four columns to twelve, so each one sizes to its own content instead of
   to the 64rem floor below */
body.admin-insights .app-main {
  max-inline-size: none;
}

body.admin-insights .twrap .team-stats {
  min-inline-size: 0;
}

/* rank movement since a club's previous game, and a round's picks against
   picking blind: green for gained, red for lost */
.move,
.edge {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.move--up,
.edge--up {
  color: var(--pos);
}

.move--down,
.edge--down {
  color: var(--neg);
}

/* the scoring rule a club leans on hardest */
.twrap .team-stats td.signature {
  background: var(--pos-wash);
  color: var(--text-primary);
  font-weight: 700;
}

/* every column centred. These tables are almost all short stat counts, and the
   default mix of start-aligned labels and end-aligned numbers left the eye
   nothing to follow across a 64rem grid. Deliberately outweighs .m and .c
   (style.css) — inside .team-stats, centre wins. */
.twrap .team-stats :is(th, td) {
  white-space: nowrap;
  text-align: center;
}

/* …except the label column, which reads as text rather than as a stat */
.twrap .team-stats :is(th, td):first-child {
  text-align: start;
}

/* …and a footer label ("Total", "Per game"), which sits against the numbers
   it sums rather than stranded at the far left of a 64rem row */
.twrap .team-stats tfoot :is(th, td):first-child {
  text-align: end;
}

/* room for the longest short names ("Bournemouth") on both sides */
.team-stats td .game {
  min-inline-size: 22rem;
}

.team-stats .crest {
  max-inline-size: none;
}

/* short stat codes in table headers (GF, SOTA…): the full name pops up on
   hover, focus or tap (js/abbrTips.js moves `title` to `data-tip`) */
.twrap th abbr:is([title], [data-tip]) {
  text-decoration: underline dotted;
  text-underline-offset: 0.2em;
  cursor: help;
}

.twrap th abbr[data-tip]:focus-visible {
  outline: 2px solid var(--accent-hi);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* the popup itself: fixed to the viewport, so .twrap's scrolling can't clip it */
.abbr-tip {
  position: fixed;
  z-index: 1000;
  max-inline-size: min(16rem, calc(100vw - 16px));
  padding: var(--pad-xs) var(--pad-sm);
  border: var(--border-hairline) solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  box-shadow: var(--shadow-lg);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--fz-xs);
  line-height: 1.3;
  pointer-events: none;
}

/* paired stats: each "against" column (GA, SHA, SVA…) sits right after its
   "for" column, quieter so the team's own numbers read first */
.twrap td.against {
  color: var(--text-muted);
}

/* while another team loads, the current one stays in place, dimmed */
#team-details {
  transition: opacity 0.15s;
}

#team-details[aria-busy='true'] {
  opacity: 0.5;
  pointer-events: none;
}

/* summary above the table: stat tiles, then a "Best game" line */
#team-summary {
  display: grid;
  gap: var(--space-sm);
  margin-block-start: var(--space-lg);
}

#team-summary p {
  margin: 0;
}

/* the best game reads inline in its line, not as a full-width row */
#team-summary .game {
  display: inline-flex;
  inline-size: auto;
  vertical-align: middle;
}

#team-summary .game span {
  flex: none;
}

/* all-teams ranking: crest + name as one link; the sorted column's header
   reads as selected, the other as a plain button to switch to */
.team-link {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-sm);
  white-space: nowrap;
}

/* last five results as W / D / L badges, most recent on the right */
.form {
  display: inline-flex;
  gap: var(--gap-xs);
}

.form__result {
  display: grid;
  place-items: center;
  inline-size: 1.5rem;
  block-size: 1.5rem;
  border-radius: var(--radius-sm);
  font-size: var(--fz-2xs);
  font-weight: 700;
  cursor: default;
}

.form__result--W {
  background: var(--pos-wash);
  color: var(--pos);
}

.form__result--D {
  background: color-mix(in srgb, var(--text-muted) 15%, transparent);
  color: var(--text-muted);
}

.form__result--L {
  background: color-mix(in srgb, var(--neg) 15%, transparent);
  color: var(--neg);
}

.sort-link {
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
}

[aria-sort='descending'] .sort-link {
  color: var(--text-primary);
}

[aria-sort='descending'] .sort-link::after {
  content: ' ↓';
}

/* public team pages (/teams/, /teams/<slug>/) and the home page teaser; the
   tables above (.team-stats, .form, td.against, .team-link) are shared with
   the admin Team stats page */
.teams-teaser :is(th, td) {
  white-space: nowrap;
}

/* a club's season summary in the Scores page's "Results by team" (built by
   js/pages/scores.js, same parts as src/pages/team.liquid): even gaps between
   its rank line, tiles, form and table, which drops its own .twrap margin */
.team-overview {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.team-overview:empty {
  display: none;
}

/* "Form" + the W / D / L badges on one line */
.team-overview__form {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin: 0;
}

.team-overview .twrap {
  margin-block: 0;
}

.team-title {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.team-title .crest {
  inline-size: 3rem;
  block-size: 3rem;
}

/* a "See all …" / "All teams" link line under a list or a button: clear of it
   (.stack-sm has no bottom margin) and of the next heading. Scoped like
   style.css's `main:not(.app-main) p` margin rule, which would win otherwise. */
main:not(.app-main) .more-link {
  margin-block: var(--space-lg);
}

/* two or more buttons side by side (Get started + Watch the walkthrough):
   centred on one line, and an even gap across and down when they wrap on a
   phone. Inline buttons align to their own text baselines, which left them a
   few pixels apart, and touched when they wrapped. The gap replaces style.css's
   adjacent-button margin. */
.cta-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
}

.cta-row > :is(.btn, .cta:not(.cta--x)) + :is(.btn, .cta:not(.cta--x)) {
  margin-inline-start: 0;
}

/* "Next picks lock in 3 days" just above a Get started button
   (js/countdown.js), with room to breathe on both sides */
.countdown {
  display: flex;
  align-items: center;
  gap: var(--gap-xs);
  color: var(--text-secondary);
  font-size: var(--fz-sm);
}

main:not(.app-main) .countdown {
  margin-block: var(--space-md) var(--space-lg);
}

/* --- admin form spacing ---------------------------------------------------
   Admin pages build forms out of bare label/input/select/textarea/button
   siblings (no field wrapper), and mix pages with a <form> and pages
   without one. Neither the generic `label + input` rule nor the
   `form :is(...) + :is(...)` rule reaches every case, so a control that is
   directly followed by the *next* field's label (or by an action button)
   gets no margin at all — fields and buttons end up touching. These rules
   give every admin page one consistent rhythm regardless of markup:
   space-xs between a label and its own control, space-lg before the next
   field or action button. */

/* inputs/selects/textareas are inline-block and .cta is inline-flex, so a
   margin alone doesn't reliably start a new line — it just shifts the next
   element within the same shared line box. Force fields onto their own
   line so the rhythm below is guaranteed rather than width-dependent.
   Covers admin AND app pages — profile.liquid uses the same bare
   label/input/button siblings admin forms do (leagues.liquid is the only
   app page that wraps fields in a div instead). */
:where(body[class^='admin'], body[class^='app'])
  :is(input, select, textarea):not([type='checkbox'], [type='radio']) {
  display: block;
}

:where(body[class^='admin'], body[class^='app']) label + :is(input, select, textarea) {
  margin-block-start: var(--space-xs);
}

:where(body[class^='admin'], body[class^='app'])
  :is(input, select, textarea, label)
  + :is(label, .cta, .btn, button) {
  margin-block-start: var(--space-lg);
}

/* The edit-match dialog has six action buttons of very different label
   lengths ("Lock" vs "Fetch data") — flex-wrap with
   justify-content:flex-end (the generic dialog-actions rule) right-justifies
   each wrapped row independently, which reads as ragged/disordered. A grid
   of even-width cells wraps predictably instead. */
body[class^='admin'] .dialog-actions {
  margin-block-start: var(--space-lg);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: var(--gap-sm);
}

/* .link (Cancel) has no button background, so it needs its own text
   centered within its stretched grid cell to match the solid .cta buttons
   either side of it. */
body[class^='admin'] .dialog-actions .link {
  justify-content: center;
}

/* admin matches table: icon markers for stats / status (label in the tooltip
   and screen-reader text), and a round-over-kick-off details cell */
.twrap :is(td.flags, td.when) {
  white-space: nowrap;
}

.flag {
  display: inline-flex;
  vertical-align: middle;
  font-size: 1.25em;
}

.flag + .flag {
  margin-inline-start: var(--gap-xs);
}

.flag--ok {
  color: var(--pos);
}

.flag--live {
  color: var(--neg);
}

.flag--off {
  color: var(--text-muted);
}

.flag--locked {
  color: var(--gold);
}

.when__round {
  display: block;
  color: var(--text-primary);
}

.when__time {
  display: block;
  color: var(--text-muted);
  font-size: var(--fz-xs);
}

/* leagues.liquid wraps each label+control pair in a plain <div> rather than
   leaving bare siblings, so the block-level div already forces a new line
   before whatever follows — it just has no gap. Give a field-wrapper div
   the same space-lg break before the next field or action button that
   bare-sibling forms get from the rule above. */
body[class^='app'] form > div + :is(div, .cta, .btn, button) {
  margin-block-start: var(--space-lg);
}

.app-main :is(h1, h2, h3) {
  margin: 0;
}

/* dense one-line UI rows keep the tighter leading */
.app-main :is(.page-head, .section-header, .stat-row, .round-tabs) {
  line-height: var(--lh-ui);
}

.app-screen {
  display: contents;
}

/* Vertical rhythm for app screens.
   select.liquid wraps content in <div class="app-screen"> (display:contents),
   so its children are direct .app-main flex items and inherit its gap.
   dashboard.liquid / scores.liquid wrap content in a plain <div id="*-screen">
   — give that the same column rhythm explicitly (global resets zero p/heading
   margins, so without this everything collapses together). */
.app-main > [id$='-screen']:not(.app-screen) {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

/* a section label hugs the block it introduces, with a larger break above it */
.app-main > [id$='-screen']:not(.app-screen) > .section-header {
  margin-block-start: var(--space-sm);
  margin-block-end: calc(var(--space-xs) - var(--space-lg));
}

/* a section-header + its content + optional trailing link/button, hidden as
   one unit until that section's data has loaded (see pages/*.js) so nothing
   pops in piecemeal or leaves a header/button floating above empty space */
.section-block {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* the header block gets a little extra air below it — applies to app-screen
   pages too (select.liquid) even though display:contents means .page-head
   ends up a direct .app-main flex item rather than this wrapper's */
.app-main > [id$='-screen'] > .page-head {
  margin-block-end: var(--space-2xs);
}

/* select.liquid's <div class="app-screen"> uses display:contents, so its
   children (including #pick-status) become direct .app-main flex items and
   inherit .app-main's much larger inter-section gap (--gap-section) instead
   of the --space-lg rhythm every other app screen uses around its status
   line. Cancel the difference so the gap above and below matches exactly. */
#pick-status {
  margin-block: calc(var(--space-lg) - var(--gap-section));
}

/* leagues/leaderboard/profile still use the older bare-<section> prose
   layout (no flex gap at all), so their status line only had the default
   paragraph margin and font-size — inconsistent with (and mostly smaller
   than) the rhythm above. The prose fallback's `section:not([class]) p`
   rule outranks .page-head__meta's class selector, so font-size/color have
   to be set here too (by id) to actually win. margin-block-start matches
   the 28px gap dashboard/scores get from their flex-gap + page-head margin. */
:is(#leagues-status, #leaderboard-status, #profile-status) {
  margin-block: calc(var(--space-lg) + var(--space-2xs)) var(--space-lg);
  font-size: var(--fz-sm);
  color: var(--text-secondary);
}

/* --- page header --------------------------------------------------------- */
.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xs) var(--space-md);
}

.page-head__title {
  font-family: var(--font-display);
  font-size: var(--fz-2xl);
  font-weight: 700;
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--text-heading);

  /* white shading to soft grey, clipped to the glyphs */
  background: var(--grad-text);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.page-head__meta {
  font-size: var(--fz-sm);
  color: var(--text-secondary);
}

.round-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-xs);
  padding: var(--pad-xs) var(--pad-lg) var(--pad-xs) var(--pad-md);
  border: var(--border-hairline) solid transparent;
  border-radius: var(--radius-pill);
  background:
    linear-gradient(var(--accent-wash), var(--accent-wash)) padding-box,
    linear-gradient(var(--bg-page), var(--bg-page)) padding-box,
    var(--edge-accent) border-box;
  box-shadow: var(--glow-accent);
  color: var(--accent-hi);
  font-family: var(--font-body);
  font-size: var(--fz-xs);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

/* The label is written by page JS after load (#dash-gw / #gw-label), so on
   first paint the pill would be an icon-only stub that then widens. Keep it
   invisible until the label exists, then fade it in at its final width. */
.round-pill {
  transition: opacity var(--dur) var(--ease);
}

.round-pill:has(> span:empty) {
  opacity: 0;
}

/* --- section header (green tick + label) ------------------------------- */
.section-header {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-block-start: var(--space-xs);
}

/* header without an icon: a short gradient tick leads the label */
.section-header::before {
  content: '';
  inline-size: var(--icon-md);
  block-size: var(--space-3xs);
  flex: none;
  background: var(--grad-accent);
  border-radius: var(--space-3xs);
}

.section-header:has(> .icon)::before {
  content: none;
}

/* header with an icon: the icon sits in a small lit badge. .icon-badge is
   the same badge anywhere else (headings, callouts) — pass it as the icon
   partial's `class`. */
.section-header > .icon,
.icon-badge {
  --icon-duo: 0.32;
  flex: none;
  box-sizing: content-box;
  inline-size: var(--icon-sm);
  block-size: var(--icon-sm);
  padding: 5px;
  border: var(--border-hairline) solid transparent;
  border-radius: var(--radius-sm);
  background:
    linear-gradient(var(--accent-wash), var(--accent-wash)) padding-box,
    linear-gradient(var(--bg-card), var(--bg-card)) padding-box,
    var(--edge-accent) border-box;
  box-shadow: var(--glow-accent);
  color: var(--accent-hi);
}

/* prose-page headings (leagues) lead with a slightly larger badge */
:is(h1, h2, h3):has(> .icon-badge) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-sm);
}

:is(h1, h2, h3) > .icon-badge {
  inline-size: var(--icon-md);
  block-size: var(--icon-md);
  padding: 6px;
  border-radius: var(--radius-md);
}

/* a badge leading one line of text: player name (user), your rank (leagues) */
:is(.player-line, .rank-callout) {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

/* a label led by a small icon ("Round" selector on user / scores) */
label.label-icon {
  display: flex;
  align-items: center;
  gap: var(--gap-xs);
}

/* public /results/ round picker: the admin/app field rhythm doesn't reach
   public pages, so give the select its own line and spacing */
.results-browse select {
  display: block;
  margin-block: var(--space-xs) var(--space-md);
}

.label-icon .icon {
  --icon-duo: 0.3;
  font-size: 1.25em;
  color: var(--accent-hi);
}

/* podium ranks in leaderboard tables: gold / silver / bronze medals */
.rank-medal {
  --icon-duo: 0.35;
  display: inline-flex;
  align-items: center;
  gap: var(--gap-2xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.rank-medal .icon {
  font-size: 1.2em;
  filter: drop-shadow(0 0 5px color-mix(in srgb, currentColor 45%, transparent));
}

.rank-medal--1 {
  color: var(--gold);
}

.rank-medal--2 {
  color: var(--silver);
}

.rank-medal--3 {
  color: var(--bronze);
}

/* a hairline runs on from the label and fades out */
.section-header::after {
  content: '';
  flex: 1;
  block-size: var(--border-hairline);
  background: linear-gradient(90deg, var(--line-strong), transparent);
}

.section-header > span,
.section-header__label {
  margin: 0; /* an <h2>: the label is the section's heading */
  line-height: inherit;
  font-family: var(--font-body);
  font-size: var(--fz-xs);
  font-weight: 600;
  letter-spacing: var(--tracking-over);
  text-transform: uppercase;
  color: var(--text-secondary);
}

/* picks auto-save state (Saving… / Saved), pushed past the header's hairline */
.section-header > .save-state {
  order: 1;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
}

.section-header > .save-state[data-type='error'] {
  color: var(--neg);
}

/* --- generic stacks --------------------------------------------------- */
.stack-sm {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.hidden {
  display: none !important;
}

/* --- match card (fixture in Your Picks) ------------------------------ */
.match-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--pad-card);
  border-radius: var(--radius-card);
}

.match-card--viewable {
  cursor: pointer;
}

.match-card--viewable:is(:hover, :focus-visible) {
  --_fill: var(--bg-elevated);
  --_edge: var(--edge-clickable-hover);
}

.match-card__date {
  text-align: center;
  font-size: var(--fz-xs);
  color: var(--text-secondary);
}

.match-card__row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-md);
}

.match-card__team {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-family: var(--font-body);
  font-size: var(--fz-md);
  font-weight: 600;
  color: var(--text-primary);
}

.match-card__team--home {
  justify-content: flex-end;
}

.match-card__team img {
  flex: none;
  inline-size: 18px;
  block-size: 18px;
}

.match-card__score {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-family: var(--font-ui);
  font-size: var(--fz-lg);
  font-weight: 700;
  color: var(--text-primary);
}

.match-card__score span {
  color: var(--text-muted);
}

.match-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}

/* A lone child sits at the end of the row: Remove with no boost pill (so it
   never jumps sideways when a pill appears), or a locked round's pill. */
.match-card__foot > :only-child {
  margin-inline-start: auto;
}

/* Remove is shaped like the boost pill next to it, so the foot row keeps a
   constant height whether or not the "Attach <boost>" pill is present —
   arming a boost then doesn't grow every pick card and shove the page. */
.match-card__remove {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-sm);
  padding: var(--pad-md) var(--pad-lg);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: linear-gradient(180deg, rgb(255 255 255 / 0.04), transparent 70%);
  font: inherit;
  font-size: var(--fz-sm);
  font-weight: 600;
  color: var(--text-secondary);
  cursor: pointer;
  transition:
    color var(--dur) var(--ease),
    border-color var(--dur) var(--ease),
    background var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease);
}

.match-card__remove {
  box-shadow: var(--shadow-raised-sm);
}

.match-card__remove:active {
  box-shadow: var(--shadow-pressed);
}

.match-card__remove:is(:hover, :focus-visible) {
  color: var(--neg);
  border-color: color-mix(in srgb, var(--neg) 70%, transparent);
  background: color-mix(in srgb, var(--neg) 10%, transparent);
}

/* --- pick option (available match row) ----------------------------- */
.pick-option {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  inline-size: 100%;
  padding: var(--pad-card);
  border-radius: var(--radius-card);
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
}

/* Row 1: the checkbox on the left, the kick-off date in the centre, the
   trailing day/time / boost pill on the right. Same 1fr auto 1fr grid as
   Row 2 (.pick-option__row, teams + vs) so the date sits over the vs badge. */
.pick-option__head {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-sm);
  min-block-size: 20px;
}

.pick-option__head > :last-child {
  justify-self: end;
}

.pick-option:is(:hover, :focus-visible) {
  --_fill: var(--bg-elevated);
}

.pick-option:is(:hover, :focus-visible):not(.pick-option--selected, .pick-option--boosted) {
  --_edge: var(--edge-clickable-hover);
}

.pick-option:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.pick-option--selected {
  --_tint: var(--accent-wash);
  --_edge: var(--edge-accent);
  box-shadow:
    var(--_shadow),
    0 12px 32px -20px color-mix(in srgb, var(--accent-hi) 70%, transparent);
}

.pick-option--boosted {
  --_edge: var(--edge-gold);
  box-shadow: var(--_shadow), var(--glow-gold);
}

.pick-option__check {
  flex: none;
  inline-size: 20px;
  block-size: 20px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-xs);
  background: var(--bg-input);
  border: 1.5px solid var(--line-strong);
  box-shadow: var(--shadow-well-input);
  color: var(--accent-ink);
  transition:
    background var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease);
}

.pick-option--selected .pick-option__check {
  background: var(--grad-accent);
  border-color: transparent;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.3),
    0 0 12px -2px color-mix(in srgb, var(--accent-hi) 60%, transparent);
}

.pick-option__check svg {
  inline-size: 12px;
  block-size: 12px;
  stroke-width: 3;
  opacity: 0;
}

.pick-option--selected .pick-option__check svg {
  opacity: 1;
}

/* Row 2: same grid as .match-score-card__row so the vs badge sits in the
   exact centre column that a finished card's score badge does — the two
   card types stay aligned down the list. */
.pick-option__row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-md);
}

.pick-option__team {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  min-inline-size: 0;
  font-family: var(--font-body);
  font-size: var(--fz-sm);
  font-weight: 600;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.pick-option__team--home {
  justify-content: flex-end;
  text-align: end;
}

/* Same treatment as .match-score-card__badge (dashboard/scores) so "v" reads
   identically everywhere it appears, instead of plain muted text here. */
.pick-option__vs {
  flex: none;
  padding: var(--pad-2xs) var(--pad-sm);
  border: var(--border-hairline) solid rgb(var(--rim-rgb) / 0.05);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  box-shadow: var(--shadow-well);
  color: var(--text-primary);
  font-family: var(--font-ui);
  font-size: var(--fz-xs);
  font-weight: 700;
}

.pick-option__team img {
  flex: none;
  inline-size: 18px;
  block-size: 18px;
}

.pick-option__date,
.pick-option__day {
  font-size: var(--fz-xs);
  color: var(--text-secondary);
}

/* Same gold-pill language as .points-badge--boost / .boost-pill--active,
   not plain colored text — "a boost is applied" should look the same
   everywhere it's shown. */
.pick-option__boost__pill {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-2xs);
  padding: var(--pad-2xs) var(--pad-md);
  border-radius: var(--radius-pill);
  background: var(--gold-wash);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gold) 30%, transparent);
  font-size: var(--fz-xs);
  font-weight: 700;
  color: var(--gold);
}

.pick-option__boost__pill svg {
  inline-size: 0.9em;
  block-size: 0.9em;
}

/* --- boost pills --------------------------------------------------- */
.boost-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  gap: var(--space-xs);
}

.boost-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--pad-md) var(--pad-lg);
  border-radius: var(--radius-md);
  color: var(--text-body);
  font: inherit;
  font-size: var(--fz-sm);
  font-weight: 600;
  cursor: pointer;
}

.boost-pill svg {
  --icon-duo: 0.35;
  flex: none;
  inline-size: 14px;
  block-size: 14px;
  color: var(--gold);
  filter: drop-shadow(0 0 4px color-mix(in srgb, var(--gold) 45%, transparent));
}

.boost-pill:is(:hover, :focus-visible):not(:disabled) {
  --_fill: var(--bg-elevated);
}

.boost-pill:is(:hover, :focus-visible):not(:disabled, .boost-pill--active) {
  --_edge: var(--edge-clickable-hover);
}

.boost-pill:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

.boost-pill__count {
  margin-inline-start: auto;
  padding: var(--pad-3xs) var(--pad-sm);
  border-radius: var(--radius-pill);
  background: var(--gold-wash);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gold) 25%, transparent);
  color: var(--gold);
  font-size: var(--fz-2xs);
  font-weight: 700;
}

.boost-pill--active {
  --_tint: var(--gold-wash);
  --_edge: var(--edge-gold);
  box-shadow: var(--_shadow), var(--glow-gold);
  color: var(--gold);
}

.boost-pill--active .boost-pill__count {
  background: var(--grad-gold);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.35);
  color: var(--ink-on-bright);
}

/* --- stat tiles -------------------------------------------------- */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(5.5rem, 1fr));
  gap: var(--space-sm);
}

.stat-tile {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  padding: var(--pad-card);
  border-radius: var(--radius-card);
}

/* duotone icon in a small lit badge (js/icons.js statTile) */
.stat-tile__icon {
  --icon-duo: 0.32;
  display: grid;
  place-items: center;
  inline-size: 2rem;
  block-size: 2rem;
  margin-block-end: var(--space-xs);
  border: var(--border-hairline) solid transparent;
  border-radius: var(--radius-sm);
  background:
    linear-gradient(var(--accent-wash), var(--accent-wash)) padding-box,
    linear-gradient(var(--bg-card), var(--bg-card)) padding-box,
    var(--edge-accent) border-box;
  box-shadow: var(--inset-hi);
  color: var(--accent-hi);
  font-size: var(--icon-md);
}

.stat-tile__value {
  font-family: var(--font-ui);
  font-size: var(--fz-xl);
  font-weight: 700;
  line-height: 1;
  color: var(--text-primary);
  background: var(--grad-text);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.stat-tile__label {
  font-family: var(--font-body);
  font-size: var(--fz-2xs);
  letter-spacing: var(--tracking-over);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* --- round tabs ----------------------------------------------- */
.round-tabs {
  display: flex;
  gap: var(--space-xs);
  overflow-x: auto;
  scrollbar-width: none;

  /* the scroll strip clips anything outside its box — pad it out far enough
     for the tabs' raised shadows, and pull it back by the same amount so the
     tabs stay aligned with the content edge */
  padding: var(--space-xs) var(--space-md) var(--space-md) var(--space-xs);
  margin: calc(-1 * var(--space-xs)) calc(-1 * var(--space-md)) calc(-1 * var(--space-md))
    calc(-1 * var(--space-xs));
}

.round-tabs::-webkit-scrollbar {
  display: none;
}

/* more tabs past an edge (data-more-* from scrollHints() in ui.js): fade that
   edge out, the cue that the row scrolls */
.round-tabs[data-more-end] {
  mask-image: linear-gradient(to right, #000 calc(100% - 4rem), transparent);
}

.round-tabs[data-more-start] {
  mask-image: linear-gradient(to left, #000 calc(100% - 4rem), transparent);
}

.round-tabs[data-more-start][data-more-end] {
  mask-image: linear-gradient(
    to right,
    transparent,
    #000 4rem,
    #000 calc(100% - 4rem),
    transparent
  );
}

.round-tab {
  flex: none;
  padding: var(--pad-xs) var(--pad-lg);
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--fz-sm);
  font-weight: 600;
  cursor: pointer;
}

button.round-tab:is(:hover, :focus-visible) {
  --_fill: var(--bg-elevated);
  color: var(--text-primary);
}

.round-tab--static {
  color: var(--text-primary);
}

/* profile Appearance switch: the round-tab segmented control at button size */
.theme-switch .round-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-sm);
  padding: var(--pad-lg) var(--pad-2xl);
  font-size: var(--fz-md);
}

.theme-switch .round-tab .icon {
  inline-size: 1.15em;
  block-size: 1.15em;
}

/* the selected round reads as a pressed-in segment (segmented control) */
.round-tab--active,
.round-tab--active:is(:hover, :focus-visible) {
  --_tint: var(--accent-wash);
  --_edge: var(--edge-accent);
  box-shadow: var(--shadow-pressed);
  color: var(--accent-hi);
}

/* a round's state as a dot before its name: amber = missed, green = picked
   (the tab also carries visually hidden "(missed)" / "(picked)" text) */
.round-tab--missed::before,
.round-tab--picked::before {
  content: '';
  display: inline-block;
  inline-size: 0.45em;
  block-size: 0.45em;
  margin-inline-end: 0.4em;
  border-radius: 50%;
  background: var(--warn);
  vertical-align: 0.1em;
}

.round-tab--picked::before {
  background: var(--pos);
}

/* --- match score card (scores list) ------------------------- */
.match-score-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  inline-size: 100%;
  padding: var(--pad-card);
  border-radius: var(--radius-card);
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.match-score-card[disabled] {
  cursor: default;
}

.match-score-card:is(:hover, :focus-visible):not([disabled]) {
  --_fill: var(--bg-elevated);
  --_edge: var(--edge-clickable-hover);
}

.match-score-card__row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-md);
}

.match-score-card__team {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-family: var(--font-body);
  font-size: var(--fz-sm);
  font-weight: 600;
  color: var(--text-primary);
}

.match-score-card__team--home {
  justify-content: flex-end;
}

.match-score-card__team img {
  flex: none;
  inline-size: 18px;
  block-size: 18px;
}

.match-score-card__badge {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-xs);
  padding: var(--pad-2xs) var(--pad-md);
  border: var(--border-hairline) solid rgb(var(--rim-rgb) / 0.05);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  box-shadow: var(--shadow-well);
  font-family: var(--font-ui);
  font-size: var(--fz-md);
  font-weight: 700;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

.match-score-card__badge span {
  color: var(--text-muted);
}

.match-score-card__points {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
}

.match-score-card__date {
  text-align: center;
  font-size: var(--fz-xs);
  color: var(--text-secondary);
}

.points-badge {
  /* at badge size a solid glyph reads better than duotone */
  --icon-duo: 1;
  display: inline-flex;
  align-items: center;
  gap: var(--gap-2xs);
  padding: var(--pad-2xs) var(--pad-md);
  border-radius: var(--radius-pill);
  background: var(--pos-wash);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--pos) 24%, transparent);
  color: var(--pos);
  font-size: var(--fz-xs);
  font-weight: 700;
}

.points-badge svg {
  inline-size: 0.9em;
  block-size: 0.9em;
}

.points-badge--muted {
  background: var(--bg-elevated);
  box-shadow: inset 0 0 0 1px rgb(var(--rim-rgb) / 0.06);
  color: var(--text-secondary);
}

/* Marks a standings total that includes provisional points from the round
   currently in progress (leaderboard / leagues / your-rank). */
.live-dot {
  font-size: var(--fz-2xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--accent-hi);
  vertical-align: 0.05em;
  margin-inline-start: var(--gap-2xs);
}

/* A boost that was actually applied to a match — gold, matching every other
   "boost is active" treatment in the app (.boost-pill--active, .boost-tag).
   Distinct from .points-badge--muted, which is for neutral states like
   "used up" in the boosts-remaining list. */

/* a round the player had no picks in (src/js/missedRounds.js) */
.missed-tag {
  --icon-duo: 1;
  display: inline-flex;
  align-items: center;
  gap: var(--gap-2xs);
  padding: var(--pad-2xs) var(--pad-md);
  border-radius: var(--radius-pill);
  background: var(--warn-wash);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--warn) 30%, transparent);
  color: var(--warn);
  font-size: var(--fz-xs);
  font-weight: 700;
  white-space: nowrap;
  vertical-align: middle;
}

/* a status line that leads with a missed-tag */
.missed-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--gap-xs);
}

.points-badge--boost {
  background: var(--gold-wash);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gold) 30%, transparent);
  color: var(--gold);
}

/* --- read-only surfaces -------------------------------------------------
   One rule for the whole app: RAISED = CLICKABLE, DENTED = READ-ONLY.
   The raised look (top sheen, lit top edge, drop shadow, hover) is reserved
   for things that respond to a tap. Display-only cards are pressed into the
   page instead (inset neumorphism — tokens.css --dent-fill / --edge-dent /
   --shadow-dent): shadow inside the top edge, a lit bottom lip, no hover,
   default cursor. Applies to:
   - a match card with no score to open (pending pick on dashboard/scores),
   - a pick card that isn't finished yet (Picks: "Your picks"), and the
     Boosts remaining rows on /app/scores/ (same .match-card markup),
   - stat tiles,
   - the round label on /app/scores/ when only one round is scored (a lone
     tab has nothing to switch to, so it renders as a <span>, not a button).
   Controls inside a dent (Remove, Attach boost, icon badges) stay raised,
   which reads naturally: something to press, sitting in a recess. */
.match-score-card[disabled],
.match-card:not(.match-card--viewable),
.stat-tile,
.round-tab--static {
  background:
    linear-gradient(var(--_tint), var(--_tint)) padding-box,
    var(--dent-fill) padding-box,
    var(--edge-dent) border-box;
  box-shadow: var(--shadow-dent);
  cursor: default;
}

/* a boost shown on a locked pick is a label, not a toggle — a dented gold
   tag (the tappable "Attach"/attached pills are buttons and stay raised) */
span.boost-pill:not([data-boostmatch]) {
  background:
    linear-gradient(var(--_tint), var(--_tint)) padding-box,
    var(--dent-fill) padding-box,
    linear-gradient(180deg, var(--shade), color-mix(in srgb, var(--gold) 35%, transparent))
      border-box;
  box-shadow:
    inset 0 1px 3px var(--shade),
    inset 0 -1px 0 color-mix(in srgb, var(--gold) 12%, transparent);
  cursor: default;
}

/* --- clickable surfaces: raised (outer neumorphism) ----------------------
   The counterpart of the dents above: anything you can tap is extruded from
   the page, lit by the same top-left light (tokens.css --sheen-convex,
   --shadow-raised*), and framed by the same faint edge as read-only
   containers (--edge-clickable; a plain --line border on hover). Hover lifts it higher; pressing
   sinks it into a dent. Set through custom properties inside :where() (zero
   specificity), so state rules (selected / boosted / active tab) keep
   control of fill, tint and edge, and stack any glow on top via
   `box-shadow: var(--_shadow), …`. */
:where(
  .match-score-card:not([disabled]),
  .match-card--viewable,
  .pick-option:not(:disabled),
  .result-card,
  .sidebar__profile
) {
  --_sheen: var(--sheen-convex);
  --_shadow: var(--shadow-raised);
  --_edge: var(--edge-clickable);
}

:where(
  .match-score-card:not([disabled]),
  .match-card--viewable,
  .pick-option:not(:disabled),
  .result-card,
  .sidebar__profile
):is(:hover, :focus-visible) {
  --_shadow: var(--shadow-raised-hover);
}

/* small controls get the tighter extrusion */
:where(button.round-tab, button.boost-pill:not(:disabled)) {
  --_sheen: var(--sheen-convex);
  --_shadow: var(--shadow-raised-sm);
  --_edge: var(--edge-clickable);
}

:where(button.round-tab, button.boost-pill:not(:disabled)):is(:hover, :focus-visible) {
  --_shadow: var(--shadow-raised);
}

:where(
  .match-score-card:not([disabled]),
  .match-card--viewable,
  .pick-option:not(:disabled),
  .result-card,
  .sidebar__profile,
  button.round-tab,
  button.boost-pill:not(:disabled)
):active {
  --_shadow: var(--shadow-pressed);
}

/* --- score-breakdown dialog --------------------------------
   A raised card (it's interactive) holding two dented, read-only panels:
   the scoreboard and the breakdown list. Filled by page JS via sd-* ids. */
.score-dialog {
  --_edge: var(--edge-hover);
  padding: var(--space-md);
  inline-size: min(94vw, 28rem);
  border-radius: var(--radius-lg);

  /* a soft green glow pooled at the top, over the normal surface layers */
  background:
    radial-gradient(
        120% 55% at 50% 0%,
        color-mix(in srgb, var(--accent-hi) 13%, transparent),
        transparent 70%
      )
      padding-box,
    var(--_surface);
  color: var(--text-body);
  box-shadow: var(--shadow-lg);

  /* centred rather than top-pinned, so cap against the full viewport; without
     this the breakdown list overflows off-screen on short viewports */
  max-block-size: calc(100dvh - 2 * var(--space-md));
  overflow: auto;
  overscroll-behavior: contain;
}

.score-dialog::backdrop {
  background: rgb(5 5 8 / 0.6);
  backdrop-filter: blur(4px);
}

.score-dialog__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-block-end: var(--space-md);
}

/* cancel the generic `dialog form[method='dialog']` top margin (meant for
   footer close-forms) so the close button lines up with the title */
.score-dialog__head form[method='dialog'] {
  margin-block-start: 0;
}

.score-dialog__title {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  font-family: var(--font-body);
  font-size: var(--fz-xs);
  font-weight: 600;
  letter-spacing: var(--tracking-over);
  text-transform: uppercase;
  color: var(--text-secondary);
}

/* scoreboard: a dented panel, teams either side of a big score */
.score-dialog__match {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-md);
  margin-block-end: var(--space-md);
  padding: var(--space-lg) var(--space-md);
  border: var(--border-hairline) solid transparent;
  border-radius: var(--radius-md);
  background:
    var(--dent-fill) padding-box,
    var(--edge-dent) border-box;
  box-shadow: var(--shadow-dent);
}

.score-dialog__team {
  min-inline-size: 0;
  font-family: var(--font-body);
  font-size: var(--fz-md);
  font-weight: 600;
  line-height: var(--lh-snug);
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.score-dialog__team--home {
  text-align: end;
}

.score-dialog__score {
  font-family: var(--font-ui);
  font-size: var(--fz-2xl);
  font-weight: 700;
  line-height: 1;
  letter-spacing: var(--tracking-tight);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  background: var(--grad-text);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* breakdown list: one dented panel, rows split by faint hairlines */
.score-dialog__rows {
  overflow: hidden; /* keep the full-bleed total band inside the corners */
  border: var(--border-hairline) solid transparent;
  border-radius: var(--radius-md);
  background:
    var(--dent-fill) padding-box,
    var(--edge-dent) border-box;
  box-shadow: var(--shadow-dent);
}

.score-dialog__rows:empty {
  display: none;
}

.score-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 0.7em var(--space-md);
}

.score-row + .score-row {
  border-block-start: var(--border-hairline) solid rgb(var(--rim-rgb) / 0.05);
}

/* the subtotal closes the itemised list with a firmer rule */
.score-row + .score-row--subtotal {
  border-block-start-color: var(--line-strong);
}

.score-row__label {
  flex: 1;
  font-family: var(--font-body);
  font-size: var(--fz-sm);
  color: var(--text-secondary);
}

.score-row__value {
  font-family: var(--font-ui);
  font-size: var(--fz-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.score-row--subtotal .score-row__value,
.score-row--subtotal .score-row__label {
  color: var(--text-primary);
  font-weight: 700;
}

/* boosted total: a green band across the foot of the list */
.score-row--total {
  padding-block: 0.85em;
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--accent) 34%, transparent),
    color-mix(in srgb, var(--accent) 10%, transparent)
  );
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--accent-hi) 30%, transparent);
}

.score-row--total .score-row__label {
  color: var(--accent-hi);
  font-weight: 700;
}

.score-row--total .score-row__value {
  font-size: var(--fz-md);
  color: var(--accent-bright);
  font-weight: 700;
}

.score-dialog__foot {
  margin: var(--space-md) 0 0;
  text-align: center;
  font-size: var(--fz-sm);
}

.score-dialog__foot .icon {
  --icon-duo: 0.3;
  font-size: 1.1em;
}

.boost-tag {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--gap-xs);
  inline-size: fit-content;
  margin: 0 auto var(--space-md);
  padding: var(--pad-xs) var(--pad-lg);
  border-radius: var(--radius-pill);
  background: var(--gold-wash);
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--gold) 30%, transparent),
    var(--glow-gold);
  color: var(--gold);
  font-size: var(--fz-xs);
  font-weight: 700;
}

.boost-tag svg {
  --icon-duo: 1;
  inline-size: 0.9em;
  block-size: 0.9em;
}

/* .icon-btn lives in style.css (shared: banner dismiss + dialog close) */

.link-muted {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-xs);
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--fz-sm);
  color: var(--text-secondary);
  cursor: pointer;
}

.link-muted:is(:hover, :focus-visible) {
  color: var(--text-primary);
  text-decoration: underline;
}

/* --- Picks: two panes on desktop ----------------------------- */
.pick-layout {
  display: grid;
  gap: var(--gap-section);
}

.pick-layout__main,
.pick-layout__aside {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  min-inline-size: 0;
}

@media (min-width: 80rem) {
  .pick-layout {
    /* wide fixture list + fixed picks/boost rail */
    grid-template-columns: minmax(0, 1fr) 22rem;
    gap: var(--space-2xl);
    align-items: start;
  }

  .pick-layout__aside {
    /* Available matches — the wide left column */
    grid-column: 1;
    grid-row: 1;
  }

  .pick-layout__main {
    /* Your picks + boosts — sticky right rail */
    grid-column: 2;
    grid-row: 1;
    position: sticky;
    inset-block-start: var(--space-lg);
  }

  /* boosts stack in the narrow rail instead of wrapping their labels */
  .pick-layout__main .boost-grid {
    grid-template-columns: 1fr;
  }
}

/* --- mobile tab bar ----------------------------------------
   Scrolls sideways instead of squeezing: every item is a fixed fifth of the
   bar, so five fill it exactly and a sixth (the Admin tab auth.js reveals
   after load, or the extra "App" tab on admin pages) sits off to the right
   to scroll to — the other tabs never resize or jump. The active tab is
   scrolled into view on load (base.liquid). */
.tabbar {
  position: fixed;
  inset-block-end: var(--space-sm);
  inset-inline: var(--space-sm);
  z-index: 20;
  display: flex;
  gap: var(--space-3xs);
  padding: var(--space-2xs);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--space-2xs);
  scrollbar-width: none;
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--bg-elevated) 88%, transparent);
  backdrop-filter: blur(16px) saturate(1.3);

  /* the rim is an inset ring, not a pseudo-element: the bar is a scroll
     container, and an absolutely positioned child would scroll away */
  box-shadow:
    var(--shadow-lg),
    inset 0 0 0 1px rgb(var(--rim-rgb) / 0.07),
    var(--inset-hi);
}

.tabbar::-webkit-scrollbar {
  display: none;
}

@media (min-width: 64rem) {
  .tabbar {
    display: none;
  }
}

.tabbar__item {
  flex: 0 0 calc((100% - 4 * var(--space-3xs)) / 5);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3xs);
  padding: var(--pad-sm) 0;
  scroll-snap-align: start;
  border-radius: var(--radius-md);
  color: var(--text-muted);
  text-decoration: none;
  font-family: var(--font-body);
  font-size: var(--fz-2xs);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  white-space: nowrap;
  transition:
    color var(--dur) var(--ease),
    background var(--dur) var(--ease);
}

.tabbar__item svg {
  inline-size: var(--icon-md);
  block-size: var(--icon-md);
}

.tabbar__item:is(:hover, :focus-visible):not(.tabbar__item--active) {
  color: var(--text-primary);
}

/* quiet active state: a soft green tint, no fill or glow */
.tabbar__item--active {
  --icon-duo: 0.35;
  background: var(--accent-wash);
  color: var(--accent-hi);
}

/* --- desktop sidebar ------------------------------------- */
.sidebar {
  display: none;
}

@media (min-width: 64rem) {
  .sidebar {
    position: sticky;
    inset-block-start: 0;
    block-size: 100dvh;
    display: flex;
    flex-direction: column;
    gap: var(--space-xl);
    padding: var(--space-xl) var(--space-md) var(--space-md);

    /* a hint of green at the top, settling into the sunken tone */
    background: linear-gradient(
      180deg,
      color-mix(in srgb, var(--accent) 7%, var(--bg-sunken)),
      var(--bg-sunken) 45%
    );

    /* vertical hairline, lit at the top and fading out downwards */
    border-inline-end: var(--border-hairline) solid var(--line);
    border-image: linear-gradient(
        180deg,
        rgb(var(--rim-rgb) / 0.1),
        rgb(var(--rim-rgb) / 0.04) 50%,
        rgb(var(--rim-rgb) / 0.01)
      )
      1;
  }
}

.sidebar__logo {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding-inline: var(--pad-lg);
  text-decoration: none;
  font-family: var(--font-display);
  font-size: var(--fz-md);
  font-weight: 700;
  letter-spacing: var(--tracking-tight);
  color: var(--text-primary);
}

.sidebar__logo :is(svg, img) {
  inline-size: 1.6rem;
  block-size: 1.6rem;
  border-radius: var(--radius-xs);
  color: var(--accent-hi);
  box-shadow:
    0 0 0 1px rgb(var(--rim-rgb) / 0.08),
    0 4px 16px -4px color-mix(in srgb, var(--accent-hi) 55%, transparent);
}

.sidebar__nav {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

.sidebar__item {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--pad-md) var(--pad-lg);
  border-radius: var(--radius-sm);
  text-decoration: none;
  font-family: var(--font-body);
  font-size: var(--fz-sm);
  font-weight: 600;
  color: var(--text-secondary);
  transition:
    color var(--dur) var(--ease),
    background var(--dur) var(--ease);
}

.sidebar__item svg {
  inline-size: var(--icon-md);
  block-size: var(--icon-md);
  transition: filter var(--dur) var(--ease);
}

.sidebar__item:is(:hover, :focus-visible) {
  background: rgb(var(--rim-rgb) / 0.04);
  color: var(--text-primary);
}

.sidebar__item--active,
.sidebar__item--active:is(:hover, :focus-visible) {
  --icon-duo: 0.4;
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--accent) 30%, transparent),
    color-mix(in srgb, var(--accent) 6%, transparent)
  );
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-hi) 16%, transparent);
  color: var(--accent-hi);
}

.sidebar__item--active svg {
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--accent-hi) 50%, transparent));
}

/* glowing indicator bar pinned to the sidebar's outer edge */
.sidebar__item--active::before {
  content: '';
  position: absolute;
  inset-block: 22%;
  inset-inline-start: calc(-1 * var(--space-md));
  inline-size: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--grad-accent);
  box-shadow: 0 0 12px color-mix(in srgb, var(--accent-hi) 70%, transparent);
}

.sidebar__spacer {
  flex: 1;
}

.sidebar__profile {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-xs) var(--space-sm) var(--space-xs) var(--space-xs);
  border-radius: var(--radius-md);
  text-decoration: none;
  font-size: var(--fz-sm);
  color: var(--text-body);
}

.sidebar__profile:is(:hover, :focus-visible) {
  --_fill: var(--bg-elevated);
  --_edge: var(--edge-clickable-hover);
  color: var(--text-primary);
}

.sidebar__profile--active,
.sidebar__profile--active:is(:hover, :focus-visible) {
  --_tint: var(--accent-wash);
  --_edge: var(--edge-accent);
  color: var(--accent-hi);
}

.sidebar__avatar {
  flex: none;
  inline-size: 30px;
  block-size: 30px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
  background: linear-gradient(180deg, rgb(255 255 255 / 0.25), transparent 55%), var(--grad-accent);

  /* ring: a gap in the card colour, then a faint mint halo */
  box-shadow:
    0 0 0 2px var(--bg-card),
    0 0 0 3px color-mix(in srgb, var(--accent-hi) 45%, transparent),
    0 4px 12px -2px color-mix(in srgb, var(--accent-hi) 45%, transparent);
  color: var(--accent-ink);
  font-family: var(--font-ui);
  font-weight: 700;
}

/* --- club autosuggest (src/js/clubCombobox.js) ---------------------------
   The wrapper is the positioning context for the popup; it sits inside a
   .column, so the input keeps the shared form styling untouched. */
.combobox {
  position: relative;
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
}

.combobox__list {
  position: absolute;
  inset-block-start: calc(100% + var(--space-3xs));
  inset-inline: 0;
  z-index: 30;
  max-inline-size: 30rem;
  max-block-size: 17rem;
  overflow-y: auto;
  margin: 0;
  padding: var(--space-3xs);
  list-style: none;
  --_fill: var(--bg-elevated);
  --_edge: var(--edge-hover);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}

.combobox__option {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--pad-sm) var(--pad-md);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: var(--fz-sm);
  color: var(--text-primary);
}

.combobox__option[aria-selected='true'],
.combobox__option:hover {
  background: linear-gradient(180deg, rgb(255 255 255 / 0.2), transparent 55%), var(--grad-accent);
  color: var(--accent-ink);
}

.combobox__crest {
  flex: none;
  object-fit: contain;
}

.combobox__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.combobox__short {
  margin-inline-start: auto;
  padding-inline-start: var(--space-xs);
  font-size: var(--fz-xs);
  color: var(--text-muted);
}

.combobox__option[aria-selected='true'] .combobox__short,
.combobox__option:hover .combobox__short {
  color: inherit;
  opacity: 0.75;
}

/* admin matches: the read-only Details dialog. Wide, because the archive's
   per-player and per-event tables have many columns; nested tables are
   compact and keep long JSON keys and values from blowing out the layout. */
#data-dialog {
  inline-size: min(80rem, calc(100vw - 2 * var(--space-md)));
}

#data-dialog h3 {
  margin-block: var(--space-md) 0;
  font-family: var(--font-mono);
}

.data-table {
  margin-block: var(--space-sm);
}

.data-table :is(th, td) {
  padding: var(--gap-xs) var(--gap-sm);
  vertical-align: top;
}

.data-table th[scope='row'] {
  font-family: var(--font-mono);
  text-transform: none;
  letter-spacing: normal;
}

/* overrides the dialog rule that right-aligns a breakdown's points column */
#data-dialog .data-table tbody td:last-of-type {
  text-align: start;
}

.data-table td {
  overflow-wrap: anywhere;
}

.data-null {
  color: var(--text-muted);
}

/* --- charts: /charts/ index cards ------------------------------------- */
.chart-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 16rem), 1fr));
  gap: var(--space-md);
  margin-inline: 0;
  padding: 0;
  list-style: none;
}

.chart-card {
  display: grid;
  gap: var(--space-2xs);
  block-size: 100%;
  padding: var(--pad-card);
  border: var(--border-hairline) solid var(--line);
  border-radius: var(--radius-card);
  background: var(--bg-card);
  color: var(--text-body);
  text-decoration: none;
  transition: border-color var(--dur) var(--ease);
}

.chart-card:is(:hover, :focus-visible) {
  border-color: var(--accent-hi);
  text-decoration: none;
}

.chart-card .icon {
  font-size: 1.5em;
  color: var(--accent-hi);
}

.chart-card strong {
  color: var(--text-primary);
  font-size: var(--fz-lg);
}

/* --- charts: points race (/charts/points-race/) ----------------------
   Every row is absolutely placed by --rank and sized by --share (0–100, its
   total against the round's leader); src/js/pointsRace.js changes only those
   two, and --race-step sets how long the move takes. */
.race {
  --race-row: 2.25rem;
  --race-step: 0ms;
  position: relative;
}

.race__controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm) var(--space-md);
  margin-block-end: var(--space-md);
}

.race__scrub {
  flex: 1 1 10rem;
  display: flex;
}

/* the scrubber: a plain track, green up to the round shown (--progress,
   set by pointsRace.js), and a round thumb — no input chrome */
.race__scrub input {
  --_track: linear-gradient(
    to right,
    var(--accent-hi) var(--progress, 100%),
    var(--line-strong) var(--progress, 100%)
  );
  appearance: none;
  inline-size: 100%;
  max-inline-size: none;
  block-size: 1.5rem;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
  cursor: pointer;
}

.race__scrub input::-webkit-slider-runnable-track {
  block-size: 0.375rem;
  border-radius: var(--radius-pill);
  background: var(--_track);
}

.race__scrub input::-moz-range-track {
  block-size: 0.375rem;
  border-radius: var(--radius-pill);
  background: var(--_track);
}

.race__scrub input::-webkit-slider-thumb {
  appearance: none;
  inline-size: 1.125rem;
  block-size: 1.125rem;
  margin-block-start: -0.375rem;
  border: 3px solid var(--bg-card);
  border-radius: 50%;
  background: var(--accent-hi);
  box-shadow: 0 0 0 1px var(--accent-lo);
  transition: transform var(--dur) var(--ease);
}

.race__scrub input::-moz-range-thumb {
  inline-size: 0.75rem;
  block-size: 0.75rem;
  border: 3px solid var(--bg-card);
  border-radius: 50%;
  background: var(--accent-hi);
  box-shadow: 0 0 0 1px var(--accent-lo);
}

.race__scrub input:is(:hover, :focus-visible)::-webkit-slider-thumb {
  transform: scale(1.15);
}

.race__scrub input:focus-visible {
  outline: 2px solid var(--accent-hi);
  outline-offset: 2px;
  border-radius: var(--radius-pill);
}

.race__now {
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: var(--fz-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.race__plot {
  position: relative;
  block-size: calc(var(--rows) * var(--race-row));
  margin: 0;
  padding: 0;
  list-style: none;
}

.race__row {
  position: absolute;
  inset-inline: 0;
  inset-block-start: 0;
  display: grid;
  grid-template-columns: minmax(6.5rem, 9rem) 1fr;
  align-items: center;
  gap: var(--space-xs);
  block-size: var(--race-row);
  transform: translateY(calc((var(--rank) - 1) * var(--race-row)));

  /* overtakes finish early in the step so rows settle between rounds */
  transition: transform calc(var(--race-step) * 0.6) ease-in-out;
}

.race__club {
  display: flex;
  align-items: center;
  gap: var(--gap-sm);
  min-inline-size: 0;
  color: var(--text-primary);
  font-size: var(--fz-sm);
  text-decoration: none;
}

.race__club:is(:hover, :focus-visible) {
  text-decoration: underline;
}

.race__club .crest {
  inline-size: 1.5rem;
  block-size: 1.5rem;
}

.race__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* the bar plus its value; the end padding keeps the leader's value inside */
.race__track {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  min-inline-size: 0;
  padding-inline-end: 4.5ch;
}

.race__bar {
  flex: none;
  inline-size: calc(var(--share) * 1%);
  block-size: calc(var(--race-row) - 0.75rem);
  border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
  background: var(--accent-hi);

  /* linear, like the total counting up beside it */
  transition: inline-size var(--race-step) linear;
}

.race__value {
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: var(--fz-xs);
  font-variant-numeric: tabular-nums;
}

.race__tip {
  position: absolute;
  z-index: 2;
  min-inline-size: 10rem;
  padding: var(--space-xs) var(--space-sm);
  border: var(--border-hairline) solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  color: var(--text-body);
  font-size: var(--fz-xs);
  pointer-events: none;
}

.race__tip strong {
  color: var(--text-primary);
}

.race__tip dl {
  display: grid;
  grid-template-columns: auto auto;
  gap: 0 var(--space-md);
  margin: var(--space-2xs) 0 0;
}

.race__tip dd {
  margin: 0;
  color: var(--text-primary);
  font-family: var(--font-mono);
  text-align: end;
}

.race__table {
  margin-block-start: var(--space-lg);
}

@media (width < 30rem) {
  .race__row {
    grid-template-columns: 7rem 1fr;
  }

  .race__club {
    font-size: var(--fz-xs);
  }
}

/* --- admin charts (/admin/charts/<name>/) ---------------------------
   Chart drafts. The pages run wider than other admin screens (body class
   admin-charts-*), each chart sits in a padded .chart-panel, and the panel
   carries the categorical palette: the dataviz reference palette, validated
   for CVD separation and contrast on these surfaces, stepped separately for
   dark and light. Identity never rests on colour alone — every chart has a
   legend or direct labels, a tooltip and a table view. */
body[class^='admin-charts'] .app-main {
  max-inline-size: 110rem;
}

/* beats the list indent of both shells — admin (.app-main > section:not([class])
   ul) and public (main:not(.app-main) ul) — so the cards line up with the text */
:is(main, .app-main) .chart-cards[role='list'] {
  margin-inline: 0;
  padding-inline-start: 0;
}

/* beats the in-text link underline (style.css: :is(p, li) a:not(.btn…)) */
:is(main, .app-main) .chart-cards[role='list'] .chart-card {
  text-decoration: none;
}

:is(main, .app-main) .chart-cards[role='list'] .chart-card:is(:hover, :focus-visible) strong {
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--accent-hi) 40%, transparent);
}

/* the short summary above a chart: one sentence per row, and its height
   held at --lines rows from the start, so the page doesn't jump when the
   data arrives */
.chart-note {
  display: grid;
  align-content: start;
  gap: var(--space-2xs);
  min-block-size: calc(var(--lines, 1) * 1lh + (var(--lines, 1) - 1) * var(--space-2xs));
  margin-block-start: var(--space-md);
  color: var(--text-body);
  line-height: var(--lh-body);
}

/* the bold face's taller metrics would otherwise grow a line by a pixel */
.chart-note strong {
  line-height: 1;
}

.chart-note__loading {
  color: var(--text-muted);
}

.chart-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
  margin-block-end: var(--space-lg);
}

.chart-tabs a {
  padding: var(--pad-sm) var(--pad-lg);
  border: var(--border-hairline) solid var(--line);
  border-radius: var(--radius-pill);
  color: var(--text-secondary);
  font-size: var(--fz-sm);
  text-decoration: none;
}

.chart-tabs a:is(:hover, :focus-visible) {
  border-color: var(--line-strong);
  color: var(--text-primary);
}

.chart-tabs a[aria-current='page'] {
  border-color: var(--accent-hi);
  background: var(--accent-wash);
  color: var(--text-primary);
}

.chart-panel {
  --part-1: #3987e5;
  --part-2: #d95926;
  --part-3: #199e70;
  --part-4: #c98500;
  --part-5: #d55181;
  --part-6: #008300;
  --part-7: #9085e9;

  /* sequential (heatmap): one blue ramp, low end receding to the surface */
  --heat-lo: #0d366b;
  --heat-hi: #b7d3f6;
  position: relative;
  margin-block: var(--space-xl);
  padding: clamp(1.25rem, 0.75rem + 2vw, 2.5rem);
  border: var(--border-hairline) solid var(--line);
  border-radius: var(--radius-card);
  background: var(--bg-card);
}

:root[data-theme='light'] .chart-panel {
  --part-1: #2a78d6;
  --part-2: #eb6834;
  --part-3: #1baf7a;
  --part-4: #eda100;
  --part-5: #e87ba4;
  --part-6: #008300;
  --part-7: #4a3aa7;
  --heat-lo: #cde2fb;
  --heat-hi: #0d366b;
}

/* beats the admin heading margin (.app-main > section:not([class]) h2) */
.app-main .chart-panel > h2:first-child {
  margin-block: 0 var(--space-xs);
}

.app-main .chart-panel__lead {
  margin-block: 0 var(--space-lg);
  color: var(--text-secondary);
  font-size: var(--fz-sm);
}

.chart-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-md);
}

/* a row, not a stacked form: no label + input / label + label spacing
   (that admin rule is :is(…, .cta) + :is(label, .cta), so class-strength) */
.app-main .chart-controls > * {
  margin: 0;
}

.chart-controls select {
  inline-size: 12rem;
}

.chart-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  cursor: pointer;
}

.app-main .chart-legend[role='list'] {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs) var(--space-lg);
  margin: var(--space-lg) 0;
  min-block-size: 1lh;
  padding: 0;
  color: var(--text-secondary);
  font-size: var(--fz-xs);
  list-style: none;
}

.chart-legend li {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  margin: 0;
}

.chart-swatch {
  inline-size: 0.75rem;
  block-size: 0.75rem;
  border-radius: 3px;
  background: var(--slot);
}

/* a bar and the value after it; the end padding keeps the longest value in */
.chart-track {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  min-inline-size: 0;
  padding-inline-end: var(--valpad, 0);
}

/* the 2px gaps between segments are the surface showing through */
.chart-bar {
  display: flex;
  gap: 2px;
  flex: none;
  block-size: 1.25rem;
  overflow: hidden;
  border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
}

.chart-seg {
  flex-basis: 0;
  min-inline-size: 2px;
  background: var(--slot);
}

[data-tip]:hover > .chart-seg,
.chart-seg[data-tip]:hover {
  filter: brightness(1.15);
}

.chart-value {
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: var(--fz-xs);
  font-variant-numeric: tabular-nums;
}

.chart-tip {
  position: absolute;
  z-index: 2;
  min-inline-size: 11rem;
  padding: var(--space-xs) var(--space-sm);
  border: var(--border-hairline) solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  color: var(--text-body);
  font-size: var(--fz-xs);
  pointer-events: none;
}

.chart-tip strong {
  color: var(--text-primary);
}

.chart-tip dl {
  display: grid;
  grid-template-columns: auto auto;
  gap: 0 var(--space-md);
  margin: var(--space-2xs) 0 0;
}

.chart-tip dd {
  margin: 0;
  color: var(--text-primary);
  font-family: var(--font-mono);
  text-align: end;
}

.chart-table .twrap .team-stats {
  min-inline-size: 0;
}

/* games of the season: the game (centred on its score, as in the tables)
   and its round, then the stacked bar */
.game-chart {
  --label: 22rem;
  --gap: var(--space-lg);
  --valpad: 4.5ch;
}

.game-chart__plot {
  --rows: 10;
  --row: 3.5rem;
  position: relative;
  display: grid;
  align-content: start;
  gap: var(--space-sm);
  padding-block-end: var(--space-lg);

  /* held at the chosen number of games before they load, so the page below
     doesn't jump (rows + gaps + the average label's foot room) */
  min-block-size: calc(
    var(--rows) * var(--row) + (var(--rows) - 1) * var(--space-sm) + var(--space-lg)
  );
}

.game-chart__row {
  display: grid;
  grid-template-columns: var(--label) 1fr;
  min-block-size: var(--row);
  align-items: center;
  gap: var(--gap);
}

.game-chart__game {
  display: grid;
  grid-template-columns: 1fr;
  align-items: center;
  padding: var(--space-2xs) var(--space-xs);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-primary);
  font: inherit;
  cursor: pointer;
}

.game-chart__game:is(:hover, :focus-visible) {
  background: var(--bg-elevated);
}

.game-chart__game .game,
.comeback .game {
  inline-size: 100%;
  margin-inline: 0;
}

.game-chart__game small {
  color: var(--text-muted);
  font-size: var(--fz-2xs);
  text-align: center;
}

.game-chart__plot .chart-bar {
  cursor: pointer;
}

/* the average game: a dashed rule through every bar, labelled at the foot */
.game-chart__avg {
  position: absolute;
  inset-block: 0;
  inset-inline-start: calc(
    var(--label) + var(--gap) + (100% - var(--label) - var(--gap) - var(--valpad)) * var(--avg)
  );
  border-inline-start: 1px dashed var(--text-muted);
  pointer-events: none;
}

.game-chart__avg span {
  position: absolute;
  inset-block-end: 0;
  inset-inline-start: var(--space-2xs);
  color: var(--text-muted);
  font-size: var(--fz-2xs);
  white-space: nowrap;
}

/* a ranked bar per club (drama, sources) */
.hbar-chart {
  --valpad: 0;
  --rows: 20;
  --row: 1.5rem;
  --extra: 0px;
  display: grid;
  align-content: start;
  gap: var(--space-xs);

  /* held at --rows rows (the page sets the club count) before they load */
  min-block-size: calc(
    var(--rows) * var(--row) + (var(--rows) - 1) * var(--space-xs) + var(--extra)
  );
}

.hbar-chart__row {
  display: grid;
  grid-template-columns: 12rem 1fr;
  min-block-size: var(--row);
  align-items: center;
  gap: var(--space-md);
}

.hbar-chart__row--league {
  min-block-size: calc(var(--row) + var(--space-sm) + var(--border-hairline));
  margin-block-end: var(--space-sm);
  padding-block-end: var(--space-sm);
  border-block-end: var(--border-hairline) solid var(--line);
  font-weight: 600;
}

.hbar-chart__label {
  display: flex;
  align-items: center;
  gap: var(--gap-sm);
  min-inline-size: 0;
  color: var(--text-primary);
  font-size: var(--fz-sm);
  white-space: nowrap;
}

.hbar-chart__label .crest {
  inline-size: 20px;
  block-size: 20px;
}

.hbar-chart__extra {
  color: var(--text-muted);
  font-size: var(--fz-xs);
  white-space: nowrap;
}

/* drama: room after the leader's bar for its value and comeback counts */
.hbar-chart--drama {
  --valpad: 15rem;
}

/* sources: every bar is 100%, its signature part named after it */

/* …plus the "All games" row's divider: its padding, border and margin */
.hbar-chart--share {
  --extra: calc(2 * var(--space-sm) + var(--border-hairline));
}

.hbar-chart--share .chart-bar {
  flex: 1;
}

.hbar-chart--share .hbar-chart__extra {
  inline-size: 9rem;
}

/* drama: the biggest comebacks, as cards with the lead after each goal */
.app-main :is(.comeback-grid, .save-grid)[role='list'] {
  --rows: 4;
  --card: 13rem;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
  grid-auto-rows: var(--card);
  gap: var(--space-md);

  /* --rows is set by charts.js from the grid's own width */
  min-block-size: calc(var(--rows) * var(--card) + (var(--rows) - 1) * var(--space-md));
  margin: 0;
  padding: 0;
  list-style: none;
}

/* beats the admin list item spacing (.app-main > section:not([class]) li) */
.app-main :is(.comeback-grid, .save-grid)[role='list'] > li {
  max-inline-size: none;
  margin: 0;
}

.comeback {
  display: grid;
  align-content: start;
  gap: var(--space-2xs);
  inline-size: 100%;
  block-size: 100%;
  padding: var(--space-md);
  border: var(--border-hairline) solid var(--line);
  border-radius: var(--radius-md);
  background: var(--bg-page);
  color: var(--text-primary);
  font: inherit;
  text-align: center;
  cursor: pointer;
}

.comeback:is(:hover, :focus-visible) {
  border-color: var(--line-strong);
}

.comeback small,
.comeback__scores {
  color: var(--text-muted);
  font-size: var(--fz-2xs);
}

.comeback__scores {
  min-block-size: 2lh;
  font-family: var(--font-mono);
}

.lead-line {
  inline-size: 100%;
  block-size: 4.5rem;
  margin-block: var(--space-xs);
}

.lead-line__home {
  fill: color-mix(in srgb, var(--part-1) 35%, transparent);
}

.lead-line__away {
  fill: color-mix(in srgb, var(--part-2) 35%, transparent);
}

.lead-line__zero {
  stroke: var(--text-muted);
  stroke-dasharray: 3 3;
  vector-effect: non-scaling-stroke;
}

.lead-line__line {
  fill: none;
  stroke: var(--text-primary);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}

/* finishing: crests on shots × goals, with the competition average dashed */
.scatter {
  inline-size: 100%;
  margin-block-start: var(--space-lg);
}

.scatter svg {
  display: block;
  overflow: visible;
}

.scatter__grid {
  stroke: var(--line);
}

.scatter__avg {
  stroke: var(--text-muted);
  stroke-dasharray: 4 4;
}

.scatter__tick,
.scatter__axis {
  fill: var(--text-secondary);
  font-size: 12px;
}

.scatter__axis {
  fill: var(--text-primary);
  font-weight: 600;
}

.scatter__corner {
  fill: var(--text-muted);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.scatter__hit {
  fill: transparent;
}

.scatter__dot {
  fill: var(--accent-hi);
}

.scatter__club {
  cursor: default;
}

.scatter__club:hover .scatter__hit {
  fill: var(--accent-wash);
  stroke: var(--accent-hi);
}

@media (width < 48rem) {
  .game-chart {
    --label: 0px;
    --gap: 0px;
  }

  .game-chart__row,
  .hbar-chart__row {
    grid-template-columns: 1fr;
  }

  .hbar-chart__row {
    gap: var(--space-2xs);
  }
}

/* keepers: the busiest games, shorter cards than the comebacks */
.app-main .save-grid[role='list'] {
  --card: 8.5rem;
}

.save-card__total {
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: var(--fz-lg);
  font-weight: 700;
}

.save-card__split {
  display: flex;
  justify-content: center;
  gap: var(--space-md);
  color: var(--text-muted);
  font-size: var(--fz-2xs);
}

.hbar-chart--keepers {
  --valpad: 4ch;
}

/* rank over time: grey lines, up to three clubs in colour, labels at the end.
   Height = rows × 26px + 56px, as BUMP_ROW / BUMP_PAD in charts.js. */
.bump {
  --rows: 20;
  min-block-size: calc((var(--rows) - 1) * 26px + 56px);
  margin-block-start: var(--space-md);
}

.bump svg {
  display: block;
  overflow: visible;
}

.bump__line {
  fill: none;
  stroke: var(--line-strong);
  stroke-width: 1.5;
  stroke-linejoin: round;
}

.bump__line--hover {
  stroke: var(--text-secondary);
  stroke-width: 2;
}

.bump__line--on {
  stroke: var(--slot);
  stroke-width: 3;
}

.bump__hit {
  fill: none;
  stroke: transparent;
  stroke-width: 12;
  cursor: pointer;
}

.bump__label {
  cursor: pointer;
}

.bump__label text {
  fill: var(--text-secondary);
  font-size: 12px;
}

.bump__label--on text,
.bump__label:hover text {
  fill: var(--text-primary);
  font-weight: 700;
}

.bump__key {
  fill: var(--slot);
}

/* heatmap: a club column, then one square-ish cell per round; scrolls
   sideways when the rounds don't fit */
.heat-scroll {
  overflow-x: auto;
  margin-block-start: var(--space-md);
}

.heat {
  --rows: 20;
  --cols: 38;
  --cell: 1.5rem;
  display: grid;
  grid-template-columns: 9rem repeat(var(--cols), minmax(1.1rem, 1fr));
  grid-auto-rows: var(--cell);
  gap: 2px;
  min-inline-size: calc(9rem + var(--cols) * 1.2rem);
  min-block-size: calc((var(--rows) + 1) * var(--cell) + var(--rows) * 2px);
}

.heat__club {
  display: flex;
  align-items: center;
  gap: var(--gap-sm);
  overflow: hidden;
  color: var(--text-primary);
  font-size: var(--fz-xs);
  white-space: nowrap;
}

.heat__club .crest {
  inline-size: 18px;
  block-size: 18px;
}

.heat__round {
  align-self: end;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: var(--fz-2xs);
  text-align: center;
}

.heat__cell {
  padding: 0;
  border: 0;
  border-radius: 3px;
  background: color-mix(in oklab, var(--heat-hi) var(--t), var(--heat-lo));
  cursor: pointer;
}

.heat__cell:is(:hover, :focus-visible) {
  outline: 2px solid var(--text-primary);
  outline-offset: -1px;
}

.heat__cell--empty {
  background: none;
  border: 1px dashed var(--line);
  cursor: default;
}

.heat-legend {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  min-block-size: 1lh;
  margin-block-end: var(--space-md);
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: var(--fz-xs);
}

.heat-legend__ramp {
  position: relative;
  inline-size: 12rem;
  block-size: 0.75rem;
  border-radius: 3px;
  background: linear-gradient(to right, var(--heat-lo), var(--heat-hi));
}

/* the median sits at the ramp's middle, since cells are coloured by rank */
.heat-legend__ramp span {
  position: absolute;
  inset-block-start: 100%;
  inset-inline-start: 50%;
  translate: -50% 0;
  color: var(--text-muted);
  font-size: var(--fz-2xs);
  white-space: nowrap;
}

.heat-legend__ramp::after {
  content: '';
  position: absolute;
  inset-block: -2px;
  inset-inline-start: 50%;
  border-inline-start: 1px solid var(--text-primary);
}

.heat-legend__label {
  margin-inline-start: var(--space-xs);
  font-family: var(--font-body);
}

/* home vs away: two dots per club joined by a bar, on one shared axis */
.dumbbell {
  --rows: 20;
  --row: 1.75rem;
  display: grid;
  align-content: start;
  gap: var(--space-2xs);
  margin-block-start: var(--space-md);
  min-block-size: calc((var(--rows) + 1) * var(--row) + var(--rows) * var(--space-2xs));
}

.dumbbell__row {
  display: grid;
  grid-template-columns: 12rem 1fr 6ch;
  align-items: center;
  gap: var(--space-md);
  min-block-size: var(--row);
}

.dumbbell__track {
  position: relative;
  block-size: var(--row);
}

.dumbbell__row--axis .dumbbell__track {
  block-size: var(--row);
}

.dumbbell__tick {
  position: absolute;
  inset-block-end: 0;
  translate: -50% 0;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: var(--fz-2xs);
}

.dumbbell__grid {
  position: absolute;
  inset-block: 0;
  border-inline-start: 1px solid var(--line);
}

.dumbbell__bar {
  position: absolute;
  inset-block-start: calc(50% - 1px);
  block-size: 2px;
  background: var(--text-muted);
}

.dumbbell__dot {
  position: absolute;
  inset-block-start: 50%;
  inline-size: 0.75rem;
  block-size: 0.75rem;
  border: 2px solid var(--bg-card);
  border-radius: 50%;
  background: var(--slot);
  translate: -50% -50%;
}

@media (width < 48rem) {
  .dumbbell__row {
    grid-template-columns: 7rem 1fr 5ch;
    gap: var(--space-xs);
  }
}

/* --- fact and story cards (admin Insights and chart pages) -------------
   Fact cards are drawn from their labels before the data loads, at a fixed
   height, so filling them in never moves the page. The selectors carry
   .app-main + [role='list'] to beat the admin prose rules for lists, list
   items and headings (.app-main > section:not([class]) …). */
.fact-groups,
.story-groups {
  display: grid;
  gap: var(--space-xl);
  margin-block: var(--space-lg) var(--space-xl);
}

/* story cards depend on the data: hold a couple of rows while loading */
.story-groups {
  min-block-size: 18rem;
  align-content: start;
}

.app-main .fact-group > .fact-group__title {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin: 0 0 var(--space-sm);
  color: var(--text-secondary);
  font-family: var(--font-body);
  font-size: var(--fz-sm);
  font-weight: 700;
  letter-spacing: var(--tracking-over);
  text-transform: uppercase;
}

.fact-group__title .icon {
  --icon-duo: 0.3;
  font-size: 1.25em;
  color: var(--accent-hi);
}

.app-main :is(.fact-grid, .story-grid)[role='list'] {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
  gap: var(--space-md);
  margin: 0;
  padding: 0;
  list-style: none;
}

.app-main .fact-grid[role='list'] {
  grid-auto-rows: 8.75rem;
}

.app-main .story-grid[role='list'] {
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 19rem), 1fr));
}

.app-main :is(.fact-grid, .story-grid)[role='list'] > li {
  max-inline-size: none;
  margin: 0;
}

.fact-card {
  display: grid;
  grid-template-rows: auto auto 1fr;
  align-content: start;
  gap: var(--space-3xs);
  inline-size: 100%;
  block-size: 100%;
  padding: var(--space-md);
  border: var(--border-hairline) solid var(--line);
  border-radius: var(--radius-card);
  background: var(--bg-card);
  color: var(--text-body);
  font: inherit;
  text-align: start;
}

button.fact-card {
  cursor: pointer;
  transition: border-color var(--dur) var(--ease);
}

button.fact-card:is(:hover, :focus-visible) {
  border-color: var(--accent-hi);
}

.fact-card__value {
  overflow: hidden;
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: var(--fz-xl);
  font-weight: 700;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.fact-card__label {
  color: var(--text-primary);
  font-size: var(--fz-sm);
  font-weight: 600;
}

/* the game or context: two lines at most, so every card keeps its height */
.fact-card__detail {
  display: -webkit-box;
  overflow: hidden;
  color: var(--text-muted);
  font-size: var(--fz-xs);
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.fact-card--loading .fact-card__value,
.fact-card--none .fact-card__value {
  color: var(--text-muted);
}

.fact-card--none .fact-card__value {
  font-size: var(--fz-lg);
}

.story-card {
  display: grid;
  align-content: start;
  gap: var(--space-2xs);
  padding: var(--space-md);
  border: var(--border-hairline) solid var(--line);
  border-radius: var(--radius-card);
  background: var(--bg-card);
}

.story-card__title {
  color: var(--text-primary);
  font-size: var(--fz-md);
}

.story-card__body {
  color: var(--text-secondary);
  font-size: var(--fz-sm);
}
