/* NOTE (26 Aug 2026): minted fresh on purpose — the previous hash, 736c3f84,
   has the previous build's bytes cached against it at the edge for 30 days. See
   the matching note at the top of js/main.js and the deploy rule in Claude.md. */
/* =========================================================
   Fonts — self-hosted (F19b, 10 Aug 2026). No third party.
   =========================================================
   This used to be `@import url(fonts.googleapis.com/...)` on line 1, and that
   line was two problems at once:

   - PRIVACY, which is why it went. An @import on the first line of the CSS
     sends the visitor's IP to Google BEFORE our own consent banner (theme.js)
     has drawn a single pixel — the banner is JS, the font request is not. It
     was the one piece of the stack that contradicted the rest of the posture.
     Self-hosting deletes the question: no third party, nothing to consent to.
   - CLS. Measured on the live host (F19): /results sat at 0.171 and disabling
     the @import took it to 0 — the shift was entirely the font swap. @import
     is a SERIAL chain (the browser must parse style.css to learn the font
     exists), so style.css finished at 21 ms and the font at 721 ms. These
     files are same-origin and <link rel="preload">-ed from every <head>, so
     they now start with the CSS instead of after it.

   ONE FILE PER FAMILY PER SUBSET, not per weight. Google's API (Inter v20,
   Sora v9) no longer serves static instances: every weight in the css2 URL
   came back pointing at the SAME woff2 — a variable font sliced by subset,
   verified by reading the woff2 table directory (fvar + gvar present in all
   four). So the ROADMAP's "static instances, not variable" line is moot; there
   was nothing to choose. It lands where that decision wanted to be anyway —
   ~171 KB total for every weight we use, where five static Inter latin
   instances alone would have been more, and the unused Inter 900 that the old
   @import downloaded on every visit costs nothing here because it's the same
   file as 400.

   latin-ext is NOT optional: without it "Kauno Žalgiris", "Beşiktaş" and
   "Fenerbahçe" fall back to system fonts mid-sentence. It is a separate file
   and a separate unicode-range, so pages with no such character never fetch it.

   Both families are SIL Open Font License — see fonts/OFL-Inter.txt and
   fonts/OFL-Sora.txt, kept in the repo as the licence requires.

   The URLs are unversioned and cached hard (see /fonts/* in _headers): a woff2
   is immutable, and `npm run stamp` deliberately doesn't touch them. Replacing
   a font means a new filename, not a new hash on the old one. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;                 /* variable: one file covers 400–800 */
  font-display: swap;
  src: url('/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 100 800;                 /* Sora's axis stops at 800 */
  font-display: swap;
  src: url('/fonts/sora-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url('/fonts/sora-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* =========================================================
   Design tokens (validated categorical palette + chart chrome
   from the dataviz reference palette — see plan doc §dataviz)
   ========================================================= */
:root {
  color-scheme: light;

  /* Chrome & ink */
  /* Measure for the standing text under a page heading. One value, because the
     defect it fixes was three of them stacked: /article ran .section-lead at
     640px, an inline 760px override, and .fx-intro at the full 1080px container,
     so one block of copy had three right edges. Widened from 640 at the same
     time — 640 in a 1080 container reads as text huddled against the left rule
     rather than as a column.

     Not the full width, though: 1080px of body text runs to about 140 characters
     a line, and the eye loses the start of the next line somewhere past 90. This
     is the widest a lede goes while a reader can still track it. */
  --lede-measure:   820px;
  --page-plane:     #f4f5f8;
  --surface-1:      #f8f9fb;
  --surface-2:      #ffffff;
  --text-primary:   #0c1322;
  --text-secondary: #4a5468;
  --text-muted:     #8b93a3;
  --gridline:       #e6e8ef;
  --border:         rgba(12, 19, 34, 0.09);

  /* Categorical (validated: PASS light+dark, secondary encoding via labels) */
  --series-home:    #2a78d6; /* blue  */
  --series-draw:    #008300; /* green */
  --series-away:    #e34948; /* red   */

  /* Status palette (fixed — never themed), for form pills */
  --status-good:    #0ca30c;
  --status-warn:    #fab219;
  --status-bad:     #d03b3b;

  /* Rating scale accents */
  --rating-track:   #e6e8ef;

  /* Brand accent + gradient (premium chrome) */
  /* Broadcast red: "this is happening now". Its own token, not --series-away
     and not --status-bad, because it means neither of those — a live score is
     not an away team and not a failure, and the three must be free to move
     independently. #e5384f was already hardcoded in .live-badge; this is that
     colour given a name and used everywhere the site says LIVE. */
  --live:           #e5384f;
  --accent:         #2a6df0;
  --accent-2:       #6a34e0;
  --accent-ink:     #ffffff;
  --brand-grad:     linear-gradient(135deg, #2a6df0 0%, #6a34e0 100%);

  /* Deep hero canvas */
  --hero-1:         #0b1224;
  --hero-2:         #141d3a;
  --hero-3:         #221a4d;

  /* Elevation */
  --shadow-sm:  0 1px 2px rgba(12, 19, 34, 0.06), 0 1px 3px rgba(12, 19, 34, 0.05);
  --shadow-md:  0 6px 16px rgba(12, 19, 34, 0.08), 0 2px 6px rgba(12, 19, 34, 0.05);
  --shadow-lg:  0 18px 44px rgba(12, 19, 34, 0.14), 0 6px 14px rgba(12, 19, 34, 0.08);
  --shadow-hero: 0 30px 60px rgba(8, 12, 30, 0.45);

  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 18px;
  --radius-xl: 24px;

  --font-body: 'Inter', system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-display: 'Sora', 'Inter', system-ui, sans-serif;

  font-size: 16px;
}

/* ---------------- Dark theme (validated dark palette) ---------------- */
:root[data-theme="dark"] {
  color-scheme: dark;

  --page-plane:     #080b14;
  --surface-1:      #10141f;
  --surface-2:      #161b28;
  --text-primary:   #f4f6fb;
  --text-secondary: #b3bccd;
  --text-muted:     #7c869a;
  --gridline:       #232937;
  --border:         rgba(255, 255, 255, 0.09);

  --series-home:    #3987e5; /* blue  (dark step) */
  --series-draw:    #008300; /* green (mode-invariant) */
  --series-away:    #e66767; /* red   (dark step) */

  --rating-track:   #232937;

  --live:           #ff5f74; /* lifted for the dark surface */
  --accent:         #5a8bff;
  --accent-2:       #9a6cff;
  --accent-ink:     #ffffff;
  --brand-grad:     linear-gradient(135deg, #3f74f5 0%, #8a52f0 100%);

  --shadow-sm:  0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-md:  0 8px 20px rgba(0, 0, 0, 0.45);
  --shadow-lg:  0 20px 46px rgba(0, 0, 0, 0.55);
  --shadow-hero: 0 30px 60px rgba(0, 0, 0, 0.6);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--page-plane);
  color: var(--text-primary);
  font-family: var(--font-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

img { max-width: 100%; display: block; }

.container {
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 20px;
}

/* Global polish */
html { scroll-behavior: smooth; }
img { max-width: 100%; }
::selection { background: color-mix(in srgb, var(--accent) 26%, transparent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 5px; }
@media (prefers-reduced-motion: reduce) { * { animation: none !important; scroll-behavior: auto; } }

/* ---------------- Header / Nav ---------------- */
.site-header {
  background: color-mix(in srgb, var(--surface-2) 82%, transparent);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 20;
}
.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 68px;
}
.logo {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.28rem;
  color: var(--text-primary);
  letter-spacing: -0.03em;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  text-decoration: none;
}
.logo:hover { text-decoration: none; }
/* The mark, not a placeholder. This was an empty rounded square filled with the
   brand gradient, while the actual logo — the three ascending bars — existed only
   in favicon.svg and was shown in the browser tab and nowhere else. Same file, so
   the header and the tab are now the same mark and it costs no extra request:
   every page already links it as its icon. */
.logo::before {
  content: "";
  width: 26px; height: 26px;
  border-radius: 8px;
  /* Two LAYERS, not one declaration: the background shorthand takes a single
     <image> per layer, and --brand-grad is itself a gradient — passing both in one
     shorthand makes the whole declaration invalid and the mark renders blank. The
     gradient stays underneath as the fallback if the SVG ever fails to load. */
  background-image: url("/favicon.svg"), var(--brand-grad);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  box-shadow: 0 4px 10px rgba(42, 109, 240, 0.4);
}
.logo span {
  background: var(--brand-grad);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
/* ONE ROW, ALWAYS. `.site-header .container` is a fixed 68px, so a nav that
   wraps does not get taller — it breaks out of the bar. That is what the eighth
   item did: "Fixtures &" sat above "Predictions", "Accuracy" above its arrow,
   and the whole header came apart. Two rules hold the line, and neither is
   decoration:
     flex-wrap: nowrap   the row cannot become two rows inside a fixed height
     white-space: nowrap a label cannot be split down the middle
   The spacing below is tighter than it was for the same reason: the container
   caps at 1120px, so the room the nav has is the SAME at every desktop width —
   it was never "tight on small screens", it was tight everywhere, and seven
   items happened to fit. */
.main-nav { display: flex; flex-wrap: nowrap; gap: 4px; }
.main-nav a {
  color: var(--text-secondary);
  font-weight: 600;
  font-size: 0.88rem;
  padding: 8px 11px;
  border-radius: 8px;
  white-space: nowrap;
  transition: color .15s ease, background .15s ease;
}
.main-nav a:hover { color: var(--text-primary); background: var(--surface-1); text-decoration: none; }
.main-nav a.active {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
/* The one accented item in the nav — the door into the two accuracy rankings.
   Coloured rather than boxed: the nav is a row of equals and a button in it
   would outrank the sections of the site, which is not what this is. It reads
   the same way the "All fixtures & predictions →" links do further down the
   page, so a returning reader recognises the shape before the word. */
.main-nav a.nav-accent { color: var(--accent); }
.main-nav a.nav-accent:hover {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
/* `active` wins where the reader is already on one of the two ranking pages, so
   the accent does not have to fight it — but the arrow should not sit inside a
   filled chip either. */
.main-nav a.nav-accent.active { color: var(--accent); }

/* Hamburger (mobile) — injected by theme.js */
.nav-toggle {
  display: none; flex-direction: column; justify-content: center; gap: 5px;
  width: 42px; height: 42px; margin-left: 8px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface-2); cursor: pointer;
}
.nav-toggle span {
  display: block; width: 18px; height: 2px; margin: 0 auto; border-radius: 2px;
  background: var(--text-primary); transition: transform .2s ease, opacity .2s ease;
}
.nav-toggle--open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle--open span:nth-child(2) { opacity: 0; }
.nav-toggle--open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* The drawer takes over at 1024px, raised from 820px when the nav went to eight
   items. Between those two widths eight labels cannot be laid out on one line at
   any spacing that is still readable, and the alternatives are both worse: a row
   that wraps breaks the fixed-height bar, and a row that scrolls sideways hides
   half the site behind a gesture nobody knows is there. The drawer already
   exists, already holds every item at a comfortable size, and is what a tablet
   reader expects. */
@media (max-width: 1024px) {
  .nav-toggle { display: flex; }
  .main-nav {
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; flex-wrap: wrap; gap: 2px; display: none;
    background: var(--surface-2); border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow-md); padding: 10px 16px 14px;
  }
  .main-nav--open { display: flex; }
  .main-nav a { padding: 13px 14px; font-size: 1rem; }
}

/* ---------------- Ad slots ---------------- */
/* A slot is a mount point, not a box: it renders NOTHING until js/ads.js has
   actually put an AdSense <ins> in it (which it marks with data-ad-done). With
   no publisher id configured there is no ad, so there is nothing to show —
   empty "Advertisement" chrome makes a live site look unfinished to visitors
   and to an AdSense reviewer alike. This is the same rule mountMobileAnchorAd()
   in js/main.js already follows for the sticky bar; the static slots were the
   exception, and dashed placeholder boxes labelled "Advertisement · 728×90"
   were shipping on five live pages.
   Keeping the empty divs in the markup is deliberate: turning ads on at D2 is
   then a config change in js/ads.js, not a markup change across five pages.
   Filled slots are fluid — never wider than their container, smaller on phones. */
.ad-slot { display: none; }
.ad-slot[data-ad-done] {
  display: block;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  border-radius: var(--radius-sm);
  margin: 28px auto;
  overflow: hidden;
}
/* Leaderboard: caps at 728px, centred; collapses to a mobile banner < 600px. */
.ad-slot--leaderboard { height: 90px; max-width: 728px; }
.ad-slot--rectangle   { height: 250px; max-width: 300px; }
.ad-slot--infeed      { min-height: 100px; }
/* An in-feed unit dropped inside a card grid spans the full row instead of
   taking one card cell. */
.grid-cards > .ad-slot { grid-column: 1 / -1; margin: 8px 0; }
/* A jump link lands the target under the sticky header without this. */
.section-heading[id] { scroll-margin-top: 88px; }
/* The card is a flex column now, so the line break the template used to stack
   the confidence chip above the link would be an empty flex item of its own. */
.match-card > br { display: none; }


@media (max-width: 600px) {
  /* Has to match the [data-ad-done] specificity above, or the desktop margin wins. */
  .ad-slot[data-ad-done] { margin: 20px auto; }
  .ad-slot--leaderboard { height: 100px; max-width: 336px; } /* 320×100 mobile banner */
  .ad-slot--infeed { min-height: 120px; }
}

/* Mobile sticky anchor — highest-ROI mobile format. Injected once by main.js
   and shown only on phones; hidden on desktop. */
.ad-anchor {
  display: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 900;
  height: 56px;
  align-items: center;
  justify-content: center;
  background: var(--surface, #fff);
  border-top: 1px solid var(--gridline, #e2e2e2);
  color: var(--text-muted);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  box-shadow: 0 -4px 14px rgba(0, 0, 0, 0.06);
}
:root[data-theme="dark"] .ad-anchor {
  background: #12161f;
  border-top-color: #2c3444;
  box-shadow: 0 -4px 14px rgba(0, 0, 0, 0.35);
}
.ad-anchor__close {
  position: absolute;
  top: -11px;
  right: 8px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid var(--gridline, #e2e2e2);
  background: var(--surface, #fff);
  color: var(--text-secondary, #555);
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
:root[data-theme="dark"] .ad-anchor__close { background: #12161f; border-color: #2c3444; }
@media (max-width: 700px) {
  .ad-anchor { display: flex; }
  /* Keep the sticky bar from covering the last of the footer. */
  .site-footer { padding-bottom: 76px; }
}

/* ---------------- Page-title hero (inner pages) ---------------- */
.hero {
  padding: 40px 0 8px;
  text-align: center;
}
.hero h1 {
  font-family: var(--font-display);
  font-size: 2.1rem;
  margin: 0 0 12px;
  letter-spacing: -0.03em;
}
.hero p {
  color: var(--text-secondary);
  max-width: var(--lede-measure);
  margin: 0 auto;
  font-size: 1.05rem;
  line-height: 1.65;
  text-wrap: pretty;
}

/* ---------------- Premium hero (homepage) ---------------- */
.hero-premium {
  position: relative;
  background:
    radial-gradient(1200px 500px at 85% -10%, rgba(106, 52, 224, 0.55), transparent 60%),
    radial-gradient(900px 460px at 8% 120%, rgba(42, 109, 240, 0.45), transparent 55%),
    linear-gradient(160deg, var(--hero-1) 0%, var(--hero-2) 55%, var(--hero-3) 100%);
  color: #eef1fb;
  overflow: hidden;
  border-bottom: 1px solid rgba(255,255,255,0.06);
}
.hero-premium::after {
  /* subtle top pitch-line texture */
  content: "";
  position: absolute; inset: 0;
  background-image: linear-gradient(rgba(255,255,255,0.035) 1px, transparent 1px);
  background-size: 100% 46px;
  pointer-events: none;
  opacity: 0.6;
}
.hero-grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: 44px;
  align-items: center;
  padding-top: 68px;
  padding-bottom: 76px;
}
@media (max-width: 900px) {
  .hero-grid { grid-template-columns: 1fr; gap: 32px; padding-top: 48px; padding-bottom: 52px; }
}
.hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #bcc7f2;
  background: rgba(255,255,255,0.07);
  border: 1px solid rgba(255,255,255,0.12);
  padding: 6px 12px;
  border-radius: 30px;
}
.hero-eyebrow::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: #35d07f; box-shadow: 0 0 0 3px rgba(53,208,127,0.25);
}
.hero-copy h1 {
  font-family: var(--font-display);
  font-size: clamp(1.8rem, 5vw, 3.4rem);
  line-height: 1.08;
  letter-spacing: -0.035em;
  margin: 18px 0 18px;
  color: #fff;
  text-wrap: balance;
}
.hero-copy h1 .grad {
  background: linear-gradient(120deg, #7aa2ff, #b98bff);
  -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
}
.hero-copy p {
  font-size: 1.1rem;
  line-height: 1.6;
  color: #c7cfe2;
  max-width: 46ch;
  margin: 0 0 28px;
  text-wrap: pretty;
}
.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; }
/* The jump to the scoreboard, four screens below. A quiet link and not a third
   button on purpose: two buttons are a choice, three are a menu, and on a phone
   they stack — a third would push the match of the day off the first screen in
   order to advertise something further down the same page. */
.hero-jump {
  align-self: center; margin-left: 4px;
  font-size: 0.92rem; font-weight: 600; color: var(--accent-ink);
  opacity: 0.82; text-decoration: none;
}
.hero-jump:hover { opacity: 1; text-decoration: underline; }
@media (max-width: 600px) { .hero-jump { align-self: flex-start; margin: 2px 0 0 2px; } }

@media (max-width: 600px) {
  .hero-grid { padding-top: 28px; padding-bottom: 34px; }
  .hero-copy h1 { margin: 14px 0 14px; }
  .hero-copy p { font-size: 1rem; line-height: 1.55; margin-bottom: 22px; }
  .hero-eyebrow { font-size: 0.68rem; padding: 5px 10px; }
  .btn--lg { padding: 13px 20px; font-size: 0.95rem; }
}

/* ---------------- Stat strip ---------------- */
.stat-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
  margin: -40px auto 8px;
  position: relative;
  z-index: 5;
}
.stat-strip--team { margin: 22px 0 8px; }
/* Five tiles, because the fifth is the track record and it earns its place: it
   is the claim the rest of the site rests on, and until it came up here it lived
   four screens down behind a client render.
   Defined HERE, above the media query, and not next to the other modifiers two
   thousand lines below: these selectors carry equal specificity, so whichever is
   written last wins, and the phone was getting five columns in a 412px viewport. */
.stat-strip--five { grid-template-columns: repeat(5, 1fr); }
/* The only tile that is a link, so it says so — and it is the one tile a reader
   should be able to tell apart at a glance. */
a.stat-tile--record { text-decoration: none; transition: border-color 0.15s, transform 0.15s; }
a.stat-tile--record .stat-value { color: var(--accent); }
a.stat-tile--record:hover { border-color: var(--accent); transform: translateY(-1px); }
@media (max-width: 720px) {
  .stat-strip { grid-template-columns: repeat(2, 1fr); gap: 12px; margin-top: -28px; }
  .stat-strip--five { grid-template-columns: repeat(2, 1fr); }
  /* Five tiles in a two-column grid leaves the last one alone in its row. Rather
     than let it sit half-width and look like an accident, the track record spans
     both columns — which is where the emphasis belonged anyway, on the phone
     where almost nobody reaches the section four screens below. */
  .stat-strip--five .stat-tile--record { grid-column: 1 / -1; }
  .stat-tile { padding: 13px 14px; border-radius: var(--radius-md); }
  .stat-tile .stat-value { font-size: 1.45rem; }
  .stat-tile .stat-value .unit { font-size: 0.85rem; }
  .stat-tile .stat-label { font-size: 0.75rem; margin-top: 5px; }
}
.stat-tile {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 18px 20px;
  box-shadow: var(--shadow-md);
}
.stat-tile .stat-value {
  font-family: var(--font-display);
  font-size: 1.85rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--text-primary);
  display: block;
  line-height: 1;
}
.stat-tile .stat-value .unit { font-size: 1rem; color: var(--accent); margin-left: 2px; }
.stat-tile .stat-label {
  display: block;
  margin-top: 7px;
  font-size: 0.82rem;
  color: var(--text-secondary);
}

/* ---------------- Section headings ---------------- */
.section-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin: 48px 0 20px;
}
.section-heading h2 {
  font-family: var(--font-display);
  font-size: 1.5rem;
  letter-spacing: -0.02em;
  margin: 0;
}
.section-heading a { font-size: 0.9rem; font-weight: 600; }

/* ---------------- Layout grids ---------------- */
/* EVERY CARD IS ITS OWN HEIGHT. A grid item stretches to the row by default,
   and the cards on one row are not the same length of thing: an upcoming match
   carries a predicted scoreline, three probabilities with their fair odds, the
   note under them and a confidence chip, while a finished one carries a score and
   a verdict. Stretched to sit beside a card twice its content, the short one gets
   a hole in it — reported 31 Aug 2026 with Barcelona (upcoming) between Lecce 0-4
   (finished) and Osasuna (live), the two of them half empty.

   Aligning to the start is the fix that costs nothing else. Grouping the grid by
   state would end the mixed rows too, and it is what was tried first — but the
   homepage re-runs this whole render on every live poll, so an order that depends
   on live state re-sorts the grid under the reader as matches kick off and finish.
   That regression is from 29 Aug 2026 and has its own guard at the bottom of
   test/hub-pages.test.js. Card heights are a layout problem; they do not need to
   be paid for in reading order. */
.grid-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
  gap: 20px;
  align-items: start;
}
.page-layout {
  display: grid;
  grid-template-columns: 1fr 300px;
  gap: 32px;
  align-items: start;
}
/* A grid item's automatic minimum size is its MIN-CONTENT width, so one wide
   child (the league table, whose cells are nowrap) pushed the `1fr` track past
   the viewport and dragged every paragraph, bar and heading out with it — the
   whole right edge of the team page was cut off on a phone. min-width:0 lets
   the track shrink to the container again and hands the overflow back to the
   one element built to scroll (.mtbl__scroll). */
.page-layout > main,
.page-layout > .sidebar { min-width: 0; }
@media (max-width: 860px) {
  .page-layout { grid-template-columns: 1fr; }
  .sidebar { order: 2; display: flex; flex-direction: column; }
  /* On mobile the sidebar stacks under the main column, so the in-content
     native ad and the sidebar rectangle would otherwise sit back-to-back.
     Push the rectangle below the "More this week" box so real content
     separates the two ad units (AdSense density rule). */
  .sidebar .ad-slot--rectangle { order: 2; margin: 8px auto 24px; }
  .sidebar .sidebar-box { order: 1; }
}

/* ---------------- Match card ---------------- */
/* A column, so the link goes on the FLOOR of the card rather than leaving the
   leftover height under it. The state breaks above mean a row no longer mixes a
   tall card with a short one, but two cards in the same state still differ by a
   wrapped venue name or a pending prediction — and a grid row is as tall as its
   tallest card whatever is in it. This is what makes the remaining slack read as
   spacing instead of as a hole. */
.match-card {
  display: flex;
  flex-direction: column;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 20px 22px;
  box-shadow: var(--shadow-sm);
  transition: transform .2s cubic-bezier(.2,.7,.3,1), box-shadow .2s ease, border-color .2s ease;
}
.match-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
  border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
}
.match-card__meta {
  display: flex;
  justify-content: space-between;
  font-size: 0.74rem;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 14px;
}
.match-card__teams {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 10px;
  margin-bottom: 6px;
}
.team {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  text-align: center;
  min-width: 0;
}
.team-badge {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 700;
  font-size: 0.86rem;
  letter-spacing: 0.02em;
  flex-shrink: 0;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
}
.team-name {
  font-weight: 700;
  font-size: 0.95rem;
  line-height: 1.2;
  color: var(--text-primary);
}
.vs-pill {
  font-size: 0.68rem;
  font-weight: 700;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 4px 9px;
  align-self: center;
}
.match-card__venue {
  font-size: 0.82rem;
  color: var(--text-secondary);
  margin-bottom: 14px;
}

/* ---------------- Prediction bar (validated categorical: home/draw/away) --- */
.prediction {
  margin-top: 8px;
}
.prediction--centered .prediction__score {
  text-align: center;
}
.prediction__score {
  font-weight: 700;
  font-size: 1.4rem;
  margin-bottom: 8px;
}
.prediction__score .label {
  font-weight: 500;
  font-size: 0.78rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  display: block;
  margin-bottom: 2px;
}
.prob-bar {
  display: flex;
  width: 100%;
  height: 20px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--gridline);
}
.prob-bar__seg {
  height: 100%;
}
.prob-bar__seg + .prob-bar__seg { margin-left: 2px; } /* surface gap between segments */
.prob-bar__seg--home { background: var(--series-home); }
.prob-bar__seg--draw { background: var(--series-draw); }
.prob-bar__seg--away { background: var(--series-away); }

.prob-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 8px;
  font-size: 0.8rem;
  color: var(--text-secondary);
}
.prob-legend__item { display: flex; align-items: center; gap: 6px; }
.prob-legend__dot {
  width: 9px; height: 9px; border-radius: 50%; display: inline-block;
}
.prob-legend__dot--home { background: var(--series-home); }
.prob-legend__dot--draw { background: var(--series-draw); }
.prob-legend__dot--away { background: var(--series-away); }

/* ---------------- Fair-odds grid (model-implied 1X2 odds) ---------------- */
.odds-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2px; /* surface gap between cells */
  margin-top: 10px;
}
.odds-grid__col {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 8px 6px 9px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.odds-grid__outcome {
  font-size: 0.74rem;
  font-weight: 600;
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  gap: 5px;
}
.odds-grid__pct {
  font-size: 0.78rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.odds-grid__odds {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.odds-caption {
  margin-top: 8px;
  font-size: 0.72rem;
  color: var(--text-muted);
  line-height: 1.4;
}

.confidence-tag {
  display: inline-block;
  align-self: flex-start;
  margin-top: 12px;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 3px 8px;
  border-radius: 20px;
  background: var(--gridline);
  color: var(--text-secondary);
}
.prediction-pending {
  margin-top: 14px;
  padding: 14px 12px;
  border: 1px dashed var(--gridline);
  border-radius: 10px;
  font-size: 0.85rem;
  color: var(--text-secondary);
  text-align: center;
}

/* Fixtures toolbar: day tabs + view toggle */
.fx-toolbar {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 12px; margin: 4px 0 16px;
}
/* Day bar: ‹ · five day pills · › · calendar. Standalone pills (not a segmented
   control), one row, never wrapping — the whole board used to print one pill per
   fetched day, which was two rows on a desktop and eight on a phone.
   min-height reserves that single row: the bar is built by renderDayTabs() once
   the fixtures JSON lands, so without it the strip goes 0 -> 1 row and shoves
   the board down (the same CLS defect F19 fixed on /results). 37px = one pill,
   measured in the browser (0.82rem at normal line-height + 9px padding and 1px
   border, top and bottom). Now exact rather than a floor, since the row count no
   longer depends on viewport width or on how many days carry a fixture. */
.daybar {
  display: flex; align-items: center; gap: 6px;
  min-height: 37px; min-width: 0;
}
.daybar__days {
  display: flex; gap: 6px; min-width: 0;
  overflow-x: auto;                       /* only bites on the narrowest phones */
  scrollbar-width: none;
}
.daybar__days::-webkit-scrollbar { display: none; }
.daybar .tab-btn {
  flex: 0 0 auto; white-space: nowrap;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 8px 15px;
}
.daybar .tab-btn.active { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.daybar__short { display: none; }
.daybar__nav, .daybar__calbtn {
  display: inline-flex; align-items: center; gap: 7px;
  flex: 0 0 auto;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-secondary);
  font-size: 0.82rem; font-weight: 600;
  cursor: pointer;
}
.daybar__nav { padding: 8px 8px; }
.daybar__calbtn { padding: 8px 13px; }
.daybar__nav:hover:not(:disabled), .daybar__calbtn:hover { border-color: var(--accent); color: var(--text-primary); }
.daybar__nav:disabled { opacity: 0.35; cursor: default; }
.daybar__pick { position: relative; flex: 0 0 auto; margin-left: 2px; }

/* Calendar popover — the only way to reach a day beyond the five on the strip,
   so it must clear the board: it floats above the cards rather than pushing them. */
.daycal {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 40;
  width: 176px; max-width: calc(100vw - 32px);
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.18);
  padding: 6px;
}
.daycal[hidden] { display: none; }
/* One row per day the board actually carries — see renderPicker(). Scrolls
   internally so the popover never grows past the fold, however wide the data
   window gets. */
.daycal__list { max-height: 320px; overflow-y: auto; }
.daycal__row {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  width: 100%; height: 38px;
  background: none; border: none; border-radius: 8px;
  color: var(--text-primary);
  font-size: 0.85rem; font-weight: 600;
  cursor: pointer;
}
.daycal__row:hover { background: var(--surface-2); }
.daycal__wd { color: var(--text-muted); font-weight: 600; }
.daycal__now { font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase; font-size: 0.78rem; }
.daycal__row.is-today { font-weight: 800; }
.daycal__row.is-sel { background: var(--accent); color: var(--accent-ink); }
.daycal__row.is-sel .daycal__wd { color: var(--accent-ink); opacity: 0.75; }
/* Phone: the wordy labels ("Yesterday", "Sat 15 Aug") don't fit five-across, so
   the same pills swap to "09/08" in CSS — one render path, two label widths.
   The arrows go too: five pills + two arrows + the calendar is ~430px of chrome
   on a 343px row, and on touch the strip scrolls under a thumb anyway. Measured
   at 375px the pills and the calendar button come to ~330px — they fit. */
@media (max-width: 600px) {
  .daybar { gap: 4px; }
  .daybar__days { gap: 4px; }
  .daybar__nav { display: none; }
  .daybar__long { display: none; }
  .daybar__short { display: inline; }
  .daybar .tab-btn { padding: 8px 9px; font-size: 0.75rem; }
  .daybar__calbtn { padding: 8px 9px; }
  .daybar__calbtn .daybar__caldate { display: none; }   /* icon only — the strip owns the date */
}
.view-toggle { gap: 4px; margin: 0; }

/* Grouped day view */
.day-group { margin-bottom: 26px; }
.day-group__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin: 0 0 12px;
}
.day-group__title {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 1rem; font-weight: 700; color: var(--text-primary);
}
.day-group__count {
  font-size: 0.72rem; font-weight: 700; color: var(--text-secondary);
  background: var(--surface-1); border: 1px solid var(--border);
  padding: 1px 8px; border-radius: 20px;
}
.day-group__table {
  font-size: 0.82rem; font-weight: 600; white-space: nowrap;
}

/* Crest size modifiers live after the base .team-crest rule (below) so their
   width/height win by source order — see the grouped block there. */
.upnext-row__pred--final { color: #1a8f52; font-weight: 800; }

/* List-row extras: goals + live/FT status */
.mrow__goals {
  margin-left: auto; font-weight: 800; font-size: 0.95rem; color: var(--text-primary);
  min-width: 16px; text-align: right;
}
.mrow__status {
  font-size: 0.62rem; font-weight: 800; letter-spacing: 0.04em;
  color: var(--text-secondary);
}
.mrow__status--live { color: #e5384f; }

/* List row: clear Prediction vs Full-time distinction */
.mrow__pred2 { display: inline-flex; flex-direction: column; align-items: center; line-height: 1.15; }
.mrow__pred2 small { font-size: 0.55rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.mrow__tag {
  font-size: 0.66rem; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase;
  padding: 3px 9px; border-radius: 20px; white-space: nowrap;
}
.mrow__tag--final { color: #fff; background: #1a8f52; }
.mrow__tag--live { color: #fff; background: #e5384f; display: inline-flex; align-items: center; gap: 5px; }
.mrow__tag--live .live-badge__dot { background: #fff; }
.mrow--played { background: color-mix(in srgb, #1a8f52 6%, transparent); }
.mrow--live { background: color-mix(in srgb, #e5384f 6%, transparent); }

/* Custom flag dropdown (Tables) */
.flag-dropdown { position: relative; min-width: 260px; max-width: 100%; }
.fd-btn {
  display: flex; align-items: center; gap: 10px; width: 100%;
  font: inherit; font-size: 0.92rem; font-weight: 600; text-align: left;
  padding: 9px 12px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface-2); color: var(--text-primary); cursor: pointer;
}
.fd-btn:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
.fd-caret { margin-left: auto; color: var(--text-secondary); }
.fd-flag { width: 22px; height: 15px; object-fit: cover; border-radius: 3px; box-shadow: 0 0 0 1px var(--border); flex-shrink: 0; }
.fd-flag--none { background: var(--surface-1); }
.fd-list {
  position: absolute; z-index: 40; top: calc(100% + 6px); left: 0; right: 0;
  margin: 0; padding: 6px; list-style: none; max-height: 320px; overflow-y: auto;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 18px 44px rgba(0,0,0,0.22);
}
.fd-opt {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 8px;
  font-size: 0.9rem; cursor: pointer;
}
.fd-opt:hover { background: var(--surface-1); }
.fd-opt--sel { background: color-mix(in srgb, var(--accent) 14%, transparent); font-weight: 700; }

/* Standings team crest */
.std-team { display: inline-flex; align-items: center; gap: 9px; }

/* Knockout bracket */
.bracket { margin-top: 30px; }
.bracket__title { font-size: 1.15rem; margin: 0 0 14px; }
.bracket__scroll { display: flex; gap: 18px; overflow-x: auto; padding-bottom: 10px; }
.bt-col { flex: 0 0 auto; min-width: 210px; display: flex; flex-direction: column; gap: 12px; justify-content: center; }
.bt-round { font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-secondary); margin: 0 0 2px; }
.bt-tie { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--surface-2); }
.bt-team { display: flex; align-items: center; gap: 8px; padding: 8px 10px; font-size: 0.86rem; }
.bt-team + .bt-team { border-top: 1px solid var(--border); }
.bt-name { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bt-score { font-weight: 800; min-width: 16px; text-align: right; }
.bt-team--win { font-weight: 800; }
.bt-team--win .bt-score { color: var(--accent); }

/* Model insights (match page) */
.insights { margin: 4px 0 24px; padding: 20px; border-radius: 14px; background: var(--surface-2); border: 1px solid var(--border); }
.insights__title { font-size: 1.15rem; margin: 0 0 16px; }
.ins-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.ins-metric { text-align: center; }
.ins-metric__val { font-size: 1.5rem; font-weight: 800; color: var(--text-primary); }
.ins-metric__lbl { font-size: 0.76rem; color: var(--text-secondary); margin: 2px 0 8px; }
.ins-bar { height: 7px; border-radius: 6px; background: color-mix(in srgb, var(--accent) 20%, transparent); overflow: hidden; }
.ins-bar span { display: block; height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent-2)); border-radius: 6px; }
/* The label takes the full width rather than sharing the first line: inline, it
   pushed the first scoreline onto the row's end and left the other two orphaned
   underneath, so the three chips of one set read as two unrelated groups. Own
   line, centred, with all three chips centred below it. */
/* ---------------- In-play win probability (match page) ----------------
   Prerendered by server/write-match-pages.js in its resting state and taken over
   by renderLiveProbability() in js/main.js once the match is under way.

   Everything that separates the live state from the resting one is doing a job.
   At rest this is a quiet card like the others on the page, because before
   kick-off it is just another table of numbers. Live, it has to read as the one
   thing on the page that is CHANGING — that is the whole product — so it gets a
   tinted ground, an accent rail, a broadcast-style badge, the leading outcome
   picked out, and a flash on the figure that actually moved. Restraint still
   applies: a card that shouts permanently is a card people learn to skip. */
.livep {
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 14px;
  padding: 20px 22px; margin: 0 0 22px; position: relative; overflow: hidden;
}
/* The live state, three signals stacked so none of them has to be loud on its
   own: a warmer ground, a solid accent rail down the left, and a lifted card. */
.livep--on {
  border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--accent) 6%, var(--surface-2)) 0%, var(--surface-2) 55%);
  box-shadow: 0 4px 20px color-mix(in srgb, var(--accent) 12%, transparent);
}
.livep--on::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
  background: linear-gradient(180deg, var(--accent), var(--series-away));
}

.livep__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.livep__title { font-size: 1.05rem; margin: 0; }
.livep__state {
  font-size: 0.74rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-muted); white-space: nowrap;
}
/* A filled badge rather than muted text: this is the one label on the page that
   says "right now", and the broadcast convention for that is a pill, not a word. */
.livep__state--on {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--accent); color: var(--accent-ink, #fff);
  padding: 5px 11px 5px 9px; border-radius: 999px; letter-spacing: 0.08em;
  box-shadow: 0 2px 10px color-mix(in srgb, var(--accent) 40%, transparent);
}
.livep__dot {
  width: 7px; height: 7px; border-radius: 50%; background: currentColor;
  animation: livep-pulse 1.5s ease-in-out infinite;
}
@keyframes livep-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.8); } }

.livep__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.livep__side {
  display: flex; flex-direction: column; align-items: center; gap: 2px; text-align: center;
  padding: 10px 6px; border-radius: 10px; border: 1px solid transparent;
  transition: background-color .3s ease, border-color .3s ease;
}
/* THE THREE CELLS SHARE THEIR ROWS, they do not merely sit side by side. As three
   independent columns each cell stacked name / percentage / odds from its own top,
   so the moment one club's name wrapped to a second line — which on a phone is most
   of them: "Wolfsberger AC" at 360px — that column's big number and its fair odds
   dropped a line below the other two and the card read as crooked. Desktop never
   showed it because the names fit on one line there, which is exactly the kind of
   bug that ships.
   Subgrid makes the name row as tall as the tallest name and the number row as tall
   as the tallest number, for all three at once; end-alignment then puts a one-line
   name on the same baseline as the second line of a two-line one, so the numbers
   line up whatever the clubs are called. The flex stack above stays as the fallback
   where subgrid is not supported — crooked, but never broken. */
@supports (grid-template-rows: subgrid) {
  .livep__grid { grid-template-rows: auto auto auto; }
  .livep__side {
    display: grid; grid-row: span 3; grid-template-rows: subgrid;
    justify-items: center; align-items: end; gap: 2px;
  }
}
/* Who is winning, without colouring the digits: the eye lands on the tinted cell
   and the number keeps wearing ink, so the figure stays readable and the identity
   is carried by the surface. */
.livep__side--lead { --livep-lead: var(--text-muted); }
.livep__side--lead-h { --livep-lead: var(--series-home); }
.livep__side--lead-a { --livep-lead: var(--series-away); }
.livep__side--lead {
  background: color-mix(in srgb, var(--livep-lead) 12%, transparent);
  border-color: color-mix(in srgb, var(--livep-lead) 35%, transparent);
}
/* Fired only on the cell whose number actually changed since the last paint. */
.livep__side--moved { animation: livep-flash 1.1s ease-out 1; }
@keyframes livep-flash {
  0%   { background: color-mix(in srgb, var(--accent) 42%, transparent); transform: scale(1.05); }
  35%  { background: color-mix(in srgb, var(--accent) 22%, transparent); transform: scale(1); }
  100% { background: color-mix(in srgb, var(--accent) 0%, transparent); }
}
.livep__side--lead.livep__side--moved { animation: livep-flash-lead 1.1s ease-out 1; }
@keyframes livep-flash-lead {
  0%   { background: color-mix(in srgb, var(--livep-lead) 45%, transparent); transform: scale(1.05); }
  100% { background: color-mix(in srgb, var(--livep-lead) 12%, transparent); transform: scale(1); }
}
/* Someone reading numbers closely did not ask for movement. The card keeps its
   colour and its hierarchy; only the motion goes. */
@media (prefers-reduced-motion: reduce) {
  .livep__dot { animation: none; }
  .livep__side--moved, .livep__side--lead.livep__side--moved { animation: none; }
}

.livep__team { font-size: 0.8rem; color: var(--text-secondary); line-height: 1.3; }
.livep__pct {
  font-size: 1.7rem; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--text-primary);
  line-height: 1.15;
}
.livep__side--lead .livep__pct { font-size: 2rem; }
/* Fair odds are the quieter number on purpose: the probability is the model's
   claim, the price is only that claim divided into one. */
.livep__odds { font-size: 0.85rem; color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* One bar, three segments, 2px of surface between them so the boundaries read as
   boundaries and not as a gradient. Thicker while live — it is the fastest read
   of the three numbers and the part that visibly slides when one of them moves. */
.livep__bar { display: flex; gap: 2px; height: 8px; margin: 14px 0 12px; }
.livep--on .livep__bar { height: 11px; }
.livep__bar span { border-radius: 4px; min-width: 3px; transition: width .5s cubic-bezier(.4,0,.2,1); }
.livep__bar span:nth-child(1) { background: var(--series-home); }
.livep__bar span:nth-child(2) { background: var(--text-muted); }
.livep__bar span:nth-child(3) { background: var(--series-away); }

/* The reduced form, for a match card in a list. Deliberately quiet: on a page of
   twenty cards, twenty of these shouting would be worse than none. */
/* The closing line of a pre-match write-up, pointing at the block that will move
   once the match starts. Set apart from the prose it follows, because it is a
   note about the page rather than part of the argument. */
.match-writeup__live {
  margin-top: 18px; padding: 12px 14px; border-radius: 10px;
  background: color-mix(in srgb, var(--accent) 6%, var(--surface-1));
  border-left: 3px solid color-mix(in srgb, var(--accent) 55%, transparent);
  font-size: 0.9rem; color: var(--text-secondary);
}

.livep-mini { margin: 8px 0 10px; }
.livep-mini__bar { display: flex; gap: 2px; height: 6px; margin-bottom: 6px; }
.livep-mini__bar span { border-radius: 3px; min-width: 2px; transition: width .5s cubic-bezier(.4,0,.2,1); }
.livep-mini__bar span:nth-child(1) { background: var(--series-home); }
.livep-mini__bar span:nth-child(2) { background: var(--text-muted); }
.livep-mini__bar span:nth-child(3) { background: var(--series-away); }
.livep-mini__row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-size: 0.76rem; color: var(--text-secondary); font-variant-numeric: tabular-nums;
}
.livep-mini__lbl {
  font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-muted); margin-right: auto;
}
.livep-mini__item { display: inline-flex; align-items: center; gap: 4px; font-weight: 700; }
.livep-mini__chip { width: 7px; height: 7px; border-radius: 2px; display: inline-block; }
.livep-mini__chip--h { background: var(--series-home); }
.livep-mini__chip--d { background: var(--text-muted); }
.livep-mini__chip--a { background: var(--series-away); }

.livep__note { font-size: 0.78rem; color: var(--text-muted); margin: 0; line-height: 1.5; }
.livep__note a { color: var(--accent); }

@media (max-width: 600px) {
  .livep { padding: 16px 14px; }
  .livep__pct { font-size: 1.4rem; }
  .livep__side--lead .livep__pct { font-size: 1.6rem; }
  .livep__team { font-size: 0.72rem; }
  .livep__side { padding: 8px 2px; }
}

.ins-scores { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 10px; margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border); }
.ins-scores__lbl { flex: 0 0 100%; text-align: center; font-size: 0.76rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-secondary); }
.ins-score { background: var(--surface-1); border: 1px solid var(--border); border-radius: 8px; padding: 5px 11px; font-size: 0.88rem; }
.ins-score b { font-size: 0.98rem; }
@media (max-width: 520px) { .ins-grid { grid-template-columns: 1fr; } }

/* Head-to-head */
.h2h { margin: 0 0 24px; }
.h2h__title { font-size: 1.15rem; margin: 0 0 12px; }
.h2h-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.h2h-row { display: flex; align-items: center; gap: 12px; padding: 9px 12px; border: 1px solid var(--border); border-radius: 9px; background: var(--surface-2); font-size: 0.88rem; }
.h2h-date { color: var(--text-secondary); min-width: 96px; font-size: 0.8rem; }
.h2h-teams { flex: 1; }
.h2h-comp { color: var(--text-secondary); font-size: 0.78rem; text-align: right; }
@media (max-width: 520px) { .h2h-comp { display: none; } }

/* Match page: form / table context / leading scorers (F21-B).
   Two-column on desktop, stacked under 640px — these are read side by side, so
   the columns collapse a step earlier than the .h2h rows above, which stay
   legible narrower. */
.mform, .mtbl, .mscr { margin: 0 0 24px; }
.mform__title, .mtbl__title, .mscr__title { font-size: 1.15rem; margin: 0 0 12px; }
.mform__cols, .mscr__cols { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.mform__team, .mscr__team { font-size: 0.9rem; margin: 0 0 8px; color: var(--text-secondary); font-weight: 700; }
.mform__list, .mscr__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.mform__row, .mscr__row { display: flex; align-items: center; gap: 9px; padding: 8px 11px; border: 1px solid var(--border); border-radius: 9px; background: var(--surface-2); font-size: 0.85rem; }
/* W/D/L uses the site's existing .form-pill--W/D/L, not a second colour scheme
   for the same three states — see the form guide on /fixtures and /tables. */
.mform__row .form-pill { flex: none; }
.mform__score { font-variant-numeric: tabular-nums; font-weight: 700; flex: none; }
.mform__opp { flex: 1; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mform__when { color: var(--text-secondary); font-size: 0.75rem; flex: none; }
.mform__comp { color: var(--text-muted); font-size: 0.75rem; flex: none; }
.mform__none, .mscr__none { font-size: 0.82rem; color: var(--text-secondary); margin: 0; }
.mcal { margin: 0 0 24px; padding: 14px 16px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface-2); }
.mcal__title { font-size: 1.05rem; margin: 0 0 8px; }
.mcal__body { margin: 0; font-size: 0.9rem; }
.mcal__note { font-size: 0.78rem; color: var(--text-muted); margin: 10px 0 0; }
.match-stamp { font-size: 0.78rem; color: var(--text-muted); margin: 10px 0 0; }
.match-hero__call { font-size: 1.05rem; margin: 14px 0 0; }
.mform__note, .mtbl__note, .mscr__note, .h2h__note { font-size: 0.78rem; color: var(--text-muted); margin: 10px 0 0; }
.mscr__name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mscr__goals { font-weight: 700; flex: none; }
.mscr__ast { color: var(--text-secondary); font-size: 0.78rem; flex: none; }
/* The table is the one block that can outgrow a narrow screen, so it scrolls
   inside its own box rather than widening the page. */
.mtbl__scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; }
.mtbl__table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.mtbl__table th, .mtbl__table td { padding: 8px 10px; text-align: center; border-bottom: 1px solid var(--border); white-space: nowrap; }
.mtbl__table th { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-secondary); font-weight: 700; }
.mtbl__table td { font-variant-numeric: tabular-nums; }
.mtbl__team { text-align: left !important; font-weight: 700; }
.mtbl__team a { color: inherit; text-decoration: none; }
.mtbl__team a:hover { text-decoration: underline; }
.mtbl__pos, .mtbl__pts { font-weight: 800; }
/* The club whose page this is. A full league table is only useful if you can
   find yourself in it at a glance. The tint is a TRANSLUCENT accent rather than
   a themed surface token: --surface-2 is #ffffff in light mode, i.e. the same
   colour the row already sits on, so it would have highlighted nothing. At 8%
   alpha the same declaration reads correctly on both themes without a second
   rule. The inset bar carries the meaning for anyone who cannot see the tint —
   the row is never marked by colour alone. */
.mtbl__row--me { background: rgba(42, 109, 240, 0.08); }
.mtbl__row--me .mtbl__pos { box-shadow: inset 3px 0 0 0 var(--accent); }
@media (max-width: 640px) { .mform__cols, .mscr__cols { grid-template-columns: 1fr; } }
@media (max-width: 520px) { .mform__when { display: none; } }

/* Prerendered team page (F22). The blocks above are reused as-is — a club's
   table row and form list are the same components as on a match page, and a
   second visual language for the same data would be a bug, not a feature. */
.team-summary { font-size: 0.95rem; line-height: 1.65; margin: 0 0 22px; color: var(--text-primary); }
.tf-list { display: flex; flex-direction: column; gap: 6px; }
.tf-row { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 9px; background: var(--surface-2); font-size: 0.86rem; text-decoration: none; color: inherit; }
.tf-row:hover { border-color: var(--accent); }
.tf-when { color: var(--text-secondary); font-size: 0.78rem; min-width: 92px; flex: none; }
.tf-opp { flex: 1; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tf-comp { color: var(--text-secondary); font-size: 0.76rem; flex: none; }
.tf-score, .tf-pred { font-weight: 700; font-variant-numeric: tabular-nums; flex: none; min-width: 52px; text-align: right; }
.tf-pred { color: var(--accent); }
@media (max-width: 560px) { .tf-comp { display: none; } }

/* Top scorers */
.topscorers { margin-top: 30px; }
.topscorers__title { font-size: 1.15rem; margin: 0 0 14px; }
.ts-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.ts-row { display: flex; align-items: center; gap: 12px; padding: 8px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface-2); }
.ts-rank { width: 20px; font-weight: 800; color: var(--text-secondary); text-align: center; }
.ts-photo { width: 38px; height: 38px; border-radius: 50%; object-fit: cover; background: var(--surface-1); flex-shrink: 0; }
.ts-photo--none { background: var(--surface-1); }
.ts-name { flex: 1; font-weight: 700; display: flex; flex-direction: column; line-height: 1.2; }
.ts-name small { font-weight: 500; color: var(--text-secondary); font-size: 0.78rem; }
.ts-stat { display: flex; flex-direction: column; align-items: center; font-weight: 800; font-size: 1.05rem; min-width: 54px; }
.ts-stat small { font-weight: 500; font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-secondary); }
.ts-stat--muted { color: var(--text-secondary); }

/* Model track record */
.accuracy { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.accuracy--empty {
  display: block; padding: 22px; border: 1px dashed var(--border); border-radius: 14px;
  color: var(--text-secondary); text-align: center; font-size: 0.92rem;
}
.acc-stat { padding: 22px; border-radius: 14px; text-align: center; background: var(--surface-2); border: 1px solid var(--border); }
.acc-stat__val { font-size: 2.4rem; font-weight: 800; color: var(--text-primary); line-height: 1; }
.acc-stat__val span { font-size: 1.2rem; color: var(--accent); }
.acc-stat__lbl { font-size: 0.82rem; color: var(--text-secondary); margin-top: 8px; }
@media (max-width: 600px) { .accuracy { grid-template-columns: 1fr; } }

/* Live now strip */
.livebar { display: flex; align-items: center; gap: 10px; margin: 0 0 10px; }
.livebar__dot { width: 10px; height: 10px; border-radius: 50%; background: #e5384f; box-shadow: 0 0 0 0 rgba(229,56,79,0.6); animation: livePulse 1.4s infinite; }
@keyframes livePulse { 0% { box-shadow: 0 0 0 0 rgba(229,56,79,0.5); } 70% { box-shadow: 0 0 0 8px rgba(229,56,79,0); } 100% { box-shadow: 0 0 0 0 rgba(229,56,79,0); } }
.livebar__label { font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase; font-size: 0.82rem; color: #e5384f; }
.live-badge { display: inline-flex; align-items: center; gap: 5px; font-weight: 800; font-size: 0.72rem; letter-spacing: 0.04em; color: var(--live); }
.live-badge__dot { width: 7px; height: 7px; border-radius: 50%; background: var(--live); animation: livePulse 1.4s infinite; }

/* Cookie consent banner (self-hosted, injected by js/theme.js) */
.cookie-banner {
  position: fixed; left: 16px; right: 16px; bottom: 16px; z-index: 200;
  max-width: 760px; margin: 0 auto;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  padding: 16px 18px;
  opacity: 0; transform: translateY(14px);
  transition: opacity 0.22s ease, transform 0.22s ease;
}
.cookie-banner--visible { opacity: 1; transform: translateY(0); }
.cookie-banner__inner {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
}
.cookie-banner__text {
  flex: 1; min-width: 220px; margin: 0;
  font-size: 0.88rem; line-height: 1.5; color: var(--text-secondary);
}
.cookie-banner__text a { color: var(--accent); text-decoration: underline; }
.cookie-banner__actions { display: flex; gap: 10px; }
.cookie-btn {
  font: inherit; font-weight: 700; font-size: 0.9rem;
  padding: 10px 20px; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--border); transition: filter 0.15s ease, background 0.15s ease;
}
.cookie-btn--reject { background: transparent; color: var(--text-primary); }
.cookie-btn--reject:hover { background: var(--surface-1); }
.cookie-btn--accept { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.cookie-btn--accept:hover { filter: brightness(1.06); }
.link-btn { background: none; border: none; color: var(--accent); cursor: pointer; font: inherit; padding: 0; text-decoration: underline; }
@media (max-width: 480px) {
  .cookie-banner__actions { width: 100%; }
  .cookie-btn { flex: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .cookie-banner { transition: opacity 0.15s ease; transform: none; }
  .cookie-banner--visible { transform: none; }
}

/* "Yesterday's results" rows — the hit/miss marker and the model's call.
   CSS ESCAPES, WRITTEN WITH CARE (\2713 = check, \2717 = cross, \B7 = middot).
   They shipped broken once and it is worth knowing how: the tool that wrote
   this file read "\2713" as an OCTAL escape — \271 is U+00B9 SUPERSCRIPT ONE —
   and put a literal "¹3" on the homepage, while "\00b7" became an actual NUL
   byte, which is why grep started calling this stylesheet a binary file. Escapes
   are kept rather than literal glyphs because they survive any re-encoding of
   the file; test/css-escapes.test.js asserts both the codepoints and the absence
   of stray control bytes.

   `.fx-list .yday-row`, not `.yday-row`: `.fx-list li` sets `padding: 8px 0`,
   which outranks a single class selector, so the 22px gutter the marker sits in
   was silently dropped and the tick overlapped the club name. */
.yday-list { margin-top: 4px; }
.fx-list .yday-row { position: relative; padding-left: 22px; }
.yday-row::before {
  /* top matches `.fx-list li`'s own padding, line-height its text, so the mark
     sits on the first line's baseline instead of floating above the row. */
  position: absolute; left: 0; top: 8px;
  font-weight: 700; font-size: 0.95em; line-height: 1.5;
}
.yday-row--hit::before { content: "\2713"; color: var(--series-draw); }
.yday-row--miss::before { content: "\2717"; color: var(--series-away); }
/* An exact scoreline is its own thing: roughly one in eleven against one in two
   for the outcome alone, so filing it under the same tick as an ordinary correct
   result threw away the rarer claim. A star, in the accent rather than the
   result green, so it reads as "and more than that" rather than as a fourth
   verdict the reader has to learn. */
.yday-row--exact::before { content: "\2605"; color: var(--accent); }
/* The score is not a link. It used to sit inside one with both club names, so
   the whole row was one blue sentence and the column of scores — the thing a
   reader scans a scoreboard for — could not be picked out of it. */
.yday-row .yday-score { color: var(--text-primary); }
/* The model's call, kept subordinate to the score on purpose — same row, quieter
   voice. On a narrow screen it drops to its own line rather than squeezing the
   scoreline, which is the part the reader came for. Spacing comes from margin,
   not from spaces inside content(): a space after an escape is consumed as the
   escape's terminator, so " \B7 " would lose the very gap it was written for. */
.yday-row .yday-call { color: var(--text-muted); font-size: 0.88em; }
.yday-row .yday-call::before { content: "\B7"; margin: 0 6px; }
@media (max-width: 520px) {
  .yday-row .yday-call { display: block; }
  .yday-row .yday-call::before { content: ""; margin: 0; }
}

}

/* Add-to-home-screen bar. Same geometry and z-index as .cookie-banner because
   it occupies the same slot — but only ever one of the two is on screen at a
   time (js/theme.js refuses to show this while the consent banner is up: that
   one is a legal control and must never be competed with). */
.a2hs-bar {
  position: fixed; left: 16px; right: 16px; bottom: 16px; z-index: 200;
  max-width: 760px; margin: 0 auto;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  padding: 14px 16px;
  opacity: 0; transform: translateY(14px);
  transition: opacity 0.22s ease, transform 0.22s ease;
}
.a2hs-bar--visible { opacity: 1; transform: translateY(0); }
.a2hs-bar__inner { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.a2hs-bar__icon { flex: none; width: 40px; height: 40px; border-radius: 10px; }
.a2hs-bar__text { flex: 1; min-width: 200px; display: flex; flex-direction: column; gap: 2px; }
.a2hs-bar__text strong { font-size: 0.94rem; color: var(--text-primary); }
.a2hs-bar__text span { font-size: 0.84rem; line-height: 1.45; color: var(--text-secondary); }
.a2hs-bar__actions { display: flex; gap: 10px; }
.a2hs-btn {
  font: inherit; font-weight: 700; font-size: 0.9rem;
  padding: 10px 20px; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--border); transition: filter 0.15s ease, background 0.15s ease;
}
.a2hs-btn--ghost { background: transparent; color: var(--text-primary); }
.a2hs-btn--ghost:hover { background: var(--surface-1); }
.a2hs-btn--go { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.a2hs-btn--go:hover { filter: brightness(1.06); }
@media (max-width: 480px) {
  .a2hs-bar__icon { display: none; }
  .a2hs-bar__actions { width: 100%; }
  .a2hs-btn { flex: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .a2hs-bar { transition: opacity 0.15s ease; transform: none; }
  .a2hs-bar--visible { transform: none; }
}

/* Post-match analysis (match page) */
.postmatch {
  margin: 8px 0 26px; padding: 20px; border-radius: 14px;
  background: var(--surface-2); border: 1px solid var(--border);
}
/* The written preview/report on a match page. Plain prose on the page
   background rather than a card: it is the body copy of the page, not another
   panel of statistics competing with the ones above it. */
.match-writeup { margin: 8px 0 26px; }
.match-writeup h2 { font-size: 1.2rem; margin: 24px 0 10px; }
.match-writeup h2:first-child { margin-top: 0; }
.match-writeup p {
  margin: 0 0 12px; font-size: 0.95rem; line-height: 1.7;
  color: var(--text-secondary); max-width: 68ch;
}
/* The "written before kick-off" note a played page carries when it is still
   showing its pre-match piece (see writeupIndex in server/write-match-pages.js).
   Two-class selector on purpose: `.match-writeup p` above is element+class and
   would otherwise out-specify a lone class and keep its own size and margins.
   It also takes over :first-child from the heading rule, so the first h2 needs
   its zero top margin handing back explicitly. */
.match-writeup .match-writeup__aside {
  margin: 0 0 18px;
  padding-left: 12px;
  border-left: 2px solid var(--border);
  font-size: 0.85rem;
}
.match-writeup__aside + h2 { margin-top: 0; }
.postmatch__title { font-size: 1.2rem; margin: 0 0 16px; }
.postmatch h3 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-secondary); margin: 18px 0 10px; }
.postmatch__goals h3:first-child, .postmatch__goals h3 { margin-top: 0; }
.pm-goals { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.pm-goals li { display: flex; align-items: center; gap: 10px; font-size: 0.9rem; }
.pm-goals__min { min-width: 38px; font-weight: 800; color: var(--accent); }
.pm-goals__scorer { font-weight: 600; }
.pm-goals__team { margin-left: auto; color: var(--text-secondary); font-size: 0.82rem; }
.pm-stat { margin-bottom: 12px; }
.pm-stat__row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; font-size: 0.85rem; font-weight: 700; margin-bottom: 5px; }
.pm-stat__label { color: var(--text-secondary); font-weight: 600; min-width: 0; }
.pm-stat__row > span:last-child { flex: none; font-variant-numeric: tabular-nums; }
.pm-stat__bar { height: 7px; border-radius: 6px; background: color-mix(in srgb, var(--accent) 22%, transparent); overflow: hidden; }
.pm-stat__fill { display: block; height: 100%; background: var(--accent); border-radius: 6px; }
.postmatch__block { margin-top: 22px; }
.postmatch__block:first-of-type { margin-top: 0; }

/* Match timeline */
.timeline { display: flex; flex-direction: column; gap: 4px; }
.tl-row { display: grid; grid-template-columns: 1fr 46px 1fr; align-items: center; gap: 8px; }
.tl-cell { display: flex; align-items: center; gap: 8px; font-size: 0.88rem; }
.tl-cell--home { justify-content: flex-end; text-align: right; }
.tl-cell--away { justify-content: flex-start; text-align: left; }
.tl-cell--home .tl-txt { order: 2; }
.tl-cell--home .tl-ico, .tl-cell--home .tl-card { order: 3; }
.tl-txt { display: flex; flex-direction: column; line-height: 1.2; }
.tl-txt small { color: var(--text-secondary); font-size: 0.72rem; }
.tl-ico { font-size: 1rem; }
.tl-min { text-align: center; font-weight: 800; font-size: 0.72rem; color: var(--text-secondary); background: var(--surface-1); border: 1px solid var(--border); border-radius: 20px; padding: 2px 0; }
.tl-card { width: 11px; height: 15px; border-radius: 2px; display: inline-block; flex-shrink: 0; }
.tl-card--yellow { background: #f2c53d; }
.tl-card--red { background: #e5384f; }

/* Line-ups */
.lu-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.lu-col { background: var(--surface-1); border: 1px solid var(--border); border-radius: 10px; padding: 14px; }
.lu-team { font-weight: 800; margin-bottom: 10px; }
.lu-form { font-weight: 600; font-size: 0.76rem; color: var(--text-secondary); background: var(--surface-2); border: 1px solid var(--border); padding: 1px 7px; border-radius: 12px; margin-left: 4px; }
.lu-xi, .lu-subs { list-style: none; margin: 0 0 6px; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.lu-xi li, .lu-subs li { display: flex; align-items: center; gap: 8px; font-size: 0.86rem; }
.lu-num { width: 22px; text-align: center; font-weight: 700; color: var(--text-secondary); font-size: 0.78rem; flex-shrink: 0; }
.lu-pos { margin-left: auto; font-size: 0.68rem; color: var(--text-muted); }
.lu-subs li { font-size: 0.82rem; color: var(--text-secondary); }
.lu-subhead { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-secondary); margin: 10px 0 6px; }
.lu-coach { font-size: 0.78rem; color: var(--text-secondary); margin-top: 8px; }
@media (max-width: 560px) { .lu-grid { grid-template-columns: 1fr; } .tl-row { grid-template-columns: 1fr 40px 1fr; } }

/* ===== Rich design polish ===== */

/* Hero match-of-the-day: probability donut + stacked legend */
.hero-card__viz {
  display: flex; align-items: center; justify-content: center; gap: 24px;
  margin: 18px 0 10px; flex-wrap: wrap;
}
.hero-donut {
  width: 134px; height: 134px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 10px 26px rgba(0,0,0,0.18);
}
.hero-donut__hole {
  width: 96px; height: 96px; border-radius: 50%; background: var(--surface-2);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.hero-donut__score { font-size: 1.7rem; font-weight: 800; color: var(--text-primary); line-height: 1; }
.hero-donut__lbl { font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-secondary); margin-top: 4px; }
.prob-legend--stack { flex-direction: column; align-items: flex-start; gap: 8px; text-align: left; }
.prob-legend--stack .prob-legend__item strong { color: var(--text-primary); }

/* Card hover lift */
.match-card { transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease; }
.match-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 18px 40px rgba(0,0,0,0.16);
  border-color: color-mix(in srgb, var(--accent) 38%, var(--border));
}
.article-card { transition: transform .18s ease, box-shadow .18s ease; }
.article-card:hover { transform: translateY(-3px); box-shadow: 0 18px 40px rgba(0,0,0,0.16); }

/* Crest shadow */
.team-crest img { filter: drop-shadow(0 2px 3px rgba(0,0,0,0.18)); }

/* Section heading accent bar */
.section-heading h2 { position: relative; padding-left: 14px; }
.section-heading h2::before {
  content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  width: 5px; height: 1.05em; border-radius: 3px;
  background: linear-gradient(var(--accent), var(--accent-2));
}

/* Subtle fade-in for grids */
@keyframes ffIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.grid-cards > * { animation: ffIn .34s ease both; }

/* Analysis card competition badge (over the image gradient) */
.article-card__image { position: relative; }
.article-card__badge {
  position: absolute; left: 14px; bottom: 12px;
  font-size: 0.72rem; font-weight: 800; letter-spacing: 0.05em; color: #fff;
  background: rgba(0,0,0,0.28); border: 1px solid rgba(255,255,255,0.25);
  padding: 3px 9px; border-radius: 20px; backdrop-filter: blur(2px);
}

/* Tables page league picker */
.table-picker { display: flex; align-items: center; gap: 10px; margin: 4px 0 18px; flex-wrap: wrap; }
.table-picker label { font-size: 0.85rem; font-weight: 600; color: var(--text-secondary); }
.select-input {
  font: inherit; font-size: 0.9rem; padding: 8px 12px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface-2); color: var(--text-primary); cursor: pointer;
  min-width: 240px; max-width: 100%;
}

.empty-note { color: var(--text-secondary); padding: 20px 0; text-align: center; }

/* Final / live result shown instead of a prediction once a match kicks off */
.result-block {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  margin-top: 14px; padding: 12px;
  background: var(--surface-1); border: 1px solid var(--border); border-radius: 10px;
}
.result-block__label {
  font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-secondary);
}
.result-block__score { font-size: 1.5rem; font-weight: 800; color: var(--text-primary); }
.result-block__dash { color: var(--text-muted); font-weight: 600; }
/* A live score is red, including the digits. It was accent-blue on the border
   and label with the score in plain ink, while the LIVE badge three lines above
   it was red — the same fact in two colours on one card. Red is what every
   scoreboard in the world uses for a match in progress, so it is the one that
   needs no explaining. */
.result-block--live { border-color: color-mix(in srgb, var(--live) 55%, var(--border)); background: color-mix(in srgb, var(--live) 5%, var(--surface-1)); }
.result-block--live .result-block__label { color: var(--live); }
.result-block--live .result-block__score { color: var(--live); }
.result-block--live .result-block__dash { color: color-mix(in srgb, var(--live) 55%, transparent); }
/* Postponed: the block holds the same slot as a score so the hero does not jump,
   but says the one true thing about it — dashed and muted, never mistakable for
   a 0-0. */
.result-block--off { border-style: dashed; }
.result-block--off .result-block__label,
.result-block--off .result-block__score { color: var(--text-muted); }
.result-block--off .result-block__score { font-size: 1rem; font-weight: 700; }
.pred-note { font-size: 0.8rem; color: var(--text-secondary); margin: 16px 0 6px; text-align: center; }

/* Team crests (real logos with a monogram fallback) */
.team-crest {
  width: 44px; height: 44px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.team-crest img { width: 100%; height: 100%; object-fit: contain; }
.team-crest__mono {
  width: 100%; height: 100%; border-radius: 50%;
  align-items: center; justify-content: center;
  color: #fff; font-weight: 700; font-size: 0.9rem;
}
/* Size modifiers — must follow the base .team-crest rule so their width/height
   win by source order (equal specificity, one class each). */
.team-crest--sm { width: 22px; height: 22px; }
.team-crest--sm .team-crest__mono { font-size: 0.55rem; }
.team-crest--lg { width: 64px; height: 64px; }
.team-crest--lg .team-crest__mono { font-size: 1.15rem; }
.team-crest--xl { width: 84px; height: 84px; }
.team-crest--xl .team-crest__mono { font-size: 1.5rem; }

/* Compact result badge on cards (shown with the prediction) */
.result-badge {
  display: flex; justify-content: center; align-items: center; gap: 8px;
  margin: 12px 0 4px;
}
.result-badge__tag {
  font-size: 0.62rem; font-weight: 800; letter-spacing: 0.05em;
  color: var(--text-secondary); background: var(--gridline);
  padding: 2px 7px; border-radius: 10px;
}
.result-badge__score { font-size: 1.05rem; font-weight: 800; color: var(--text-primary); }
.result-badge--live .result-badge__tag { color: #fff; background: var(--accent); }

.card-footer-link {
  display: inline-block;
  margin-top: auto;
  padding-top: 14px;
  font-size: 0.85rem;
  font-weight: 600;
}

/* ---------------- Filters (fixtures page) ---------------- */
.filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 24px 0;
}
.filter-chip {
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-secondary);
  font-size: 0.85rem;
  font-weight: 600;
  padding: 7px 14px;
  border-radius: 20px;
  cursor: pointer;
}
.filter-chip.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

/* ---------------- Article card ---------------- */
.article-card {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform .2s cubic-bezier(.2,.7,.3,1), box-shadow .2s ease;
}
.article-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
.article-card:hover .article-card__image { transform: scale(1.04); }
.article-card__image {
  height: 148px;
  background: linear-gradient(135deg, #2a78d6 0%, #1c5cab 100%);
  transition: transform .35s ease;
  position: relative;
}
.article-card__image::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 40%, rgba(0,0,0,0.22));
}
.article-card__body { padding: 18px 20px 22px; }
.article-card__tag {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--accent);
  margin-bottom: 6px;
  display: block;
}
.article-card__title { font-family: var(--font-display); font-size: 1.08rem; margin: 0 0 8px; line-height: 1.3; letter-spacing: -0.01em; }
.article-card__excerpt { font-size: 0.9rem; color: var(--text-secondary); margin: 0 0 10px; }
.article-card__date { font-size: 0.78rem; color: var(--text-muted); }

/* ---------------- Article page typography ---------------- */
.article-hero {
  padding: 40px 0 10px;
}
.article-hero__tag {
  color: var(--accent);
  font-weight: 700;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.article-hero h1 {
  font-size: 2rem;
  margin: 10px 0 14px;
  letter-spacing: -0.01em;
}
.article-hero__byline {
  color: var(--text-muted);
  font-size: 0.88rem;
}
.article-body {
  max-width: 720px;
  font-size: 1.05rem;
  color: var(--text-primary);
}
.article-body p { margin: 0 0 20px; }
.article-body blockquote {
  border-left: 3px solid var(--accent);
  margin: 28px 0;
  padding: 4px 0 4px 20px;
  font-size: 1.15rem;
  color: var(--text-secondary);
  font-style: italic;
}
.article-body h2 { font-size: 1.3rem; margin: 32px 0 14px; }

/* ---------------- Embedded mini prediction ---------------- */
.embedded-prediction {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 20px 22px;
  margin: 28px 0;
}
.embedded-prediction__label {
  font-size: 0.78rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-bottom: 10px;
}

/* ---------------- Match detail page ---------------- */
.match-hero {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 28px 30px;
  margin: 28px 0;
}
.match-hero__meta {
  font-size: 0.85rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-bottom: 14px;
}
/* This is the match page's <h1> — the fixture IS the heading, so the block that
   already shows both clubs carries it rather than a second, redundant title
   line. margin-top must be zeroed explicitly: the class overrides the browser's
   h1 bottom margin but not its top one, which would push the hero down. */
.match-hero__teams {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  /* column-gap only. The gap must NOT apply between rows: the tagline below is a
     wrapped flex item, and a row gap tuned for the space between two crests
     throws it half a line down the page. Every override below sets column-gap
     for the same reason. */
  column-gap: 32px;
  row-gap: 0;
  font-size: 1.6rem;
  font-weight: 700;
  margin-top: 0;
  margin-bottom: 6px;
  text-align: center;
}
/* The h1's trailing line: "Eliteserien prediction & result". It is inside the
   heading on purpose (F26-5) — the heading a crawler weighs most used to be two
   club names and nothing else. flex-basis 100% drops it onto its own row under
   the crests; the negative margin pulls it back out of the 32px flex gap so it
   reads as part of the heading rather than a separate line. */
.mh-tagline {
  flex: 0 0 100%;
  margin-top: 10px;
  font-family: var(--font-body);
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--text-muted);
  letter-spacing: 0;
  text-transform: none;
}
.match-hero__teams .vs {
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--text-muted);
}
/* Badged match-hero header */
.match-hero__teams--badged { column-gap: 28px; align-items: center; }
.mh-team {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  color: var(--text-primary);
  min-width: 130px;
}
.mh-team:hover { text-decoration: none; }
.mh-team:hover .mh-team__name { color: var(--accent); }
.mh-team__name {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.15;
  transition: color .15s ease;
}
.team-badge--lg {
  width: 64px;
  height: 64px;
  font-size: 1.15rem;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.14), var(--shadow-md);
}
.match-hero__venue {
  text-align: center;
  color: var(--text-secondary);
  font-size: 0.9rem;
  margin-bottom: 20px;
}

/* --- Mobile: keep the match hero from cramping against the right edge -------
   Long two-word names (e.g. "Houston Dynamo") used to push the away side into
   the margin. We give both teams equal, shrinkable columns and trim the
   generous padding / gaps so the header stays balanced, and let the fair-odds
   cards breathe. */
@media (max-width: 560px) {
  .match-hero { padding: 20px 16px; }
  .match-hero__teams--badged { column-gap: 12px; }
  .match-hero__teams--badged .mh-team { min-width: 0; flex: 1; }
  .match-hero__teams--badged .mh-team__name { font-size: 1rem; text-wrap: balance; }
  .match-hero__teams--badged .vs { flex: 0 0 auto; }

  .odds-grid { gap: 4px; }
  .odds-grid__col { padding: 8px 4px 9px; }
  .odds-grid__outcome { font-size: 0.68rem; gap: 4px; }
  .odds-grid__odds { font-size: 1.05rem; }
}

.stats-table { width: 100%; border-collapse: collapse; margin: 18px 0; }
.stats-table th, .stats-table td {
  text-align: left;
  padding: 10px 8px;
  border-bottom: 1px solid var(--gridline);
  font-size: 0.92rem;
  font-variant-numeric: tabular-nums;
}
.stats-table th { color: var(--text-muted); font-weight: 600; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.03em; }

.factors-list p { margin: 0 0 14px; color: var(--text-secondary); }

/* ---------------- Disclaimer / notice box ---------------- */
.notice-box {
  background: #fdf6e8;
  border: 1px solid #eda100;
  border-radius: var(--radius-md);
  padding: 16px 18px;
  font-size: 0.88rem;
  color: #4a3d1a;
  margin: 24px 0;
}
.notice-box strong { display: block; margin-bottom: 4px; color: #3a2c00; }
:root[data-theme="dark"] .notice-box {
  background: #2a2510;
  border-color: #a5741a;
  color: #ecd9a8;
}
:root[data-theme="dark"] .notice-box strong { color: #f5e6bf; }

/* ---------------- Sidebar ---------------- */
.sidebar-box {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 18px 20px;
  margin-bottom: 24px;
}
.sidebar-box h3 { font-size: 0.95rem; margin: 0 0 12px; }
.sidebar-box ul { margin: 0; padding: 0; list-style: none; }
.sidebar-box li { padding: 8px 0; border-bottom: 1px solid var(--gridline); font-size: 0.88rem; }
.sidebar-box li:last-child { border-bottom: none; }

/* ---------------- Buttons ---------------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--brand-grad);
  color: #fff;
  font-weight: 600;
  font-size: 0.92rem;
  padding: 11px 20px;
  border-radius: 10px;
  border: none;
  cursor: pointer;
  box-shadow: 0 8px 20px rgba(42, 109, 240, 0.32);
  transition: transform .15s ease, box-shadow .15s ease, filter .15s ease;
}
.btn:hover { text-decoration: none; transform: translateY(-2px); box-shadow: 0 12px 26px rgba(42, 109, 240, 0.42); filter: brightness(1.05); }
.btn--lg { padding: 14px 26px; font-size: 1rem; border-radius: 12px; }
.btn-ghost {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 11px 20px;
  border-radius: 10px;
  font-weight: 600;
  font-size: 0.92rem;
  color: #dfe5f5;
  border: 1px solid rgba(255,255,255,0.2);
  background: rgba(255,255,255,0.05);
  transition: background .15s ease, border-color .15s ease;
}
.btn-ghost:hover { text-decoration: none; background: rgba(255,255,255,0.12); border-color: rgba(255,255,255,0.35); }
.btn--lg.btn-ghost { padding: 14px 24px; }

/* ---------------- Footer ---------------- */
.site-footer {
  border-top: 1px solid var(--border);
  margin-top: 60px;
  padding: 32px 0 40px;
  color: var(--text-muted);
  font-size: 0.85rem;
}
.site-footer .footer-disclaimer {
  max-width: 760px;
  margin-bottom: 16px;
  line-height: 1.6;
}
.site-footer .footer-links { display: flex; gap: 18px; flex-wrap: wrap; }
.site-footer .footer-links a { color: var(--text-secondary); }

/* Fixtures list rows */
.fixture-group { margin-bottom: 36px; }
.fixture-group h3 {
  font-size: 1rem;
  color: var(--text-secondary);
  border-bottom: 1px solid var(--gridline);
  padding-bottom: 8px;
  margin-bottom: 16px;
}

/* =========================================================
   WhoScored-inspired components
   ========================================================= */

/* ---------------- Theme toggle ---------------- */
.theme-toggle {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-secondary);
  border-radius: 8px;
  width: 36px;
  height: 36px;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: 8px;
}
.theme-toggle:hover { color: var(--accent); border-color: var(--accent); }
.nav-wrap { display: flex; align-items: center; }

/* ---------------- Form guide pills (W / D / L) ---------------- */
.form-guide {
  display: inline-flex;
  gap: 3px;
  vertical-align: middle;
}
.form-pill {
  width: 18px;
  height: 18px;
  border-radius: 4px;
  font-size: 0.66rem;
  font-weight: 700;
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  letter-spacing: 0;
}
.form-pill--W { background: var(--status-good); }
.form-pill--D { background: var(--status-warn); color: #3a2c00; }
.form-pill--L { background: var(--status-bad); }
.form-label {
  font-size: 0.68rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-right: 6px;
}

/* ---------------- Rating badge (0–10) ---------------- */
.rating-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 42px;
  padding: 3px 6px;
  border-radius: 5px;
  font-size: 0.9rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: #fff;
  letter-spacing: -0.01em;
}
/* rating colour ramps by band (WhoScored-style): higher = stronger */
.rating-badge--elite { background: #1b7a3d; }   /* >= 7.0 */
.rating-badge--good  { background: #3f9a53; }   /* 6.8–6.99 */
.rating-badge--avg   { background: #7d8a55; }   /* 6.6–6.79 */
.rating-badge--low   { background: #9a7d3f; }   /* < 6.6 */

.rating-inline {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.72rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* team row inside a match card: name + form + rating */
.team-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
}
.team-row--away { justify-content: flex-end; }
.team-meta {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 8px;
}

/* ---------------- Stat comparison bars (match centre) ---------------- */
.compare-block { margin: 8px 0 4px; }
.compare-row { margin-bottom: 14px; }
/* Three columns rather than `space-between`: with flex, a label that wrapped
   onto two lines sat flush against the home value ("0.47ATTACK STRENGTH…") and
   drifted off-centre. Grid keeps the two numbers pinned to the edges and the
   label centred between them with a real gutter. */
.compare-row__head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 2px 12px;
  align-items: baseline;
  font-size: 0.9rem;
  margin-bottom: 6px;
  font-variant-numeric: tabular-nums;
}
.compare-row__label {
  font-size: 0.8rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  line-height: 1.35;
  text-align: center;
  text-wrap: balance;
}
.compare-row__val { font-weight: 700; white-space: nowrap; }
.compare-row__val--away { text-align: right; }
/* On a phone there is no room for label-between-numbers: these labels run to
   six words. Give the label its own line and let the two values face each
   other across the row directly above the bar they describe. */
@media (max-width: 600px) {
  .compare-row { margin-bottom: 18px; }
  .compare-row__head {
    grid-template-columns: 1fr auto;
    gap: 3px 12px;
    font-size: 0.95rem;
  }
  .compare-row__label {
    grid-column: 1 / -1;
    grid-row: 1;
    text-align: center;
    font-size: 0.74rem;
  }
  .compare-row__val--home { grid-column: 1; grid-row: 2; }
  .compare-row__val--away { grid-column: 2; grid-row: 2; }
}
.compare-row__val--home { color: var(--series-home); }
.compare-row__val--away { color: var(--series-away); }
.compare-bar {
  display: flex;
  height: 10px;
  border-radius: 5px;
  overflow: hidden;
  background: var(--gridline);
}
.compare-bar__home { background: var(--series-home); height: 100%; }
.compare-bar__away { background: var(--series-away); height: 100%; margin-left: 2px; }

.compare-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  font-size: 0.8rem;
  color: var(--text-secondary);
  margin-bottom: 14px;
}
.compare-legend__item { display: flex; align-items: center; gap: 6px; }
.compare-legend__dot { width: 9px; height: 9px; border-radius: 50%; }
.compare-legend__dot--home { background: var(--series-home); }
.compare-legend__dot--away { background: var(--series-away); }

/* ---------------- Tabs (Overall / Home / Away) ---------------- */
.tabs {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  margin: 4px 0 18px;
}
.tab-btn {
  background: var(--surface-2);
  border: none;
  border-right: 1px solid var(--border);
  color: var(--text-secondary);
  font-size: 0.82rem;
  font-weight: 600;
  padding: 7px 16px;
  cursor: pointer;
}
.tab-btn:last-child { border-right: none; }
.tab-btn.active { background: var(--accent); color: var(--accent-ink); }

/* ---------------- Standings table ---------------- */
.standings-wrap { overflow-x: auto; }
/* A league played in zones or groups shows one table per group — MLS's two
   conferences, a European league that has split, the Argentine Clausura. The
   heading sits tighter to its table than a section title would, so the pair
   reads as one block rather than as two sections. */
.standings-group {
  font-size: 1rem;
  margin: 22px 0 8px;
}
.standings-group:first-child { margin-top: 0; }
.standings-note {
  color: var(--text-secondary);
  font-size: 0.85rem;
  margin: 0 0 10px;
  padding: 6px 10px;
  background: var(--surface-2, rgba(127, 127, 127, 0.08));
  border-radius: 6px;
}
.standings {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  font-size: 0.9rem;
}
.standings th, .standings td {
  padding: 11px 10px;
  text-align: center;
  border-bottom: 1px solid var(--gridline);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.standings thead th {
  color: var(--text-muted);
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  background: var(--surface-1);
}
.standings td.team-cell, .standings th.team-cell { text-align: left; }
.standings .pos-cell { color: var(--text-muted); font-weight: 600; }
.standings tbody tr:last-child td { border-bottom: none; }
.standings tbody tr:hover { background: var(--surface-1); }
.standings .team-name { font-weight: 600; color: var(--text-primary); }
.standings .pts-cell { font-weight: 700; }
/* The FIFA World Ranking column, on national-team tables only (E18). Muted and
   in the same tabular figures as the rest: it is context for the points, not a
   second table. On mobile it is the first column that scrolls, right behind the
   frozen team name — which is where it belongs before a ball is kicked, when it
   is the only number on the row that is not a zero. */
.standings .fifa-cell { color: var(--text-muted); font-weight: 600; }
.qual-line { border-left: 3px solid var(--status-good); }

/* Season projection table (Monte-Carlo final positions) */
.proj-block { margin-top: 28px; }
.proj-note {
  color: var(--text-secondary);
  font-size: 0.85rem;
  line-height: 1.55;
  margin: 0 0 12px;
  max-width: 72ch;
}
.proj-table .proj-exp { font-weight: 700; color: var(--accent); }
/* The bar sits behind the number rather than beside it, so the column stays
   narrow enough for a phone and the whole table still scans top-to-bottom. */
.proj-cell { position: relative; min-width: 74px; }
.proj-bar {
  position: absolute;
  inset: 4px 6px;
  border-radius: 4px;
  background: var(--surface-1);
  overflow: hidden;
}
.proj-bar__fill {
  display: block;
  height: 100%;
  background: color-mix(in srgb, var(--accent) 32%, transparent);
}
.proj-num { position: relative; font-variant-numeric: tabular-nums; }
.proj-zero, .proj-na { color: var(--text-muted); }

/* The move since the last update whose numbers changed.
   NOT --status-good / --status-bad, and that is measured rather than a
   preference: run through the dataviz palette checker against this surface, the
   site's green/red pair separates by ΔE 4.2 for a deuteranope — under the 6
   floor, so a red-green reader sees one colour in that column. The teal/orange
   pair below separates by 11.2 (protan) in light and 13.6 in dark, and passes
   every check in both modes. The form pills keep green/red: there the letter W
   or L carries the meaning and the colour only decorates it.
   The sign is printed regardless, so the column reads correctly with no colour
   at all. `tabular-nums` keeps the signs in a line down the column, which is the
   whole point of a delta column. */
.proj-move {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 0.85rem;
  white-space: nowrap;
}
.proj-move--up { color: #0d8a7a; }
.proj-move--down { color: #d9720b; }
.proj-move--flat, .proj-move--na { color: var(--text-muted); font-weight: 500; }
:root[data-theme="dark"] .proj-move--up { color: #22a094; }
:root[data-theme="dark"] .proj-move--down { color: #d97524; }

@media (prefers-reduced-motion: no-preference) {
  .proj-bar__fill { transition: width 240ms ease; }
}

/* --- Mobile: compact standings with frozen # + Team columns ---------------
   The table has 11 columns and overflows on phones. We shrink the type, then
   pin the position (#) and team columns so they stay in view while the stats
   and form columns scroll horizontally. The rounded frame is moved onto the
   scroll wrapper because the table's own overflow:hidden would otherwise
   capture position:sticky and stop it working. */
@media (max-width: 700px) {
  .standings-wrap {
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;      /* clip the rounded corners */
    overflow-x: auto;      /* ...while keeping the horizontal scroll */
  }
  .standings {
    border: none;
    border-radius: 0;
    overflow: visible;     /* let .standings-wrap be the sticky scroll parent */
    font-size: 0.8rem;
  }
  .standings th, .standings td { padding: 8px 6px; }

  /* Column 1: position (#). Fixed width so the team column's left offset is
     exact. */
  .standings th:first-child,
  .standings td:first-child {
    position: sticky;
    left: 0;
    z-index: 2;
    width: 34px;
    min-width: 34px;
    background: var(--surface-2);
  }
  .standings thead th:first-child { z-index: 3; background: var(--surface-1); }

  /* Column 2: team, pinned just right of the # column. */
  .standings th.team-cell,
  .standings td.team-cell {
    position: sticky;
    left: 34px;
    z-index: 2;
    background: var(--surface-2);
    border-right: 1px solid var(--gridline); /* seam so it reads as "floating" */
  }
  .standings thead th.team-cell { z-index: 3; background: var(--surface-1); }

  /* Cap + ellipsize long team names so the frozen column stays narrow. */
  .standings td.team-cell .std-team { max-width: 160px; }
  .standings td.team-cell .std-team span {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Row hover tint sits behind the opaque frozen cells, so repaint them too. */
  .standings tbody tr:hover td:first-child,
  .standings tbody tr:hover td.team-cell { background: var(--surface-1); }
}

/* small helper */
.section-lead {
  color: var(--text-secondary);
  margin: 0 0 8px;
  max-width: var(--lede-measure);
  line-height: 1.65;
  text-wrap: pretty;
}

/* =========================================================
   Hero "Match of the day" card
   ========================================================= */
.hero-featured { display: flex; justify-content: center; }
.hero-card {
  width: 100%;
  max-width: 440px;
  background: var(--surface-2);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 22px 24px 24px;
  box-shadow: var(--shadow-hero);
  position: relative;
}
.hero-card__ribbon {
  position: absolute;
  top: -12px; left: 24px;
  background: var(--brand-grad);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 6px 12px;
  border-radius: 20px;
  box-shadow: 0 6px 16px rgba(42,109,240,0.4);
}
.hero-card__meta {
  display: flex; justify-content: space-between;
  font-size: 0.74rem; font-weight: 600;
  color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em;
  margin: 8px 0 16px;
}
.hero-card .match-card__teams { margin-bottom: 16px; }
.hero-card__predline {
  text-align: center;
  margin-bottom: 12px;
}
.hero-card__predline .lbl {
  font-size: 0.72rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em;
  display: block; margin-bottom: 2px;
}
.hero-card__score {
  font-family: var(--font-display);
  font-size: 2.1rem; font-weight: 800; letter-spacing: -0.02em; line-height: 1;
}
.hero-card .prob-bar { height: 22px; }
.hero-card__foot {
  display: flex; align-items: center; justify-content: space-between;
  margin-top: 16px; gap: 12px;
}

/* Confidence chip variants (used in hero + can extend elsewhere) */
.conf-chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase;
  padding: 5px 10px; border-radius: 20px;
}
.conf-chip::before { content:""; width: 7px; height: 7px; border-radius: 50%; }
.conf-chip--high { background: color-mix(in srgb, var(--status-good) 16%, transparent); color: #0a7a0a; }
.conf-chip--high::before { background: var(--status-good); }
.conf-chip--medium { background: color-mix(in srgb, var(--status-warn) 20%, transparent); color: #9a6a00; }
.conf-chip--medium::before { background: var(--status-warn); }
.conf-chip--low { background: color-mix(in srgb, var(--text-muted) 18%, transparent); color: var(--text-secondary); }
.conf-chip--low::before { background: var(--text-muted); }
:root[data-theme="dark"] .conf-chip--high { color: #4ad24a; }
:root[data-theme="dark"] .conf-chip--medium { color: #f5c451; }

.hero-card__cta { font-weight: 700; font-size: 0.9rem; white-space: nowrap; }

/* =========================================================
   Compact league-grouped fixtures list (Flashscore-style)
   ========================================================= */
.fx-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin: 6px 0 4px;
}
.fx-legend {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  font-size: 0.76rem;
  color: var(--text-muted);
  margin: 4px 0 18px;
}
.fx-legend span { display: inline-flex; align-items: center; gap: 5px; }
.fx-legend .dot { width: 8px; height: 8px; border-radius: 50%; }
.fx-legend .dot--high { background: var(--status-good); }
.fx-legend .dot--medium { background: var(--status-warn); }
.fx-legend .dot--low { background: var(--text-muted); }
.fx-legend .pickswatch {
  width: 16px; height: 12px; border-radius: 3px;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border));
}

.team-badge--sm {
  width: 22px; height: 22px;
  font-size: 0.58rem;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.14);
}

.lg-group {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  margin-bottom: 18px;
  overflow: hidden;
}
.lg-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--gridline);
  background: var(--surface-1);
}
.lg-badge {
  min-width: 32px; height: 22px;
  border-radius: 5px;
  color: #fff;
  font-size: 0.64rem; font-weight: 800; letter-spacing: 0.02em;
  display: flex; align-items: center; justify-content: center;
  padding: 0 5px;
}
.lg-name { font-family: var(--font-display); font-weight: 700; font-size: 0.95rem; letter-spacing: -0.01em; }
.lg-link { margin-left: auto; font-size: 0.8rem; font-weight: 600; }

.mrow {
  display: grid;
  grid-template-columns: 58px minmax(0, 1fr) auto auto 18px;
  align-items: center;
  gap: 16px;
  padding: 11px 16px;
  border-bottom: 1px solid var(--gridline);
  color: inherit;
  transition: background .12s ease;
}
.mrow:last-child { border-bottom: none; }
.mrow:hover { background: var(--surface-1); text-decoration: none; }
.mrow__time {
  font-size: 0.84rem;
  color: var(--text-secondary);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  display: flex; flex-direction: column; line-height: 1.25;
}
.mrow__time small { color: var(--text-muted); font-size: 0.68rem; font-weight: 500; }
.mrow__teams { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.mrow__team { display: flex; align-items: center; gap: 9px; min-width: 0; }
.mrow__name {
  font-weight: 600; font-size: 0.93rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mrow__pred { display: flex; align-items: center; gap: 9px; }
.mrow__score {
  font-family: var(--font-display);
  font-weight: 800; font-size: 1rem;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 5px 11px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
}
.conf-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; }
.conf-dot--high { background: var(--status-good); }
.conf-dot--medium { background: var(--status-warn); }
.conf-dot--low { background: var(--text-muted); }
.mrow__odds { display: flex; gap: 6px; }
.oc {
  display: flex; flex-direction: column; align-items: center;
  min-width: 46px;
  padding: 4px 6px;
  border-radius: 8px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  font-weight: 700; font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}
.oc small { font-size: 0.58rem; color: var(--text-muted); font-weight: 700; margin-bottom: 1px; }
.oc--pick {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-color: color-mix(in srgb, var(--accent) 42%, var(--border));
  color: var(--accent);
}
.oc--pick small { color: var(--accent); }
.mrow__go { color: var(--text-muted); font-size: 1.35rem; text-align: center; line-height: 1; }

@media (max-width: 680px) {
  .mrow { grid-template-columns: 44px minmax(0,1fr) auto 14px; gap: 9px; padding: 11px 12px; }
  .mrow__odds { display: none; }
  /* Give team names room and let them wrap rather than clip on narrow screens. */
  .mrow__team { gap: 7px; align-items: center; }
  .mrow__name { font-size: 0.85rem; white-space: normal; line-height: 1.15; }
  .mrow__pred2 small { font-size: 0.5rem; }
  .mrow__score { font-size: 0.9rem; padding: 4px 8px; }
}

/* =========================================================
   Team profile page
   ========================================================= */
.team-badge--xl {
  width: 84px; height: 84px;
  font-size: 1.5rem; border-radius: 20px;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.14), var(--shadow-md);
}
.team-hero {
  display: flex;
  align-items: center;
  gap: 22px;
  padding: 28px 0 8px;
  flex-wrap: wrap;
}
.team-hero__info { flex: 1; min-width: 200px; }
.team-hero__info h1 {
  font-family: var(--font-display);
  font-size: 2.1rem; letter-spacing: -0.03em; margin: 0 0 4px;
}
.team-hero__meta { color: var(--text-secondary); font-size: 0.95rem; margin-bottom: 12px; }
.team-hero__row { display: flex; align-items: center; gap: 22px; flex-wrap: wrap; }
.team-hero__form { display: inline-flex; align-items: center; gap: 8px; }
.team-hero__facts {
  display: flex; gap: 26px; flex-wrap: wrap;
  padding: 14px 20px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.team-hero__facts div { font-size: 0.95rem; font-weight: 600; }
.team-hero__facts span {
  display: block; font-size: 0.72rem; font-weight: 600; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 3px;
}
.team-blurb {
  color: var(--text-secondary); font-size: 1.05rem; max-width: var(--lede-measure);
  margin: 18px 0 0; line-height: 1.65; text-wrap: pretty;
}
.team-h2 {
  font-family: var(--font-display); font-size: 1.25rem; letter-spacing: -0.02em;
  margin: 4px 0 16px;
}

/* ---------------- Team page on a phone ----------------
   The desktop team page is a wide, three-numbers-a-row layout squeezed into
   ~412px. Everything below trades horizontal room for vertical: the hero stops
   competing with the crest, the league table wraps team names instead of
   scrolling sideways, and the fixture rows put the date on a second line. */
@media (max-width: 600px) {
  .team-hero { gap: 14px; padding: 20px 0 4px; align-items: center; }
  .team-crest--xl { width: 58px; height: 58px; }
  .team-crest--xl .team-crest__mono { font-size: 1.05rem; }
  .team-hero__info { min-width: 0; }
  .team-hero__info h1 { font-size: 1.5rem; line-height: 1.15; letter-spacing: -0.02em; }
  .team-hero__meta { font-size: 0.85rem; margin-bottom: 0; }
  .team-hero__facts { gap: 14px 18px; padding: 12px 14px; }

  .team-summary { font-size: 0.92rem; line-height: 1.62; margin-bottom: 18px; }
  .team-h2 { font-size: 1.1rem; margin-bottom: 12px; }
  .team-blurb { font-size: 0.95rem; }

  /* Table: at this size a sideways scroll hides half the columns, so tighten
     the numeric cells and let the one long column (the team name) wrap. */
  .mtbl__table { font-size: 0.8rem; }
  .mtbl__table th, .mtbl__table td { padding: 7px 3px; }
  .mtbl__table th:first-child, .mtbl__table td:first-child { padding-left: 1px; }
  .mtbl__table th:last-child, .mtbl__table td:last-child { padding-right: 1px; }
  .mtbl__table th:nth-child(2) { text-align: left; }
  .mtbl__team { white-space: normal; line-height: 1.25; padding-right: 6px; }
  .mtbl__row--me .mtbl__pos { box-shadow: inset 2px 0 0 0 var(--accent); }

  /* Form and head-to-head rows: wrap the scoreline instead of clipping it. */
  .mform__row, .mscr__row { gap: 8px; padding: 8px 10px; }
  .mform__opp { white-space: normal; overflow: visible; line-height: 1.35; }

  /* Fixture / result rows: opponent + number on line one, date under it. */
  .tf-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 2px 10px;
    align-items: baseline;
    padding: 9px 12px;
  }
  .tf-opp { white-space: normal; overflow: visible; grid-column: 1; grid-row: 1; }
  .tf-score, .tf-pred { grid-column: 2; grid-row: 1; min-width: 0; }
  .tf-when { grid-column: 1 / -1; grid-row: 2; min-width: 0; font-size: 0.74rem; }
}

/* Strength meters */
.strengths { display: grid; gap: 16px; margin-bottom: 34px; }
.strength__head {
  display: flex; justify-content: space-between; font-size: 0.9rem; margin-bottom: 6px;
}
.strength__val { font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text-secondary); }
.strength__track {
  height: 8px; border-radius: 5px; background: var(--rating-track); overflow: hidden;
}
.strength__fill {
  height: 100%; border-radius: 5px; background: var(--brand-grad);
}

/* Upcoming fixtures list */
.upnext {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); overflow: hidden;
}
.upnext-row {
  display: grid; grid-template-columns: 72px 1fr auto; align-items: center; gap: 14px;
  padding: 12px 16px; border-bottom: 1px solid var(--gridline); color: inherit;
  transition: background .12s ease;
}
.upnext-row:last-child { border-bottom: none; }
.upnext-row:hover { background: var(--surface-1); text-decoration: none; }
.upnext-row__date { font-size: 0.84rem; font-weight: 600; color: var(--text-secondary); display: flex; flex-direction: column; line-height: 1.25; }
.upnext-row__date small { color: var(--text-muted); font-size: 0.68rem; font-weight: 500; }
.upnext-row__opp { font-weight: 600; font-size: 0.95rem; display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
.upnext-row__opp small { color: var(--text-muted); font-size: 0.72rem; font-weight: 500; }
.upnext-row__pred {
  font-family: var(--font-display); font-weight: 800; font-size: 1rem;
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: 8px; padding: 4px 10px; font-variant-numeric: tabular-nums;
}

/* Recent results (sidebar) */
.results { display: grid; gap: 8px; }
.result-row { display: grid; grid-template-columns: 22px 1fr auto; align-items: center; gap: 10px; font-size: 0.88rem; }
.result-row__opp { color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.result-row__score { font-weight: 700; font-variant-numeric: tabular-nums; }
.muted-note { color: var(--text-muted); font-size: 0.9rem; }

/* =========================================================
   Best XI — pitch visualization
   ========================================================= */
.bestxi-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(260px, 0.85fr);
  gap: 28px;
  align-items: start;
  margin-top: 8px;
}
@media (max-width: 860px) { .bestxi-layout { grid-template-columns: 1fr; } }

.pitch {
  position: relative;
  aspect-ratio: 68 / 105;
  width: 100%;
  border-radius: var(--radius-lg);
  background:
    repeating-linear-gradient(0deg, #1f9d53 0 9.09%, #1c9550 9.09% 18.18%);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  border: 1px solid rgba(0,0,0,0.15);
}
.pitch__lines { position: absolute; inset: 0; }
.pitch__lines > * { position: absolute; border: 2px solid rgba(255,255,255,0.55); box-sizing: border-box; }
.pitch__halfway { top: 50%; left: 0; width: 100%; height: 0; border-width: 0 0 2px 0; }
.pitch__circle { top: 50%; left: 50%; width: 24%; aspect-ratio: 1; border-radius: 50%; transform: translate(-50%, -50%); }
.pitch__box { left: 50%; width: 58%; height: 15%; transform: translateX(-50%); }
.pitch__box--top { top: 0; border-top: none; }
.pitch__box--bottom { bottom: 0; border-bottom: none; }
.pitch__spot { width: 5px; height: 5px; border-radius: 50%; background: rgba(255,255,255,0.7); border: none; left: 50%; transform: translateX(-50%); }
.pitch__spot--top { top: 11%; }
.pitch__spot--bottom { bottom: 11%; }

.player {
  position: absolute;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  color: #fff;
  width: 84px;
  text-align: center;
}
.player:hover { text-decoration: none; }
.player:hover .player__badge { transform: scale(1.08); }
.player__badge {
  position: relative;
  width: 44px; height: 44px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-2);
  box-shadow: 0 3px 8px rgba(0,0,0,0.35), inset 0 0 0 2px rgba(255,255,255,0.4);
  transition: transform .15s ease;
}
.player__badge .team-crest { width: 34px; height: 34px; }
.player__badge .team-crest__mono { font-size: 0.72rem; }
.player__rating {
  position: absolute;
  bottom: -6px; right: -8px;
  background: #fff; color: #0c1322;
  font-size: 0.64rem; font-weight: 800;
  padding: 1px 4px; border-radius: 6px;
  box-shadow: 0 2px 5px rgba(0,0,0,0.3);
  font-variant-numeric: tabular-nums;
}
.player__name {
  font-size: 0.78rem; font-weight: 700;
  text-shadow: 0 1px 3px rgba(0,0,0,0.6);
  line-height: 1.1;
  margin-top: 4px;
}
.player__pos {
  font-size: 0.62rem; font-weight: 700; letter-spacing: 0.04em;
  color: rgba(255,255,255,0.85);
  text-shadow: 0 1px 2px rgba(0,0,0,0.6);
}

/* Best XI ranked list */
.bx-panel {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.bx-panel h3 {
  font-family: var(--font-display);
  font-size: 1rem; margin: 0;
  padding: 14px 16px; border-bottom: 1px solid var(--gridline);
  background: var(--surface-1);
}
.bx-row {
  display: grid;
  grid-template-columns: 34px 22px 1fr auto;
  align-items: center; gap: 10px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--gridline);
  color: inherit;
}
.bx-row:last-child { border-bottom: none; }
.bx-row:hover { background: var(--surface-1); text-decoration: none; }
.bx-row__pos { font-size: 0.7rem; font-weight: 700; color: var(--text-muted); }
.bx-row__name { font-weight: 600; font-size: 0.9rem; display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.bx-row__name small { color: var(--text-muted); font-size: 0.72rem; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.formation-tag {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 0.8rem; font-weight: 700; color: var(--text-secondary);
  background: var(--surface-1); border: 1px solid var(--border);
  padding: 6px 12px; border-radius: 20px;
}

/* ---------------- Match-page "League table" button + pop-up ------------- */
.match-hero__table-btn {
  display: inline-flex; align-items: center; gap: 7px;
  margin: 4px auto 18px; padding: 7px 15px;
  font-size: 0.82rem; font-weight: 600; color: var(--text-secondary);
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: 20px; cursor: pointer;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.match-hero__table-btn:hover {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  background: color-mix(in srgb, var(--accent) 8%, var(--surface-1));
}
.mh-table-icon { font-size: 0.9em; line-height: 1; }

/* Generic modal */
html.modal-open { overflow: hidden; }
.modal-overlay {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  background: rgba(8, 12, 30, 0.55);
  backdrop-filter: blur(3px);
  animation: modal-fade .16s ease;
}
.modal {
  display: flex; flex-direction: column;
  width: 100%; max-width: 680px; max-height: 96vh;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 14px; box-shadow: var(--shadow-lg);
  overflow: hidden;
  animation: modal-pop .18s ease;
}
/* Wider variant for tables that need the room to fit without scrolling. */
.modal--wide { max-width: 760px; }
.modal__head {
  display: flex; align-items: center; gap: 12px;
  padding: 15px 18px; border-bottom: 1px solid var(--border);
}
.modal__title { margin: 0; font-size: 1rem; font-weight: 700; line-height: 1.3; }
.modal__close {
  margin-left: auto; flex: 0 0 auto;
  width: 32px; height: 32px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface-1);
  color: var(--text-secondary); font-size: 1.3rem; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: color .15s ease, background .15s ease;
}
.modal__close:hover { color: var(--text-primary); background: var(--surface-2); }
.modal__body { padding: 14px 18px; overflow-y: auto; }
.modal__foot {
  padding: 12px 18px; border-top: 1px solid var(--border);
  display: flex; justify-content: flex-end;
}
.modal-note, .modal-loading, .modal-empty {
  font-size: 0.76rem; color: var(--text-muted); margin: 0 0 8px;
}
.modal-loading, .modal-empty { margin: 8px 0; }
.modal__body .standings-wrap { margin: 0; }
.btn--sm { padding: 8px 14px; font-size: 0.82rem; border-radius: 9px; box-shadow: none; }

/* Compact standings inside the pop-up so the whole table fits without scrolling. */
.modal__body { padding-top: 10px; padding-bottom: 12px; }
.modal .standings { font-size: 0.8rem; }
.modal .standings th, .modal .standings td { padding: 4px 6px; }
.modal .standings thead th { font-size: 0.64rem; }
.modal .standings .team-cell { max-width: none; }
.modal .standings .team-crest { width: 17px; height: 17px; }
.modal .standings .team-crest img { width: 17px; height: 17px; }
.modal .standings .std-team span { font-size: 0.8rem; }
/* Form column is the widest/tallest cell — trim it in the compact view. */
.modal .standings th:last-child, .modal .standings td:last-child { padding-left: 4px; padding-right: 4px; }

/* Knockout-tie view inside the pop-up */
.tie-legs { display: flex; flex-direction: column; gap: 14px; }
.tie-leg__meta {
  font-size: 0.72rem; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.03em; margin: 0 0 6px;
}
.tie-legs .bt-tie { max-width: 420px; }
.tie-legs .bt-name { font-weight: 600; }
.tie-legs .bt-score { margin-left: auto; font-weight: 700; font-variant-numeric: tabular-nums; }
.tie-agg {
  margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border);
  font-size: 0.9rem; color: var(--text-secondary);
}
.tie-agg strong { color: var(--text-primary); }

/* Highlighted rows: the two sides of the current match */
.standings tr.std-row--focus td { background: color-mix(in srgb, var(--accent) 14%, var(--surface-2)); }
.standings tr.std-row--focus:hover td { background: color-mix(in srgb, var(--accent) 20%, var(--surface-2)); }
.standings tr.std-row--focus .std-team { font-weight: 800; color: var(--text-primary); }
.standings tr.std-row--focus td:first-child {
  box-shadow: inset 3px 0 0 var(--accent);
}

@keyframes modal-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes modal-pop { from { opacity: 0; transform: translateY(8px) scale(0.98); } to { opacity: 1; transform: none; } }

@media (max-width: 600px) {
  .modal { max-height: 92vh; }
  .modal__body { padding: 12px 12px; }
}

/* --- Results & history page ------------------------------------------------ */
.results-summary { margin: 18px 0 6px; }
/* F19. The filter is built by renderResults() after the JSON lands, so the box
   goes 0 -> control and shoves the results list down. The summary above it was
   fixed by prerendering (server/write-hub-pages.js); this one is a UI control
   rather than content, so it reserves its space instead. Unlike the chip block it
   replaced, the height no longer depends on how many competitions are in the
   archive — the rail scrolls sideways and the panel is closed on load — so the
   reservation is exact rather than a one-row approximation: 35px of rail, 6px of
   rail padding, then the 37px "All leagues" button. */
.results-filter {
  display: block;
  margin: 14px 0 18px;
  min-height: 78px;
  position: relative;
}


/* --- Results: league filter (replaces the 40-chip block) -------------------
   The old filter wrapped to seven rows on a desktop and about fifteen on a
   phone. This is one rail that scrolls sideways plus a searchable panel, so the
   first result stays near the top of the page on every screen. */
.lgf__rail {
  display: flex; gap: 8px; overflow-x: auto; padding: 2px 2px 6px;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
}
.lgf__rail::-webkit-scrollbar { display: none; }
.lgf__chip {
  font: inherit; font-size: 0.85rem; font-weight: 600; cursor: pointer;
  display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
  flex: 0 0 auto; scroll-snap-align: start;
  padding: 7px 14px; border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text-secondary);
  transition: background .15s, color .15s, border-color .15s, box-shadow .15s;
}
.lgf__chip:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); color: var(--text-primary); }
.lgf__chip.is-active {
  background: var(--accent); color: var(--accent-ink, #fff); border-color: var(--accent);
  box-shadow: 0 2px 10px color-mix(in srgb, var(--accent) 30%, transparent);
}
.lgf__flag {
  width: 18px; height: 12px; object-fit: cover; border-radius: 2px;
  box-shadow: 0 0 0 1px var(--border); flex-shrink: 0;
}
.lgf__flag--none { display: none; }

.lgf__more {
  font: inherit; font-size: 0.82rem; font-weight: 600; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 4px; padding: 7px 14px; border-radius: 999px;
  border: 1px dashed var(--border); background: transparent; color: var(--text-secondary);
}
.lgf__more:hover { border-color: var(--accent); color: var(--text-primary); }
.lgf__more.is-open { border-style: solid; border-color: var(--accent); color: var(--text-primary); }
.lgf__caret { transition: transform .15s; }
.lgf__more.is-open .lgf__caret { transform: rotate(180deg); }

.lgf__panel {
  margin-top: 10px; padding: 12px; border-radius: 14px;
  border: 1px solid var(--border); background: var(--surface-2);
  box-shadow: 0 10px 30px rgba(12, 19, 34, 0.08);
}
.lgf__search {
  font: inherit; font-size: 0.9rem; width: 100%; box-sizing: border-box;
  padding: 9px 12px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface-1); color: var(--text-primary);
}
.lgf__search:focus { outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); outline-offset: 1px; }
.lgf__list {
  list-style: none; margin: 10px 0 0; padding: 0;
  max-height: 320px; overflow-y: auto;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr)); gap: 2px;
}
.lgf__opt {
  font: inherit; width: 100%; cursor: pointer; text-align: left;
  display: flex; align-items: center; gap: 9px;
  padding: 9px 10px; border-radius: 9px; border: 0; background: transparent;
  color: var(--text-primary); font-size: 0.88rem;
}
.lgf__opt:hover { background: var(--surface-1); }
.lgf__opt.is-active { background: color-mix(in srgb, var(--accent) 14%, transparent); font-weight: 700; }
.lgf__opt-name { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.lgf__opt-name small { color: var(--text-muted); font-size: 0.75rem; }
.lgf__opt-count {
  margin-left: auto; font-size: 0.75rem; font-weight: 600; color: var(--text-muted);
  background: var(--surface-1); border-radius: 999px; padding: 1px 8px;
}
.lgf__opt.is-active .lgf__opt-count { background: var(--surface-2); }
.lgf__none { padding: 12px 10px; color: var(--text-muted); font-size: 0.88rem; }

/* --- Results: which competition the track record is about ------------------ */
.acc-context {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 12px; padding: 10px 14px; border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 25%, var(--border));
  background: color-mix(in srgb, var(--accent) 7%, var(--surface-2));
}
.acc-context .lgf__flag { width: 22px; height: 15px; }
.acc-context__name { font-weight: 800; color: var(--text-primary); }
.acc-context__meta { font-size: 0.85rem; color: var(--text-secondary); }
.acc-context__meta::before { content: "· "; color: var(--text-muted); }
.acc-context__clear {
  margin-left: auto; font: inherit; cursor: pointer; line-height: 1;
  padding: 4px 9px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text-secondary);
}
.acc-context__clear:hover { border-color: var(--accent); color: var(--text-primary); }
/* The delta lives on the cards on a desktop and in this bar on a phone, where
   the cards collapse to a strip with no room for a second line. */
.acc-context__delta { display: none; }

.acc-stat__delta {
  display: block; margin-top: 6px; font-size: 0.74rem; font-weight: 700;
  letter-spacing: 0.01em;
}
.acc-stat__delta--up { color: var(--status-good); }
.acc-stat__delta--down { color: var(--status-bad); }
.acc-stat__delta--flat { color: var(--text-muted); font-weight: 600; }

/* The second column of the by-competition table on /accuracy. Coloured because
   its sign is the whole point: a competition can rank high on hit rate and still
   sit below an uninformed guess, and the eye has to catch that without reading. */
/* The three tables on /accuracy. The class has been on that markup since the page
   was written and has never had a rule anywhere in this file — both the
   calibration table and the by-competition table have been shipping as bare
   browser default, on the page carrying the one thing no competitor publishes.
   Matched to .mtbl__table so the two read as the same component.

   Left-aligned throughout rather than numbers-right: two of the three tables have
   text in the middle columns (match, verdict), and tabular numerals line the
   figures up without needing the alignment to do it. */
.std-table {
  width: 100%; border-collapse: collapse; font-size: 0.88rem;
  margin: 6px 0 0;
}
.std-table th, .std-table td {
  padding: 9px 14px 9px 0; text-align: left;
  border-bottom: 1px solid var(--border);
}
.std-table th {
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--text-secondary); font-weight: 700; white-space: nowrap;
  border-bottom: 1px solid var(--gridline);
}
.std-table td { font-variant-numeric: tabular-nums; color: var(--text-secondary); }
.std-table td:first-child { color: var(--text-primary); font-weight: 600; }
.std-table tbody tr:last-child td { border-bottom: 0; }
.std-table a { color: inherit; text-decoration: none; }
.std-table a:hover { text-decoration: underline; }
/* Competition and match names on /accuracy, each behind its country's flag. The
   flag sits on the first line's baseline and the name wraps beside it instead of
   under it, so a three-word competition keeps one left edge. */
/* ---------------- /team-accuracy ----------------
   The head and the tail of the club ranking, lifted above a 300-row table so a
   phone gets the answer without scrolling. Two columns where there is room, one
   where there is not — and the stack order is best-then-worst either way, which
   is the order the question is asked in. */
.ta-edges {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 18px;
  margin: 26px 0 32px;
}
.ta-edge {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px 18px;
  background: var(--surface-2);
}
.ta-edge h2 { margin: 0 0 10px; }
/* A hairline in the accent colour rather than a filled panel: the two lists are
   the same kind of fact, and colouring one green and one red would read as
   praise and blame for the clubs, which is not what the figures say. */
.ta-edge--best { border-top: 3px solid var(--status-good); }
.ta-edge--worst { border-top: 3px solid var(--status-bad); }
.ta-edge-list { margin: 0; padding-left: 20px; }
.ta-edge-list li { margin: 0 0 7px; font-size: 0.9rem; line-height: 1.45; }
.ta-edge-list a { color: inherit; }

/* The switch between the two rankings. Identical markup, identical position and
   identical weight on both pages — that sameness IS the feature. The club table
   and the competition table are the same question asked at two levels, and while
   one was a page and the other a section halfway down /accuracy, whichever a
   reader landed on read as the answer and the other did not appear to exist. */
.acc-switch {
  display: inline-flex;
  gap: 2px;
  margin: 0 0 22px;
  padding: 3px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-2);
}
.acc-switch__tab {
  padding: 8px 16px;
  border-radius: 7px;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text-secondary);
  text-decoration: none;
  white-space: nowrap;
}
.acc-switch__tab:hover { color: var(--text-primary); }
.acc-switch__tab--on {
  background: var(--accent);
  color: var(--accent-ink);
}
@media (max-width: 420px) {
  /* Two tabs, full width, rather than two small targets floated left. */
  .acc-switch { display: flex; }
  .acc-switch__tab { flex: 1; text-align: center; padding: 9px 8px; }
}

.ta-filter { margin: 18px 0 10px; }
.ta-filter__label {
  display: block;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 6px;
}
.ta-filter__input {
  width: 100%;
  max-width: 340px;
  padding: 10px 12px;
  font: inherit;
  font-size: 0.95rem;
  color: var(--text-primary);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.ta-filter__input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.ta-filter__count {
  margin: 8px 0 0;
  font-size: 0.85rem;
  color: var(--text-muted);
  min-height: 1.2em;   /* holds its line so the table does not jump as you type */
}
.ta-table .ta-rank { color: var(--text-muted); font-weight: 500; width: 3em; }
/* js/table-filter.js hides rows with the `hidden` attribute. The UA sheet's
   [hidden] should already beat `tr { display: table-row }` on specificity, but
   this is the classic place that reasoning fails quietly — a filter that hides
   nothing looks like a broken feature, not like a CSS precedence puzzle — so it
   is stated rather than relied upon. */
.ta-table tr[hidden] { display: none; }

/* The two entry points into the track record, on the homepage. Side by side on a
   desktop, stacked on a phone, and each a full tap target rather than a link
   inside a sentence — this is the one place on the homepage where someone is
   deciding whether to trust a number, so the next step has to be obvious at a
   glance and reachable with a thumb. */
.record-links {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 14px;
  margin: 20px 0 0;
}
.record-link {
  display: block;
  padding: 16px 18px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 10px;
  background: var(--surface-2);
  text-decoration: none;
  transition: border-color 0.15s ease, transform 0.15s ease;
}
.record-link:hover { border-color: var(--accent); transform: translateY(-1px); }
.record-link__title {
  display: block;
  font-family: 'Sora', system-ui, sans-serif;
  font-weight: 700;
  font-size: 1rem;
  color: var(--text-primary);
  margin-bottom: 5px;
}
.record-link__sub {
  display: block;
  font-size: 0.87rem;
  line-height: 1.45;
  color: var(--text-secondary);
}
@media (prefers-reduced-motion: reduce) {
  .record-link { transition: none; }
  .record-link:hover { transform: none; }
}

.std-name { display: flex; align-items: baseline; gap: 8px; }
.std-flag {
  width: 18px; height: 12px; object-fit: cover; border-radius: 2px;
  box-shadow: 0 0 0 1px var(--border); flex-shrink: 0;
}
/* Held open rather than removed: the UEFA ties and the World Cup have no country
   to fly, and dropping the box would step their names out of the column. */
.std-flag--none { box-shadow: none; background: transparent; }
/* Only printed where two leagues share a name (Serie A is Brazil's and Italy's),
   so it has to read as a qualifier on the name and not as part of it. */
.std-sub { color: var(--text-muted); font-weight: 500; }
.std-sub::before { content: " · "; }
.cmp-skill { font-variant-numeric: tabular-nums; font-weight: 700; white-space: nowrap; }
/* Qualified with td: `.std-table td` sets a colour and outranks a lone class,
   so the sign lost its colour the moment the table got a rule. */
.std-table td.cmp-skill--up { color: var(--status-good); }
.std-table td.cmp-skill--down { color: var(--status-bad); }

/* --- Results on a phone ----------------------------------------------------
   .accuracy used to drop to a single column here, which turned three numbers
   into three tall blocks and pushed the results themselves off the screen. One
   strip instead: same three figures, about a fifth of the height. */
@media (max-width: 600px) {
  /* Tighter columns so more of a four-column table clears the fold before the
     horizontal scroll has to do the rest. */
  .std-table { font-size: 0.82rem; }
  .std-table th, .std-table td { padding: 8px 10px 8px 0; }

  /* /team-accuracy has six columns, which is two more than a phone can hold
     without sideways scrolling — and a table you have to drag is a table nobody
     reads to the end of. The exact-score column is the one that goes: it is the
     model's hardest ask and its least useful figure to someone deciding whether
     to trust a call, and it is still there on any wider screen and in the HTML
     either way. The rank number goes too — the rows are already in order. */
  .ta-table .ta-exact, .ta-table .ta-rank { display: none; }

  /* Wrap rather than scroll sideways. The rail was built to scroll because the
     filter it replaced was 40 chips deep; what it actually holds is at most
     eight, and on a phone everything past the fold reads as absent — a
     horizontal scroll with no visible edge is a control most people never
     find. Two short rows of buttons is not the fifteen-row block this
     replaced, which is the reason the rail exists. */
  .lgf__rail { flex-wrap: wrap; overflow-x: visible; }
  .lgf__chip { font-size: 0.8rem; padding: 6px 11px; }

  .results-summary .accuracy {
    grid-template-columns: repeat(3, 1fr); gap: 0;
    border: 1px solid var(--border); border-radius: 12px;
    background: var(--surface-2); overflow: hidden;
  }
  .results-summary .acc-stat {
    padding: 10px 4px; border: 0; border-radius: 0; background: transparent;
  }
  .results-summary .acc-stat + .acc-stat { border-left: 1px solid var(--border); }
  .results-summary .acc-stat__val { font-size: 1.35rem; }
  .results-summary .acc-stat__val span { font-size: 0.8rem; }
  .results-summary .acc-stat__lbl { font-size: 0.68rem; margin-top: 3px; }
  /* Cards only — the copy inside the context bar is the one that survives here,
     so the selector has to name .accuracy rather than the whole summary box. */
  .results-summary .accuracy .acc-stat__delta { display: none; }
  .acc-context__delta { display: inline-flex; }
  .acc-context__delta .acc-stat__delta { display: inline; margin-top: 0; font-size: 0.74rem; }
  .acc-context { gap: 7px; padding: 9px 11px; }
  .acc-context__name { font-size: 0.92rem; }
  .lgf__list { max-height: 260px; grid-template-columns: 1fr; }
}

.res-day { margin-bottom: 22px; }
.res-day__head {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--font-display, inherit); font-weight: 700; font-size: 1rem;
  color: var(--text-primary); margin: 0 2px 8px;
}
.res-day__count {
  font-size: 0.75rem; font-weight: 600; color: var(--text-muted);
  background: var(--surface-2); border-radius: 999px; padding: 1px 8px;
}
.res-day__rows {
  border: 1px solid var(--border); border-radius: var(--radius-md, 10px);
  overflow: hidden; background: var(--surface-1);
}
.res-row {
  display: grid; align-items: center;
  grid-template-columns: 1fr auto minmax(120px, auto) auto;
  gap: 12px; padding: 10px 14px;
  text-decoration: none; color: inherit;
  border-top: 1px solid var(--border);
  transition: background .12s;
}
.res-row:first-child { border-top: 0; }
.res-row:hover { background: var(--surface-2); }
.res-row__teams { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.res-row__team { display: flex; align-items: center; gap: 8px; min-width: 0; }
.res-row__name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.res-row__score {
  font-family: var(--font-display, inherit); font-weight: 800; font-size: 1.15rem;
  color: var(--text-primary); letter-spacing: 0.5px; white-space: nowrap;
}
.res-row__score small { color: var(--text-muted); font-weight: 600; margin: 0 3px; }
.res-row__pred {
  display: flex; flex-direction: column; line-height: 1.25;
  font-weight: 700; color: var(--text-secondary); font-size: 0.9rem;
}
.res-row__pred small { font-weight: 600; color: var(--text-muted); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.4px; }
.res-row__pick { font-weight: 600; color: var(--text-muted); font-size: 0.78rem; }
.rv {
  justify-self: end; white-space: nowrap;
  font-size: 0.75rem; font-weight: 700; padding: 3px 9px; border-radius: 999px;
}
.rv--hit { color: var(--status-good, #1b7a3d); background: color-mix(in srgb, var(--status-good, #1b7a3d) 14%, transparent); }
.rv--exact { color: var(--accent-ink, #fff); background: var(--accent); }
.rv--miss { color: var(--status-bad, #b0303a); background: color-mix(in srgb, var(--status-bad, #b0303a) 14%, transparent); }
.rv--na { color: var(--text-muted); background: var(--surface-2); }

@media (max-width: 620px) {
  .res-row { grid-template-columns: 1fr auto auto; row-gap: 6px; }
  .res-row__pred { grid-column: 1 / -1; flex-direction: row; gap: 6px; align-items: baseline; }
  .res-row__pred small { align-self: center; }
}

/* --- Prerendered hub lists (F15) -------------------------------------------
   fixtures.html and results.html ship their day's matches as real HTML so a
   crawler's first pass sees them and can follow them; js/main.js replaces the
   block with the interactive board a moment later. These rules only ever style
   that brief pre-hydration state and the no-JavaScript view, so they are plain
   on purpose — they must look deliberate, not broken. */
.fx-intro {
  color: var(--text-muted);
  font-size: 0.9rem;
  margin: 0 0 18px;
  max-width: var(--lede-measure);
  line-height: 1.6;
  text-wrap: pretty;
}
.fx-list {
  list-style: none;
  margin: 0 0 22px;
  padding: 0;
}
.fx-list li {
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.92rem;
  line-height: 1.5;
}
.fx-list li:last-child { border-bottom: 0; }
/* The prerendered results list prints each row's flag inline in a sentence, not
   in a column like /accuracy's table — so it needs the baseline nudge and the
   gap that .std-name's flex row gives it there. Only rows that HAVE a flag get
   one (server/write-hub-pages.js asks flagFor, not flagHTML), so there is no
   --none box to hold open here. */
.fx-list .std-flag { display: inline-block; vertical-align: -1px; margin-right: 6px; }
.fx-list a { font-weight: 600; }

/* The index of competition pages at the foot of /tables. Fifty one-line rows
   read as a wall in a single column, so they go into as many columns as the
   viewport allows — two on a phone is still one line per row, which is the
   point. `break-inside: avoid` because a row split across a column boundary is
   the failure mode of every CSS column list. */
.league-index-block { margin: 34px 0 8px; }
.league-index {
  columns: 260px auto;
  column-gap: 26px;
}
.league-index li { break-inside: avoid; }
.league-index .muted {
  color: var(--text-muted);
  font-size: 0.85em;
  margin-left: 6px;
}
.fx-time {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  margin-right: 8px;
}
/* The homepage mounts its prerendered list inside #featured-fixtures, which is a
   CSS grid of cards. Without this the intro and the list would each take a
   single column and sit half-width for the moment before hydration — same
   reason .grid-cards > .ad-slot spans the row. */
.grid-cards > .fx-intro,
.grid-cards > .fx-list { grid-column: 1 / -1; }
/* Match of the day, before hydration replaces it with the donut card. */
.fx-kicker {
  color: var(--accent);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  margin: 0 0 6px;
  text-transform: uppercase;
}
.fx-hero__title { font-size: 1.25rem; margin: 0 0 4px; }
.fx-hero__meta { color: var(--text-muted); font-size: 0.88rem; margin: 0 0 10px; }

/* ---------------- Results: where the model reads best ---------------- */
/* A closed <details> by default: the page's job is the match-by-match record,
   and this is a five-row aside on top of it. No JS, no modal — it prerenders
   into the HTML, so a crawler reads the ranking and a reader with scripts off
   can still open it. */
.lgperf {
  margin: 0 0 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
}
.lgperf__sum {
  cursor: pointer; padding: 12px 16px; font-weight: 700; font-size: 0.92rem;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  list-style: none;
}
.lgperf__sum::-webkit-details-marker { display: none; }
.lgperf__sum::before {
  content: "›"; display: inline-block; font-weight: 700; color: var(--text-muted);
  transition: transform .18s ease;
}
.lgperf[open] .lgperf__sum::before { transform: rotate(90deg); }
.lgperf__hint { font-weight: 500; font-size: 0.8rem; color: var(--text-muted); margin-left: auto; }
.lgperf__body { padding: 0 16px 14px; }
.lgperf__intro, .lgperf__note {
  font-size: 0.82rem; color: var(--text-muted); line-height: 1.55; margin: 0 0 10px;
}
.lgperf__note { margin: 10px 0 0; }
.lgperf__table { width: 100%; }
.lgperf__table th, .lgperf__table td { text-align: right; }
.lgperf__table th:first-child, .lgperf__table td:first-child { text-align: left; }

/* The one-line note under the hero donut saying WHICH number is on it — the
   in-play split or the call published before kick-off. Without it the card
   changes meaning silently the moment a match starts. */
.hero-card__basis {
  margin: 8px 0 0;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--text-muted);
}

/* ---------------- The in-play probability track (match pages) ---------------- */
/* Three stacked bands, not three hairlines. The bar directly above this is the
   same three territories at this minute; the track is that bar given a time
   axis, so the two objects share a vocabulary and a goal reads as ground
   changing hands rather than as a wiggle in a line.

   The SVG holds geometry only and keeps preserveAspectRatio="none", so it fills
   the column at a fixed height like a broadcast graphic. That is a non-uniform
   scale, which would turn a circle into an egg and stretch every letter — so
   the balls, the cards, the beating clock dot and the minute figures are HTML
   spans positioned over it in percent. js/prob-track.js explains why the
   percentages land exactly. */
.prob-track { margin: 4px 0 0; }
.prob-track__plot { position: relative; height: 196px; }
.prob-track__plot svg { width: 100%; height: 100%; display: block; border-radius: 10px; }

/* The fills. Each band is strongest at the edge it owns — the floor for home,
   the ceiling for away — so the two sides read as territory held from their own
   end, and the draw sits between them as neutral ground. */
.pt-band { stroke: none; }
.pt-stop--h0 { stop-color: var(--series-home); stop-opacity: 0.26; }
.pt-stop--h1 { stop-color: var(--series-home); stop-opacity: 0.72; }
.pt-stop--d0 { stop-color: var(--text-muted); stop-opacity: 0.30; }
.pt-stop--d1 { stop-color: var(--text-muted); stop-opacity: 0.22; }
.pt-stop--a0 { stop-color: var(--series-away); stop-opacity: 0.72; }
.pt-stop--a1 { stop-color: var(--series-away); stop-opacity: 0.26; }
:root[data-theme="dark"] .pt-stop--h0 { stop-opacity: 0.20; }
:root[data-theme="dark"] .pt-stop--h1 { stop-opacity: 0.62; }
:root[data-theme="dark"] .pt-stop--a0 { stop-opacity: 0.62; }
:root[data-theme="dark"] .pt-stop--a1 { stop-opacity: 0.20; }

/* Over the fills, because under them they would be invisible. White in both
   themes: a rule across a coloured band is a seam, and a seam is the surface
   showing through. */
.pt-grid { stroke: #fff; stroke-width: 1; opacity: 0.24; }
.pt-grid--mid { opacity: 0.4; }
:root[data-theme="dark"] .pt-grid { opacity: 0.12; }
:root[data-theme="dark"] .pt-grid--mid { opacity: 0.22; }
.pt-half { stroke: var(--text-primary); stroke-width: 1; stroke-dasharray: 2 5; opacity: 0.28; }

/* The boundaries: the only two edges that exist in a stacked hundred percent.
   Each wears the colour of the band whose edge it is. */
.pt-line { fill: none; stroke-width: 2.4; stroke-linejoin: round; stroke-linecap: round; }
.pt-line--h { stroke: var(--series-home); }
.pt-line--a { stroke: var(--series-away); }
/* Events sit UNDER the boundaries and are deliberately faint: they explain a
   step, they are not the subject — the ball on top of the step is. */
.pt-mark { stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.4; }
.pt-mark--h { stroke: var(--series-home); }
.pt-mark--a { stroke: var(--series-away); }
.pt-mark--red { stroke: var(--status-bad); stroke-dasharray: 2 2; opacity: 0.7; }
/* The minutes not played yet. Without it the bands stop halfway across and the
   picture looks broken; with it, the empty ground is the rest of the match. */
.pt-future { fill: var(--text-muted); opacity: 0.08; }
/* The clock, on a match still being played. Solid where the event marks are
   dashed — it is a position, not an event — and in the live red, breathing. */
.pt-now { stroke: var(--live); stroke-width: 1.5; opacity: 0.8; animation: pt-breathe 2.6s ease-in-out infinite; }
@keyframes pt-breathe { 0%, 100% { opacity: 0.9; } 50% { opacity: 0.35; } }

/* --- the glyph layer, in HTML over the plot ------------------------------- */
.pt-ball, .pt-card, .pt-edge, .pt-x { position: absolute; }
/* A goal. White with a dark panel and a ring in the scoring side's colour: at
   13 pixels a real football is a smudge, a ringed white disc with one pentagon
   in it is unmistakably one. */
.pt-ball {
  width: 13px; height: 13px; border-radius: 50%;
  transform: translate(-50%, -50%);
  background: #fff;
  box-shadow: 0 0 0 2px currentColor, 0 1px 4px rgba(12, 19, 34, 0.4);
}
.pt-ball--h { color: var(--series-home); }
.pt-ball--a { color: var(--series-away); }
.pt-ball::after {
  content: ""; position: absolute; inset: 3px; background: #131a2b;
  clip-path: polygon(50% 0, 100% 38%, 81% 100%, 19% 100%, 0 38%);
}
/* Scored in the last few minutes, on a match in play: one ring going out, so
   the newest thing that happened is the thing that moves. */
.pt-ball--fresh::before {
  content: ""; position: absolute; inset: -3px; border-radius: 50%;
  border: 2px solid currentColor; animation: pt-ring 1.9s ease-out infinite;
}
@keyframes pt-ring {
  0% { transform: scale(0.75); opacity: 0.85; }
  100% { transform: scale(2.3); opacity: 0; }
}
/* A sending-off, pinned to the top rail so it never lands on a ball. */
.pt-card {
  width: 9px; height: 12px; border-radius: 2px;
  transform: translate(-50%, -50%) rotate(-9deg);
  background: var(--status-bad);
  box-shadow: 0 1px 3px rgba(12, 19, 34, 0.4);
}
/* Where each boundary stands at the clock. Two dots beating in step: the whole
   "this is happening" signal, and the only thing on the card that moves. */
.pt-edge {
  width: 9px; height: 9px; border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--surface-2); box-shadow: 0 0 0 3px currentColor;
}
.pt-edge--h { color: var(--series-home); }
.pt-edge--a { color: var(--series-away); }
.pt-edge::after {
  content: ""; position: absolute; inset: -2px; border-radius: 50%;
  background: currentColor; animation: pt-beat 2.2s ease-out infinite;
}
@keyframes pt-beat {
  0% { transform: scale(0.6); opacity: 0.5; }
  70%, 100% { transform: scale(2.6); opacity: 0; }
}
/* Kick-off, half time, ninety. No y axis: the bands fill the plot floor to
   ceiling, so the height already says a hundred percent. */
.pt-x {
  bottom: -2px; transform: translateX(-50%);
  font-size: 0.68rem; color: var(--text-muted); font-variant-numeric: tabular-nums;
}
.pt-x--start { transform: none; }
.pt-x--end { transform: translateX(-100%); }
/* Inside the live card the track sits under the three big numbers, so it gets a
   rule above it and a little less height than the post-match one. */
.prob-track--live { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
.prob-track--live .prob-track__plot { height: 172px; }
/* Shown in place of the picture while the goal minutes are catching up with the
   score — one poll cycle at most. Same weight as the note under the card: it is
   an explanation, not a warning. */
.prob-track__pending {
  margin: 14px 0 0; padding-top: 12px; border-top: 1px solid var(--border);
  font-size: 0.82rem; color: var(--text-muted);
}
/* THREE KEYS, ONE ROW, ALWAYS. As flowing pills they wrapped the away club onto
   a line of its own on a phone — reported 31 Aug 2026 — which reads as a
   different kind of thing rather than the third of three. A grid of equal
   columns cannot wrap; a long club name gives up characters instead, which is
   the right thing to lose. */
.prob-track__keys {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  gap: 6px; margin-top: 12px;
}
.pt-key {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  padding: 6px 9px; border-radius: 999px;
  background: var(--surface-1); border: 1px solid var(--border);
  font-size: 0.78rem; color: var(--text-secondary); white-space: nowrap;
}
/* Only the name may shorten. The dot and the percentage are fixed, so a club
   with a long name loses letters rather than losing its figure. */
.pt-key__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.pt-key strong {
  flex: none; margin-left: auto; padding-left: 4px;
  color: var(--text-primary); font-variant-numeric: tabular-nums;
}
.pt-key__dot { width: 9px; height: 9px; border-radius: 3px; flex: none; }
.pt-key__dot--h { background: var(--series-home); }
.pt-key__dot--d { background: var(--text-muted); }
.pt-key__dot--a { background: var(--series-away); }
.prob-track__note {
  margin: 10px 0 0; font-size: 0.78rem; line-height: 1.55; color: var(--text-muted);
}
@media (max-width: 640px) {
  .prob-track__plot { height: 168px; }
  .prob-track--live .prob-track__plot { height: 150px; }
  .pt-key { padding: 5px 8px; font-size: 0.72rem; gap: 5px; }
  .prob-track__note { font-size: 0.74rem; }
  /* INSIDE THE LIVE CARD THE KEYS DROP THEIR PERCENTAGES, because the same three
     numbers are two inches higher up the same card, in 1.7rem digits with the fair
     odds under them. Three of them is one thing the pill cannot fit: at 360px it
     held a dot, a number and four letters of a club — "W... 20%", "La... 50%" —
     so the page spent the width on the figure it had already printed and cut the
     only thing the key is FOR, which is saying which colour is which side.
     Reported from a phone, 1 sep 2026.
     Only in .livep. On a finished match the same chart sits in the post-match
     block with no trio above it, and there the key is where those numbers are
     published. */
  .livep .pt-key strong { display: none; }
  .livep .pt-key { justify-content: center; }
  .livep .pt-key__name { white-space: normal; overflow: visible; line-height: 1.25; }
}
@media (prefers-reduced-motion: reduce) {
  .pt-now, .pt-edge::after, .pt-ball--fresh::before { animation: none; }
  .pt-ball--fresh::before, .pt-edge::after { display: none; }
}

/* ---------------- Calibration curve (/accuracy) ---------------- */
/* Square because x and y are the same quantity on the same scale: the reference
   line has to sit at a real 45 degrees or the picture flatters the model by
   aspect ratio. Sized to sit beside the table on a wide screen and above it on a
   phone. */
.cal-figure {
  margin: 14px 0 18px; padding: 0;
  display: grid; grid-template-columns: 260px 1fr; gap: 18px; align-items: center;
}
@media (max-width: 640px) { .cal-figure { grid-template-columns: 1fr; } }
.cal-chart { width: 100%; max-width: 260px; height: auto; display: block; }
.cal-figure figcaption { font-size: 0.82rem; line-height: 1.6; color: var(--text-muted); margin: 0; }
.cal-grid { stroke: var(--border); stroke-width: 1; }
/* The diagonal is a reference, not a second series — dashed and quiet so it
   cannot compete with the data drawn over it. */
.cal-ideal { stroke: var(--text-muted); stroke-width: 1.2; stroke-dasharray: 4 4; opacity: 0.7; }
.cal-line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; }
.cal-dot { fill: var(--accent); fill-opacity: 0.85; }
.cal-tick { fill: var(--text-muted); font-size: 9px; font-variant-numeric: tabular-nums; }

/* ---------------- The frozen call, beside a finished result ---------------- */
/* A match that has been played shows what the model said BEFORE it and whether
   that landed. Same three states as /results, same badges, so a reader learns
   the marks once. */
.mv { margin: 8px 0 2px; }
.mv__row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 0.78rem; color: var(--text-secondary);
}
.mv__lbl {
  text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.66rem;
  color: var(--text-muted);
}
.mv__score { font-weight: 700; font-variant-numeric: tabular-nums; }
.mv__pick { color: var(--text-muted); margin-right: auto; }
/* List rows: the same thing in the space the odds occupied before kick-off. */
.mrow__verdict { display: inline-flex; align-items: center; gap: 6px; }
.mrow__verdict small {
  display: block; text-transform: uppercase; letter-spacing: 0.05em;
  font-size: 0.6rem; color: var(--text-muted);
}
.mrow__tip { font-weight: 700; font-variant-numeric: tabular-nums; }

/* ---------------- The score grid (upcoming match pages) ---------------- */
/* The model's own arithmetic, drawn. Cells are tinted by OUTCOME — home, draw,
   away — at an opacity set per cell in --o, so the three shaded areas read as
   the 1X2 split rather than needing a colour key. */
.score-grid { margin: 18px 0 4px; }
.score-grid__title { font-size: 1rem; margin: 0 0 10px; }
.score-grid__wrap { display: inline-block; max-width: 100%; overflow-x: auto; }
.sg { border-collapse: separate; border-spacing: 3px; font-variant-numeric: tabular-nums; }
.sg th {
  font-size: 0.7rem; font-weight: 600; color: var(--text-muted);
  padding: 0 4px; text-align: center;
}
.sg-cell {
  width: 38px; height: 30px; text-align: center; border-radius: 4px;
  font-size: 0.72rem; font-weight: 700; color: var(--text-primary);
  background-color: color-mix(in srgb, var(--sg-tint) calc(var(--o) * 100%), transparent);
}
.sg-cell span { opacity: 0.9; }
.sg-cell--h { --sg-tint: var(--series-home); }
.sg-cell--d { --sg-tint: var(--text-muted); }
.sg-cell--a { --sg-tint: var(--series-away); }
.sg-axes { display: flex; justify-content: space-between; gap: 12px; margin-top: 6px; }
.sg-axis { font-size: 0.7rem; color: var(--text-muted); }
.score-grid__note {
  font-size: 0.78rem; line-height: 1.55; color: var(--text-muted);
  margin: 10px 0 0; max-width: 62ch;
}

/* ---------------- Goals against chances (finished match pages) ---------------- */
.pm-fin {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 8px 0; border-bottom: 1px solid var(--border);
}
.pm-fin:last-of-type { border-bottom: 0; }
.pm-fin__team { font-weight: 700; min-width: 9rem; }
.pm-fin__nums { font-variant-numeric: tabular-nums; color: var(--text-secondary); }
/* The verdict carries the colour, not the numbers: over- and under-performance
   are the claim, the figures are the evidence. */
.pm-fin__verdict { font-size: 0.82rem; margin-left: auto; }
.pm-fin--over .pm-fin__verdict { color: var(--status-good); }
.pm-fin--under .pm-fin__verdict { color: var(--status-bad); }
.pm-fin--level .pm-fin__verdict { color: var(--text-muted); }
.pm-fin__note {
  font-size: 0.78rem; line-height: 1.55; color: var(--text-muted);
  margin: 10px 0 0; max-width: 62ch;
}
