/* ============================================================================
   MODULE 14 — DESIGN TOKENS. THE SINGLE SOURCE OF TRUTH FOR HOW RUSS LOOKS.
   ============================================================================

   These are the WC RUSS tokens, carried across verbatim — names and hex values
   both — because the identity is proven and Module 14's brief is explicit that
   it must not be reinvented. Module 12 shipped a provisional palette with
   different names (`--bg`, `--surface`, `--accent`, `--gold`, `--pink`); every
   component was migrated to the names below in one pass, so the site now speaks
   ONE vocabulary rather than a canonical set plus an alias layer.

   ONE DELIBERATE DEVIATION, MEASURED — `--text-muted`
   --------------------------------------------------
   The brief asks for the palette verbatim AND for WCAG AA contrast. On this
   palette those two requirements conflict in exactly one place. Measured
   (relative luminance, WCAG 2.1):

       #7a86a8 on --bg-primary     5.32  ✓
       #7a86a8 on --bg-card        4.74  ✓
       #7a86a8 on --bg-card-hover  4.27  ✗   AA normal text needs 4.5

   That pairing is reachable, not theoretical: `.log tr:hover td` and
   `.table .row.clickable:hover` both swap the row to `--bg-card-hover`, and
   both carry muted text. So the token is lightened to the SMALLEST value that
   clears 4.5 on all three surfaces: hue 224.3° and saturation 0.209 are
   unchanged, HSL lightness moves 0.569 → 0.585. Every other token in this
   block is byte-for-byte the brief's.

       #7f8aab on --bg-primary     5.62  ✓
       #7f8aab on --bg-card        5.00  ✓
       #7f8aab on --bg-card-hover  4.51  ✓

   An accessibility floor outranks two hex digits of fidelity, and the check is
   executable rather than asserted: `tests/test_design_tokens.py` recomputes
   every foreground/background pair and fails if any drops below AA.
   ========================================================================= */

:root {
  /* ---- Backgrounds ---- */
  --bg-primary:      #080c14;   /* deep obsidian canvas */
  --bg-card:         #111722;   /* slate card surface */
  --bg-card-hover:   #161e2c;   /* hover elevated state */
  --bg-card-raised:  #1d273a;   /* nested sub-panel / cell surface */

  /* ---- Accents ---- */
  --accent-green:    #10b981;   /* emerald 500 — positive / home / win */
  --accent-red:      #fb5574;   /* rose calibrated (5.30:1 AA) — away / loss / relegation */
  --accent-gold:     #fbbf24;   /* amber 400 — champion / highlight / 1st place */
  --accent-blue:     #38bdf8;   /* sky 400 — draw / market benchmark / info */
  --accent-highlight:#818cf8;   /* indigo 400 — active focus / selection */

  /* ---- Medals ---- */
  --medal-gold:      var(--accent-gold);
  --medal-silver:    #cbd5e1;   /* silver slate */
  --medal-bronze:    #fb923c;   /* bronze orange */

  /* ---- Text: 3-tier hierarchy ---- */
  --text-primary:    #f8fafc;   /* slate 50 — crisp main text */
  --text-secondary:  #cbd5e1;   /* slate 300 — labels & secondary text */
  --text-muted:      #94a3b8;   /* slate 400 — de-emphasised & captions */

  /* Channel triples for the heat-map tones */
  --rgb-green: 16, 185, 129;    /* --accent-green */
  --rgb-gold:  251, 191, 36;    /* --accent-gold  */
  --rgb-red:   251, 85, 116;    /* --accent-red   */
  --rgb-blue:  56, 189, 248;    /* --accent-blue  */

  /* ---- Structure ---- */
  --border-color:     #1e293b;  /* slate 800 subtle borders */
  --border-highlight: #2d3b55;  /* slate 700 elevated borders */
  --radius:     10px;
  --radius-sm:  6px;
  --shadow:     0 4px 24px rgba(0, 0, 0, 0.45);

  /* ---- Derived surfaces ---- */
  --accent-soft: rgba(16, 185, 129, 0.10);
  --accent-dim:  #059669;
  --bg-blur:     rgba(8, 12, 20, 0.94);
  --scrim:       rgba(0, 0, 0, 0.72);

  /* ---- Typography ---- */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* Font sizes */
  --fs-micro: 0.65rem;
  --fs-xs:    0.72rem;
  --fs-sm:    0.85rem;
  --fs-md:    0.92rem;
  --fs-lg:    1.15rem;
  --fs-xl:    1.45rem;
  --fs-2xl:   2rem;

  /* ---- Spacing and motion ---- */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 24px; --sp-6: 32px; --sp-7: 48px;
  --fade: 200ms ease;
  --fill: 450ms cubic-bezier(0.22, 1, 0.36, 1);

  /* The single sticky header, #279. Everything that sticks (.subnav, sticky
     table heads) offsets by it, so it must equal the header's real height. */
  --nav-height: 56px;
}


* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg-primary);
  color: var(--text-primary);
  font-family: var(--font);
  font-size: var(--fs-md);
  line-height: 1.6;                 /* brief Part 1 */
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
h1 { font-size: var(--fs-xl); }
h2 { font-size: var(--fs-lg); }
h3 { font-size: var(--fs-md); }
/* #109's sub-heading inside a modal panel that already owns the h3. Sized one
   step down so the hierarchy is visible, not just semantic. */
h4 { font-size: var(--fs-sm); color: var(--text-secondary); }
a { color: var(--accent-green); }

/* ---------------------------------------------------------------- header

   BACKLOG #279. MEASURED 2026-09-05, before this change: the masthead was
   113.5px on desktop and 127.5px on a phone, and `nav.tabs` added 56.8px and
   61px UNDER it — 170.2px of a 900px viewport (18.9%) and 188.5px of an 844px
   one (22.3%), before a single number. The operator's "roughly 20% of vertical
   screen real estate" was exact.

   They are one strip now. The brand, the season and the mood avatar sit on the
   left at a fixed width; the tab pills take the remaining width and scroll
   inside it, which is what they already did — the scroll container simply
   stopped owning a whole row of its own.

   `--nav-height` MUST track this element: every sticky thing on the site
   (`.subnav`, `.table .row.head`) offsets by it, and the header is now the
   only sticky chrome there is. */
.masthead {
  position: sticky; top: 0; z-index: 20;
  /* `--nav-height` is the TOTAL height including the border, because every
     sticky element on the site offsets by it. box-sizing makes that true
     rather than approximately true: before #279 the header measured 57.8px
     against a 57px token and the 0.8px was simply unnoticed. */
  box-sizing: border-box; height: var(--nav-height);
  border-bottom: 1px solid var(--border-color);
  background: var(--bg-blur);
  backdrop-filter: blur(8px);
  padding: 0 var(--sp-5);
}
.masthead-inner {
  max-width: 1180px; margin: 0 auto;
  display: flex; align-items: center; gap: var(--sp-4);
  height: 100%;
}
.brand {
  display: flex; align-items: center; gap: var(--sp-2);
  flex-shrink: 0; text-decoration: none; color: inherit;
  border-radius: var(--radius-sm);
}
.brand:hover .brand-name { opacity: 0.85; }
.brand:focus-visible { outline: 2px solid var(--accent-green); outline-offset: 3px; }
/* The mascot. `alt=""` and aria-hidden because the adjacent text already names
   the site — a screen reader announcing "Russ the walrus, RUSS EPL" is noise.
   Swapped to the model's current mood by app.js. */
.brand-avatar {
  width: 34px; height: 34px; flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: var(--bg-card-raised);
  border: 1px solid var(--border-color);
  padding: 1px;
}
.brand-name { font-size: var(--fs-lg); font-weight: 650; letter-spacing: -0.01em; white-space: nowrap; }
.brand .mark {
  /* Brief Part 1: weight 900, letter-spacing 0.15em, rendered as a green->blue
     gradient via background-clip. `color: transparent` is what lets the
     gradient show through the glyphs; the plain colour underneath it is the
     fallback for any engine that does not support background-clip: text. */
  color: var(--accent-green);
  font-weight: 900;
  letter-spacing: 0.15em;
  background: linear-gradient(90deg, var(--accent-green), var(--accent-blue));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
@supports not ((background-clip: text) or (-webkit-background-clip: text)) {
  .brand .mark { -webkit-text-fill-color: var(--accent-green); }
}
.brand .season { color: var(--text-muted); font-size: var(--fs-xs); white-space: nowrap; }
/* What the brand sheds as the screen narrows, in order of how little it costs.
   MEASURED at 390px before this rule: brand + name + season took 184px of the
   390px row and left ONE tab pill fully visible, which is worse than the 2 the
   stacked layout managed. The avatar alone is 34px and still carries the mood,
   so the wordmark is what goes — the browser tab, the page title and the
   footer all still say RUSS EPL. */
@media (max-width: 900px) { .brand .season { display: none; } }
@media (max-width: 620px) { .brand-name { display: none; } }

.disclosure {
  margin-top: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border-highlight);
  border-left: 3px solid var(--accent-gold);
  border-radius: var(--radius-sm);
  background: var(--bg-card);
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}
.disclosure strong { color: var(--text-primary); }

.disclosure.quip-disclosure {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
}
.disclosure-inner {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  width: 100%;
}
.disclosure-avatar {
  width: 36px;
  height: 36px;
  border-radius: var(--radius-sm);
  background: var(--bg-card-raised);
  border: 1px solid var(--border-color);
  flex-shrink: 0;
  padding: 2px;
}
.disclosure-text {
  flex: 1;
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-secondary);
}
.disclosure-text strong {
  color: var(--accent-gold);
  display: block;
  margin-bottom: 2px;
  font-weight: 700;
}

/* ------------------------------------------------------------------- nav */
/* Inside `.masthead` since #279, so it no longer owns a row, no longer sticks
   on its own and no longer paints its own background — it is the right-hand
   part of the header strip. `min-width: 0` is what actually lets it scroll:
   without it the flex item refuses to shrink below its content and pushes the
   brand off the left edge instead. */
.tabs {
  flex: 1 1 auto; min-width: 0;
  display: flex; gap: var(--sp-1);
  overflow-x: auto;
  scrollbar-width: none;                 /* the fade below IS the affordance */
}
.tabs::-webkit-scrollbar { display: none; }

/* BACKLOG #223. MEASURED at 390x844: the strip is 1008px inside a 366px
   container, so SIX of the eight tabs sat off-screen. It scrolled — but with
   no scrollbar, no arrow and no partially-visible tab, nothing said so, and a
   phone reader saw "Season Overview · Predicted Table" and no reason to think
   Match Predictions, RUSS vs Market, Betting Record, Model Accuracy,
   Projections or Teams existed at all.
   The fade is driven by `data-overflow`, set by `syncTabOverflow()` in app.js,
   so it appears ONLY on the side that actually has more to reach and vanishes
   at each end — a permanent fade would lie about the last tab. `app.js` also
   scrolls the active tab into view, which is the other half of the fix: land
   on Teams and you see tabs on BOTH sides, which teaches the strip scrolls.

   `black` here is NOT a palette colour and must never become a token: a mask
   uses only the ALPHA channel of its gradient, so the stops mean "opaque" and
   "transparent", nothing more, and they must stay fixed if the palette is
   ever restyled. Written as keywords rather than hex for exactly that reason —
   `tests/test_design_tokens.py` forbids colour literals outside `:root`, and
   it is right to, but its rule is about colours and this is an alpha ramp. */
.tabs[data-overflow="right"] { mask-image: linear-gradient(to right, black calc(100% - 44px), transparent); }
.tabs[data-overflow="left"]  { mask-image: linear-gradient(to right, transparent, black 44px); }
.tabs[data-overflow="both"]  { mask-image: linear-gradient(to right, transparent, black 44px, black calc(100% - 44px), transparent); }

.tabs-inner { display: flex; gap: var(--sp-1); margin: 0 0 0 auto; }
.tab-btn {
  appearance: none; border: 1px solid transparent; background: transparent;
  color: var(--text-secondary); font: inherit; font-size: var(--fs-sm);
  padding: var(--sp-1) var(--sp-3); border-radius: 999px;   /* pill, brief Part 1 */
  cursor: pointer; white-space: nowrap; transition: color var(--fade), background var(--fade);
  display: inline-flex; align-items: center; justify-content: center;
  /* WCAG 2.5.8 wants 24px; the layout harness asserts it. 36px keeps the pill
     comfortably above that inside a 57px row. */
  min-height: 36px;
}
.tab-label-full { display: inline; }
.tab-label-mobile { display: none; }
.tab-btn:hover { color: var(--text-primary); background: var(--bg-card-hover); }
.tab-btn[aria-selected="true"] {
  color: var(--bg-primary); background: var(--accent-green); font-weight: 620;
}

/* Sub-view switch inside a tab — the Predicted Table's three views (operator,
   2026-08-27). Deliberately quieter than `.tab-btn`: these are views of one
   page, not pages, and giving them the same weight as the main navigation
   would read as six tabs rather than three-inside-one. Tokens only. */
.chip {
  appearance: none; border: 1px solid var(--border-color); background: transparent;
  color: var(--text-secondary); font: inherit; font-size: var(--fs-xs);
  padding: var(--sp-1) var(--sp-3); border-radius: 999px;
  min-height: 36px; display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; white-space: nowrap;
  transition: color var(--fade), background var(--fade), border-color var(--fade);
}
.chip:hover { color: var(--text-primary); background: var(--bg-card-hover); }
.chip.active {
  color: var(--bg-primary); background: var(--accent-green);
  border-color: var(--accent-green); font-weight: 620;
}

/* In-page section navigation (BACKLOG #231) — sticky jump links for dense tabs */
.subnav {
  position: sticky;
  top: var(--nav-height);
  z-index: 4;
  background: var(--bg-primary);
  border-bottom: 1px solid var(--border-color);
  padding: var(--sp-2) 0;
  margin-bottom: var(--sp-4);
}
.subnav-track {
  display: flex;
  gap: var(--sp-2);
  overflow-x: auto;
  white-space: nowrap;
  scrollbar-width: none;
}
.subnav-track::-webkit-scrollbar {
  display: none;
}
.section[id] {
  scroll-margin-top: calc(var(--nav-height) + 48px);
}

/* Paired dot plot — BACKLOG #129's comparison view, beside the radar. One row
   an axis, ONE shared baseline, both clubs on it, and the gap drawn as the rule
   between them. This is the chart that answers "who is better at what"; the
   radar cannot, because its values sit on spokes with no common baseline.
   Tokens only, per Module 14. */
.dotplot-rows { display: grid; gap: var(--sp-2); margin-top: var(--sp-2); }
.dotrow { display: grid; grid-template-columns: 8.5em 1fr 3em; align-items: center; gap: var(--sp-2); font-size: var(--fs-xs); }
.dotrow-label { color: var(--text-secondary); }
.dotrow-track { display: block; min-width: 0; }
.dotrow-gap { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.dotplot { width: 100%; height: 12px; display: block; overflow: visible; }
.dotplot .axis { stroke: var(--border-color); stroke-width: 1; }
.dotplot .dotgap { stroke: var(--border-highlight); stroke-width: 3; }
.dotplot .dot { fill: var(--accent-green); }
.dotplot .dot.market { fill: var(--accent-red); }

/* BACKLOG #311 Part 4: Adaptive hybrid radar layout */
.radar-desktop-multiples {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.radar-multiple-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: var(--sp-2);
  background: var(--bg-card);
  border-radius: var(--radius);
  border: 1px solid var(--border-color);
}
.radar-subheading {
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-secondary);
  margin: 0 0 var(--sp-1) 0;
}
.radar-mobile-control {
  display: none;
}
@media (max-width: 768px) {
  .radar-desktop-multiples {
    display: none !important;
  }
  .radar-mobile-control {
    display: block !important;
  }
}

/* ------------------------------------------------------------------ main */
main { max-width: 1180px; margin: 0 auto; padding: var(--sp-5); }
.panel { animation: fade var(--fade) both; }
@keyframes fade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; } }

.section { margin-bottom: var(--sp-6); }
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); margin-bottom: var(--sp-3); flex-wrap: wrap; }
.section-note { color: var(--text-muted); font-size: var(--fs-xs); }

.card {
  background: var(--bg-card); border: 1px solid var(--border-color);
  border-radius: var(--radius); padding: var(--sp-4);
  transition: border-color var(--fade), transform var(--fade),
              background var(--fade);
}
/* Brief Part 1: hover lifts 2px and the border turns green. Applied only to
   cards that DO something when clicked — a hover affordance on a static panel
   promises an interaction that is not there. */
.card.clickable:hover, .card.interactive:hover {
  background: var(--bg-card-hover);
  border-color: var(--accent-green);
  transform: translateY(-2px);
}
.grid { display: grid; gap: var(--sp-3); }
.grid.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid.cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid.cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }

.stat { background: var(--bg-card); border: 1px solid var(--border-color); border-radius: var(--radius); padding: var(--sp-3) var(--sp-4); }
.stat .label { color: var(--text-muted); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.06em; }
.stat .value { font-size: var(--fs-xl); font-weight: 680; margin-top: var(--sp-1); }
.stat .sub { color: var(--text-secondary); font-size: var(--fs-xs); }
.value-good { color: var(--accent-green); }
.value-bad { color: var(--accent-red); }
.value-gold { color: var(--accent-gold); }
/* BACKLOG #284. A club whose outlook has not moved is not a green or a red
   result — it is a non-event, and colouring it either way would read as one. */
.value-flat { color: var(--text-muted); }

/* #284: the Δ vs pre-season column, and the info buttons on the abbreviated
   headings. `col-head` keeps the label and its ⓘ on one baseline so the header
   row does not grow a second line. */
.col-head { display: inline-flex; align-items: baseline; gap: 2px; justify-content: flex-end; }
.delta-cell { font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------- commentary */
.quip {
  display: flex;
  gap: var(--sp-3);
  align-items: flex-start;
  border-left: 3px solid var(--accent-green);
  background: var(--accent-soft);
  padding: var(--sp-3) var(--sp-4);
  border-radius: 0 var(--radius) var(--radius) 0;
  color: var(--text-primary);
  font-size: var(--fs-sm);
}
.quip-avatar {
  width: 48px;
  height: 48px;
  border-radius: var(--radius-sm);
  background: var(--bg-card-raised);
  border: 1px solid var(--border-color);
  flex-shrink: 0;
  padding: 2px;
}
.quip-content {
  flex: 1;
  min-width: 0;
}
.quip-header {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  margin-bottom: var(--sp-1);
}
.quip .who {
  color: var(--accent-green);
  font-weight: 650;
  letter-spacing: 0.02em;
}
.quip .quip-role {
  color: var(--text-muted);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.quip-text {
  margin: 0;
  line-height: 1.5;
  color: var(--text-primary);
}
.quip .placeholder-flag {
  color: var(--text-muted);
  font-size: var(--fs-xs);
  display: block;
  margin-top: var(--sp-2);
  font-style: italic;
}

/* Full composite commentary (e.g. team profile overview) */
.quip-full {
  display: flex;
  gap: var(--sp-4);
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-left: 4px solid var(--accent-green);
  padding: var(--sp-4);
  border-radius: var(--radius);
}
.quip-full .quip-sidebar {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  flex-shrink: 0;
  min-width: 84px;
}
.quip-full .quip-avatar {
  width: 64px;
  height: 64px;
  border-radius: var(--radius);
  background: var(--bg-card-hover);
  border: 1px solid var(--border-color);
  padding: 4px;
}
.quip-full .quip-byline {
  text-align: center;
}
.quip-full .quip-body {
  flex: 1;
  min-width: 0;
}
.quip-sections {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--sp-3);
}
.quip-section-item {
  background: var(--accent-soft);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  padding: var(--sp-3);
}
.quip-section-label {
  color: var(--accent-green);
  font-size: var(--fs-xs);
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: var(--sp-1);
}

/* Compact quip in match cards */
.card-quip {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  background: var(--accent-soft);
  border-radius: var(--radius-sm);
  padding: var(--sp-1) var(--sp-2);
  margin-top: var(--sp-2);
  font-size: var(--fs-xs);
  color: var(--text-secondary);
  min-width: 0;
  width: 100%;
  box-sizing: border-box;
}
.card-quip-avatar {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--bg-card-raised);
}
.card-quip-text {
  flex: 1 1 0%;
  min-width: 0;
  white-space: normal;
  line-height: 1.35;
  font-size: var(--fs-xs);
}

/* ------------------ Headline Quip (One prominent headline per tab) */
.quip.headline-quip, .quip[id^="headline-quip"] {
  display: flex;
  gap: var(--sp-4);
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-left: 4px solid var(--accent-green);
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--radius);
  margin-bottom: var(--sp-4);
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
}
.headline-quip-sidebar {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  flex-shrink: 0;
  min-width: 80px;
}
.headline-quip-avatar {
  width: 56px;
  height: 56px;
  border-radius: var(--radius-sm);
  background: var(--bg-card-raised);
  border: 1.5px solid var(--border-color);
  padding: 3px;
}
.headline-quip-byline {
  text-align: center;
}
.headline-quip-byline .who {
  display: block;
  font-weight: 700;
  color: var(--accent-green);
  font-size: var(--fs-sm);
  letter-spacing: 0.03em;
}
.headline-quip-byline .quip-role {
  color: var(--text-muted);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.headline-quip-body {
  flex: 1;
  min-width: 0;
}
.headline-quip-title {
  font-size: var(--fs-base);
  font-weight: 700;
  color: var(--text-primary);
  margin-bottom: var(--sp-1);
  letter-spacing: -0.01em;
}

/* ------------------ Inline RUSS Explainer / Note */
.russ-inline-note {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  background: var(--accent-soft);
  border: 1px solid var(--border-color);
  border-left: 3px solid var(--accent-green);
  border-radius: var(--radius-sm);
  padding: var(--sp-2) var(--sp-3);
  margin: var(--sp-2) 0;
  font-size: var(--fs-xs);
  color: var(--text-secondary);
  line-height: 1.5;
}
.russ-inline-avatar {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--bg-card-raised);
  border: 1px solid var(--border-color);
  margin-top: 1px;
}
.russ-inline-content {
  flex: 1;
  min-width: 0;
}
.russ-inline-label {
  font-weight: 700;
  color: var(--accent-green);
  margin-right: var(--sp-1);
}
.russ-inline-text {
  color: var(--text-primary);
}

/* -------------------------------------------------- hero prediction banner */
.hero-prediction-card {
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-top: 4px solid var(--accent-gold);
  border-radius: var(--radius);
  padding: var(--sp-4) var(--sp-5);
  margin-bottom: var(--sp-4);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.hero-prediction-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--border-color);
}
.hero-badge-group {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.hero-prediction-tag {
  color: var(--text-muted);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
}
.hero-factors-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}
.hero-factor-card {
  background: var(--bg-card-raised);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  padding: var(--sp-3) var(--sp-4);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  transition: transform var(--speed-fast), border-color var(--speed-fast);
}
.hero-factor-card:hover {
  border-color: var(--border-highlight);
  background: var(--bg-card-hover);
}
.hero-factor-card.gold-factor {
  border-top: 3px solid var(--accent-gold);
}
.hero-factor-card.blue-factor {
  border-top: 3px solid var(--accent-blue);
}
.hero-factor-card.red-factor {
  border-top: 3px solid var(--accent-red);
}
.hero-factor-card.indigo-factor {
  border-top: 3px solid var(--accent-highlight);
}
.hero-factor-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--sp-2);
}
.hero-factor-category {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
  color: var(--text-muted);
}
.hero-factor-card.gold-factor .hero-factor-category { color: var(--accent-gold); }
.hero-factor-card.blue-factor .hero-factor-category { color: var(--accent-blue); }
.hero-factor-card.red-factor .hero-factor-category { color: var(--accent-red); }
.hero-factor-card.indigo-factor .hero-factor-category { color: var(--accent-highlight); }

.hero-factor-headline {
  margin-bottom: var(--sp-2);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.hero-factor-team {
  font-size: var(--fs-base);
  font-weight: 700;
}
.hero-factor-metric-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.hero-factor-metric-main {
  font-size: var(--fs-lg);
  font-weight: 800;
  color: var(--text-primary);
}
.hero-factor-metric-label {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.hero-factor-bar-wrap {
  height: 6px;
  border-radius: 999px;
  background: var(--border-color);
  overflow: hidden;
  margin-bottom: var(--sp-3);
}
.hero-factor-bar-fill {
  height: 100%;
  border-radius: 999px;
  transition: width var(--fill);
}
.hero-factor-bar-fill.gold { background: var(--accent-gold); }
.hero-factor-bar-fill.blue { background: var(--accent-blue); }
.hero-factor-bar-fill.red { background: var(--accent-red); }
.hero-factor-bar-fill.indigo { background: var(--accent-highlight); }

.hero-factor-detail {
  font-size: var(--fs-xs);
  color: var(--text-secondary);
  line-height: 1.45;
  border-top: 1px solid var(--border-color);
  padding-top: var(--sp-2);
  margin-top: auto;
}
.hero-factor-detail strong {
  color: var(--text-primary);
}

.hero-narrative-container {
  display: flex;
  gap: var(--sp-4);
  align-items: flex-start;
  background: var(--bg-card-raised);
  border: 1px solid var(--border-color);
  border-left: 3px solid var(--accent-gold);
  border-radius: var(--radius-sm);
  padding: var(--sp-3) var(--sp-4);
}
.hero-narrative-avatar-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
  flex-shrink: 0;
}
.hero-narrative-avatar {
  width: 52px;
  height: 52px;
  border-radius: var(--radius-sm);
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  padding: 3px;
}
.hero-narrative-byline {
  text-align: center;
}
.hero-narrative-byline .who {
  display: block;
  font-weight: 700;
  color: var(--accent-gold);
  font-size: var(--fs-xs);
  letter-spacing: 0.03em;
}
.hero-narrative-byline .quip-role {
  color: var(--text-muted);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.hero-narrative-text {
  flex: 1;
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--text-primary);
  margin: 0;
}

/* ------------------------------------------------------------- prob bars */
.probbar { display: flex; height: 10px; border-radius: 999px; overflow: visible; background: var(--bg-card-raised); width: 100%; min-width: 0; box-sizing: border-box; position: relative; }
.probbar-fills { display: flex; height: 100%; width: 100%; border-radius: 999px; overflow: hidden; }
.probbar-fills > span { display: block; transition: width var(--fill); }
.probbar > span { display: block; transition: width var(--fill); }
.probbar .h { background: var(--accent-green); }
.probbar .d { background: var(--accent-blue); }
.probbar .a { background: var(--accent-red); }
.market-tick { position: absolute; top: -2px; bottom: -2px; width: 2px; background: var(--text-primary); border-radius: 1px; pointer-events: none; z-index: 2; transform: translateX(-50%); box-shadow: 0 0 2px var(--bg-card); }
.probbar-legend { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-2); font-size: var(--fs-xs); color: var(--text-secondary); margin-top: var(--sp-1); width: 100%; min-width: 0; box-sizing: border-box; }
.probbar-legend .h { color: var(--accent-green); white-space: nowrap; display: inline-flex; align-items: baseline; gap: 3px; }
.probbar-legend .d { color: var(--accent-blue); white-space: nowrap; display: inline-flex; align-items: baseline; gap: 3px; }
.probbar-legend .a { color: var(--accent-red); white-space: nowrap; display: inline-flex; align-items: baseline; gap: 3px; justify-content: flex-end; }
.probbar-legend .mkt-tag { color: var(--text-muted); font-size: var(--fs-micro); font-variant-numeric: tabular-nums; letter-spacing: 0.02em; font-weight: 500; }
.prob-legend-hint { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-xs); color: var(--text-muted); padding: 2px var(--sp-2); border-radius: var(--radius-sm); background: var(--bg-card-raised); border: 1px solid var(--border-color); white-space: nowrap; }
.prob-legend-hint .key-russ { color: var(--text-primary); font-weight: 600; display: inline-flex; align-items: center; gap: 3px; }
.prob-legend-hint .key-mkt { color: var(--text-secondary); display: inline-flex; align-items: center; gap: 3px; }
.prob-legend-hint .tick-sample { display: inline-block; width: 2px; height: 10px; background: var(--text-primary); border-radius: 1px; vertical-align: middle; }
.xpos-cell { display: flex; flex-direction: column; align-items: flex-end; justify-content: center; }
.teamcard-form { display: flex; justify-content: space-between; align-items: center; margin-top: var(--sp-2); margin-bottom: var(--sp-2); }

.meter { height: 8px; border-radius: 999px; background: var(--bg-card-raised); overflow: hidden; }
.meter > span { display: block; height: 100%; background: var(--accent-green); transition: width var(--fill); }
.meter.gold > span { background: var(--accent-gold); }
.meter.red > span { background: var(--accent-red); }

/* ----------------------------------------------------------------- table */
/* BACKLOG #104, second pass. `.table` used to carry `overflow: hidden` purely
   to clip the first/last row's square corners to the container's own
   border-radius. That declaration also makes `.table` the nearest ancestor
   with a "scrolling mechanism" for CSS purposes (MDN, position:sticky), and
   because `.table` never itself scrolls (no max-height — it grows to fit all
   rows, so scrollHeight === clientHeight always), the sticky header below
   could never engage: it just sat in normal flow, one row above the first
   data row, contributing nothing and clashing with nothing on its own. The
   corner-clip is moved onto the first/last row directly (border-radius
   respects an element's own background) so `.table` no longer blocks the
   sticky rule from resolving against the real scrolling ancestor — the page,
   same as `.tabs`. NOT visually confirmed — no browser here (#52, #66). */
.table { display: grid; font-size: var(--fs-sm); border: 1px solid var(--border-color); border-radius: var(--radius); }
.table .row { display: grid; grid-template-columns: var(--table-cols); align-items: center; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3); border-top: 1px solid var(--border-color); }
.table .row:first-child { border-top: none; border-radius: var(--radius) var(--radius) 0 0; }
.table .row:last-child { border-radius: 0 0 var(--radius) var(--radius); }
/* Offset comes from --nav-height instead of a magic 52px, z-index puts the
   header in front of the data rows explicitly rather than relying on paint
   order, and the background stays OPAQUE (--bg-card has no alpha) with a
   solid bottom border so a row scrolling underneath cannot show through it. */
.table .row.head { background: var(--bg-card); color: var(--text-muted); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.05em; position: sticky; top: var(--nav-height); z-index: 5; border-bottom: 1px solid var(--border-color); }
.table .row.clickable { cursor: pointer; transition: background var(--fade); }
.table .row.clickable:hover { background: var(--bg-card-hover); }
.table .num { text-align: right; font-variant-numeric: tabular-nums; }
.table .team-cell { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.table .team-cell .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* BACKLOG #181. The twelve columns of the predicted table are fixed pixel
   tracks (app.js sets them inline as --table-cols) and sum to 460px; add
   11 x 8px gaps and 2 x 12px of row padding and the row cannot be narrower
   than 572px before the flexible team column has any width at all. The card's
   content box is only viewport - 58px here (main 2x12, card 2x16, borders
   2x1), so a 390px phone offers about 332px. #104 removed `.table`'s
   `overflow: hidden`, which had been silently clipping that excess off the
   right edge, so without the rule below the overflow now pushes the WHOLE
   PAGE sideways — worse than the clipping it replaced. The table scrolls
   inside its own box instead: the same idiom as `.fixture-table` and
   `.heat-wrap`. The floor on `.team-cell` overrides the `min-width: 0` above
   so the team column keeps room for the 22px crest, its 8px gap and a few
   ellipsised characters, rather than collapsing to nothing once the fixed
   tracks have taken everything.

   THE TRADEOFF, written down because #104 and check 40 exist precisely to
   stop it being introduced silently. A non-visible overflow on one axis makes
   the element a scroll container on BOTH — the CSS overflow rules forbid a
   visible/non-visible pair and promote the visible one — so below this
   breakpoint `.row.head`'s sticky positioning resolves against `.table`
   instead of the page, and `.table` has no max-height, so it never scrolls
   vertically and the header does not stick. That is exactly the #104 defect,
   deliberately re-accepted BELOW 720px ONLY. It is not a regression of #104:
   above the breakpoint the header still sticks, and below it the header row
   is ~668px wide against a ~330px viewport, so a header pinned to the top
   would show two of its twelve columns. Losing it there costs almost nothing;
   a page-wide horizontal scrollbar costs a lot. No pure-CSS arrangement keeps
   both — a horizontally scrolling ancestor IS a scroll container. The only
   alternative that keeps the header sticky is giving `.table` a max-height so
   it scrolls vertically too (the `.board-wrap`/`.log-wrap` idiom), which
   turns the table into a nested scroll pane on a phone; that was judged worse
   and is recorded in #181 rather than silently discarded.
   NOT visually confirmed — no browser here (#52, #66). */
@media (max-width: 720px) {
  .table { overflow-x: auto; }
  .table .row.head { position: static; top: auto; }

  /* BACKLOG #225. The previous `min-width: 96px` on `.team-cell` could never
     work, and MEASURING it is what showed why: a min-width on a grid ITEM does
     not grow its TRACK. The team track is `1fr`, the other eleven tracks are
     fixed and sum to 460px, and the phone offers ~364px — so `1fr` resolved to
     ZERO and the item was squeezed to the width of its crest.
     MEASURED at 390x844 before this rule: the team cell was 34px wide and the
     name element had a clientWidth of 0. Not "truncated" — ALL TWENTY club
     names were absent from the predicted table on a phone, leaving crests and
     numbers. The longest name ("Brighton & Hove Albion") needs 118px of text.

     Sizing the TRACK is the fix. `grid-auto-flow: column` plus
     `grid-auto-columns` lets the two named tracks be explicit and every
     remaining cell size itself, so this one rule covers both the 12-column
     predicted view and the 10-column live view without knowing which is which
     — and it overrides `grid-template-columns` directly rather than the
     `--table-cols` custom property, which app.js sets inline and a stylesheet
     could otherwise only beat with `!important`.
     176px, MEASURED up from 158px: the cell also carries the forecast-movement
     arrow, so the name gets ~49px less than the track, not the 30px the crest
     and its gap alone would take. At 176px every one of the twenty names fits.

     row already scrolls sideways here, so the cost is a slightly longer scroll
     and the benefit is being able to read which club a row is about. */
  .league-table .row {
    grid-template-columns: 36px 176px;
    grid-auto-flow: column;
    grid-auto-columns: minmax(34px, max-content);
  }
  .league-table .team-cell { min-width: 0; }

  /* The SAME defect, in the second table that uses this idiom, found by
     re-measuring after the rule above was scoped off it. `.fixture-table`'s
     third track is `minmax(0, 1fr)` for the opponent, and its nine other
     tracks (em-based, ~343px at this font) plus gaps and padding already
     exceed the ~332px a phone offers — so the 1fr resolved to zero.
     MEASURED at 390x844: 38 of 38 opponent cells at 0px width, on all 20 club
     pages. Desktop was never affected (0 of 38), where `1fr` correctly fills
     the leftover — so this override is narrow-width only.

     The tracks are RESTATED rather than auto-flowed. The first attempt reused
     the `grid-auto-flow: column` idiom from `.league-table` above and made the
     page 1,264px TALLER: in a grid that already overflows its container the
     free space is negative, so `minmax(2.4em, max-content)` tracks resolve
     toward their MINIMUM, and the date column collapsed from 6.4em to 2.4em
     and wrapped "2026-08-21" onto three lines — 72px rows instead of 39px.
     That idiom suits the league table because every other cell there is a
     short number; it does not suit this one. Restating keeps all ten of
     app.js's tracks exactly as designed and changes only the one that
     collapses.
     KEEP IN SYNC with `var columns` in `fixtureTable()` in app.js: same ten
     tracks, with `minmax(0,1fr)` replaced by 130px (the 118px longest club
     name plus the cell's padding). */
  .fixture-table .row {
    grid-template-columns: 2.2em 2.4em 130px 6.4em 3em 3em 3em 3.4em 4em 2.4em;
  }
}
.fixture-table { overflow-x: auto; }
.fixture-table .row.head { position: static; top: auto; }
.zone-champion { box-shadow: inset 3px 0 0 var(--accent-gold); }
.zone-europe { box-shadow: inset 3px 0 0 var(--accent-green); }
.zone-relegation { box-shadow: inset 3px 0 0 var(--accent-red); }

.crest { width: 22px; height: 22px; border-radius: 50%; flex: 0 0 auto; background: var(--bg-card-raised); }
.crest.lg { width: 34px; height: 34px; }
.crest-fallback { display: inline-flex; align-items: center; justify-content: center; font-size: var(--fs-xs); font-weight: 700; color: var(--text-primary); }

/* ----------------------------------------------------------- match cards */
.matchgrid { display: grid; gap: var(--sp-3); grid-template-columns: repeat(2, minmax(0, 1fr)); }
.match-card {
  background: var(--bg-card); border: 1px solid var(--border-color);
  border-radius: var(--radius); padding: var(--sp-3) var(--sp-4);
  cursor: pointer; transition: border-color var(--fade), transform var(--fade);
  min-width: 0; max-width: 100%; box-sizing: border-box;
}
.match-card:hover { border-color: var(--accent-green); transform: translateY(-2px); background: var(--bg-card-hover); }
.match-card .top {
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--sp-1) var(--sp-2); font-size: var(--fs-xs); color: var(--text-muted);
  min-width: 0; flex-wrap: wrap;
}
.match-card .top > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.match-card .top > span:last-child { flex-shrink: 0; display: inline-flex; align-items: center; gap: var(--sp-1); }
.match-card .teams {
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center; gap: var(--sp-2); margin: var(--sp-3) 0 var(--sp-2);
  min-width: 0;
}
.match-card .side { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; max-width: 100%; }
.match-card .side.away { justify-content: flex-end; }
.match-card .side > * { min-width: 0; max-width: 100%; }
.match-card .side .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.match-card .score { font-size: var(--fs-lg); font-weight: 700; font-variant-numeric: tabular-nums; flex-shrink: 0; text-align: center; padding: 0 var(--sp-1); }
.match-card .foot {
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--sp-1) var(--sp-2); font-size: var(--fs-xs); color: var(--text-secondary);
  margin-top: var(--sp-2); flex-wrap: wrap; min-width: 0;
}
.match-card .foot > span:first-child { min-width: 0; overflow-wrap: break-word; word-break: break-word; }
.match-card .foot > .badge { flex-shrink: 0; }

.badge { font-size: var(--fs-xs); padding: 2px var(--sp-2); border-radius: 999px; border: 1px solid var(--border-highlight); color: var(--text-secondary); white-space: nowrap; }
.badge.actual { border-color: var(--accent-dim); color: var(--accent-green); }
.badge.predicted { border-color: var(--border-highlight); color: var(--text-muted); }
.badge.hit { border-color: var(--accent-dim); color: var(--accent-green); }
.badge.miss { border-color: var(--accent-red); color: var(--accent-red); }
.badge.near-miss { border-color: var(--accent-gold); color: var(--accent-gold); background: rgba(245, 158, 11, 0.1); }
.badge.gold { border-color: var(--accent-gold); color: var(--accent-gold); }
.badge.pink { border-color: var(--accent-highlight); color: var(--accent-highlight); }

/* Interactive badge links and flags (WCAG 2.2 SC 2.5.8: 24x24 hit area) */
a.badge, button.badge, a.quarantine-flag-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
a.quarantine-flag-link {
  min-width: 24px;
  min-height: 24px;
  vertical-align: middle;
}
a.badge::after, button.badge::after, a.quarantine-flag-link::after {
  content: "";
  position: absolute;
  top: -6px;
  bottom: -6px;
  left: -4px;
  right: -4px;
}

.arrow-up::before { content: "▲"; color: var(--accent-green); }
.arrow-down::before { content: "▼"; color: var(--accent-red); }

/* --------------------------------------------------------------- controls */
.controls { display: flex; gap: var(--sp-3); align-items: center; flex-wrap: wrap; margin-bottom: var(--sp-3); }
select, input[type="search"] {
  background: var(--bg-card-hover); color: var(--text-primary); border: 1px solid var(--border-highlight);
  border-radius: var(--radius-sm); padding: var(--sp-2) var(--sp-3); font: inherit; font-size: var(--fs-sm);
}
select:focus, input:focus { outline: 2px solid var(--accent-highlight); outline-offset: 1px; }

/* --------------------------------------------------------------- team links
   BACKLOG 105. A club name is a way in to that club, everywhere it appears.
   Deliberately NOT styled as a coloured hyperlink: on a page where nearly every
   proper noun is a club, twenty accent-green words per screen is noise, and the
   accent already means something here (it is RUSS's home-win colour). So the
   affordance is an underline on hover plus a real focus ring, and the text
   keeps whatever colour its surroundings give it.

   The focus ring is the non-negotiable half. These are role="link" spans, not
   <a>, so nothing is inherited: without an explicit :focus-visible rule a
   keyboard user has no idea where they are. */
.team-link { cursor: pointer; border-radius: var(--radius-sm); }
.team-link:hover .name { text-decoration: underline; text-underline-offset: 2px; }
.team-link:hover { color: var(--accent-green); }
.team-link:focus-visible {
  outline: 2px solid var(--accent-highlight); outline-offset: 2px;
}
/* Fallback for engines without :focus-visible — a ring on plain :focus is
   worse than one only on keyboard focus, and far better than none. */
@supports not selector(:focus-visible) {
  .team-link:focus { outline: 2px solid var(--accent-highlight); outline-offset: 2px; }
}
.team-cell, .team-cell.team-link {
  display: flex; align-items: center; gap: var(--sp-2); min-width: 0;
}
.team-cell.reverse { flex-direction: row-reverse; }
.team-cell .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fixture-label .vs { color: var(--text-muted); }
.fixture-label .team-link { display: inline-flex; }

/* ----------------------------------------------------------------- modal */
.modal-backdrop {
  position: fixed; inset: 0; background: var(--scrim);
  display: flex; align-items: flex-start; justify-content: center;
  padding: var(--sp-5); overflow-y: auto; z-index: 50; animation: fade var(--fade) both;
}
.modal {
  background: var(--bg-card); border: 1px solid var(--border-highlight);
  border-radius: var(--radius); box-shadow: var(--shadow);
  width: min(860px, 100%); margin: auto 0;
}
.modal-head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-3); padding: var(--sp-4); border-bottom: 1px solid var(--border-color); }
/* BACKLOG #255 — the modal title IS the way to a club page now, so it has to
   look like one. Elsewhere a `.team-link` underlines only on hover, which is
   fine when it sits in a row the reader is already scanning; here it replaced
   an explicit button and a hover-only affordance would be invisible on a
   phone. Dotted at rest, solid on hover. `currentColor`, so no literal. */
.modal-head h2 .team-link .name,
.modal-head h2 .team-link:hover .name {
  text-decoration-thickness: 1px; text-underline-offset: 4px;
}
.modal-head h2 .team-link .name { text-decoration: underline dotted; }
.modal-head h2 .team-link:hover .name { text-decoration: underline solid; }
.modal-body { padding: var(--sp-4); }
.modal-close { appearance: none; background: var(--bg-card-hover); border: 1px solid var(--border-highlight); color: var(--text-secondary); border-radius: var(--radius-sm); width: 30px; height: 30px; cursor: pointer; font-size: var(--fs-md); line-height: 1; }
.modal-close:hover { color: var(--text-primary); border-color: var(--accent-highlight); }
/* MEASURED (real Chromium, 390px, `.modal-backdrop` scrolled to a tall tab's
   own content): the tab bar scrolled to y=-1417px, fully off-screen and
   unreachable without scrolling all the way back up first -- the mechanism
   behind a reader's report that switching tabs mid-scroll "doesn't scroll
   ... correctly". `.modal-backdrop` (not the window) is this modal's own
   scroll container, so a sticky child pins to ITS top, not the viewport's;
   `background` matches `.modal`'s own so scrolled content doesn't show
   through the strip once it detaches into its own compositing layer. */
.modal-tabs {
  display: flex;
  gap: var(--sp-1);
  padding: 0 var(--sp-4);
  border-bottom: 1px solid var(--border-color);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--bg-card);
}
.modal-tabs::-webkit-scrollbar { display: none; }
.modal-tab {
  appearance: none;
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--fs-sm);
  padding: var(--sp-3) var(--sp-3);
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
.modal-tab[aria-selected="true"] { color: var(--accent-highlight); border-bottom-color: var(--accent-highlight); }

.kv { display: grid; grid-template-columns: auto 1fr; gap: var(--sp-1) var(--sp-3); font-size: var(--fs-sm); }
.kv dt { color: var(--text-muted); }
.kv dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }

.compare { display: grid; grid-template-columns: 1fr auto 1fr; gap: var(--sp-2); align-items: center; font-size: var(--fs-sm); }
.compare .label { color: var(--text-muted); font-size: var(--fs-xs); text-align: center; }
.compare .left { text-align: left; font-variant-numeric: tabular-nums; }
.compare .right { text-align: right; font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------ svg */
.chart { width: 100%; height: auto; display: block; }
.chart text { fill: var(--text-muted); font-size: 9px; font-family: var(--font); }
.chart .axis { stroke: var(--border-color); stroke-width: 1; }
.chart .grid-line { stroke: var(--border-color); stroke-width: 1; stroke-dasharray: 2 3; }
.chart .zero-line { stroke: var(--text-muted); stroke-width: 1.5; stroke-dasharray: 4 3; opacity: 0.75; }
.chart .bar { fill: var(--accent-green); }
.chart .bar.gold { fill: var(--accent-gold); }
.chart .bar.red { fill: var(--accent-red); }
.chart .bar.muted { fill: var(--bg-card-raised); }
.chart .line { fill: none; stroke: var(--accent-green); stroke-width: 2; }
.chart .line.market { stroke: var(--accent-blue); stroke-dasharray: 4 3; }
.chart .dot { fill: var(--accent-green); }
.chart .dot.market { fill: var(--accent-blue); }
.chart .dot.hit { fill: var(--accent-green); opacity: 0.85; }
.chart .dot.miss { fill: var(--accent-red); opacity: 0.85; }
.chart .step-corp { fill: none; stroke: var(--accent-green); stroke-width: 2.5; }
.chart .sharpness-bar { fill: rgba(108, 171, 221, 0.35); stroke: var(--accent-blue); stroke-width: 1; }
.chart .spark-rail { stroke: var(--border-color); stroke-width: 1.5; stroke-linecap: round; }
.chart .spark-80 { stroke: rgba(108, 171, 221, 0.4); stroke-width: 4; stroke-linecap: round; }
.chart .spark-50 { stroke: var(--accent-blue); stroke-width: 7; stroke-linecap: round; }
.chart .spark-med { stroke: var(--accent-gold); stroke-width: 2; }
.chart .q-dot { fill: var(--accent-blue); opacity: 0.8; }
.chart .q-dot.gold { fill: var(--accent-gold); }
.chart .q-dot.red { fill: var(--accent-red); }
.chart .ideal { stroke: var(--text-muted); stroke-width: 1; stroke-dasharray: 3 3; fill: none; }

/* Season-trajectory charts (team page, "how RUSS's view has evolved"). The CI
   band and milestone area share the badge tones' rgba-over-token pattern
   (`rgba(var(--rgb-x), alpha)`) rather than a literal colour. */
.chart .ci-band { fill: rgba(var(--rgb-green), 0.15); stroke: rgba(var(--rgb-green), 0.4); stroke-width: 1; }
.chart .xpts-line { fill: none; stroke: var(--accent-green); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
.chart .xpts-dot { fill: var(--accent-green); stroke: var(--accent-green); stroke-width: 2; }
.chart .xpts-dot.latest { fill: var(--text-primary); }
.chart .milestone-area { fill: rgba(var(--rgb-blue), 0.12); }
.chart .milestone-area.gold { fill: rgba(var(--rgb-gold), 0.12); }
.chart .milestone-area.good { fill: rgba(var(--rgb-green), 0.12); }
.chart .milestone-area.bad { fill: rgba(var(--rgb-red), 0.12); }
.chart .milestone-line { fill: none; stroke: var(--accent-blue); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
.chart .milestone-line.gold { stroke: var(--accent-gold); }
.chart .milestone-line.good { stroke: var(--accent-green); }
.chart .milestone-line.bad { stroke: var(--accent-red); }
.chart .milestone-dot { fill: var(--accent-blue); stroke: var(--accent-blue); stroke-width: 2; }
.chart .milestone-dot.gold { fill: var(--accent-gold); stroke: var(--accent-gold); }
.chart .milestone-dot.good { fill: var(--accent-green); stroke: var(--accent-green); }
.chart .milestone-dot.bad { fill: var(--accent-red); stroke: var(--accent-red); }
.chart .milestone-dot.latest { fill: var(--text-primary); }
.legend { display: flex; gap: var(--sp-3); font-size: var(--fs-xs); color: var(--text-secondary); flex-wrap: wrap; }
.legend .swatch { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 4px; vertical-align: -1px; }
.swatch-accent { background: var(--accent-green); }
.swatch-draw { background: var(--accent-blue); }
.swatch-gold { background: var(--accent-gold); }
.swatch-away { background: var(--accent-red); }

/* One swatch per compound-accuracy band, so the strip-plot legend can be
   generated from the shipped ladder instead of hand-listing four of five. */
.swatch-band-exceptional { background: var(--accent-green); }
.swatch-band-strong      { background: var(--accent-blue); }
.swatch-band-moderate    { background: var(--accent-gold); }
.swatch-band-subpar      { background: var(--accent-gold); opacity: 0.5; }
.swatch-band-poor        { background: var(--accent-red); }

/* BACKLOG #107. Type up one band from --fs-xs (0.7rem, ~11px) — the numerals
   ARE the content of this grid. The cell's background is written inline by
   shade(); the base `background` here is only what shows if that is missing. */
.heatmap { display: grid; gap: 2px; font-size: var(--fs-xs); max-width: 320px; margin: 0 auto; width: 100%; }
.heatmap .cell { aspect-ratio: 1 / 1; border-radius: 3px; display: flex; align-items: center; justify-content: center; color: var(--text-primary); font-variant-numeric: tabular-nums; background: var(--bg-card-hover); min-width: 0; }
.heatmap .cell.best { outline: 2px solid var(--accent-highlight); }
.heatmap .cell.actual { outline: 2px solid var(--accent-gold); box-shadow: 0 0 0 1px var(--bg-card); position: relative; font-weight: 800; }
.heatmap .cell.actual.best { outline: 2px solid var(--accent-green); box-shadow: 0 0 0 1.5px var(--accent-gold); }
.heatmap .axis-label { color: var(--text-secondary); font-size: var(--fs-sm); display: flex; align-items: center; justify-content: center; }

.form-run { display: flex; gap: var(--sp-1); }
.form-pip { width: 22px; height: 22px; border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center; font-size: var(--fs-xs); font-weight: 700; color: var(--bg-primary); }
.form-pip.W { background: var(--accent-green); }
.form-pip.D { background: var(--accent-blue); }
.form-pip.L { background: var(--accent-red); }

.dot-track { position: relative; height: 26px; }
.dot-track .rail { position: absolute; top: 12px; left: 0; right: 0; height: 2px; background: var(--bg-card-raised); border-radius: 999px; }
.dot-track .dot { position: absolute; top: 7px; width: 12px; height: 12px; border-radius: 50%; transform: translateX(-50%); }
.dot-track .dot.russ { background: var(--accent-green); }
.dot-track .dot.russ.home { background: var(--accent-green); }
.dot-track .dot.russ.draw { background: var(--accent-blue); }
.dot-track .dot.russ.away { background: var(--accent-red); }
.dot-track .dot.market { background: var(--text-primary); border: 2px solid var(--bg-card); width: 10px; height: 10px; top: 8px; border-radius: 2px; transform: translateX(-50%) rotate(45deg); box-shadow: 0 0 2px var(--shadow); }
.dot-track-legend { display: flex; align-items: center; gap: var(--sp-3); font-size: var(--fs-xs); color: var(--text-muted); margin-bottom: var(--sp-2); }
.dot-track-legend .key-item { display: inline-flex; align-items: center; gap: var(--sp-1); }
.dot-track-legend .sample-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--accent-green); }
.dot-track-legend .sample-diamond { display: inline-block; width: 7px; height: 7px; background: var(--text-primary); border-radius: 1px; transform: rotate(45deg); }
/* The distance between the two dots, drawn. Used by the top-bets page, where
   the gap between the base rate and RUSS IS the ranking, so it is shown as a
   length rather than left as a subtraction for the reader. */
.dot-track .span { position: absolute; top: 11px; height: 4px; border-radius: 999px; background: var(--accent-dim); }

/* the bookmaker board on a match preview — one row per book, scrollable */
.board-wrap { max-height: 320px; overflow: auto; border: 1px solid var(--border-color); border-radius: var(--radius); margin-top: var(--sp-2); }
.board { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.board th { position: sticky; top: 0; z-index: 5; background: var(--bg-card); text-align: right; color: var(--text-muted); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.05em; padding: var(--sp-1) var(--sp-2); border-bottom: 1px solid var(--border-color); }
.board th:first-child { text-align: left; }
.board td { padding: var(--sp-1) var(--sp-2); border-bottom: 1px solid var(--border-color); text-align: right; font-variant-numeric: tabular-nums; }
.board td:first-child { text-align: left; color: var(--text-secondary); }
.board td.top { color: var(--accent-green); font-weight: 700; }
.board tr.aggregate td { color: var(--text-muted); font-style: italic; }
.board tr:hover td { background: var(--bg-card-hover); }

/* ------------------------------------------------- module 13 features */

/* position heatmap (catalogue B8) — CSS grid, one cell per team-position */
.heat-wrap { overflow-x: auto; border: 1px solid var(--border-color); border-radius: var(--radius); }
.heat-grid { display: grid; gap: 1px; min-width: 820px; padding: var(--sp-2); }
/* BACKLOG #108. Was --fs-xs / --text-muted: the smallest type on the page in
   the lightest colour it has. --text-secondary on --bg-card measures 6.99:1
   against --text-muted's 5.00:1, and one band larger makes the position
   numbers readable at a glance. min-width grew 720 -> 820px to keep a 21-column
   grid from crushing the larger type; .heat-wrap already scrolls horizontally. */
.heat-head { text-align: center; font-size: var(--fs-sm); color: var(--text-secondary); padding-bottom: var(--sp-1); }
.heat-head.champion { color: var(--accent-gold); }
.heat-head.relegation { color: var(--accent-red); }
.heat-team { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-sm); color: var(--text-primary); padding-right: var(--sp-2); min-width: 0; }
.heat-team .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.heat-cell {
  aspect-ratio: 1 / 1; min-height: 22px; border-radius: 2px;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-sm); color: var(--text-primary);
  font-variant-numeric: tabular-nums; cursor: default;
}
.heat-cell.champion { border: 1px solid var(--accent-gold); }
.heat-cell.relegation { border: 1px solid var(--accent-red); }

/* RUSS mood (feature 7) */
.mood { display: flex; gap: var(--sp-4); align-items: center; flex-wrap: wrap; }
.mood-art { border-radius: var(--radius); background: var(--bg-card-hover); flex: 0 0 auto; }
.mood-name { font-size: var(--fs-xl); font-weight: 700; text-transform: capitalize; color: var(--accent-green); }
.mood .label { color: var(--text-muted); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.06em; }

/* info tooltips (catalogue E20) */
.infotip {
  display: inline-flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; border-radius: 50%; cursor: help;
  border: 1px solid var(--border-highlight); color: var(--text-muted);
  font-size: 10px; font-style: italic; font-weight: 700; vertical-align: 1px;
}
.infotip:hover, .infotip:focus { color: var(--accent-green); border-color: var(--accent-green); outline: none; }

/* BACKLOG #253 — WCAG 2.2 SC 2.5.8 requires a 24x24 CSS px target. The glyph
   is 15px and MEASURED at 390x844 there are 44 of them across the site: 19 on
   the landing page, 12 on a club page, 8 on RUSS vs Market.

   The fix is an INVISIBLE expanded hit area rather than a bigger circle, which
   is the option #253 itself predicted would be the answer: `ⓘ` sits inline
   inside running prose, so growing the glyph to 24px would push the line-height
   of every paragraph that carries one. The `::after` is transparent, sits
   outside the border-box, and changes no layout — `position: absolute` takes it
   out of flow entirely, and its parent is the only positioned ancestor.

   SC 2.5.8 is satisfied by the target's HIT AREA, not by its visible size, and
   the spacing exception does not apply here because two tips are never adjacent
   — every one of them is separated by at least a word of prose. */
.infotip { position: relative; }
.infotip::after {
  content: "";
  position: absolute;
  /* 15px glyph + 4.5px on each side = 24px. */
  top: -4.5px; right: -4.5px; bottom: -4.5px; left: -4.5px;
}
/* BACKLOG #227. The tooltip is a BUTTON now, so it needs the button reset —
   `title=` never fired on a phone and most of the site's vocabulary had no
   explanation at all. The panel it opens is inline and in normal flow, which
   is what makes it work on a narrow screen: nothing has to be positioned, and
   nothing can end up off the edge. */
.gloss { display: inline; }
button.infotip { appearance: none; background: transparent; font-family: inherit; padding: 0; }
button.infotip:focus-visible { outline: 2px solid var(--accent-green); outline-offset: 2px; }
.gloss-body {
  display: block; margin: var(--sp-2) 0 0; padding: var(--sp-2) var(--sp-3);
  background: var(--bg-card-raised); border-left: 2px solid var(--accent-green);
  border-radius: 0 var(--radius) var(--radius) 0;
  color: var(--text-secondary); font-size: var(--fs-sm); font-weight: 400;
  text-transform: none; letter-spacing: normal; max-width: 62ch;
}
/* A class selector beats the UA stylesheet's `[hidden] { display: none }`, so
   `.gloss-body`'s `display: block` above silently un-hid every panel — they
   rendered 103px tall and permanently open while `el.hidden` still reported
   true. Caught by measuring the RENDERED height rather than the attribute,
   which is the same mistake this whole wave keeps finding in other people's
   code. Re-state the hiding at equal specificity. */
.gloss-body[hidden] { display: none; }

/* The glossary itself, in the footer on every page. */
.glossary { margin: var(--sp-4) 0; border-top: 1px solid var(--border-color); padding-top: var(--sp-3); }
.glossary > summary {
  cursor: pointer; color: var(--text-secondary); font-size: var(--fs-sm);
  font-weight: 600; list-style: revert; padding: var(--sp-1) 0;
}
.glossary > summary:hover { color: var(--accent-green); }
.glossary > summary:focus-visible { outline: 2px solid var(--accent-green); outline-offset: 2px; }
.glossary-list { margin: var(--sp-3) 0 0; display: grid; gap: var(--sp-3); }
@media (min-width: 900px) {
  /* Two columns on a desktop: fourteen entries down one 78ch column made the
     footer taller than most of the pages above it. */
  .glossary-list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3) var(--sp-5); }
}
.glossary-list dt { color: var(--text-primary); font-weight: 600; font-size: var(--fs-sm); }
.glossary-list dd { margin: var(--sp-1) 0 0; font-size: var(--fs-sm); line-height: 1.55; }
.glossary-list dd .plain { color: var(--text-secondary); }
.glossary-list dd .more { color: var(--text-muted); }

/* goal-distribution bars (catalogue A3, upgraded in BACKLOG #288) */
.goalbars { display: flex; gap: var(--sp-1); align-items: flex-end; height: 84px; }
.goalbars.horizontal { display: flex; flex-direction: column; gap: var(--sp-1); width: 100%; height: auto; }
.goalbars.horizontal .goalbar { display: flex; flex-direction: row; align-items: center; gap: var(--sp-2); width: 100%; height: auto; }
.goalbar { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; height: 100%; }
.goalbar > span { width: 100%; border-radius: 2px 2px 0 0; min-height: 1px; transition: height var(--fill); }
.goalbar small { color: var(--text-muted); font-size: var(--fs-xs); margin-top: 2px; }
.goalbar .goal-label { width: 24px; color: var(--text-muted); font-size: var(--fs-xs); font-weight: 600; text-align: right; flex-shrink: 0; }
.goalbar .goal-bar-track { flex: 1; height: 14px; background: var(--bg-card-raised); border-radius: 3px; overflow: hidden; display: flex; }
.goalbar .goal-bar-fill { height: 100%; border-radius: 3px; min-width: 2px; transition: width var(--fill); }
.goalbar .goal-pct-val { width: 46px; color: var(--text-secondary); font-variant-numeric: tabular-nums; font-size: var(--fs-xs); text-align: right; flex-shrink: 0; }

ul.plain { margin: var(--sp-2) 0 0; padding-left: var(--sp-4); color: var(--text-secondary); font-size: var(--fs-sm); }
ul.plain li { margin-bottom: var(--sp-1); }

/* ------------------------------------------------------------ empty state */
.empty {
  border: 1px dashed var(--border-highlight); border-radius: var(--radius);
  padding: var(--sp-5); text-align: center; color: var(--text-secondary);
  background: var(--bg-card);
}
.empty .why { color: var(--text-muted); font-size: var(--fs-sm); margin-top: var(--sp-2); max-width: 60ch; margin-left: auto; margin-right: auto; }

.log { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
/* BACKLOG #222 (the half of #104 that was never checked). `top` on a sticky
   element is measured from the nearest scrolling ancestor, NOT the viewport —
   and `.log` is ALWAYS inside `.log-wrap`, which has a max-height and
   `overflow: auto`, so it IS that ancestor (13 of 13 instances in app.js; the
   selector never appears unwrapped). `top: var(--nav-height)` therefore parked
   the header 57px INSIDE the wrapper and covered the first data row
   permanently — the title favourite, the biggest disagreement, the newest bet.
   MEASURED before the fix: header bottom minus first row top = exactly 57px on
   all 13 tables, at 1440x900 and 390x844.

   `--nav-height` is correct only where the page itself is the scrolling
   ancestor, which is `.table .row.head` below. `.board th` sits in
   `.board-wrap` — the same idiom as here — and already uses `top: 0`, which is
   why it measured 0px overlap and is the reference this now matches. */
.log th { text-align: left; color: var(--text-muted); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.05em; padding: var(--sp-2); border-bottom: 1px solid var(--border-color); position: sticky; top: 0; z-index: 5; background: var(--bg-card); }
.log td { padding: var(--sp-2); border-bottom: 1px solid var(--border-color); font-variant-numeric: tabular-nums; }
.log tr:hover td { background: var(--bg-card-hover); }
.log-wrap { max-height: 520px; overflow: auto; border: 1px solid var(--border-color); border-radius: var(--radius); }

/* BACKLOG #281. MEASURED before this change: 321px on desktop, 502px on a
   phone — two footers, three fully-expanded timestamp explanations and the
   whole glossary, on every page of the site. Now one wrapping row of small
   print, with the three explanations collapsed behind their info buttons and
   the glossary moved to Model Accuracy where reference material belongs. */
footer { max-width: 1180px; margin: 0 auto; padding: var(--sp-1) var(--sp-5); color: var(--text-muted); font-size: var(--fs-xs); border-top: 1px solid var(--border-color); }
.footer-bar {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--sp-1) var(--sp-3);
  line-height: 1.5;
}
.footer-bar > * { margin: 0; }
.footer-brand { color: var(--text-secondary); }
.footer-note { color: var(--text-muted); }

/* --------------------------------------------------------- responsive */
@media (max-width: 768px) {
  main, .masthead { padding-left: var(--sp-3); padding-right: var(--sp-3); }
  :root { --nav-height: 52px; }
  .tab-btn { min-height: 44px; padding: var(--sp-2) var(--sp-4); }
  .tab-label-full { display: none; }
  .tab-label-mobile { display: inline; }
  .grid.cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid.cols-5 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .grid.cols-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .matchgrid { grid-template-columns: minmax(0, 1fr); }
  .table { font-size: var(--fs-xs); }
  .heat-grid { min-width: 740px; }
  .mood { justify-content: center; text-align: center; }
  .hero-prediction-body { flex-direction: column; align-items: stretch; gap: var(--sp-3); }
  .hero-prediction-avatar-col { flex-direction: row; align-items: center; text-align: left; }
  .hero-prediction-byline { text-align: left; margin-left: var(--sp-2); }
  .hero-prediction-headline { font-size: var(--fs-base); }
  .quip-full { flex-direction: column; align-items: stretch; }
  .quip-full .quip-sidebar { flex-direction: row; align-items: center; text-align: left; }
  .quip-sections { grid-template-columns: 1fr; }
  .quip.headline-quip, .quip[id^="headline-quip"] {
    flex-direction: column;
    align-items: stretch;
    gap: var(--sp-3);
    padding: var(--sp-3);
  }
  .headline-quip-sidebar {
    flex-direction: row;
    align-items: center;
    gap: var(--sp-3);
  }
  .headline-quip-byline {
    text-align: left;
  }
  .modal-backdrop { padding: var(--sp-2); }
  .fixture-table, .heat-wrap, .board-wrap, .log-wrap {
    -webkit-overflow-scrolling: touch;
    scroll-behavior: smooth;
  }
}
@media (max-width: 480px) {
  .grid.cols-2, .grid.cols-3, .grid.cols-4, .grid.cols-5 { grid-template-columns: 1fr; }
  /* ...except stat rows, which stay two-up. A tile is a label and a number and
     needs ~180px, so one-per-row spends height on nothing. See BACKLOG #298-A:
     six of these per selection were 672px of a 920px card. */
  .grid.stat-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .compare { grid-template-columns: 1fr; text-align: center; }
  .compare .left, .compare .right { text-align: center; }
  h1 { font-size: var(--fs-lg); }
  .modal-head { padding: var(--sp-3); }
  .modal-body { padding: var(--sp-3); }
  .match-card { padding: var(--sp-3); }
  .heatmap { font-size: var(--fs-xs); }
  .probbar-legend { gap: var(--sp-1); font-size: var(--fs-micro); }
  .probbar-legend .h, .probbar-legend .d, .probbar-legend .a { gap: 2px; }
  .prob-legend-hint { font-size: var(--fs-micro); padding: 2px var(--sp-1); }
}

/* ------------------------------------------------- scrollbar (brief Part 1)
   8px, track on the page background, thumb in the border colour so it reads as
   structure rather than content. Firefox takes the standard two-value form;
   WebKit needs its own pseudo-elements. Both are given. */
* { scrollbar-width: thin; scrollbar-color: var(--border-color) var(--bg-primary); }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--bg-primary); }
::-webkit-scrollbar-thumb {
  background: var(--border-color); border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover { background: var(--border-highlight); }

/* ============================================================ MODULE 14
   Catalogue components added by the design-system module. Tokens only —
   no literal below this line, same rule as the rest of the file. */

/* A5 — segmented distribution bar */
.segbar {
  display: flex; height: 12px; border-radius: 999px; overflow: hidden;
  background: var(--bg-card-raised); border: 1px solid var(--border-color);
}
.segbar .seg { display: block; height: 100%; transition: width var(--fill); }
.segbar .seg.good     { background: var(--accent-green); }
.segbar .seg.neutral  { background: var(--accent-blue); }
.segbar .seg.bad      { background: var(--accent-red); }
.segbar .seg.gold     { background: var(--accent-gold); }
.segbar .seg.muted    { background: var(--border-highlight); }
.segbar-key {
  display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-2);
  font-size: var(--fs-xs); color: var(--text-secondary);
}
.seg-key { display: inline-flex; align-items: center; gap: var(--sp-1); }
.seg-key .swatch { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }
.seg-key .swatch.good    { background: var(--accent-green); }
.seg-key .swatch.neutral { background: var(--accent-blue); }
.seg-key .swatch.bad     { background: var(--accent-red); }
.seg-key .swatch.gold    { background: var(--accent-gold); }
.seg-key .swatch.muted   { background: var(--border-highlight); }

/* E18 — history / honours badges. The three medal tokens are the design
   system's only sanctioned literals and they are used only here and on the
   table's zone tints. */
.honours { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.honour {
  min-height: 24px; padding: 2px var(--sp-2); border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
  font-size: var(--fs-micro); font-weight: 600; font-variant-numeric: tabular-nums;
  border: 1px solid var(--border-highlight);
  background: var(--bg-card-raised); color: var(--text-secondary);
  white-space: nowrap;
}
.honour.gold      { background: var(--medal-gold);   color: var(--bg-primary); border-color: var(--medal-gold); }
.honour.silver    { background: var(--medal-silver); color: var(--bg-primary); border-color: var(--medal-silver); }
.honour.bronze    { background: var(--medal-bronze); color: var(--bg-primary); border-color: var(--medal-bronze); }
.honour.top-four  { border-color: var(--accent-green); color: var(--accent-green); }
.honour.relegated { border-color: var(--accent-red);   color: var(--accent-red); }

/* A4 — goal-pattern bars with a league-average marker */
.pattern {
  position: relative; height: 10px; border-radius: 999px; overflow: hidden;
  background: var(--bg-card-raised); border: 1px solid var(--border-color);
}
.pattern .fill { display: block; height: 100%; transition: width var(--fill); }
.pattern .fill.good { background: var(--accent-green); }
.pattern .fill.bad  { background: var(--accent-red); }
.pattern .marker {
  position: absolute; top: -2px; bottom: -2px; width: 2px;
  background: var(--text-primary); transform: translateX(-1px);
}

/* D14 — home | label | away stat comparison */
.statgrid {
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: var(--sp-1) var(--sp-3);
  align-items: center; min-width: 0;
}
.statgrid .sg-home, .statgrid .sg-away {
  font-variant-numeric: tabular-nums; font-weight: 600; color: var(--text-secondary);
}
.statgrid .sg-home { text-align: right; }
.statgrid .sg-away { text-align: left; }
.statgrid .sg-home.better, .statgrid .sg-away.better { color: var(--accent-green); }
.statgrid .sg-label {
  font-size: var(--fs-xs); color: var(--text-muted); text-transform: uppercase;
  letter-spacing: 0.06em; text-align: center; white-space: nowrap;
}

/* E16 — the flat state. A move too small to call is drawn as a dash rather
   than omitted, so "unchanged" and "not measured" stay distinguishable. */
.arrow-flat::before { content: "\2013"; color: var(--text-muted); }

/* ============================================================ TEAM PAGES
   Added 2026-08-21. Every value below is a token: the "no colour literal
   outside :root" rule is enforced by three separate checks and this section
   is no exception. Layout only — the components inside a team page (.card,
   .table, .board, .segbar, .honours) are the ones the rest of the site uses.
   ======================================================================== */

.teamgrid {
  display: grid; gap: var(--sp-3);
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
.teamcard { display: flex; flex-direction: column; gap: var(--sp-2); }
.teamcard-head { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.teamcard-head .name { font-weight: 600; }
.teamcard-head .meta {
  font-size: var(--fs-xs); color: var(--text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.teamcard-nums {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-2); font-variant-numeric: tabular-nums;
}
.teamcard-actual { display: flex; align-items: baseline; gap: var(--sp-1); }
.teamcard-actual strong { font-size: var(--fs-md); font-weight: 700; color: var(--text-primary); }
.teamcard-projected { display: flex; align-items: baseline; gap: var(--sp-1); }
.teamcard-projected strong { font-size: var(--fs-sm); font-weight: 600; color: var(--accent-blue); }
.chart .line.projected-line { stroke: var(--accent-blue); stroke-dasharray: 4 3; }
.chart .line.actual-line { stroke: var(--accent-green); stroke-width: 2.5px; }

.teamhead { display: flex; flex-direction: column; gap: var(--sp-4); }
.teamhead-id { display: flex; align-items: center; gap: var(--sp-4); min-width: 0; }
.teamhead-id h2 { font-size: var(--fs-xl); }

/* Ten columns is a lot on a phone: the table scrolls inside its own box
   rather than pushing the page sideways. */
.fixture-table { overflow-x: auto; }

@media (max-width: 720px) {
  .teamgrid { grid-template-columns: 1fr; }
  .teamhead-id { flex-direction: column; align-items: flex-start; }
}

/* The permanent footer's three freshness stamps (operator, 2026-08-27). Each
   carries the sentence that says what it does NOT cover, because a bare
   timestamp invites the assumption that everything was updated just now. */
/* #281: one inline row, not a grid of paragraphs. Each stamp still answers a
   different question, so each keeps its sentence — inside the info button,
   which costs no height until a reader asks for it. */
.freshness { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-1) var(--sp-4); margin: 0; font-size: var(--fs-xs); text-align: left; }
.freshness dt { color: var(--text-muted); white-space: nowrap; }
.freshness dt::after { content: ""; }
.freshness dd { margin: 0 0 0 var(--sp-1); color: var(--text-secondary); display: inline-flex; align-items: baseline; gap: var(--sp-1); }
.freshness dd time { font-variant-numeric: tabular-nums; color: var(--text-primary); }
.freshness .freshness-note { color: var(--text-muted); }

/* ============================================================================
   WAVE 4 PHASE 2: Landing Page & Model Accuracy Visuals (BACKLOG #232, #236)
   ============================================================================ */

/* V3: League Race Matrix Chart */
.race-matrix-wrap {
  width: 100%;
  overflow-x: auto;
  margin-bottom: var(--sp-3);
}
.race-matrix-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}
.race-matrix-table th {
  text-align: left;
  padding: var(--sp-2) var(--sp-3);
  color: var(--text-muted);
  font-size: var(--fs-xs);
  font-weight: 600;
  border-bottom: 1px solid var(--border-color);
  white-space: nowrap;
}
.race-matrix-table td {
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border-color);
  vertical-align: middle;
}
.race-matrix-table tr:hover td {
  background: var(--bg-card-hover);
}
.race-matrix-cell {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 90px;
}
.race-prob-num {
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  min-width: 38px;
  text-align: right;
  font-weight: 500;
}
.race-bar-track {
  flex: 1;
  height: 6px;
  background: var(--bg-card-raised);
  border-radius: var(--radius-sm);
  overflow: hidden;
  min-width: 48px;
}
.race-bar-fill {
  height: 100%;
  border-radius: var(--radius-sm);
  transition: width var(--fill);
}
.race-bar-fill.gold { background: var(--accent-gold); }
.race-bar-fill.blue { background: var(--accent-blue); }
.race-bar-fill.red  { background: var(--accent-red); }

/* BACKLOG #269 — the race board: the active column, the expandable row and the
   collapsed per-club detail that replaced three stacked list sections. */
.race-switch { margin-bottom: var(--sp-3); }

.race-matrix-table th.is-active {
  color: var(--text-primary);
  border-bottom: 2px solid var(--accent-green);
}

/* BACKLOG #283. The column headings sort the board; the three chips that used
   to do it above the table are gone. A real <button> inside the <th>, not a
   click handler on the <th> itself — a clickable table header is unreachable
   by keyboard, invisible to a screen reader, and would fail both the
   touch-target and the unstyled-control rules in the layout harness.
   `padding` and `min-height` keep it over the WCAG 2.5.8 24px floor while
   sitting inside a header row that must stay compact. */
.race-sort {
  appearance: none; background: transparent; border: 1px solid transparent;
  color: inherit; font: inherit; font-size: var(--fs-xs); font-weight: 600;
  padding: var(--sp-1) var(--sp-2); border-radius: var(--radius-sm);
  min-height: 28px; cursor: pointer; white-space: nowrap;
  display: inline-flex; align-items: center; gap: var(--sp-1);
  transition: color var(--fade), background var(--fade);
}
.race-sort:hover { color: var(--text-primary); background: var(--bg-card-hover); }
.race-sort:focus-visible { outline: 2px solid var(--accent-green); outline-offset: 1px; }
.race-sort.active { color: var(--text-primary); }
/* The caret is the visible twin of `aria-sort`. Every column carries one so
   the headings do not shift width when the sort moves; the inactive ones are
   dimmed rather than absent. */
.race-sort-caret {
  width: 0; height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  opacity: 0.25;
}
.race-sort-caret.active { opacity: 1; color: var(--accent-green); }
.race-matrix-table td.is-active { background: var(--bg-card-raised); }
.race-matrix-table tr.race-row.open td.is-active { background: transparent; }

/* Rows are tappable, so they need a tap target and a pointer. 44px is the
   minimum from #242; the padding above already clears it on a phone. */
.race-matrix-table tr.race-row { cursor: pointer; }
.race-matrix-table tr.race-row:focus-visible {
  outline: 2px solid var(--accent-green);
  outline-offset: -2px;
}
.race-matrix-table tr.race-row.open td {
  background: var(--bg-card-hover);
  border-bottom-color: transparent;
}
.race-row-caret {
  display: inline-block;
  width: 0; height: 0;
  margin-right: var(--sp-2);
  border-left: 5px solid var(--text-muted);
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform var(--fill);
  vertical-align: middle;
}
.race-row-caret.open { transform: rotate(90deg); }

.race-matrix-table tr.race-detail-row td {
  background: var(--bg-card-hover);
  padding-top: 0;
}
.race-detail-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin-bottom: var(--sp-2);
}

/* #297: 20-Team Season Race Benchmark with Paired RUSS vs Market Bars */
.paired-race-cell {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 130px;
}
.paired-bar-row {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}
.paired-bar-label {
  font-size: 10px;
  color: var(--text-muted);
  width: 32px;
  text-transform: uppercase;
  font-weight: 600;
  letter-spacing: 0.02em;
}
.paired-bar-track {
  flex: 1;
  height: 6px;
  background: var(--bg-card-raised);
  border-radius: var(--radius-sm);
  overflow: hidden;
  min-width: 48px;
}
.paired-bar-fill {
  height: 100%;
  border-radius: var(--radius-sm);
  transition: width var(--fill);
}
.paired-bar-fill.russ-fill { background: var(--accent-green); }
.paired-bar-fill.market-fill { background: var(--accent-blue); }
.paired-prob-num {
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  min-width: 38px;
  text-align: right;
  font-weight: 500;
}
.paired-edge-badge {
  font-size: 10px;
  padding: 1px 4px;
  border-radius: var(--radius-sm);
  font-variant-numeric: tabular-nums;
  display: inline-block;
  margin-top: 2px;
}
.paired-legend-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-2);
  font-size: var(--fs-xs);
  flex-wrap: wrap;
}
.race-detail-line {
  margin: 0 0 var(--sp-1);
  font-size: var(--fs-sm);
  line-height: 1.5;
}
.race-detail-index {
  margin-top: var(--sp-3);
  border-top: 1px solid var(--border-color);
  padding-top: var(--sp-2);
}
.race-detail-index > summary {
  cursor: pointer;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  padding: var(--sp-2) 0;
  min-height: 44px;
  display: flex;
  align-items: center;
}
.race-detail-index[open] > summary { color: var(--text-primary); }
.race-detail-index .race-detail {
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--border-color);
}

/* V1: Outcome Stacked Bar & Historical Baseline Rail */
.outcome-stacked-wrap {
  margin-top: var(--sp-2);
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.outcome-track {
  display: flex;
  height: 8px;
  width: 100%;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--bg-card-raised);
}
.outcome-seg {
  height: 100%;
  transition: width var(--fill);
}
.outcome-seg.home { background: var(--accent-green); }
.outcome-seg.draw { background: var(--accent-blue); }
.outcome-seg.away { background: var(--accent-red); }

.baseline-reference {
  font-size: var(--fs-micro);
  color: var(--text-muted);
  display: flex;
  justify-content: space-between;
  line-height: 1.2;
}

/* V2: Season Progress Mini Meter */
.season-progress-meter {
  margin-top: var(--sp-2);
}

/* V4: RUSS's Mood 10-Dot Result Strip */
.mood-strip-wrap {
  margin-top: var(--sp-2);
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.mood-strip {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.mood-dot {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 1px solid var(--border-highlight);
  cursor: help;
  position: relative;
  transition: transform var(--fade);
}
.mood-dot:hover {
  transform: scale(1.25);
}
.mood-dot.expected   { background: var(--accent-green); }
.mood-dot.normal     { background: var(--accent-blue); }
.mood-dot.surprising { background: var(--accent-red); }

.mood-strip-legend {
  display: flex;
  gap: var(--sp-3);
  font-size: var(--fs-micro);
  color: var(--text-muted);
  margin-top: var(--sp-1);
}
.mood-legend-item {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}
.mood-legend-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
}
.mood-legend-dot.expected   { background: var(--accent-green); }
.mood-legend-dot.normal     { background: var(--accent-blue); }
.mood-legend-dot.surprising { background: var(--accent-red); }

/* V9: Model Accuracy Benchmark Scale */
.benchmark-scale-container {
  margin-top: var(--sp-3);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--border-color);
}
.benchmark-scale-svg {
  width: 100%;
  height: 52px;
  display: block;
}

/* V10: Accessible Calibration Table Toggle */
.disclosure-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  background: var(--bg-card-raised);
  color: var(--text-secondary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-xs);
  cursor: pointer;
  margin-top: var(--sp-3);
  transition: background var(--fade), border-color var(--fade);
}
.disclosure-toggle:hover {
  background: var(--bg-card-hover);
  border-color: var(--border-highlight);
  color: var(--text-primary);
}
.disclosure-toggle svg {
  transition: transform var(--fade);
}
.disclosure-toggle[aria-expanded="true"] svg {
  transform: rotate(180deg);
}

/* Phase 3: Subsegment Controls (BACKLOG #233 C5, #235 C9) */
.subsegment-controls {
  display: flex;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
  flex-wrap: wrap;
  align-items: center;
}
.subsegment-pill {
  appearance: none;
  border: 1px solid var(--border-color);
  background: var(--bg-card-raised);
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--fs-xs);
  padding: var(--sp-2) var(--sp-4);
  border-radius: 999px;
  min-height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--fade), background var(--fade), border-color var(--fade);
}
.subsegment-pill:hover {
  color: var(--text-primary);
  background: var(--bg-card-hover);
  border-color: var(--border-highlight);
}
.subsegment-pill.active {
  color: var(--bg-primary);
  background: var(--accent-green);
  border-color: var(--accent-green);
  font-weight: 620;
}

/* V6: Top Calls Dumbbell Chart */
.dumbbell-container {
  width: 100%;
  overflow-x: auto;
  margin-top: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.dumbbell-svg {
  width: 100%;
  min-width: 520px;
  display: block;
}

/* V7 & V8: Diverging Outrights & Quarantine Charts */
.diverging-container {
  width: 100%;
  overflow-x: auto;
  margin-top: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.diverging-svg {
  width: 100%;
  min-width: 480px;
  display: block;
}
.diverging-club-link {
  cursor: pointer;
  text-decoration: none;
  outline: none;
}
.diverging-club-link:hover text,
.diverging-club-link:focus text {
  text-decoration: underline;
}

/* V13: Team Hero Metric Strip */
.metric-strip-svg {
  width: 100%;
  max-width: 140px;
  height: 10px;
  display: block;
  margin-top: var(--sp-1);
}

/* V14: Scored vs Conceded Paired Bars */
.paired-bars-box {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}
.paired-bar-row {
  display: grid;
  grid-template-columns: 70px 1fr 45px;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-xs);
}
.paired-bar-label {
  color: var(--text-secondary);
}
.paired-bar-track {
  display: flex;
  height: 10px;
  background: var(--bg-tertiary);
  border-radius: 3px;
  overflow: hidden;
  position: relative;
}
.paired-bar-fill.scored {
  background: var(--accent-green);
}
.paired-bar-fill.conceded {
  background: var(--accent-red);
}
.paired-bar-val {
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  text-align: right;
  color: var(--text-primary);
}

/* C13: Ephemeral Fixture Collapsing */
.fixture-table tr.fixture-row-collapsed,
.fixture-table .row.fixture-row-collapsed,
.fixture-table .row.is-hidden {
  display: none !important;
}

/* ============================================================================
   COMPOUND ACCURACY & 4-PILLAR EVALUATION FRAMEWORK (BACKLOG #261)
   ============================================================================ */

.compound-hero-card {
  background: var(--bg-card);
  border: 1px solid var(--border-highlight);
  border-radius: var(--radius);
  padding: var(--sp-5);
  margin-bottom: var(--sp-4);
  position: relative;
  overflow: hidden;
}

.compound-hero-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--sp-4);
  flex-wrap: wrap;
}

.compound-hero-main {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
}

.compound-hero-score {
  font-size: var(--fs-2xl);
  font-weight: 800;
  color: var(--text-primary);
  letter-spacing: -0.02em;
  line-height: 1;
}

.compound-quality-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--fs-xs);
  font-weight: 600;
  padding: 3px 10px;
  border-radius: 999px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.compound-quality-badge.exceptional {
  background: var(--bg-card-raised);
  color: var(--accent-green);
  border: 1px solid var(--accent-green);
}

.compound-quality-badge.strong {
  background: var(--bg-card-raised);
  color: var(--accent-blue);
  border: 1px solid var(--accent-blue);
}

.compound-quality-badge.moderate {
  background: var(--bg-card-raised);
  color: var(--accent-gold);
  border: 1px solid var(--accent-gold);
}

/* `subpar` is one of the five bands in `compound_eval.QUALITY_BANDS` and had
   no rule here, so every match scoring 25-45% -- 5 of the 20 played, a quarter
   of the season -- rendered with no band colour at all. */
.compound-quality-badge.subpar {
  background: var(--bg-card-raised);
  color: var(--accent-gold);
  border: 1px dashed var(--accent-gold);
}

.compound-quality-badge.poor {
  background: var(--bg-card-raised);
  color: var(--accent-red);
  border: 1px solid var(--accent-red);
}

/* The per-match compound badge is a `.badge` carrying a band slug, and none of
   these five existed, so every match card's compound score rendered in the
   neutral default and the band colouring was invisible on the surface that
   shows it most often. */
.badge.exceptional { border-color: var(--accent-green); color: var(--accent-green); background: rgba(var(--rgb-green), 0.10); }
.badge.strong      { border-color: var(--accent-blue);  color: var(--accent-blue);  background: rgba(var(--rgb-blue), 0.10); }
.badge.moderate    { border-color: var(--accent-gold);  color: var(--accent-gold);  background: rgba(var(--rgb-gold), 0.10); }
.badge.subpar      { border-color: var(--accent-gold);  color: var(--accent-gold);  background: transparent; border-style: dashed; }
.badge.poor        { border-color: var(--accent-red);   color: var(--accent-red);   background: rgba(var(--rgb-red), 0.10); }

/* The headline's own floor, sat directly under it. Deliberately quieter than
   the score and louder than the footnotes: a reader who takes nothing else
   from the card should still leave knowing what "no skill" scores here. */
.compound-hero-baseline {
  display: flex;
  align-items: baseline;
  gap: var(--sp-1);
  flex-wrap: wrap;
  margin-top: var(--sp-1);
  font-size: var(--fs-xs);
  color: var(--text-secondary);
}

.compound-hero-baseline-value {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--accent-green);
}

/* The progression chart's stand-in before there are enough rounds to plot. */
.compound-trend-early-rows { display: flex; flex-direction: column; gap: var(--sp-1); }
.compound-trend-early-row {
  display: flex; align-items: baseline; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2);
  background: var(--bg-card-raised); border-radius: 6px;
}
.compound-trend-early-gw {
  font-size: var(--fs-xs); color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.04em; min-width: 4.5em;
}
.compound-trend-early-val {
  font-size: var(--fs-lg); font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}
.compound-trend-early-cum {
  font-size: var(--fs-xs); color: var(--text-secondary); margin-left: auto;
  font-variant-numeric: tabular-nums;
}

.compound-pillars-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}

@media (max-width: 860px) {
  /* The headline's own floor, sat directly under it. Deliberately quieter than
   the score and louder than the footnotes: a reader who takes nothing else
   from the card should still leave knowing what "no skill" scores here. */
.compound-hero-baseline {
  display: flex;
  align-items: baseline;
  gap: var(--sp-1);
  flex-wrap: wrap;
  margin-top: var(--sp-1);
  font-size: var(--fs-xs);
  color: var(--text-secondary);
}

.compound-hero-baseline-value {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--accent-green);
}

/* The progression chart's stand-in before there are enough rounds to plot. */
.compound-trend-early-rows { display: flex; flex-direction: column; gap: var(--sp-1); }
.compound-trend-early-row {
  display: flex; align-items: baseline; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2);
  background: var(--bg-card-raised); border-radius: 6px;
}
.compound-trend-early-gw {
  font-size: var(--fs-xs); color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.04em; min-width: 4.5em;
}
.compound-trend-early-val {
  font-size: var(--fs-lg); font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}
.compound-trend-early-cum {
  font-size: var(--fs-xs); color: var(--text-secondary); margin-left: auto;
  font-variant-numeric: tabular-nums;
}

.compound-pillars-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 480px) {
  /* The headline's own floor, sat directly under it. Deliberately quieter than
   the score and louder than the footnotes: a reader who takes nothing else
   from the card should still leave knowing what "no skill" scores here. */
.compound-hero-baseline {
  display: flex;
  align-items: baseline;
  gap: var(--sp-1);
  flex-wrap: wrap;
  margin-top: var(--sp-1);
  font-size: var(--fs-xs);
  color: var(--text-secondary);
}

.compound-hero-baseline-value {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--accent-green);
}

/* The progression chart's stand-in before there are enough rounds to plot. */
.compound-trend-early-rows { display: flex; flex-direction: column; gap: var(--sp-1); }
.compound-trend-early-row {
  display: flex; align-items: baseline; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2);
  background: var(--bg-card-raised); border-radius: 6px;
}
.compound-trend-early-gw {
  font-size: var(--fs-xs); color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.04em; min-width: 4.5em;
}
.compound-trend-early-val {
  font-size: var(--fs-lg); font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}
.compound-trend-early-cum {
  font-size: var(--fs-xs); color: var(--text-secondary); margin-left: auto;
  font-variant-numeric: tabular-nums;
}

.compound-pillars-grid {
    grid-template-columns: 1fr;
  }
}

.pillar-card {
  background: var(--bg-card-raised);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  padding: var(--sp-3);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.pillar-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--sp-1);
}

.pillar-title {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-secondary);
}

.pillar-weight {
  font-size: var(--fs-micro);
  color: var(--text-muted);
  background: var(--bg-primary);
  padding: 1px 5px;
  border-radius: 3px;
}

.pillar-score-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin: var(--sp-1) 0;
}

.pillar-score-val {
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--text-primary);
}

.pillar-contribution {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.pillar-track {
  position: relative;
  width: 100%;
  height: 6px;
  background: var(--bg-tertiary);
  border-radius: 999px;
  overflow: hidden;
  margin-top: var(--sp-1);
}

/* BACKLOG #307: where a forecaster with NO skill lands on this pillar.
   Every one of the four floors sits a long way above zero — calibration's is
   66.7%, because that pillar is scaled against the worst POSSIBLE Brier
   rather than an achievable one — so a bar drawn from 0 with no marker
   reads as far more headroom than the pillar actually has. Same defect
   R35.2 removed from the standalone figures, in a different place. Drawn
   rather than only written, because the bar is what a reader looks at. */
.pillar-floor {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--text-primary);
  opacity: 0.55;
}

.pillar-fill {
  height: 100%;
  border-radius: 999px;
  transition: width var(--fill);
}

.pillar-fill.directional { background: var(--accent-green); }
.pillar-fill.calibration { background: var(--accent-blue); }
.pillar-fill.xg_alignment { background: var(--accent-gold); }
.pillar-fill.scoreline { background: var(--accent-highlight); }

/* Gameweek Summary Banner in Matches Tab */
.gw-summary-banner {
  background: var(--bg-card-raised);
  border: 1px solid var(--border-color);
  border-left: 4px solid var(--accent-green);
  border-radius: var(--radius-sm);
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-4);
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-3);
}

.gw-summary-title {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text-primary);
}

.gw-summary-stats {
  display: flex;
  gap: var(--sp-4);
  align-items: center;
  flex-wrap: wrap;
}

.gw-summary-stat-item {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

.gw-summary-stat-label {
  font-size: var(--fs-micro);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.gw-summary-stat-value {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text-primary);
}

/* Distribution Strip Plot */
.strip-plot-container {
  width: 100%;
  overflow-x: auto;
  margin: var(--sp-2) 0;
}

.strip-plot-svg {
  width: 100%;
  min-width: 520px;
  display: block;
}


/* ============================================================================
   COLLAPSE — the shared expand/collapse component (BACKLOG #270/#271/#272)

   MEASURED 2026-09-05, before this existed: `<details>` elements in `#app`,
   across all eight tabs — zero. The site had no progressive disclosure at all
   outside the footer glossary.

   It holds METHOD, APPARATUS and PROVENANCE only. Never a probability, never a
   verdict — see the rule beside `collapsible()` in app.js.
   ============================================================================ */

details.collapse {
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  background: var(--bg-card);
  margin-top: var(--sp-3);
}
details.collapse + details.collapse { margin-top: var(--sp-2); }

details.collapse > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3);
  /* #242 — 44px is the minimum tap target and a summary is a control. */
  min-height: 44px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-secondary);
}
details.collapse > summary::-webkit-details-marker { display: none; }
details.collapse > summary::marker { content: ""; }

/* The affordance. A summary with no caret reads as a heading, and a reader who
   does not know it opens will never tap it. */
details.collapse > summary::after {
  content: "";
  flex: none;
  width: 0; height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform var(--fill);
}
details.collapse[open] > summary::after { transform: rotate(90deg); }
details.collapse[open] > summary {
  color: var(--text-primary);
  border-bottom: 1px solid var(--border-color);
}
details.collapse > summary:focus-visible {
  outline: 2px solid var(--accent-green);
  outline-offset: -2px;
}
details.collapse > summary:hover { background: var(--bg-card-hover); }

.collapse-title { flex: 1 1 auto; }
.collapse-body { padding: var(--sp-3); }

/* A collapse inside a modal has the modal's own padding around it already. */
.modal-body details.collapse { background: var(--bg-card-raised); }

/* BACKLOG #460 — a collapse nested inside a `.card` (the league comparison
   panel's "flat strip always visible, 2D detail behind an expand" layout)
   used to draw its OWN border and background on top of the card's, so two
   views of one chart read as two stacked boxes rather than one chart with
   a fold-out half. This variant drops the second border/background/margin
   so the toggle reads as part of the card it lives in, not a card of its
   own. Confirmed by the operator's mobile screenshots, 2026-09-18. */
.card details.collapse.nested-collapse {
  border: none;
  background: transparent;
  border-radius: 0;
  margin-top: var(--sp-3);
  border-top: 1px solid var(--border-color);
}
.card details.collapse.nested-collapse > summary {
  padding-left: 0;
  padding-right: 0;
}
.card details.collapse.nested-collapse[open] > summary {
  border-bottom: 1px solid var(--border-color);
}
.card details.collapse.nested-collapse > .collapse-body {
  padding-left: 0;
  padding-right: 0;
}

/* BACKLOG #460 — the league comparison chart's one-line takeaway, stated in
   words before the reader has to read the chart. The chart still carries
   the same numbers; this is the "so what" that used to only exist in the
   share card's caption and the aria-label nobody sees. */
.league-comparison-headline {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: var(--sp-3);
  line-height: 1.4;
}
.league-comparison-headline strong { color: inherit; }

/* BACKLOG #271 — the calls ledger: a ranked table whose rows open in place,
   replacing twelve stacked cards (MEASURED 7,636px, 9.0 phone screens). */
table.log tr.call-row { cursor: pointer; }
table.log tr.call-row:focus-visible {
  outline: 2px solid var(--accent-green);
  outline-offset: -2px;
}
table.log tr.call-row.open > td {
  background: var(--bg-card-hover);
  border-bottom-color: transparent;
}
table.log tr.call-detail-row > td {
  background: var(--bg-card-hover);
  padding: 0 var(--sp-2) var(--sp-3);
}
/* The card inside an open row is already inside a bordered table cell — a
   second border around it reads as a box in a box. */
table.log tr.call-detail-row .card {
  border: none;
  background: transparent;
  padding: 0;
}
.calls-ledger td:first-child { white-space: normal; min-width: 130px; }

/* BACKLOG #277 / R6 — the one key the twenty club bars share, in the section
   footer instead of repeated under every card. */
.segbar-key-shared {
  margin: var(--sp-2) 0 var(--sp-3);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--border-color);
}

/* BACKLOG #250 item 5 — the median finish, shown beside the mean only when the
   two differ by half a place or more. Muted and small: it is context for the
   number beside it, not a second headline. */
.xpos-median {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  margin-left: var(--sp-1);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   BACKLOG #298-A — the reconstructed rounds, and #345 part 1, which retired
   the notice card these rules used to style.

   Only the ROW MARKER remains, and it now carries the whole distinction on its
   own: gameweeks 1-3 were rebuilt from pre-kickoff data rather than selected
   live. Deliberately NOT styled as an error or a warning — these rounds are
   honest work and the numbers are trustworthy, they are simply not evidence
   the rule works, because the rule was written after they were played. That is
   a category, not a fault, so it reads as a quiet aside. Red would say the
   wrong thing.

   Small, because it repeats on every reconstructed row. It used to lean on the
   banner above for the explanation; there is no banner now, so `recordRow`
   carries the full sentence in the marker's `title`. */
.rebuilt-tag {
  display: inline-block;
  margin-left: var(--sp-2);
  padding: 0 6px;
  border: 1px solid var(--accent-blue);
  border-radius: 3px;
  font-size: var(--fs-micro);
  line-height: 1.5;
  color: var(--accent-blue);
  white-space: nowrap;
}

/* ------------------------------------------------------ share cards (#371)

   The match-up modal can hand you the match as a picture. Three things are
   styled here and nothing else: the button that starts it, the sheet a browser
   that cannot attach a file to its share sheet falls back to, and the marker
   the modal wraps each declared section in.

   `.modal-section` is `display: contents` ON PURPOSE and it is load-bearing.
   BACKLOG #371's sync mechanism needs every pre-kickoff section to carry its
   own `data-section` marker in the DOM, and a marker that generated a box
   would have changed the rendered geometry of a modal this change was not
   supposed to touch. `display: contents` lays the children out exactly where
   they were — including as grid items of the `grid cols-3` headline row — so
   the marker costs nothing and is still queryable. */
.modal-section { display: contents; }

.modal-head-actions { display: flex; align-items: center; gap: var(--sp-2); flex-shrink: 0; }

/* Icon-only (operator, 2026-09-12) — same 30x30 box as `.modal-close` beside
   it, so the two controls in `.modal-head-actions` read as one pair rather
   than a button and a label. WCAG 2.5.8's 24x24 floor is comfortably inside
   this, and `website_layout.js`'s hit-area check still measures it. */
.share-btn {
  appearance: none; cursor: pointer;
  background: var(--bg-card-hover); border: 1px solid var(--border-highlight);
  color: var(--text-secondary); border-radius: var(--radius-sm);
  width: 30px; height: 30px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 1;
}
.share-btn:hover:not(:disabled) { color: var(--text-primary); border-color: var(--accent-green); }
.share-btn:disabled { opacity: 0.6; cursor: progress; }
.share-btn svg { display: block; }

.share-sheet {
  position: fixed; inset: 0; z-index: 40;
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-4); background: var(--scrim); overflow-y: auto;
}
.share-sheet-inner {
  background: var(--bg-card); border: 1px solid var(--border-highlight);
  border-radius: var(--radius); box-shadow: var(--shadow);
  width: min(460px, 100%); padding: var(--sp-4); margin: auto 0;
}
.share-sheet-head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-3); }
.share-preview {
  display: block; width: 100%; height: auto; margin-top: var(--sp-3);
  border: 1px solid var(--border-color); border-radius: var(--radius-sm);
}
.share-actions, .share-links {
  display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3);
}
.share-action {
  appearance: none; cursor: pointer; text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--bg-card-hover); border: 1px solid var(--border-highlight);
  color: var(--text-secondary); border-radius: var(--radius-sm);
  min-height: 44px; padding: 0 var(--sp-4); flex: 1 1 auto;
  font-family: var(--font); font-size: var(--fs-sm); font-weight: 600; line-height: 1;
}
.share-action:hover { color: var(--text-primary); border-color: var(--accent-highlight); }
.share-action.primary {
  background: var(--accent-soft); border-color: var(--accent-green); color: var(--accent-green);
}

/* The quarantine banner — operator, 2026-09-12. It reads as a CAUTION, not a
   failure: `--accent-gold` is what "NOT LOCKED" wears, and a quarantined club
   is withheld from the betting card rather than marked wrong. The left spine
   carries the colour so the body text keeps full contrast against the raised
   surface. */
.quarantine-banner {
  border: 1px solid var(--accent-gold);
  border-left-width: 6px;
  border-radius: var(--radius);
  background: var(--bg-card-raised);
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-4);
}
.quarantine-head {
  display: flex; align-items: center; gap: var(--sp-2);
  flex-wrap: wrap; margin-bottom: var(--sp-2);
}
.quarantine-clubs { font-weight: 600; color: var(--text-secondary); }
.quarantine-club { margin: 0 0 var(--sp-2); color: var(--text-secondary); font-size: var(--fs-sm); }
.quarantine-club strong { color: var(--text-primary); }

/* Conviction legend bar & matches mobile responsive controls (BACKLOG #372) */
.conviction-legend-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  align-items: center;
  font-size: var(--fs-xs);
}
@media (max-width: 600px) {
  .matches-legend-block {
    align-items: flex-start !important;
    width: 100%;
  }
  .prob-legend-hint, .conviction-legend-bar {
    align-self: flex-start;
    flex-wrap: wrap;
  }
}


/* ==========================================================================
   THE TWO HEADLINE BLOCKS — BACKLOG #380
   Reasoning: data/ACCURACY_PAGE_PHILOSOPHY.md §1. Two equally weighted
   blocks replace a 3-tile KPI row plus a duplicate hero card. They are
   deliberately the SAME weight as each other: neither the hero nor
   calibration is the "real" headline, because they answer different
   questions and the charter forbids implying one restates the other.
   ========================================================================== */
.headline-blocks {
  margin-bottom: var(--sp-4);
  align-items: stretch;
}

.headline-block {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  border: 1px solid var(--border-highlight);
}

.headline-block-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}

.headline-block-label {
  font-weight: 700;
  text-transform: uppercase;
  font-size: var(--fs-micro);
  color: var(--text-muted);
  letter-spacing: 0.04em;
}

.headline-block-value {
  font-size: var(--fs-2xl);
  font-weight: 800;
  color: var(--text-primary);
  letter-spacing: -0.02em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

/* An empty headline is a real state, not a failure — the rolling window has
   not filled. It must read as "not yet", never as a broken zero. */
.headline-block-empty { color: var(--text-muted); font-weight: 700; }

.headline-block-sub {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--text-secondary);
  line-height: 1.45;
}

.headline-block-note {
  margin-top: auto;
  font-size: var(--fs-micro);
  line-height: 1.45;
}

/* The hero's two components, and calibration's Resolution/sample pair.
   Same row shape for both, because in both cases these are the supporting
   numbers that keep the headline above them honest. */
.headline-edges {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--border-color);
}

.headline-edge-row {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  flex-wrap: wrap;
  font-size: var(--fs-xs);
}

.headline-edge-label { font-weight: 600; color: var(--text-primary); min-width: 7.5em; }
.headline-edge-value { font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text-primary); }
.headline-edge-value.is-positive { color: var(--accent-green); }
.headline-edge-value.is-negative { color: var(--accent-red); }
.headline-edge-floor { color: var(--text-muted); font-size: var(--fs-micro); }

/* BACKLOG #383 — the descriptive tier card. No status colour on any tier,
   deliberately: a green "High conviction" tile tells a reader to want more
   of it, and confidence is MEASURED flat and uncorrelated with accuracy. */
.descriptive-tier-card .stat { border-left-color: var(--border-color); }

/* BACKLOG #381 — the sparse chart labels. The current-point value is the
   only number drawn on the line, so it carries the weight the six
   overlapping strings used to share between them. */
.chart-current-label { font-weight: 700; }
.chart-baseline-label, .chart-strip-caption { letter-spacing: 0.01em; }

@media (max-width: 720px) {
  .headline-block-value { font-size: var(--fs-xl); }
  .headline-edge-label { min-width: 0; }
}

/* BACKLOG #467 — Strength of Schedule Faced (CannonStats Quadrant Scatter) */
.sos-scatter-card {
  margin-top: var(--sp-4);
  padding: var(--sp-4);
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
}

.sos-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}

.sos-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 4px 10px;
  border-radius: 999px;
  font-size: var(--fs-xs);
  font-weight: 600;
  border: 1px solid currentColor;
}

.sos-badge.q1 { color: var(--accent-green); background: rgba(16, 185, 129, 0.1); border-color: rgba(16, 185, 129, 0.3); }
.sos-badge.q2 { color: var(--accent-blue); background: rgba(56, 189, 248, 0.1); border-color: rgba(56, 189, 248, 0.3); }
.sos-badge.q3 { color: var(--accent-gold); background: rgba(251, 191, 36, 0.1); border-color: rgba(251, 191, 36, 0.3); }
.sos-badge.q4 { color: var(--accent-red); background: rgba(251, 85, 116, 0.1); border-color: rgba(251, 85, 116, 0.3); }

.sos-svg-wrap {
  width: 100%;
  overflow-x: auto;
  margin: var(--sp-3) 0;
  position: relative;
}

.sos-scatter-svg {
  display: block;
  width: 100%;
  max-width: 720px;
  height: auto;
  margin: 0 auto;
}

.sos-point {
  cursor: pointer;
}

.sos-point:hover circle.point-dot,
.sos-point:focus-visible circle.point-dot {
  stroke-width: 3px;
}

.sos-point.is-selected circle.point-dot {
  stroke: var(--accent-highlight);
  stroke-width: 3.5px;
}

.sos-point text.point-label {
  font-family: inherit;
  font-size: 10px;
  font-weight: 700;
  fill: var(--text-primary);
  pointer-events: none;
  user-select: none;
}

.sos-point.is-selected text.point-label {
  fill: var(--accent-highlight);
  font-weight: 800;
  font-size: 11px;
}

.sos-quadrant-watermark {
  font-family: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  user-select: none;
  pointer-events: none;
}

.sos-axis-line {
  stroke: var(--border-color);
  stroke-width: 1;
  stroke-dasharray: 4 4;
}

.sos-axis-text {
  font-family: inherit;
  font-size: 10px;
  fill: var(--text-muted);
  user-select: none;
}

.sos-detail-strip {
  background: var(--bg-card-raised);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  padding: var(--sp-3);
  margin-top: var(--sp-3);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-3);
}

.sos-table-wrap {
  margin-top: var(--sp-3);
  overflow-x: auto;
}

.sos-match-outcome-pill {
  display: inline-block;
  min-width: 22px;
  text-align: center;
  padding: 1px 6px;
  border-radius: 4px;
  font-size: var(--fs-xs);
  font-weight: 700;
}
.sos-match-outcome-pill.W { background: rgba(16, 185, 129, 0.15); color: var(--accent-green); }
.sos-match-outcome-pill.D { background: rgba(56, 189, 248, 0.15); color: var(--accent-blue); }
.sos-match-outcome-pill.L { background: rgba(251, 85, 116, 0.15); color: var(--accent-red); }

/* Tactical Stylistic Clash & Match Preview Breakdown (BACKLOG #483) */
.tactical-clash-panel {
  margin-top: var(--sp-4);
}
.tactical-clash-panel .card {
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
}
.tactical-clash-panel .badge {
  background: var(--bg-badge, rgba(255, 255, 255, 0.06));
  color: var(--text-secondary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  padding: 2px var(--sp-2);
}
