/* ==========================================================================
   Prediq — stylesheet

   Sections, in order. Kept accurate because h1 checks it against the section
   headers below — a list of contents that has drifted from its contents is
   worse than none, since it is trusted for exactly as long as it takes someone
   to act on it. (It had already lost "the record" before that check existed.)

   Order: Design tokens → Reset → Layout → The bar → Status strip → Match card
          → Recent form strip → Outcome rows → Readout: confidence + markets
          → Insights → Day dividers → Fixtures with no prediction → Notices
          → Loading skeleton → Method page → Ratings table → The fixture pager
          → The record → Footer → Responsive → Reduced motion
   ========================================================================== */

/* --- 1. Design tokens ---------------------------------------------------- */
:root {
  /* Palette. Near-black page, one amber accent, everything else grey.
     The amber only ever encodes data — never decoration. */
  --ink:    #0C0D10;   /* page background */
  --panel:  #14161B;   /* raised card */
  --line:   #23262E;   /* hairline dividers */
  --mute:   #6E7480;   /* labels, secondary text */
  --dim:    #9AA0AB;   /* body text on cards */
  --text:   #E8E9ED;   /* primary text */
  --warm:   #F0A22E;   /* the single accent */

  /* Result colours for the form strip, and the only hues on the site besides
     amber. Deliberately desaturated: amber means "the model's pick" and has to
     stay the loudest thing on a card, so a past win must not compete with it.
     The scoreline is legible without these — own goals are printed first, so
     the digits already say who won and the colour only agrees. */
  --win-bg:  #17351F;
  --win-fg:  #79BC8B;
  --draw-bg: #22252D;
  --draw-fg: #9AA0AB;
  --loss-bg: #37201E;
  --loss-fg: #C98A82;

  --sans: 'Space Grotesk', system-ui, -apple-system, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  --measure: 42rem;    /* single-column max width */
  --radius: 3px;       /* barely-there rounding */

  /* The fixed bar's height, named because two other rules depend on knowing it:
     the shell reserves space below it, and scroll-padding-top keeps an #anchor
     from landing underneath it. A literal in three places would drift. */
  --bar-h: 3.25rem;

  color-scheme: dark;
}

/* --- 2. Reset ------------------------------------------------------------ */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* Every in-page jump — the skip link, a footnote, a heading link — would
     otherwise land with its target hidden behind the fixed bar. */
  scroll-padding-top: calc(var(--bar-h) + 1rem);
}

body {
  margin: 0;
  background: var(--ink);
  color: var(--text);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, p, dl, dd, ul, ol, figure, figcaption {
  margin: 0;
  padding: 0;
}

ul { list-style: none; }

a {
  color: inherit;
  text-decoration: none;
}

b, strong { font-weight: 500; }

/* Keyboard focus stays visible everywhere. */
:focus-visible {
  outline: 2px solid var(--warm);
  outline-offset: 3px;
  border-radius: 2px;
}

/* Above the fixed bar, or focusing it would reveal a link hidden behind the
   bar — which is the one thing this link exists to avoid. */
.skip {
  position: fixed;
  left: -9999px;
  top: 0;
  z-index: 30;
  padding: 0.6rem 1rem;
  background: var(--warm);
  color: var(--ink);
  font: 500 13px/1 var(--mono);
}
.skip:focus { left: 0; }

/* Shared small-caps label, used for every field name on the page. */
.label {
  font: 400 10px/1 var(--mono);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--mute);
}

/* --- 3. Layout ---------------------------------------------------------- */
.shell {
  max-width: var(--measure);
  margin: 0 auto;
  /* The bar is fixed, so it occupies no space in the flow and the first thing
     on the page would sit underneath it. This reserves exactly the bar plus the
     breathing room the page had before. */
  padding: calc(var(--bar-h) + 2.25rem) 1.5rem 4rem;
}

/* --- 4. The bar --------------------------------------------------------- */
/* Fixed rather than sticky. Sticky would need an ancestor tall enough to stick
   within and would stop working the moment any ancestor gained an overflow
   value; fixed is answerable to nothing but the viewport. The cost is that it
   reserves no space, which .shell does above. */
.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 20;
  height: var(--bar-h);
  background: var(--ink);
  border-bottom: 1px solid var(--line);
}

/* Same measure and side padding as .shell, so the wordmark sits directly above
   the page content rather than a few pixels off it. */
.topbar-in {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  height: 100%;
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0 1.5rem;
}

.wordmark {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 17px;
  letter-spacing: -0.02em;
  color: var(--text);
}
.wordmark .dot { color: var(--warm); }

.nav {
  display: flex;
  align-items: center;
  /* No wrapping. The bar has a fixed height that .shell reserves space for, so
     a wrapped second row would be clipped rather than pushing the bar taller. */
  flex-wrap: nowrap;
  gap: 1.25rem;
  font: 400 13px/1 var(--mono);
  color: var(--mute);
}
.nav a {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding-bottom: 2px;
  border-bottom: 1px solid transparent;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.nav a:hover {
  color: var(--text);
  border-bottom-color: var(--warm);
}
/* The page you are on. There is no build step to share this bar, so the marker
   is written into each page's markup — and h1 asserts all four pages carry a
   byte-identical bar apart from it. */
.nav a.is-here {
  color: var(--text);
  border-bottom-color: var(--line);
}

/* Icons are Lucide (ISC), hand-inlined rather than loaded. The library is a
   JavaScript dependency that rewrites the DOM on load, and method.html
   deliberately loads no JavaScript at all — an icon set that only appears on
   three pages out of four is worse than none. Inline SVG also costs no request
   and inherits currentColor, so the hover and is-here states above need no
   separate icon rule.

   Stroke and fill are set here rather than as attributes on all sixteen copies
   of the markup. fill:none is the load-bearing one: SVG fills black by default,
   so a stroke-drawn icon that loses this rule renders as a solid blob rather
   than as nothing, which is the kind of failure that looks deliberate. */
.nav-i {
  width: 15px;
  height: 15px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* The glyph's optical centre sits slightly above the text baseline box. */
  margin-top: -1px;
}

.thesis {
  max-width: 32rem;
  color: var(--dim);
  font-size: 15px;
  line-height: 1.65;
  text-wrap: pretty;
}

/* --- 5. Status strip ---------------------------------------------------- */
.status {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex-wrap: wrap;
  margin-top: 2.75rem;
  padding: 0.85rem 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  font: 400 11px/1 var(--mono);
  letter-spacing: 0.06em;
  color: var(--mute);
  text-transform: uppercase;
}
.status b {
  font-weight: 500;
  color: var(--text);
}
.status-sep { color: var(--line); }
.status-mute { color: var(--warm); }

/* --- 6. Match card ------------------------------------------------------ */
.fixtures {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  margin-top: 1.25rem;
}

.match {
  padding: 1.5rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* Staggered entrance — --i is set per card in the markup. */
  animation: rise 0.5s cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
  animation-delay: calc(var(--i, 0) * 70ms);
}

@keyframes rise {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

.match-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 1.1rem;
  border-bottom: 1px solid var(--line);
}

.comp {
  font: 400 10px/1 var(--mono);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--mute);
}

.kick {
  font: 500 12px/1 var(--mono);
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}

/* --- 7. Recent form strip ------------------------------------------------ */
/* One line per side: team name, then five crest-and-scoreline units, most
   recent first. Both lines are left-aligned and share one label column width,
   so the crests form a column you can read down and compare — mirroring the
   away side to the right would put the two runs back to back and make them
   almost impossible to line up.

   The label column is declared once, on the row, because the two lines are
   separate elements and only a shared value keeps them in step.

   Colour here is redundant by design. Own goals print first, so "2–1" is a win
   from the digits alone; the background only agrees with them. That matters
   both for colour blindness and for keeping amber — which means "the model's
   pick" — the loudest thing on the card. */
.form-row {
  --form-label: 5.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-top: 0.9rem;
}

.form-side {
  display: grid;
  grid-template-columns: var(--form-label) minmax(0, 1fr);
  align-items: center;
  gap: 0.55rem;
}

.form-team {
  font: 400 10px/1 var(--mono);
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--mute);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.form-runs {
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
  min-width: 0;
}

.form-game {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 5px 2px 3px;
  border-radius: 2px;
  background: var(--draw-bg);
  cursor: help;
}
.form-game.is-w { background: var(--win-bg); }
.form-game.is-l { background: var(--loss-bg); }

.form-crest {
  width: 14px;
  height: 14px;
  flex: none;
  object-fit: contain;
}
/* Same footprint as a real crest, so a side ESPN gave us no images for still
   lines up with one it did. */
.form-crest.is-none {
  display: grid;
  place-items: center;
  border-radius: 2px;
  background: var(--line);
  font: 500 8px/1 var(--mono);
  color: var(--mute);
}

.form-score {
  font: 500 10px/1 var(--mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--draw-fg);
}
.form-game.is-w .form-score { color: var(--win-fg); }
.form-game.is-l .form-score { color: var(--loss-fg); }

.form-none {
  font: 400 11px/1 var(--mono);
  color: var(--mute);
}

/* --- 8. Outcome rows ---------------------------------------------------- */
.outcomes {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  margin-top: 1.25rem;
}

.outcome {
  display: grid;
  grid-template-columns: minmax(0, 7.5rem) 1fr 3.25rem;
  align-items: center;
  gap: 0.9rem;
}

.outcome-name {
  font-size: 15px;
  font-weight: 400;
  color: var(--dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.outcome-bar {
  height: 3px;
  background: var(--line);
  border-radius: 999px;
  overflow: hidden;
}
.outcome-bar > i {
  display: block;
  width: var(--w, 0%);
  height: 100%;
  background: var(--mute);
  border-radius: inherit;
  animation: fill 0.7s cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
  animation-delay: calc(var(--i, 0) * 70ms + 120ms);
}

@keyframes fill {
  from { width: 0; }
  to   { width: var(--w, 0%); }
}

.outcome-pct {
  font: 500 15px/1 var(--mono);
  color: var(--dim);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.outcome-pct em {
  font-style: normal;
  font-size: 10px;
  color: var(--mute);
}

/* The model's favoured outcome is the only place amber touches text. */
.outcome.is-pick .outcome-name { color: var(--text); font-weight: 500; }
.outcome.is-pick .outcome-pct  { color: var(--warm); }
.outcome.is-pick .outcome-bar > i { background: var(--warm); }

/* --- 9. Readout: confidence + markets ----------------------------------- */
.readout {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem;
  flex-wrap: wrap;
  margin-top: 1.4rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--line);
}

.conf {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.meter {
  display: inline-flex;
  gap: 3px;
}
.meter > i {
  width: 14px;
  height: 4px;
  background: var(--line);
  border-radius: 1px;
}
.meter > i.on { background: var(--warm); }

.conf-word {
  font: 400 11px/1 var(--mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--dim);
}

.markets {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem 1.1rem;
}
.markets > div {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
}
.markets dt {
  font: 400 10px/1 var(--mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mute);
}
.markets dd {
  font: 500 12px/1 var(--mono);
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}

/* --- 10. Insights -------------------------------------------------------- */
.insights {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}
.insights li {
  position: relative;
  padding-left: 1rem;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--dim);
  text-wrap: pretty;
}
.insights li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 4px;
  height: 1px;
  background: var(--mute);
}
.insights b {
  color: var(--text);
  font-family: var(--mono);
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
}

/* --- 11. Day dividers ---------------------------------------------------- */
/* A week across six competitions is a long list. These are the cheapest
   possible way to make it scannable: a hairline and a small-caps label. */
.day {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  margin: 1.15rem 0 -0.1rem;
  font: 400 10px/1 var(--mono);
  font-weight: 400;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--mute);
}
.day:first-child { margin-top: 0.35rem; }
.day::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

/* --- 12. Fixtures with no prediction ------------------------------------- */
/* A fixture the model refuses to price still gets a card. Hiding it would
   misrepresent what is actually on this week. */
.match.is-blank { border-style: dashed; }

.blank-teams {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-top: 1.25rem;
  font-size: 15px;
  color: var(--dim);
}
.blank-v {
  font: 400 11px/1 var(--mono);
  color: var(--mute);
}

.blank-note {
  margin-top: 0.85rem;
  font-size: 13px;
  line-height: 1.55;
  color: var(--mute);
  text-wrap: pretty;
}

/* A fixture whose prediction is still being computed — phase 1 has the match,
   phase 2 has the numbers. Deliberately NOT dashed: a dashed border is the
   site's mark for "no prediction is coming", and one is coming here. The border
   stays solid and only the note moves, which is the smallest signal that reads
   as activity rather than as a broken card. */
.match.is-pending .blank-note {
  animation: pulse 1.4s ease-in-out infinite;
}

/* --- 13. Notices --------------------------------------------------------- */
.notice {
  padding: 1.35rem 1.5rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.notice.is-warn { border-left: 2px solid var(--warm); }

.notice-title {
  font: 500 13px/1.4 var(--mono);
  color: var(--text);
}
.notice-body {
  margin-top: 0.5rem;
  max-width: 34rem;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--mute);
  text-wrap: pretty;
}

/* --- 14. Loading skeleton ------------------------------------------------ */
/* Shown only during the first load, so the page never sits blank looking
   broken. aria-hidden, because there is nothing here to read. */
.match.is-skeleton { animation: none; }

.sk {
  display: block;
  height: 9px;
  background: var(--line);
  border-radius: 2px;
  animation: pulse 1.4s ease-in-out infinite;
}
.sk-xs  { width: 2.5rem; }
.sk-sm  { width: 6rem; }
.sk-md  { width: 100%; }
.sk-bar { height: 3px; border-radius: 999px; }

@keyframes pulse {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 0.22; }
}

/* --- 15. Method page ----------------------------------------------------- */
/* Prose in the same voice as the cards: one narrow column, small-caps section
   labels, and dense two-column rows for anything numeric. No diagrams — a
   labelled row says the same thing in a tenth of the height. */
.page-title {
  margin-top: 2.75rem;
  font-size: 22px;
  font-weight: 500;
  letter-spacing: -0.015em;
}

.sec { margin-top: 2.5rem; }

.sec-h {
  font: 400 11px/1 var(--mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--warm);
}

.copy {
  max-width: 36rem;
  margin-top: 0.9rem;
  color: var(--dim);
  font-size: 14.5px;
  line-height: 1.7;
  text-wrap: pretty;
}
.copy + .copy { margin-top: 0.8rem; }
.copy b { color: var(--text); }
.copy code {
  font: 400 13px/1 var(--mono);
  color: var(--text);
}

/* Links inside prose. Styled on the element rather than behind a class, so a
   link added to any page later cannot silently arrive unstyled. */
.copy a {
  color: var(--text);
  border-bottom: 1px solid var(--line);
  transition: border-color 0.15s ease;
}
.copy a:hover { border-bottom-color: var(--warm); }

/* Numbered build steps. The numeral is mono and amber-free — it is a label,
   not data. list-style is killed here rather than in the reset, because this
   list draws its own numeral in ::before and would otherwise show two. A plain
   <ol> added later should still number itself. */
.steps {
  margin-top: 1.1rem;
  list-style: none;
  counter-reset: step;
}
.steps > li {
  position: relative;
  padding-left: 2.1rem;
  counter-increment: step;
}
.steps > li + li { margin-top: 1.1rem; }
.steps > li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0.15em;
  font: 500 11px/1.4 var(--mono);
  color: var(--mute);
}
.step-h {
  font-size: 14.5px;
  font-weight: 500;
  color: var(--text);
}
.steps .copy { margin-top: 0.35rem; }

/* Label → value rows. The densest honest way to publish a parameter list. */
.spec { margin-top: 1.1rem; }
.spec > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--line);
}
.spec > div:first-child { border-top: 1px solid var(--line); }
.spec dt {
  font-size: 13.5px;
  color: var(--dim);
}
.spec dd {
  flex: none;
  font: 500 12.5px/1.4 var(--mono);
  color: var(--text);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.spec dd .unit {
  color: var(--mute);
  font-weight: 400;
}

/* The limits list, where every line is something Prediq does NOT know. */
.limits { margin-top: 1.1rem; }
.limits li {
  position: relative;
  padding-left: 1.15rem;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--dim);
  text-wrap: pretty;
}
.limits li + li { margin-top: 0.4rem; }
.limits li::before {
  content: "×";
  position: absolute;
  left: 0;
  top: 0;
  font: 400 12px/1.75 var(--mono);
  color: var(--mute);
}
.limits b { color: var(--text); }

/* --- 16. Ratings table --------------------------------------------------- */
/* A real table, because this is real tabular data. Every width is fixed, so
   the league tables stacked down the page share one grid rather than each
   finding its own and making the eye re-learn the layout six times. Everything
   numeric is mono and tabular-nums: a rating column is only readable if the
   digits sit above each other. */
.rt {
  width: 100%;
  margin-top: 1.25rem;
  border-collapse: collapse;
  table-layout: fixed;
}
.rt + .rt { margin-top: 2.5rem; }

/* The caption carries the league name and what the fit was built from. Stating
   the match count beside the ratings is the point: a rating is only as good as
   the evidence under it, and here the evidence is named. */
.rt-cap {
  caption-side: top;
  text-align: left;
  padding-bottom: 0.65rem;
}
.rt-cap-name {
  display: block;
  font-size: 14.5px;
  font-weight: 500;
  color: var(--text);
}
.rt-cap-meta {
  display: block;
  margin-top: 0.2rem;
  font: 400 10px/1.5 var(--mono);
  letter-spacing: 0.05em;
  color: var(--mute);
  font-variant-numeric: tabular-nums;
}

.rt-h {
  padding: 0 0 0.5rem;
  border-bottom: 1px solid var(--line);
  font: 400 10px/1 var(--mono);
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--mute);
  text-align: right;
  white-space: nowrap;
}

/* A sortable header is a real button, so it is reachable by keyboard and
   announced as pressable. It is then styled back down to look like the header
   text it sits inside — the affordance is the hover, not a box. */
.rt-sort {
  margin: 0;
  padding: 0;
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  transition: color 0.15s ease;
}
.rt-sort:hover { color: var(--text); }
.rt-sort.is-on { color: var(--warm); }

/* aria-sort tells assistive software which way the table runs. This is the same
   fact drawn for everyone else. */
.rt-caret { margin-left: 0.3em; }

.rt-row { border-bottom: 1px solid var(--line); }
.rt-row:hover { background: var(--panel); }
.rt-row > * {
  padding: 0.5rem 0;
  vertical-align: baseline;
}

/* Column geometry, declared once and shared by the header and the body cells
   under the same class. Team takes whatever is left. */
.rt-rank    { width: 2.1rem; text-align: left; }
.rt-team    { text-align: left; font-weight: 400; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rt-pl      { width: 2.8rem; }
.rt-attack  { width: 3.4rem; }
.rt-defence { width: 3.4rem; }
.rt-xpts    { width: 3.8rem; }

.rt-name {
  font-size: 14px;
  color: var(--text);
}
/* Division tag, cross-league table only, where a hundred names in one list is
   otherwise impossible to place. */
.rt-abbr {
  margin-left: 0.5rem;
  font: 400 9px/1 var(--mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--mute);
}

/* Figures. Scoped under .rt-row so the header keeps its own small-caps type. */
.rt-row .rt-rank,
.rt-row .rt-pl,
.rt-row .rt-attack,
.rt-row .rt-defence,
.rt-row .rt-xpts {
  font: 400 12.5px/1.4 var(--mono);
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}
.rt-row .rt-rank { font-size: 11px; color: var(--mute); }
.rt-row .rt-pl   { color: var(--mute); }
.rt-row .rt-xpts { font-weight: 500; color: var(--text); }

/* The leader. Amber encodes data here as everywhere else — the datum is
   "first in the current sort". */
.rt-row.is-top .rt-xpts { color: var(--warm); }

/* Under the match threshold a rating still rests mostly on last season. The
   row is dimmed rather than hidden or footnoted, because the figure is not
   wrong — it is a weaker claim, and the two should not look identical. This
   rule deliberately follows is-top: a leader with four matches played gets the
   dimming, not the amber. */
.rt-row.is-thin .rt-name { color: var(--dim); }
.rt-row.is-thin .rt-pl,
.rt-row.is-thin .rt-attack,
.rt-row.is-thin .rt-defence,
.rt-row.is-thin .rt-xpts { color: var(--mute); }

.rt.is-skeleton .rt-row:hover { background: none; }

/* The scale switch. Amber is reserved for data, so the selected side is marked
   by weight and a raised background rather than by the accent.

   The frame is shared with the fixture pager in section 17: one hairline border,
   square-cornered children clipped by the parent's radius. Grouped here rather
   than written out twice — two near-identical blocks are how a border ends up
   1px on one control and 2px on the other, and they are meant to read as the
   same kind of thing. Spacing is not shared: each sits under something
   different, so each sets its own margin below. */
.toggle,
.pager {
  display: inline-flex;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.toggle { margin-top: 1.75rem; }
.toggle-b {
  margin: 0;
  padding: 0.45rem 0.9rem;
  background: none;
  border: 0;
  color: var(--mute);
  font: 400 11px/1 var(--mono);
  letter-spacing: 0.06em;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}
.toggle-b + .toggle-b { border-left: 1px solid var(--line); }
.toggle-b:hover { color: var(--text); }
.toggle-b.is-on {
  color: var(--text);
  background: var(--line);
  font-weight: 500;
  cursor: default;
}

/* Seven day tabs have to fit a 42rem page, so they are narrower than the
   two-item scale switch: less horizontal padding, and the weekday drops out
   under 34rem (see section 20).

   Named for what they are rather than for the page they started on: the fixtures
   list and the record share one strip, and a class called .lg-tab on the
   fixtures page would send the next reader of this file to the wrong section. */
.daytab {
  display: flex;
  align-items: baseline;
  gap: 0.35em;
  padding: 0.45rem 0.7rem;
}
.daytab-d { font-variant-numeric: tabular-nums; }

/* The strip belongs to the status bar it sits under, not to the list below it.
   .toggle's own 1.75rem was set for a switch under a page heading and leaves it
   floating between the two here. :not(:empty) matters: with no JavaScript the
   container stays empty, and an empty div with a margin would push the fixture
   list down for exactly the readers who got no tabs. */
#days:not(:empty) { margin-top: 1.15rem; }

#days .toggle {
  margin-top: 0;
  /* Scroll rather than clip. .toggle hides its overflow to keep the rounded
     corners clean, which is right for a two-item switch but wrong for eight days
     on a 360px phone: the last day would be cut off with no way to reach it.
     Overflowing sideways is visible and recoverable; being clipped is neither.
     At any width where the strip fits, `auto` shows nothing. */
  max-width: 100%;
  overflow-x: auto;
}

/* Today is the default view on the fixtures page and the day most readers of the
   record want first, so it is marked in the strip itself rather than left to be
   inferred from the heading below. Weight, not colour — amber stays reserved for
   data, and the selected tab already owns the raised background. */
.daytab.is-today .daytab-wd { color: var(--dim); font-weight: 500; }
.daytab.is-on.is-today .daytab-wd { color: var(--text); }

/* --- 17. The fixture pager -----------------------------------------------
   Under the last card of the page: a back chevron, a count, a forward chevron.

   The frame is the scale switch's, grouped with it in section 16 rather than
   restated here. Deliberately, and not out of laziness: the day strip and the
   pager are the only two controls on the fixtures page, they are the same kind
   of thing — pick which matches to look at — and giving the second one a look of
   its own would imply it did something different in kind.

   Left-aligned, sharing the strip's left edge rather than centred under the
   list. The two controls then line up vertically with each other, one at the top
   of the list and one at the bottom, which is a true description of what they
   are. A centred pager would be the only centred element on the site.
--------------------------------------------------------------------------- */
.pager {
  /* `align-self`, and it is not optional. .fixtures is a column flex container,
     which blockifies its children: the inherited `inline-flex` alone is ignored
     there and the pill stretches the full width of the page, hairline border and
     all, with the count marooned beside a chevron at the far left. Shrink to fit
     and sit at the leading edge. */
  align-self: flex-start;
  align-items: stretch;   /* so the label's dividers run the full height */
  /* Small because it is not the whole gap: .fixtures already puts 1.25rem
     between its children, so this reads as 2.25rem under the last card. */
  margin-top: 1rem;
}

/* Mostly .toggle-b, but not shared with it: that button holds mono text and this
   one holds a 15px drawing, so the padding, the typography and the disabled
   state all differ. Only the frame above is genuinely the same. */
.pager-b {
  display: flex;
  align-items: center;
  margin: 0;
  padding: 0.45rem 0.85rem;
  background: none;
  border: 0;
  color: var(--mute);
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}
.pager-b:hover:not(:disabled) { color: var(--text); background: var(--line); }

/* The end of the list is shown, not removed. A chevron that vanishes on the last
   page changes the pill's width and slides the count sideways, which reads as
   the control having broken rather than as having run out of matches. Disabled
   is also the accessible answer: the button keeps its place and announces itself
   as unavailable instead of disappearing from under the reader's cursor.

   Receding to exactly the border colour is the point — an arrow that has become
   part of the frame is inert at a glance. It fails colour contrast against the
   page, which is correct here and should not be "fixed": WCAG exempts inactive
   controls, and the whole job of this state is to not look pressable. */
.pager-b:disabled { color: var(--line); cursor: default; }

/* Geometry comes from dom.js; everything presentational is here, because these
   are CSS properties on SVG and not attributes. `fill: none` is load-bearing —
   a path fills black by default, so without it each chevron renders as a solid
   wedge rather than a stroked arrow. */
.pager-i {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* "6–10 of 23". The count is why the pager can be trusted: it says how much list
   there is, which the endlessly scrolling version never did.
   Tabular figures, and after the font shorthand rather than before it — `font`
   resets font-variant-numeric, so the other order silently drops them and the
   pill twitches by a pixel or two on every press. */
.pager-n {
  display: flex;
  align-items: center;
  padding: 0 0.9rem;
  border-left: 1px solid var(--line);
  border-right: 1px solid var(--line);
  color: var(--mute);
  font: 400 11px/1 var(--mono);
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
}

/* --- 18. The record ------------------------------------------------------
   One row per match: what was claimed on the left, what happened on the right.
   Built as a grid rather than a table because the cells stack two deep and a
   table cell holding its own two-line block fights the row's baseline.

   Deliberately quiet. The only colour is the verdict mark, and the row itself
   is not tinted green or red: a page of eighty rows washed in green would read
   as promotion rather than as a record, and the same page in red would be no
   more honest for being unflattering. Amber stays reserved for data, as
   everywhere else.
------------------------------------------------------------------------------*/

/* The day's counts, above the rows. Its own line so the numbers are the first
   thing read, before the eye starts down the list. */
.lg-tally {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 1.75rem;
  font: 400 11.5px/1.5 var(--mono);
  letter-spacing: 0.04em;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}
.lg-tally b { color: var(--text); font-weight: 500; }
.lg-tally-sep { color: var(--line); }
.lg-tally-mute { color: var(--mute); }

/* Column geometry, declared once and shared by the header and every row. The
   fixture takes whatever is left, which at 42rem is about 21rem — enough for
   two full club names on one line. */
.lg-h,
.lg-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 5.2rem 3.6rem 6.4rem;
  gap: 0.85rem;
  align-items: baseline;
}

.lg-h {
  margin-top: 1.25rem;
  padding: 0 0 0.5rem;
  border-bottom: 1px solid var(--line);
  font: 400 10px/1 var(--mono);
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--mute);
}
/* Each header label is named so that one of them can be dropped on a narrow
   screen, and none of them may wrap: a two-word label wrapping inside a 3.6rem
   column would double the header's height and pull it out of line with the rows
   beneath. This is why the labels are short — "result" rather than "full time". */
.lg-h-fixture,
.lg-h-said,
.lg-h-happened,
.lg-h-verdict { white-space: nowrap; }

.lg-row {
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--line);
}
.lg-row:hover { background: var(--panel); }

/* The list ends well clear of the paragraph that follows it, which otherwise
   reads as a caption on the last row rather than as prose about the page. */
.lg-rows { margin-bottom: 1.75rem; }

/* --- the fixture ---
   min-width:0 on the cell as well as on the names inside it. A grid track sized
   `minmax(0, 1fr)` still gives its item an automatic minimum content size, so
   without this the long-name truncation below never fires — the cell grows and
   pushes the other three columns off instead. */
.lg-fixture { min-width: 0; }

.lg-teams {
  display: flex;
  align-items: baseline;
  gap: 0.4em;
  min-width: 0;
  font-size: 13.5px;
  color: var(--text);
}
/* Either name may be the long one, so both shrink and both truncate rather than
   one pushing the other out of the row. */
.lg-team {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lg-v { flex: none; color: var(--mute); font-size: 11px; }

.lg-meta {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-top: 0.15rem;
  font: 400 10px/1.4 var(--mono);
  letter-spacing: 0.05em;
  color: var(--mute);
}
.lg-comp {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lg-kick { flex: none; font-variant-numeric: tabular-nums; }

/* --- what we said ---
   Two rows inside the cell: the call and its probability side by side, then the
   likeliest scoreline beneath. The scoreline spans both columns so it starts at
   the cell's left edge rather than under the percentage. */
.lg-said {
  display: grid;
  grid-template-columns: auto auto;
  justify-content: start;
  column-gap: 0.4rem;
  row-gap: 0.15rem;
  font: 400 11.5px/1.4 var(--mono);
  letter-spacing: 0.03em;
  color: var(--dim);
}
.lg-pick { color: var(--text); }
.lg-prob { color: var(--dim); font-variant-numeric: tabular-nums; }
.lg-score-said {
  grid-column: 1 / -1;
  color: var(--mute);
  font-variant-numeric: tabular-nums;
}

/* --- what happened --- */
.lg-happened {
  font: 400 11px/1.4 var(--mono);
  letter-spacing: 0.03em;
  color: var(--mute);
}
.lg-ft {
  display: block;
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.lg-outcome { display: block; margin-top: 0.15rem; }
.lg-pending { color: var(--mute); }

/* --- the verdict ---
   The mark sits in its own column so that "gave it 24%" and the flag beneath it
   line up down the page, which is what makes the column scannable. */
.lg-verdict {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 0.4rem;
  row-gap: 0.15rem;
  font: 400 10.5px/1.4 var(--mono);
  letter-spacing: 0.04em;
  color: var(--mute);
}
.lg-mark {
  grid-row: 1 / span 2;
  align-self: start;
  font-size: 13px;
  line-height: 1.15;
}
.lg-assigned {
  align-self: baseline;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}
.lg-flag { grid-column: 2; color: var(--mute); }

/* The one place colour appears. Green and red are the same tokens the form strip
   uses for a win and a loss, so a reader who has seen one page already knows
   what they mean here. */
.lg-row.is-hit  .lg-mark { color: var(--win-fg); }
.lg-row.is-miss .lg-mark { color: var(--loss-fg); }
.lg-row.is-open .lg-mark { color: var(--line); }

/* A recorded prediction with no result yet. Dimmed, not hidden: these are the
   rows that cannot flatter the model, and dropping them would leave a record
   made only of matches whose outcome is already known. */
.lg-row.is-open .lg-teams { color: var(--dim); }

.lg-row.is-skeleton { pointer-events: none; }
.lg-row.is-skeleton:hover { background: none; }

/* --- 19. Footer --------------------------------------------------------- */
.foot {
  margin-top: 3.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
}

.foot-note {
  max-width: 30rem;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--mute);
  text-wrap: pretty;
}

.foot-meta {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 1.5rem;
  font: 400 11px/1 var(--mono);
  letter-spacing: 0.05em;
  color: var(--mute);
}
.foot-meta a {
  padding-bottom: 2px;
  border-bottom: 1px solid var(--line);
  transition: color 0.15s ease, border-color 0.15s ease;
}
.foot-meta a:hover {
  color: var(--text);
  border-bottom-color: var(--warm);
}

/* --- 20. Responsive ----------------------------------------------------- */
@media (max-width: 34rem) {
  .shell { padding: calc(var(--bar-h) + 1.5rem) 1.15rem 3rem; }
  .topbar-in { padding: 0 1.15rem; }

  /* Wordmark plus four icon-and-label pairs needs about 26rem of the 20rem a
     320px phone has. The labels go and the icons carry the nav on their own —
     which is the whole reason the nav has icons rather than only words.
     Hidden by clipping rather than display:none, deliberately: the icon is
     aria-hidden, so the label IS the link's accessible name, and removing it
     from the accessibility tree would leave four links that announce as
     nothing but their href. h1 asserts this rule never becomes display:none. */
  .nav { gap: 1.15rem; }
  .nav-t {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .nav-i { width: 17px; height: 17px; }

  .match { padding: 1.15rem; }
  .notice { padding: 1.15rem; }

  .outcome {
    grid-template-columns: minmax(0, 5.5rem) 1fr 3rem;
    gap: 0.7rem;
  }
  .outcome-name { font-size: 14px; }

  .readout { gap: 0.9rem; }
  .markets { gap: 0.4rem 0.85rem; }

  /* A label column plus five crest-and-score units will not fit on one 320px
     line, so the name moves above its own strip. That costs two lines per card
     and keeps the crests, which are the part carrying information. The gap
     grows at the same time, because the two sides now need to read as two
     blocks rather than four loose lines. */
  .form-row { gap: 0.7rem; }
  .form-side {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 0.25rem;
  }
  .form-game { gap: 3px; padding: 2px 4px 2px 2px; }
  .form-crest { width: 13px; height: 13px; }
  .form-score { font-size: 9px; }

  /* A long parameter name and its value stop competing for one line. */
  .spec > div { flex-wrap: wrap; gap: 0.15rem 1rem; }
  .spec dd { text-align: left; }

  /* Six columns will not fit at 320px, so the rank goes. It is the only column
     that carries nothing the order of the rows does not already say. */
  .rt-rank { display: none; }
  .rt-pl      { width: 2.4rem; }
  .rt-attack  { width: 3rem; }
  .rt-defence { width: 3rem; }
  .rt-xpts    { width: 3.3rem; }
  .rt-row .rt-pl,
  .rt-row .rt-attack,
  .rt-row .rt-defence,
  .rt-row .rt-xpts { font-size: 12px; }
  .rt-name { font-size: 13.5px; }
  .rt + .rt { margin-top: 2rem; }
  .toggle-b { padding: 0.45rem 0.7rem; }

  /* Seven tabs with a weekday each need about 34rem of tab, which is the whole
     screen. The weekday goes and the day number stays, so all seven days remain
     on screen and reachable — the alternative was a scrolling strip with two
     days hidden off the right edge and nothing to say they were there. The full
     date is still on each button's title. */
  .daytab-wd { display: none; }
  .daytab { padding: 0.45rem 0.55rem; }

  /* Except on today's tab, where the weekday span holds the word "Today" and
     there is no day number beside it — hiding it would leave an empty button.
     This rule has to follow the one above to win, since both are one class. */
  .daytab.is-today .daytab-wd { display: inline; }

  /* The verdict column is the first to go. "gave it 24%" is the one figure on
     this page that has nowhere else to live, so the row keeps three columns and
     the verdict moves under the fixture, indented to the fixture's edge and
     spanning to the row's. The mark stays inline with it rather than becoming a
     fourth stacked line. */
  .lg-h,
  .lg-row {
    grid-template-columns: minmax(0, 1fr) 4.6rem 3.2rem;
    gap: 0.6rem;
  }
  .lg-h-verdict { display: none; }
  .lg-verdict {
    grid-column: 1 / -1;
    grid-template-columns: auto auto minmax(0, 1fr);
    margin-top: 0.35rem;
  }
  .lg-mark { grid-row: 1; }
  .lg-flag { grid-column: 3; }
  .lg-teams { font-size: 13px; }
  .lg-said { font-size: 11px; }
}

/* --- 21. Reduced motion ------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  /* This list is explicit rather than a blanket `* { animation: none }`, so
     every new animation has to be added here by hand. Adding one and forgetting
     this block is the failure mode — the hygiene check greps for it. */
  .match,
  .outcome-bar > i,
  .match.is-pending .blank-note,
  .sk {
    animation: none;
  }
  .outcome-bar > i { width: var(--w, 0%); }
  * { transition-duration: 0.01ms !important; }
}
