/* WOFF2 only. The TTFs were the sole format until 2026-10-02, when WOFF2
   landed alongside them: uncompressed, the three together were 960 KB against
   328 KB, the biggest single thing a first-time visitor downloads. The TTF
   fallback that came with that change is gone as of 2026-10-09, because the
   TTFs stopped being published on 2026-10-06 and the fallback URL had been a
   404 ever since - which is precisely what this comment used to warn against
   ("drop the TTFs and that second line together, never apart"). They were
   dropped apart; this closes it. Every browser that can run these games has
   supported WOFF2 since 2016, and the files still live in assets/fonts as the
   source for tools/subset_fonts.py - they are just not shipped. */
@font-face {
  font-family: "Anton";
  src: url("../assets/fonts/Anton-Regular-v3.woff2") format("woff2");
  font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("../assets/fonts/Inter-Regular-v3.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("../assets/fonts/Inter-Bold-v3.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}

/* Same design system as quizebra.com/geography (geoquiz/css/style.css):
   every shared component below was COPIED from that file and had only
   --accent-rgb/--accent-dim swapped, to this project's own #ff6172 (2026-10-10, after #fc674a, coral #ffa08a and the football blue #7ec6ff)
   in place of geography's mint (#6ee7b7). Since 2026-09-29 every rgba()
   tint derives from --accent-rgb, so no tint literals remain to swap.
   Keep doing it that way. Never hand-re-derive a rule from memory - see
   chat, 2026-09-10: an earlier hand-ported version silently drifted (missing
   the narrow start-panel card width, the game-mode header shrink, the hub's
   375-511px responsive tier, and more), which is exactly what copying +
   a mechanical color swap avoids.

   How to diff the two (corrected 2026-09-28 - this comment used to claim the
   two were "byte-for-byte identical below". They are not, and trusting that
   cost real review time. Do not restore that claim):
   - Diff by RULE, never by line range. Geography interleaves its shared
     rules with its own game sections, so the same rule sits at a wildly
     different offset in each file - .your-rank-note is around line 586 here
     and around 1386 there.
   - Expect more genuine divergence than you would guess. A comment-stripped,
     accent-normalised comparison on 2026-09-28 found ~205 rules shared by
     selector, of which ~26 differed in body. Some of that is deliberate
     per-game re-fitting (.ccard, .pair-bucket, .comparison-*, .arrange-card);
     some is real unreconciled drift (.reveal-row.reveal-correct's colour,
     the .result-time-label padding-top, the .timer-* and
     .gate-panel bodies). Treat any body difference as unexplained until you
     have worked out which kind it is.
   - The two are not symmetric. Geography carries rules this file has no use
     for - .card.hub-card.topic-card and six
     .hub-title-*-fb variants its own portal page uses to show FOOTBALL
     cards. Both files do set .card.hub-card's background; geography just has
     extra overrides layered on top.
   - A mechanical colour swap does NOT catch the accent baked into the
     .hub-title-arrow-* data-URI SVGs further down (%23ff6172, URL-encoded) -
     those have to be edited by hand. */
:root {
  /* The page background, and now actually equal to it. body was moved to a
     literal #000000 on 2026-09-14 while this token stayed at #0f0f0f, so the
     one thing named after the background was the one colour that was not it.
     Both remaining users (.gate-panel, .gt-search-input) are overridden in
     the game scopes below, so this is a no-op on every football page - it
     just stops the name lying, and gives the "punched through to the page"
     surfaces a token to say so with. */
  --bg: #000000;
  /* The card, and the two surfaces above it (--tile below, --control further
     down), at the values this pass settled on. They lived in two scoped blocks
     - the hub by its card grid, a game by its play area - while the pass was
     still game-by-game, which left any page in NEITHER scope on the old
     lighter set (#161616/#1e1e1e/#2a2a2a). Every football page is in one of
     the two, but geography's contact and privacy pages were not, and painted a
     card grey used nowhere else on either site. Stated once here instead
     (2026-09-30): one definition, nothing to fall outside of.

     --panel-edge deliberately does NOT move with them. With the fills darker
     and the borders where they were, the borders carry more of the structure
     and everything reads MORE defined, not less. */
  --panel: #101010;
  --panel-edge: #2a2a2a;
  /* A raised tile INSIDE a .card, always paired with a var(--panel-edge)
     border and a radius - .ccard, .pair-bucket.filled, .duel-card,
     .recap-entry, .gt-club-card, .gt-history-row, .arrange-card,
     .arrange-arrow, .bt-candidate, .bt-pitch-card. Lighter than --panel
     on purpose. */
  --tile: #171717;

  /* --accent-rgb is the SINGLE SOURCE for the accent. --accent and every
     rgba() tint are derived from it, so change THIS line, not --accent.
     It does NOT reach the data-URI SVG chevrons further down, where the
     accent is URL-encoded (%23ff6172) - a CSS variable cannot be
     substituted inside a url(). Grep %23 when changing the accent. */
  --accent-rgb: 255, 97, 114; /* #FF6172 (owner, 2026-10-10, after page-only trials of #FF505F; before it #FC674A, coral #ffa08a, the football blue) */
  --accent: rgb(var(--accent-rgb));
  --accent-dim: #732c33;
  /* Selected / targetable surfaces: .ccard.targetable,
     .pair-bucket.targetable, .pair-bucket.selected, .gt-history-row.active */
  --accent-wash: rgba(var(--accent-rgb), 0.08);
  /* Text sitting ON an accent-filled surface. Pure black rather than the
     #0a0f0c it used to be: that was a fourth near-black belonging to no
     other part of the design, and black reads slightly better on the
     accent (11.41:1) besides being a colour the site already uses. */
  --on-accent: #000000;

  --good: #6ee7b7;
  /* Geography's own accent, for a cross-topic link back to that side of the
     site (2026-10-01). The mirror of geoquiz/css/style.css's --accent-football.
     Same value as --good by coincidence, not by meaning - do not merge them. */
  --accent-geography: #6ee7b7;
  /* Football's accent, for the hub's cross-link to that site (owner, 2026-10-10).
     Same value as the football site's own --accent. */
  --accent-football: #7ec6ff;
  /* #ff496e, not the #ff4d5e this was (direct instruction, 2026-09-29).
     The site ran two reds six degrees of hue apart: this token at 354, and a
     hardcoded #ff496e at 348 that began life as Find the Country's
     wrong-guess colour on the geography side, spread to Order the Country's
     reveal, and came here with Order the Player when that game was ported.
     Picking the pinker one collapses them into a single red rather than
     leaving one rule permanently off-palette. */
  --bad: #ff496e;
  --text: #e7e7e7; /* was #f4f4f4 - see --text-soft below */
  /* Was #cfcfcf. The site ran three text greys; Guess the Player's pass
     showed two carry the whole job - one bright (--text), one quiet - and
     the middle one only ever blurred the distinction. Set to --text-dim's
     value so every existing use follows without hunting all 20 of them;
     the token itself should be retired once those are rewritten. */
  --text-soft: #999999;
  --text-dim: #999999;

  /* Neutral control fill - .btn-secondary, #next-btn, .practice-back-link,
     #submit-btn.not-ready, .gt-guess-btn:disabled. Same value as --panel-edge today, but a
     different role: that one is for borders, this one for fills. */
  --control: #202020;
  --control-hover: #333333;
  --hairline: rgba(255, 255, 255, 0.1); /* footer / SEO dividers */
  color-scheme: dark;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

/* Hides the ENTIRE page instantly when arriving via a hub card's own
   ?autostart=1 link, so nothing flashes before quiz.js's async init() gets
   a chance to decide what to show itself (that has to wait on a data fetch
   first). visibility (not display) so .wrap keeps its normal layout box -
   no reflow/scroll jump when it reappears. The .autostarting class is set
   synchronously by a tiny inline <script> in each game's own <head>, before
   first paint, and removed by quiz.js's own init() in the same synchronous
   pass as the .hidden toggles that follow it (never across an await), so
   whatever becomes visible when this class is dropped is already the
   final, correct state - not just the intro screen specifically (an
   earlier version of this rule only targeted #start-panel, which still let
   the CARD's own narrower "intro" width rule apply during the gap, since
   that rule keys off the hidden ATTRIBUTE, not display - a visible width
   jump right as the intro was replaced; found via review, 2026-09-13).
   Same fix applied to geoquiz's own style.css. */
html.autostarting .wrap { visibility: hidden; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Inter", system-ui, sans-serif;
  min-height: 100vh; /* fallback for browsers without dvh */
  min-height: 100dvh; /* mobile address-bar-safe viewport height */
  display: flex;
  justify-content: center;
  padding: 40px 16px 32px;
}

.wrap { width: 100%; max-width: 480px; }

.site-header { text-align: center; margin-bottom: 29px; }
.site-logo-mark {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 0 auto 10px;
  text-decoration: none; /* it's an <a> on the hub page (2026-09-15, links home) -
    no visible text inside to underline anyway, but explicit rather than relying
    on that - the portal's own logo stays a plain <div>, no link-to-self needed */
}
/* 60px, not the 61.425px this was fitted to. The old value was measured
   against something rather than chosen, and 1.425px of it was never
   doing any work. */
.site-logo-icon { display: block; width: 60px; height: auto; }
/* The inline letter-spacing every "DAILY CHALLENGES" kicker ships with (3.6419px)
   overrides the em value below. It began life as a FITTED number - wide enough
   that the kicker rendered exactly as wide as the title above it - and this
   site kept an older 4.2759px long after geography re-fitted to 3.6419px on
   2026-09-30, so the two sites spaced the same two words differently. The
   owner asked for them to match (direct instruction, 2026-10-09), so it is now
   a shared CONSTANT rather than a per-page fit: geography already used the one
   value on every page regardless of its title, and football's seven pages now
   do too. Change it on both sites together, or they drift again. */
.kicker {
  display: inline-block;
  font: 700 12px/1 "Inter", sans-serif;
  letter-spacing: 0.28em;
  color: var(--accent);
  margin-bottom: 6px;
}
/* "See all games" - a way back to the portal (2026-09-15, direct instruction:
   the hub page had NO link back to "/" at all before this - only the site
   logo above, now also made a link, see .site-logo-mark's own comment).
   Sits below the game grid, above the divider/intro paragraph below it.

   THIS page's accent, not the other topic's (direct instruction,
   2026-09-29). It used to be geography's mint, as a nod to the topic you
   were one click from - a nice idea, but it made the one link on the page
   the only thing outside this site's own palette, and a reader has no way
   to know the colour is a hint rather than a slip. Geography's own copy of
   this rule flipped the same way, to its mint, so each hub now speaks in
   one accent. */
.hub-see-all {
  display: block;
  text-align: center;
  margin-top: 24px;
  color: var(--accent);
  font: 700 13px/1 "Inter", sans-serif;
  text-decoration: none;
}
.hub-see-all:hover { text-decoration: underline; }


/* .hub-intro is gone (2026-10-01): the hub's intro paragraph now sits inside a
   <details class="game-seo"> like every game page's prose, which already
   carried the same quiet grey, the same size, the same hairline divider and
   the same left alignment. Geography's own stylesheet took the same change. */


/* Site-wide footer - see geoquiz/css/style.css's own comment on this rule
   (byte-for-byte identical here, same shared-component convention as the
   rest of this file). */
.site-footer {
  margin-top: 24px;
  padding-top: 20px;
  border-top: 1px solid var(--hairline);
  text-align: center;
  font: 400 12px/1.6 "Inter", sans-serif;
  color: var(--text-dim);
}
/* The links sit at --text, a step above the footer's own --text-dim, so the
   separators stay quiet and the clickable words are what carry the row.
   Direct instruction, 2026-10-03. */
.site-footer a { color: var(--text); text-decoration: none; }
.site-footer a:hover { text-decoration: underline; }
/* Both YouTube links read just "YouTube" - the accent IS the label, the only
   thing saying which channel. */
.site-footer a.yt-geo { color: var(--accent-geography); }
.site-footer a.yt-fb { color: var(--accent); }
/* The chess channel (owner, 2026-10-16): this site's own accent. */
.site-footer a.yt-chess { color: var(--accent); }
/* HUB footers, which carry a single channel: hover swaps the two roles, the
   plain links taking the accent and the YouTube link taking the grey.
   Direct instruction, 2026-10-03. */
.site-footer a:hover { color: var(--accent); }
.site-footer a.yt-geo:hover, .site-footer a.yt-fb:hover, .site-footer a.yt-chess:hover { color: var(--text); }
/* CROSS-TOPIC footers - the portal, Contact and Privacy, the three pages that
   show BOTH channels. There, hover only underlines and nothing changes colour:
   with two links both reading "YouTube", recolouring either on hover would
   erase the one cue telling them apart. Keyed on the footer actually holding
   both links rather than on a page class, so a footer that gains or loses a
   channel picks up the right behaviour on its own. (Inert in this file today -
   no football page shows both - but kept in step with geoquiz/css/style.css so
   the two cannot drift.) */
.site-footer:has(.yt-geo):has(.yt-fb) a:hover { color: var(--text); }
.site-footer:has(.yt-geo):has(.yt-fb) a.yt-geo:hover { color: var(--accent-geography); }
.site-footer:has(.yt-geo):has(.yt-fb) a.yt-fb:hover { color: var(--accent); }
.site-footer .footer-sep { margin: 0 6px; color: var(--text-dim); }
.site-header h1 {
  font: 400 40px/1 "Anton", sans-serif; /* was 42.29px, fitted to .start-title's rendered width */
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin: 0;
  color: var(--text-soft);
}
/* Game-mode-only overrides (while #play-area is visible, i.e. after "Start
   Playing") - must not touch the intro's fitted sizing/spacing. */
.wrap:has(#play-area:not([hidden])) .site-logo-icon { width: 42px; } /* 70% of 60px, top stays put since only height shrinks */
.wrap:has(#play-area:not([hidden])) .site-logo-mark { margin: -18px auto 4px; } /* title sits closer to the logo, whole thing shifted up */
.wrap:has(#play-area:not([hidden])) .site-header h1 { font-size: 32px; margin-top: 8px; } /* matches .result-title's size */
.wrap:has(#play-area:not([hidden])) .site-header { margin-bottom: 15px; } /* brings the card closer beneath the title */

.card {
  background: var(--panel);
  border: 1px solid var(--panel-edge);
  border-radius: 20px;
  padding: 10px 24px 24px;
  box-shadow: 0 20px 60px -20px rgba(0, 0, 0, 0.6);
}
/* Narrower, content-hugging width only while the intro screen is showing - the
   play-area screens need the full wrap width, so this must not apply once
   start-panel is hidden after "Start Playing". */
.card:has(#start-panel:not([hidden])) {
  width: 258px; /* 200px content column + 29px inset each side, matching the card-top-to-kicker-top distance */
  margin: 0 auto;
  padding-left: 29px;
  padding-right: 29px;
  padding-bottom: 29px;
  max-width: calc(100% - 20px); /* safety net so this doesn't overflow narrow phones */
}

/* The intro card is the same object as that game's card on the hub, so it is
   dressed the same: the hub's 18px inset, its near-white border and its hard
   accent-coloured offset, instead of .card's dim edge and soft black shadow.

   Width is deliberately NOT matched. A hub card is 232px because two sit side
   by side in a grid; this one is alone on the page at 258px, and shrinking it
   to the hub's phone width of 180px would strand it in the middle of the
   screen.

   The LOOK now reaches the club fan quizzes too (direct instruction,
   2026-10-05, after the country quizzes shipped with it): their intro card
   used to keep .card's dim edge and soft black shadow, which made it the one
   intro on either site dressed differently.

   The 18px inset does NOT reach them, which is why this is two rules rather
   than one unrestricted one. That number is layout, not dress: a club card is
   258px wide with a 29px inset, and its title sentence is fitted against the
   Start button inside that column, so widening it by 22px would re-flow a
   line that was tuned to wrap in two. */
.card:has(#start-panel:not([hidden])) {
  border-color: var(--text);
  box-shadow: 3px 3px 0 0 var(--accent);
}
body:not(.clubquiz) .card:has(#start-panel:not([hidden])) {
  padding: 18px;
}

/* Hub page (quizebra.com/football/) - one card per game, wrapping as more
   get added. */
.hub-wrap .site-header h1,
.hub-wrap .site-header #site-title { color: var(--text); } /* the #site-title arm
   added 2026-10-01: the hub's real <h1> moved into the fold and the visible
   wordmark became a <div id="site-title">, which does NOT inherit this - the base
   .site-header h1 is --text-soft, so without this the wordmark dimmed to #999.
   /football/clubs/ uses an <h1 id="site-title"> and so matches BOTH arms; the
   id arm wins on specificity, so it lands on the same --text either way. */
#hub-kicker {
  display: block; /* .kicker's default inline-block doesn't respond to margin-top the
    normal way; block does */
  margin-top: 2px;
}
.hub-wrap .site-header { margin-bottom: 16px; }
.hub-wrap { max-width: 480px; } /* exactly 2 cards (232px) + the gap between them
  (16px) - forces a strict 2-per-row grid at any viewport width */
.hub-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 16px;
}
.hub-card {
  display: block;
  width: 232px;
  max-width: calc(100% - 20px);
  text-decoration: none;
  color: inherit;
  transition: transform 0.12s ease, border-color 0.12s ease;
  /* The offset takes the site's own accent instead of --text, matching the
     portal's two topic cards. Colour only: 3px 3px 0 0 is unchanged, so it
     keeps the flat, crisp edge rather than becoming a glow. --accent is blue
     at :root here, so every card picks it up with no per-card rule.

     The border deliberately stays --text for now, which splits the pairing the
     comment above describes: shadow and border no longer match each other. */
  box-shadow: 3px 3px 0 0 var(--accent);
  border-color: var(--text);
}
.card.hub-card {
  padding: 18px;
  /* --panel, the same fill every game's card uses, so a hub card and the card
     it links to are the same surface (direct instruction, 2026-09-29; it was
     pure black from 2026-09-14). The white border and the offset shadow are
     NOT changed - they are what lifts the grid off the page, and the games
     have no equivalent to be consistent with. */
  background: var(--panel);
}
.hub-card .start-panel { padding-top: 0; }
.hub-card .start-title { margin-top: 0; }
.hub-card .start-rules { margin-bottom: 14px; white-space: nowrap; } /* 2026-10-06: ported from geoquiz so both hubs' cards are identical. */
/* "Rank the Player" - same pattern as geography's "Rank the Country" (direct
   instruction, 2026-09-20) - byte-identical rule copied from geoquiz's own
   style.css (same title height there too, confirmed live: 23.1875px, so the
   same px stops apply unchanged); renders in football's own --accent (blue)
   automatically since it's the same CSS variable, not a literal color. Was
   .hub-title-grid (thin gridlines) - removed, this title's only user in this
   file. */
.hub-title-stripes {
  background: linear-gradient(
    to bottom,
    var(--accent) 0, var(--accent) 7.6375px,
    var(--text) 7.6375px, var(--text) 12.275px,
    var(--accent) 12.275px, var(--accent) 16.9125px,
    var(--text) 16.9125px, var(--text) 21.55px,
    var(--accent) 21.55px, var(--accent) 23.1875px
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* "Who Has More?" - accent color at the very bottom fading to the title's
   normal color at the very top (direct instruction, 2026-09-14). */
.hub-title-vertical-fade {
  background: linear-gradient(to top, var(--accent), var(--text));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* "Guess the Player" - same trick as geography's "Guess the Country"
   (.hub-title-stlucia - a shape centered over the title text, background-
   clip:text over a solid fallback), but a plain filled circle (a ball, nod
   to the game itself) instead of a country silhouette - no separate image
   asset needed, a radial-gradient draws the circle directly (direct
   instruction, 2026-09-14). */
.hub-title-circle {
  /* Stop was 55%/56% (~97px diameter) - shrunk to ~75% of that (direct
     instruction, 2026-09-20, "doesn't have to be exact") by scaling the
     stop itself: 55*0.75≈41. */
  background: radial-gradient(circle, var(--accent) 41%, transparent 42%) center / auto 400% no-repeat, var(--text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* "Guess the Club" - .hub-title-circle's own trick inverted, direct
   instruction, 2026-09-20: the circle itself in var(--text) (white),
   everything outside it in var(--accent) (blue) - opposite of "Guess the
   Player" above. Replaces .hub-title-flag (a 3-band stripe pattern), this
   title's only user in this file - removed that rule. Same 41%/42% shrink
   as .hub-title-circle above, to keep the two visually matched. */
.hub-title-circle-invert {
  background: radial-gradient(circle, var(--text) 41%, transparent 42%) center / auto 400% no-repeat, var(--accent);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* "Build the Team" - same pattern as geography's "Find the Color" (direct
   instruction, 2026-09-20) - byte-identical rule copied from geoquiz's own
   style.css; renders in football's own --accent (blue) automatically since
   it's the same CSS variable. Was .hub-title-diagonal (a diagonal split) -
   removed, this title's only user in this file. */
.hub-title-gradient {
  background: linear-gradient(to right, var(--accent), var(--text));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* Chess titles each run the accent-to-light gradient their own way (owner,
   2026-10-10): Juicers from the top left, Horde from the top right, Mate in
   One from the bottom left, Mate in Two from the bottom right. The accent is
   always first. */
.hub-title-gradient.grad-to-br { background-image: linear-gradient(to bottom right, var(--accent), var(--text)); }
.hub-title-gradient.grad-to-bl { background-image: linear-gradient(to bottom left, var(--accent), var(--text)); }
.hub-title-gradient.grad-to-tr { background-image: linear-gradient(to top right, var(--accent), var(--text)); }
.hub-title-gradient.grad-to-tl { background-image: linear-gradient(to top left, var(--accent), var(--text)); }
/* The box hugs the word, so the gradient runs corner to corner across the
   letters themselves; on a full-width box the word only caught its middle. */
.hub-title-gradient.grad-to-br,
.hub-title-gradient.grad-to-bl,
.hub-title-gradient.grad-to-tr,
.hub-title-gradient.grad-to-tl { width: fit-content; margin-inline: auto; }
/* "Order the Player" - same pattern as geography's "Order the Country"
   (direct instruction, 2026-09-20): one down arrow behind "Order", one up
   arrow behind "Player" - same shape family as .arrange-arrow's own move-
   up/move-down buttons there. Hand-drawn inline SVG triangles (tight
   viewBox, no padding) so "auto 100%" lands each triangle's own top/bottom
   exactly on the text's own top/bottom. Baked with football's own accent
   hex (#ff6172) directly, not var(--accent) - a data-URI SVG can't
   reference a CSS custom property. Was .hub-title-split (a plain 50/50
   horizontal split) - removed, this title's only user in this file. */
.hub-title-arrow-down {
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 87'%3E%3Cpolygon points='0,0 100,0 50,87' fill='%23ff6172'/%3E%3C/svg%3E") center / auto 100% no-repeat, var(--text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.hub-title-arrow-up {
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 87'%3E%3Cpolygon points='50,0 100,87 0,87' fill='%23ff6172'/%3E%3C/svg%3E") center / auto 100% no-repeat, var(--text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* TWO-STATE letter-spacing - this value is NOT dead, and NOT the one you see
   on a freshly-loaded hub. The markup ships "Take the #1 spot" with its own
   fitted letter-spacing INLINE (3.8739px). When the live #1 stat arrives, the
   hub's own script swaps the text to "Can you beat this?" and CLEARS that
   inline value, so this rule's value - fitted for that second, LONGER string
   ("Can you beat this?", 18 chars vs 16) - takes over. It needs LESS spacing
   precisely because it is wider, both landing on .leaderboard-row's 188px. Moving the inline value into this rule would make both
   strings render at the first one's spacing. Same mechanism on both sites. */
.hub-beat-label {
  font: 700 16px "Inter", sans-serif;
  letter-spacing: 0.15em; /* was 2.4106px, i.e. this same ratio written out
    against the 16px above - relative so it survives a size change */
  color: var(--accent);
  margin: 0 0 2px; /* 2026-10-06: ported from geoquiz so both hubs' cards are identical. */
}
.hub-card .leaderboard { width: 188px; margin: 0 auto 18px; } /* 2026-10-06: ported from geoquiz so both hubs' cards are identical. */
.hub-card .btn-primary {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 188px;
  margin: 0 auto;
  height: 30px;
  padding: 0 14px;
  border-radius: 8px;
}
@media (hover: hover) {
  a.hub-card:hover { border-color: var(--accent); transform: translateY(-2px); }
}

/* Force 2 cards per row down to 375px viewport width instead of dropping to 1
   per row - same tier as geography's own hub. */
@media (min-width: 375px) and (max-width: 511px) {
  .hub-wrap { margin: 0 -11px; width: calc(100% + 22px); }
  /* 6px. This was 9px, which came to 369.0px of cards-plus-gap against
     368.667px of grid - short by a THIRD of a pixel, so the second card
     wrapped and the hub fell to one card per row on exactly the phones this
     tier exists for (an iPhone SE/mini at 375px, or a 390px one showing a
     scrollbar). 8px fixed that, but only by 0.667px, which is not a margin -
     it is the same cliff one rounding difference the other way. Safari
     implements zoom in its own right and 232 x 0.7759 need not land on 180.0
     there.

     6px leaves 2.667px, and 6 is a value the site already uses (it is
     --control-gap on phones). The cards are 180px wide here; 6px between
     them still reads as a grid. */
  .hub-grid { gap: 6px; }
  .hub-card { zoom: 0.7759; }
  /* The 8.5px override that used to sit here is gone. It made hub cards
     SMALLER than the 9.8px base, not larger - and once the base is a
     round 10px the shared value fits inside the zoomed card on its own
     (measured: 10.5px is the point the longest subtitle overflows its
     nowrap line). One size for this line everywhere now. */
  /* The 11px this used to drop to is below the 12px floor the games settled
     on, and the footer is not inside a zoomed card, so it had no reason to
     go under it. */
}

.start-panel {
  text-align: center;
  padding: 10px 0 0;
}
.start-panel .kicker { display: block; margin-top: 8px; margin-bottom: 2px; }
.start-title {
  font: 400 26px "Anton", sans-serif; /* shared/fixed across every game on purpose */
  letter-spacing: 0.02em;
  /* Never two lines, on any device (2026-10-01, reported from a real phone).
     The per-title letter-spacing above fits each title to the card's content
     column, which leaves the longest ones at 98-99% of the box on a phone -
     one hair of cross-device rendering difference away from wrapping. A title
     is a single short phrase and this whole fitting system assumes one line,
     so say so rather than relying on the fit holding everywhere. */
  white-space: nowrap;
  text-transform: uppercase;
  line-height: 1.15;
  margin: 4px 0 2px;
}
.start-rules {
  font: 700 10px "Inter", sans-serif; /* was 9.8px */
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: -2px 0 22px; /* 2026-10-06: ported from geoquiz so both hubs' cards are identical. */
}

.result-panel { text-align: center; margin-top: 10px; }
.result-time-label {
  font: 700 12px "Inter", sans-serif;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-soft);
  margin: 0 0 -2px;
}
.result-title {
  font: 400 32px "Anton", sans-serif;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin: 0 0 6px;
  color: var(--accent);
}
.result-title.wrong { color: var(--bad); }
.result-sub {
  font: 400 15px "Inter", sans-serif;
  color: var(--text-soft);
  margin: -10px 0 16px;
}

.btn-primary {
  display: block;
  font: 700 15px "Inter", sans-serif;
  color: var(--on-accent);
  background: var(--accent);
  border: none;
  border-radius: 12px;
  padding: 14px 22px;
  cursor: pointer;
  width: 100%;
  text-align: center;
}
#start-btn { width: 200px; margin-top: 14px; } /* the card's content column */

/* The intro card's insides, matched to the same game's card on the hub. The
   two use near-identical markup; what differed was three overrides the hub
   card has and this one did not, plus #start-btn's own width and top margin,
   which is an id and so beat any class chain. Measured before: the button was
   47px tall against the hub's 30px, 200px wide inside a 220px column while the
   rank row was 220px, and the title sat 14px lower than the hub's.

   Width is 100%, not the hub's literal 188px: that number is 194px of hub
   content column minus a small inset, and this card's column is 220px, so
   188px here would leave the button floating with 16px either side. Filling
   the column keeps the thing that was actually wrong fixed, which is that the
   button and the rank row are now exactly the same width as each other. */
body:not(.clubquiz) #start-panel { padding-top: 0; }
/* .start-title carries margin: 4px 0 2px, which on the hub card collapses away
   and here does not, leaving the intro title sitting 4px lower than the hub's.
   Zeroed so the two cards start their content at the same place. */
body:not(.clubquiz) #start-panel .start-title { margin-top: 0; }
/* The hub trims 3px off the shared .start-rules margin-bottom (22px -> 19px);
   without this everything from the beat label down sat 3px lower than the hub's. */
body:not(.clubquiz) #start-panel .start-rules { margin-bottom: 14px; } /* 2026-10-06: ported from geoquiz so both hubs' cards are identical. */
body:not(.clubquiz) #start-panel .leaderboard { width: 100%; margin: 0 auto 18px; } /* 2026-10-06: ported from geoquiz so both hubs' cards are identical. */
body:not(.clubquiz) #start-panel .btn-primary {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  margin: 0 auto;
  height: 30px;
  padding: 0 14px;
  border-radius: 8px;   /* the hub's, matching .leaderboard-row's corner */
}
@media (hover: hover) {
  .btn-primary:hover { filter: brightness(1.08); }
}

.btn-secondary {
  font: 700 15px "Inter", sans-serif;
  color: var(--text);
  background: var(--control);
  border: none;
  border-radius: 12px;
  padding: 14px 22px;
  cursor: pointer;
  width: 100%;
  margin-bottom: 10px;
}
@media (hover: hover) {
  .btn-secondary:hover { background: var(--control-hover); }
}

#share-btn:not([hidden]) {
  background: var(--accent);
  color: var(--on-accent);
}
@media (hover: hover) {
  #share-btn:not([hidden]):hover { filter: brightness(1.08); background: var(--accent); }
}

.result-actions {
  display: flex;
  gap: 10px;
}
.result-actions .btn-primary { width: auto; flex: 1; }
.result-actions .btn-secondary { width: auto; flex: 1; margin-bottom: 0; }
#hub-link {
  display: block;
  margin-top: 14px;
  text-align: center;
  font: 700 13px "Inter", sans-serif;
  line-height: 1.3;
  color: var(--accent);
  text-decoration: none;
}
.card:has(#hub-link:not([hidden])) { padding-bottom: 14px; }
@media (hover: hover) {
  #hub-link:hover { text-decoration: underline; }
}

.play-area {
  /* position:relative is kept: it is the containing block for anything a game
     positions against the play screen as a whole. --title-h came off with the
     practice badges (2026-09-29) - it sized nothing else. */
  position: relative;
}

.leaderboard {
  list-style: none;
  margin: 18px 0 20px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  text-align: left;
}
.leaderboard-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  background: transparent;
  border: 1px solid var(--panel-edge);
  border-radius: 10px;
}
/* The #1 row always renders first (rows are already rank-sorted) - direct
   instruction, 2026-09-14, applies on every subpage that shows a real
   leaderboard, and also to a hub-card's own single-row "#1" preview (see
   .hub-card .leaderboard) since that's always rank 1 too. */
/* --tile, not the #181818 literal it was: one step off --tile's #171717 and
   indistinguishable from it, while being the only hard-coded surface left
   in the file. */
.leaderboard-row:first-child { background: var(--tile); }
.leaderboard-row.you { border-color: var(--accent-dim); }
/* --text on all three spans of a leaderboard row (direct instruction,
   2026-09-30). They were --text-soft, which is #999999 since the colour
   pass - the same quiet grey as --text-dim - so a results screen listed its
   players at the page's quietest colour. The hub and intro cards' own "#1"
   row was lifted to --text first and this brings every other leaderboard
   onto the same value, which is why the .hub-leaderboard block below no
   longer restates it.

   .leaderboard-row.you keeps its accent rank: that rule scores (0,3,0)
   against this (0,1,0). */
.lb-rank { font: 700 11px "Inter", sans-serif; color: var(--text); width: 20px; }
.leaderboard-row.you .lb-rank { color: var(--accent); }
.lb-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 400 12px "Inter", sans-serif;
  color: var(--text);
}
.lb-time {
  flex-shrink: 0;
  width: 34px;
  text-align: right;
  font: 400 12px "Inter", sans-serif;
  color: var(--text);
}

/* --- the "#1" preview row ------------------------------------------------
   A hub card and a game's own intro card render this from the same markup,
   and .hub-leaderboard is on the <ol> in BOTH, so whatever this row needs
   BEYOND a normal leaderboard row is stated once here, keyed on that class -
   the two surfaces cannot drift apart because there is only ever one rule to
   change (a standing instruction, 2026-09-30). Geography's hub and its six
   intro cards carry the same class and the same block.

   The row's text is no longer among those rules: every leaderboard on both
   sites takes --text now (see .lb-rank above), so this block only has to say
   where this row DIFFERS - its fill.

   Pure black, not the --tile the shared .leaderboard-row:first-child rule
   gives every other leaderboard's top row (direct instruction, 2026-09-30).
   On a real result screen that row sits on a card among other rows and a
   lighter fill marks it out; here it is the only row there is, so a fill a
   step ABOVE the card made the card look like it had a lid on it. var(--bg)
   takes it to the page's own black, one step BELOW the card, which reads as
   punched through - the same relationship the Guess games' search field has
   with the card it sits in.

   :first-child so this scores (0,3,0) and beats that shared rule on weight
   rather than on source order, which is what a same-weight selector would
   have depended on. */
.hub-leaderboard .leaderboard-row:first-child {
  background: var(--bg);
}

/* flex-wrap is for .lb-name-taken below, which takes a full-width line of its
   own inside this row. Nothing else in here wraps, so the normal state is
   unaffected. */
.lb-name-edit { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* Only present when a chosen name was already held by someone else (names are
   held permanently now - see player_names in the geoquiz database). Its own
   line rather than squeezed in beside the input: both the input and the Save
   button are already tight on a phone, and this is a sentence, not a label. */
.lb-name-taken {
  flex: 0 0 100%;
  font: 400 11px/1.4 "Inter", sans-serif;
  color: var(--text-dim);
}
.lb-name-input {
  flex: 1; min-width: 0; font: 700 16px "Inter", sans-serif; color: var(--text);
  background: transparent; border: 1px solid var(--panel-edge); border-radius: 6px;
  padding: 3px 6px;
  transition: font-size 0.15s ease, padding 0.15s ease;
}
.lb-name-input:focus {
  outline: none;
  border-color: var(--accent);
  font-size: 20px;
  padding: 6px 8px;
}
.lb-name-save {
  flex: 0 0 auto; font: 700 11px "Inter", sans-serif; color: var(--accent);
  background: transparent; border: 1px solid var(--accent-dim); border-radius: 6px;
  padding: 3px 8px; cursor: pointer;
}
.lb-name-save:disabled { opacity: 0.6; cursor: default; }
@media (hover: hover) {
  .lb-name-save:hover:not(:disabled) { border-color: var(--accent); }
}

.your-rank-note {
  font: 700 12px "Inter", sans-serif;
  color: var(--text-dim);
  margin: 0 0 16px;
}

/* Load-failure block (#round-error) - shown by showRoundLoadError() in Rank
   the Player / Order the Player / Build the Team when a round can't be built.
   That function hides every other element in .play-area, so this block is the
   only thing left in the card, and it had NO rule at all until now
   (2026-09-28): the Retry button quiz.js injects rendered full-width and
   flush against the card's own 10px top padding, in an otherwise empty card,
   with nothing on screen saying what had gone wrong.
   The other three games (Who Has More?, Guess the Club, Guess the Player)
   have no #round-error element and relabel their own Start button instead
   ("Couldn't load - tap to retry"), which still sits inside the intro card's
   kicker/title/rules context - that's why only these three need a message
   line of their own here.
   Geography's style.css carries this same rule, ported across in the same
   change (2026-09-28). It needs the text styling on the CONTAINER rather
   than only on .round-error-msg because /name over there uses the class on a
   bare "Type at least one letter." text line with no button at all - keep
   both copies in step. */
.round-error {
  text-align: center;
  margin-top: 18px;
  font: 700 13px "Inter", sans-serif;
  color: var(--bad);
}
.round-error-msg { margin: 0 0 12px; }
/* 200px + centered, matching #start-btn's own width - .btn-primary's default
   is width:100%, which behind an empty card reads as a banner, not a control. */
.round-error .btn-primary { width: 200px; margin: 0 auto; }
/* Retry disables itself on tap (see showRoundLoadError) so a double-tap can't
   run two concurrent init()s.

   Only filter:none is left here. This rule used to carry the whole disabled
   look, because no shared .btn-primary:disabled rule existed; one does now
   (the unpressable treatment further down, at (1,2,1)), and it beats this
   selector's (0,3,0) on the id column, so the opacity and cursor this rule
   used to set were dead weight.

   filter still matters: .btn-primary's own @media (hover:hover) rule
   brightens on hover and would otherwise light up a disabled button under
   the cursor. Nothing in the shared treatment touches filter. */
.round-error .btn-primary:disabled { filter: none; }

@media (max-width: 480px) {
  body { padding: 40px 14px 32px; }
  .kicker { margin-bottom: 7px; }
  .card { padding: 14px 16px 24px; }
  .wrap:has(#play-area:not([hidden])) .site-header h1 { font-size: 24px; }
  .start-rules { margin-bottom: 18px; }
  .result-panel { margin-top: 6px; }
  .result-title { font-size: 24px; margin-bottom: 4px; }
  .result-panel:has(#result-time-label:not([hidden])) .result-title { margin-top: -4px; }
  .result-sub { font-size: 13px; margin-bottom: 8px; }
  .btn-primary { padding: 8px 18px; }
  .btn-secondary { padding: 8px 18px; margin-bottom: 8px; }
  /* removed 2026-10-06: #share-btn is no longer inside .result-actions, so
     this could never match again. The label fit handles the sizing now. */
  .leaderboard { margin-top: 10px; }
}

@media (max-width: 375px), (max-height: 700px) {
  body { padding: 22px 12px 18px; }
  .card { padding: 10px 12px; }
  .start-rules { margin-bottom: 14px; }
}

/* ---------------------------------------------------------------------
   Rank the Player - 8-player/8-category ranking-assignment puzzle, ported
   from geography's "Rank the Country" (same mechanic/markup, minus flag
   imagery - no player-photo pipeline yet, plain name cards instead).
   --------------------------------------------------------------------- */
.rank-pool {
  display: grid;
  /* 1fr, not auto+justify-content:center (was: a fixed-width 74px card,
     centered as a narrower block than the full-width category grid below
     it) - 1fr stretches all 4 cards to fill the same full content width
     .pair-module's buckets already span, so the pool's outer left/right
     edges land exactly on the bucket row's own edges (direct instruction,
     2026-09-12). gap stays the fixed px value it always was - only the
     cards themselves grow, not the space between them. */
  grid-template-columns: repeat(4, 1fr);
  /* start, not the grid default (stretch) - without this, once the pool
     drops to a single row (5-8 players placed), the remaining row's track
     stretches to fill the space min-height below reserves for a full 2-row
     pool, and every card's own default align-items:stretch then stretches
     WITH it, making the last few cards visibly taller than the first ones
     were. start keeps every card at its own natural height regardless of
     row count (direct instruction, 2026-09-12). */
  align-content: start;
  align-items: start;
  gap: 9px;
  margin-bottom: 22px;
  /* Single source of truth for every .ccard's own fixed height below, and for
     the pool's own row-reservation math, so the two can never drift apart.

     The 58px here is the pre-pass value; Rank the Player's own block
     redefines it as --control-h * 1.25 (60 desktop / 55 phone). At that size
     a two-line name measures 32.8px against a 30px content box, so it eats
     ~2.8px of the padding rather than sitting inside it - measured, not
     clipped: nothing spills past the border and scrollHeight never exceeds
     clientHeight. There is no slack left, so a third line or a larger name
     size would need the height raised with it. */
  --ccard-h: 58px;
  /* 1-row height by default - the taller 2-row reservation right below only
     applies while a 5th+ card is still actually in the pool (i.e. more than
     one row's worth remain). So the category grid below stays put through
     8->5 remaining players (still legitimately 2 rows), then deliberately
     moves up once the pool drops to a single row of <=4 (direct
     instruction, 2026-09-12: "when only players of one row are left, move
     the rest up") - not reserved-forever blank space for a vanished 2nd
     row, which is what a flat always-2-rows min-height would do. */
  min-height: var(--ccard-h);
}
.rank-pool:has(.ccard:nth-child(5)) {
  min-height: calc(var(--ccard-h) * 2 + 9px);
}
/* No flag image (no player-photo pipeline yet) - a plain name card, vertically
   centered, rather than geography's fixed flag-wrap + bottom-aligned name-zone
   split. */
.ccard {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--tile);
  border: 1px solid var(--panel-edge);
  border-radius: 12px;
  padding: 14px 6px;
  /* No explicit width - stretches to fill its 1fr grid column (see
     .rank-pool's own comment), same width for all 4 regardless of name
     length, by construction (a grid column's size, not a per-card fit).
     height (not min-height, direct instruction 2026-09-12) - a 1-word name
     (e.g. "Cafu", 1 line via appendPlayerName) was otherwise shorter than a
     2+-word name (2 lines), sitting at a smaller natural/floor height
     instead of matching every other card. Fixed height + the align-items:
     center already above centers ANY name (1 or 2 lines) within the exact
     same box. */
  height: var(--ccard-h);
  cursor: pointer;
  transition: border-color 0.12s ease, opacity 0.12s ease, background 0.12s ease;
}
.ccard.selected { border-color: var(--accent); }
.ccard.targetable { border-color: var(--accent); background: var(--accent-wash); }
.ccard.dimmed { opacity: 0.35; }
.ccard-name-zone { width: 100%; display: flex; align-items: center; justify-content: center; }
.ccard-name {
  /* Matches .pair-bucket's own category text size exactly (12px base / 11px
     under 480px, see that breakpoint override below) - direct instruction,
     2026-09-12. */
  font-size: 12px;
  font-weight: 600;
  text-align: center;
  line-height: 1.2;
  white-space: normal;
  color: var(--text);
}

.pair-stack { display: flex; flex-direction: column; gap: 10px; }
.pair-module { display: flex; flex-direction: row; align-items: stretch; gap: 8px; }
.pair-bucket {
  flex: 1;
  /* 0, not the flex default (auto) - without this, a flex item's minimum
     width is based on its own content's min-content size, which for a
     white-space:nowrap category line (e.g. "Premier League Titles 🏆" on a
     very narrow screen) can exceed this bucket's fair 1-of-2 share of the
     row, forcing the bucket (and the whole pair-module row) wider than
     intended instead of letting the text itself shrink to fit (direct
     instruction, 2026-09-12 - see buildBucket's own shrinkCategoryLabelToFit
     call in quiz.js for the other half of this fix). */
  min-width: 0;
  border: 1.5px solid var(--panel-edge);
  border-radius: 12px;
  padding: 10px 10px 10px 16px;
  font: 600 12px "Inter", sans-serif;
  color: var(--text);
  display: flex;
  align-items: center;
  cursor: pointer;
  transition: border-color 0.12s ease, background 0.12s ease, opacity 0.12s ease;
}
.pair-bucket.targetable { border-color: var(--accent); background: var(--accent-wash); }
.pair-bucket.selected { border-color: var(--accent); background: var(--accent-wash); }
.pair-bucket.dimmed { opacity: 0.35; }
.pair-bucket.filled { cursor: default; flex-direction: column; align-items: stretch; gap: 6px; background: var(--tile); }
.bucket-label {
  font: 600 10px "Inter", sans-serif;
  letter-spacing: 0.02em;
  color: var(--text);
}

/* Every category label everywhere on this page (live-grid buckets - both
   empty and filled - and the results comparison table) is built by quiz.js's
   buildCategoryLabel as this same 2-child structure: "{dir word} {arrow}"
   over "{stat text} {emoji}" - direct instruction, 2026-09-12: always 2
   lines, never all-caps. font-size/color come entirely from the surrounding
   context (.pair-bucket/.bucket-label/.comparison-category below), not set
   here, so the one JS builder renders correctly in all three places. nowrap
   on each line, not the wrapping trick the comparison table used before this
   change - each line is now its own real line by construction (2 flex
   children), not text wrapping to fit a column, so nowrap here is safe and
   keeps a stray extra-long value from silently becoming a 3rd line. */
.category-label {
  display: flex;
  flex-direction: column;
  min-width: 0;
  gap: 1px;
}
/* Direct child of an EMPTY .pair-bucket only (the filled state nests it one
   level deeper, inside .bucket-label, which already stretches via its own
   parent's align-items:stretch) - without flex:1 here, this box never
   actually fills the bucket's width (a flex item's default flex-grow is 0),
   so it sits at its own natural/shrunk content width, left-aligned, with
   the bucket's leftover width just showing as empty space to its right -
   same available width shrinkCategoryLabelToFit already computes from the
   BUCKET, but wasted if this box itself doesn't also claim that width. */
.pair-bucket > .category-label { flex: 1; }
.category-label-dir,
.category-label-stat {
  white-space: nowrap;
}
/* text-dim, not accent - the arrow (a plain child span, no color rule of its
   own) inherits this too, matching the rest of the dir line rather than
   standing out on its own. */
.category-label-dir { font-weight: 700; color: var(--text-dim); }
/* "Career-High" reads as part of "Market Value"/whichever line it's on, not
   its own separate thing - explicit override back to the stat line's own
   color (text), not the dir line's inherited text-dim. */
.stat-callout-small { font-size: 1em; color: var(--text); }
.bucket-country { display: flex; align-items: center; gap: 6px; }
.bucket-country-name {
  flex: 1;
  min-width: 0;
  font: 700 12px "Inter", sans-serif;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bucket-undo {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid var(--panel-edge);
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
}
@media (hover: hover) {
  .bucket-undo:hover { border-color: var(--bad); color: var(--bad); }
}

#submit-btn { margin-top: 18px; }
#submit-btn.not-ready {
  background: var(--control);
  color: var(--text-dim);
}
@media (hover: hover) {
  #submit-btn.not-ready:hover { background: var(--control); filter: none; }
}
.submit-hint {
  font: 700 12px "Inter", sans-serif;
  color: var(--bad);
  text-align: center;
  margin: 10px 0 0;
}



.comparison-header.comparison-row { margin-bottom: 10.7px; }
.comparison-col-title {
  margin: 0;
  min-width: 0;
  text-align: center;
  font: 700 9px "Inter", sans-serif;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.comparison-col-pct {
  margin: 0 0 2px;
  text-align: center;
  font: 400 17px "Inter", sans-serif;
  /* The bright grey, both columns (direct instruction, 2026-10-01). "Your
     Picks" held the accent and "Best Picks" the quiet grey - the accent has a
     job on this screen now (it marks the categories you got right, see rule
     3b), and these two numbers are a pair that should read as a pair. The
     muted variant that carried the second colour is gone with it, along with
     buildComparisonColTitle's muted param. Same change as
     Rank the Country's. */
  color: var(--text);
}
.comparison-row {
  display: grid;
  /* A reasonable starting width, not a precise fit - quiz.js's
     shrinkCategoryLabelToFit auto-shrinks this column's own text (down to
     whatever it actually needs) rather than this value having to exactly
     match the longest possible line (e.g. "Career-High Market Value 💰",
     always combined onto one line now). Kept from growing too wide and
     eating into the 2 assignment columns' own share of the row. */
  grid-template-columns: 145px 1fr 1fr;
  align-items: end;
  gap: 6px;
  /* Positive and clearly bigger than .comparison-entry's own 1px internal
     gap (was -2px, a negative margin left over from the old 1-line layout -
     rows were literally packed CLOSER than an entry's own name-to-value
     spacing, the opposite of reading as separate groups; fixed 2026-09-12). */
  margin-bottom: 8px;
}
.comparison-row:last-child { margin-bottom: 0; }
/* Nudges the "Your Assignment"/"Optimal Assignment" clusters apart from
   each other (direct instruction, 2026-09-12) without touching the column
   TRACKS themselves (which would also move the category column/shift row
   widths) - asymmetric padding shrinks each column's own centered content
   box from one side only, pushing its centered text off-true-center toward
   the other side. nth-child(2)/(3) match both the header row's "col" divs
   (buildComparisonColTitle) and every data row's .comparison-entry - same 2
   positions in every row's 3-child structure. */
.comparison-row > *:nth-child(2) { padding-right: 16px; }
.comparison-row > *:nth-child(3) { padding-left: 4px; }
.comparison-category {
  /* Same size as .comparison-country/.comparison-rank below (10px, was 8px)
     and the same line-height (1.25, set on .comparison-entry now too) -
     direct instruction, 2026-09-12: every text size in this table matches
     the player name's own size, and the category cell's 2 lines land
     exactly level with the entry cells' 2 lines (was: a smaller category
     font needed its own translateY(-2px) nudge to fake-align against the
     larger name/rank text on a single shared line - obsolete now that both
     cells are genuinely the same size/line-height/2-line structure, removed
     rather than re-tuned). */
  font: 600 10px "Inter", sans-serif;
  letter-spacing: 0.02em;
  line-height: 1.25;
  color: var(--text);
}
/* Left-aligned via flex, not text-align (direct instruction, 2026-09-12:
   was right-aligned) - buildCategoryLabel's 2 lines are 2 separate flex
   children, so each needs its own edge pinned to the column's left edge
   independently (text-align only aligns text runs within a single line box,
   not sibling flex items). */
.comparison-category .category-label { align-items: flex-start; }
/* 2 lines now (direct instruction, 2026-09-12), not rank-squeezed-after-name
   on one line - flex column, same technique as .category-label. Same
   line-height as .comparison-category (1.25) so both cells' 2 lines land on
   the same vertical positions, not just the same font-size. */
.comparison-entry {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  line-height: 1.25;
}
.comparison-country {
  display: block;
  text-align: center;
  font: 700 10px "Inter", sans-serif;
  color: var(--text);
  white-space: nowrap;
}
/* The verdict moved onto the entry and into this page's own block, where it
   can beat the board greys on specificity - see rule 3b further down. */
.comparison-rank { font: 700 10px "Inter", sans-serif; color: var(--text-soft); white-space: nowrap; }
#rank-percentile + .leaderboard, #percentile-msg + .leaderboard { margin-top: 16px; }

@media (max-width: 480px) {
  .rank-pool { gap: 7px; margin-bottom: 18px; }
  .ccard { padding: 12px 5px; }
  .pair-stack { gap: 8px; }
  .pair-module { gap: 6px; }
  .pair-bucket { padding: 8px 8px 8px 13px; font-size: 11px; }
  .ccard-name { font-size: 11px; }
  .bucket-country-name { font-size: 11px; }
}
@media (max-width: 375px), (max-height: 700px) {
  .pair-bucket { padding: 7px 7px 7px 11px; }
}

/* ============================================================ WHO HAS MORE?
   "Scoreboard" duel screen (chosen 2026-09-11 from 3 mocked variants) - two
   stacked player cards with a circular VS badge pinned on their shared seam;
   on reveal the winning card fills solid (green) with its big number, the
   wrong-tapped card fills solid red, correct/wrong is otherwise unmarked.
   Reuses the same tokens/scale as Rank the Player throughout (--ccard
   equivalent, --accent/--good/--bad, Anton for numbers/headline). */
.round-pill {
  align-self: center;
  font: 700 10px "Inter", sans-serif;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  background: rgba(var(--accent-rgb), 0.1);
  border: 1px solid var(--accent-dim);
  border-radius: 100px;
  padding: 3px 10px;
  margin: 2px auto 10px;
  width: fit-content;
}
.metric-q {
  font: 400 26px/1.2 "Anton", sans-serif; /* matches #round-label's own size in every geoquiz game */
  text-transform: uppercase;
  letter-spacing: 0.01em;
  color: var(--text);
  text-align: center;
  margin: 0 0 36px; /* 16px base + 20px to push the timer/cards cluster down (2026-09-11) */
}
.metric-q em { color: var(--accent); font-style: normal; }

.duel-stack { position: relative; display: flex; flex-direction: column; gap: 10px; margin-top: 16px; margin-bottom: 14px; }
.duel-card {
  font-family: inherit;
  color: var(--text);
  /* Explicit, not left to inherit - a bare <button> has no guaranteed
     inherited text color across browsers; desktop Chrome/Firefox happen to
     inherit dark text with no rule at all, but iOS Safari renders an
     unstyled <button>'s text in the OS accent/tint color (blue) instead,
     which is exactly what showed up as "player names are blue on phone,
     white on laptop" (2026-09-14) - .name/.num both already use
     `color: inherit`, so they only needed this one explicit declaration on
     their own button ancestor to stop depending on a browser default. */
  background: var(--tile);
  border: 1.5px solid var(--panel-edge);
  border-radius: 14px;
  padding: 18px 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Sized to already fit the REVEAL state's content (name + a 30px Anton
     number stacked) from the very start, so tapping a card doesn't grow it -
     both states share this exact floor and just center less/more content
     inside it via the same align-items/justify-content (2026-09-11). */
  min-height: 92px;
  width: 100%;
  cursor: pointer;
  transition: border-color 0.12s ease, background 0.12s ease, opacity 0.12s ease;
}
@media (hover: hover) {
  .duel-card:not(:disabled):hover { border-color: var(--accent); }
}
/* Guessing-phase name is 2px larger than its reveal size, and the same
   element (not a recreated one) shrinks down on reveal - see reveal() in
   quiz.js, which only toggles .revealed rather than rebuilding these nodes,
   specifically so this can transition instead of jumping (2026-09-11). */
.duel-card .name {
  font: 700 18px "Inter", sans-serif;
  color: inherit;
  text-align: center;
  transition: font-size 0.25s ease;
}
.duel-card.revealed .name { font-size: 16px; }
.duel-card .revealrow { display: flex; flex-direction: column; align-items: center; gap: 2px; }
/* Starts collapsed - grows into view on reveal, which (since .revealrow
   sits centered inside .duel-card) is what actually produces the "name
   moves up" effect: the row gets taller and re-centers, no transform
   needed on .name itself. */
.duel-card .num {
  font: 400 30px "Anton", sans-serif;
  color: inherit;
  line-height: 1;
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  transition: max-height 0.25s ease, opacity 0.2s ease 0.05s;
}
.duel-card.revealed .num { max-height: 40px; opacity: 1; }
.duel-card:disabled { cursor: default; opacity: 0.55; }
/* Same correct/wrong language as every other game's .opt-btn (geoquiz's maps
   quiz, style.css shared design system) - solid fill, not a border/tint, so
   it reads as one unambiguous signal: the winning card is always marked
   correct (whether or not it was the tap), and a wrong tap is marked wrong
   on the exact card tapped. color: inherit on .name/.num (not a hardcoded
   var(--text)) so both go dark together with the card's own fill-driven
   text color - a literal color on a child would otherwise stay light
   against a light green/red fill, unreadable (caught 2026-09-11). */
/* --on-accent, not the #06210f / #2a0509 pair these carried: those were the
   on-good and on-bad counterparts to --on-accent's old #0a0f0c, and that token
   is pure black now. Neither ever painted - the scoped rule further down
   already sets this colour, and Who Has More? is the only page with these
   cards - but a literal that never renders is still a literal to read. */
.duel-card.correct { background: var(--good); border-color: var(--good); color: var(--on-accent); opacity: 1; }
.duel-card.wrong { background: var(--bad); border-color: var(--bad); color: var(--on-accent); opacity: 1; animation: shake 0.35s ease; }
/* Separate from correct/wrong - marks which card was actually tapped. Same
   highlight color as the hover state (var(--accent)) but visibly thicker,
   and placed after .correct/.wrong above so it wins the border-color
   regardless of which fill state also applies (2026-09-11). */
.duel-card.clicked { border-width: 3px; border-color: var(--accent); }
/* Pre-duel "3...2...1" gate (direct instruction, 2026-09-16 - replaces an
   earlier plain-text .btn-primary version from 2026-09-14) - takes
   #duel-stack's place (see quiz.js's scheduleRoundStart) for the very first
   duel of a session only, so the metric question above is already visible
   while the player gets a moment before the real 8s timer starts. Same
   solid-panel-plus-big-number treatment as geoquiz's own .gate-panel/
   .gate-number (flags/maps quiz.js) - height is fixed at #duel-stack's own
   194px footprint (2 cards + 10px gap) rather than that version's
   aspect-ratio, since this panel replaces a stacked pair of cards, not an
   image. Unlike the flag viewport, #duel-stack's height never changes across
   breakpoints, so .gate-number does not NEED a mobile font-size override to
   fit - but it has one anyway since 2026-10-07, so the number is the same
   size here as on geography at the same width.

   The margins matched #duel-stack's when both carried 16/14; the surfaces
   pass zeroed the stack's on 2026-09-29 and left these behind, so the card
   lost 14px of height the moment the gate fired. They are matched again in
   this game's own block below, along with the fill - see there. No button,
   and no tap to skip (owner, 2026-10-16; see scheduleRoundStart in
   quiz.js). */
.gate-panel {
  width: 100%;
  height: 194px;
  background: var(--bg);
  border-radius: 14px;
  margin-top: 16px;
  margin-bottom: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.gate-number {
  font: 400 120px "Anton", sans-serif;
  color: var(--accent);
  line-height: 1;
}
/* Scales with the viewport (owner, 2026-10-07), matching geoquiz value for
   value so the two sites' countdowns are the same size on the same phone.
   This panel's own height never changes across breakpoints, so it does not
   NEED to shrink to fit - it shrinks so the two sites agree. */
@media (max-width: 480px) {
  .gate-number { font-size: 100px; }
}
@media (max-width: 375px), (max-height: 700px) {
  .gate-number { font-size: 90px; }
}

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-6px); }
  75% { transform: translateX(6px); }
}

.duel-vs {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--panel);
  border: 1.5px solid var(--panel-edge);
  display: flex;
  align-items: center;
  justify-content: center;
  font: 400 12px "Anton", sans-serif;
  color: var(--text-dim);
  z-index: 2;
  pointer-events: none;
}

/* Timer bar - same structure/behavior as geoquiz's maps quiz .timer-wrap
   (byte-identical shape, ported 2026-09-11: track + shrinking fill + a
   time label that travels with the fill's leading edge), sized to sit
   directly above the top duel card rather than above a map. */
/* .timer-wrap and .duel-status occupy the same slot (.duel-status-slot,
   below), only one visible at a time - their own natural heights differ
   wildly (a 10px track vs a 22px Anton line), so the FIXED height and
   vertical centering live on that shared outer slot, not on either of
   these two individually. Doing it on .timer-wrap itself instead would
   silently break #timer-num's calibrated "6px above the track" positioning
   (its bottom:100% is relative to .timer-wrap's own box, which a forced
   height here would inflate) - found while implementing, 2026-09-11. */
.timer-wrap { position: relative; margin: 0; width: 100%; }
.timer-track {
  width: 100%;
  height: 10px;
  border-radius: 6px;
  /* The page's own black, so the unfilled track reads as a groove cut through
     the card. #050505 was a sixth dark grey that never painted - the scoped
     rule that used to sit further down set var(--bg) over it - but it read as
     a real value. */
  background: var(--bg);
  border: 1px solid var(--panel-edge);
  overflow: hidden;
}
.timer-fill {
  height: 100%;
  width: 100%;
  transform-origin: left;
  background: var(--accent);
  transition: background 0.3s linear; /* transform is driven frame-by-frame in JS, not transitioned */
}
#timer-num {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 0;
  transform: translateX(-50%);
  font: 700 13px "Inter", sans-serif;
  color: var(--accent);
  white-space: nowrap;
}
#timer-num::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 100%;
  transform: translateX(-50%);
  width: 2px;
  height: 6px;
  background: currentColor;
}

/* Per-duel "Correct!" / "Wrong" / "Time's Up" - takes the timer bar's exact
   place once a duel resolves (same swap as maps: the reveal state replaces
   the ticking bar with a verdict, not a second element beside it). Same
   accent/bad color language as .result-title (not good/bad green/red - that
   pair is reserved for the card fills themselves). */
.duel-status {
  width: 100%;
  font: 400 32px "Anton", sans-serif; /* matches .result-title's own size exactly */
  text-transform: uppercase;
  letter-spacing: 0.02em;
  text-align: center;
  color: var(--good); /* matches the correct-card fill color, not accent (2026-09-11) */
  margin: 0;
}
.duel-status.wrong { color: var(--bad); }

@media (max-width: 480px) {
  .duel-status { font-size: 24px; } /* matches .result-title's own mobile size */
}

/* flex-end, not center (2026-09-11 fix) - centering left extra empty space
   BELOW the (shorter) timer bar before .duel-stack's own 16px margin-top
   kicked in, breaking the exact bar-to-card gap the flags quiz uses.
   Bottom-aligning both states keeps that gap exactly 16px either way, and
   still reserves the same total height so cards never shift on swap. */
/* 40px, not 30px (2026-09-11) - .duel-status grew to match .result-title's
   32px size, which no longer fits the old 30px slot. flex-end still pins
   whichever is showing to the slot's bottom edge, so the exact 16px
   bar-to-card gap holds regardless of the slot's total height. */
.duel-status-slot { height: 40px; display: flex; align-items: flex-end; }

/* End-of-run recap (2026-09-11) - same idea as every geoquiz game's own
   end-of-run recap strip (a row of small per-round results shown above the
   score), adapted from a horizontal row of tiny flag/map thumbnails to a
   side-by-side "Name Value" pair per duel, since a duel's content (two
   player names + real values) doesn't compress into a small icon the way a
   flag does. Only the card actually tapped gets colored, and only its
   border (green if that tap was correct, red if it wasn't) - name/value
   text always stays grey, never filled (direct instruction, 2026-09-13). */
.duel-recap {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 18px;
  max-height: 260px;
  overflow-y: auto;
}
/* Side-by-side pair, one duel per line - same shape as the live .duel-stack
   cards (2026-09-13, replacing an earlier stacked-full-width layout). The 8px
   gap is within one duel's own pair; .duel-recap's own larger 6px gap is what
   separates one duel from the next. */
.recap-row { display: flex; flex-direction: row; gap: 8px; }
/* "Name (value)" as one centered line (2026-09-13, replacing an earlier
   name-dot-value split-alignment layout) - shrinks the card's height down to
   one text line of padding instead of two. Neither child has flex:1 (unlike
   the split layout) so the pair sizes to its own content and sits centered
   as a group; .recap-name still shrinks/ellipses first if the pair together
   is wider than the card, via flex's default shrink + min-width:0. */
.recap-entry {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 3px;
  padding: 5px 8px;
  border-radius: 8px;
  background: var(--tile);
  border: 1px solid var(--panel-edge);
  color: var(--text-dim);
}
/* The card the player actually tapped is marked, not judged (direct
   instruction, 2026-10-02: "instead of making the frame red or blue, just
   make the one picked the lighter grey color"). It takes --control-hover
   (#333333) against the --tile (#171717) its partner sits on, and keeps the
   same neutral border. That pair started as --control (#202020) vs --tile,
   widened the same day on "make the difference between selected and not
   selected a bit bigger" - two steps up the grey ramp rather than one.
   A timed-out duel (no tap) leaves both plain.

   .correct and .wrong deliberately resolve to the SAME look now. The classes
   still carry which it was, so the distinction is in the DOM if it is ever
   wanted again, but the verdict is read off the values instead: the accent
   value is the one that was higher, so "my grey card is not the accent one"
   is the whole story. Until 2026-10-02 this border was --good / --bad.

   Names, not frames, carry the verdict - see .recap-name.higher below. */
.recap-entry.correct,
.recap-entry.wrong { background: var(--control-hover); }
.recap-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 700 11px "Inter", sans-serif;
}
/* Which of the pair actually held more, on the NAME (direct instruction,
   2026-10-02 - it was on the value until then). This is a DIFFERENT fact
   from which card was tapped, which .recap-entry above marks in grey: so a
   wrong pick is a grey card whose name is red, sitting beside an untinted
   card whose name is the accent. Equal values leave both untinted, which
   cannot happen today (the generator dedupes on value) but would otherwise
   paint one side "lower" as a lie. */
.recap-name.higher { color: var(--accent); }
.recap-name.lower { color: var(--bad); }
.recap-value { flex-shrink: 0; font: 400 12px "Anton", sans-serif; }

@media (max-width: 480px) {
  .metric-q { font-size: 27px; } /* matches #round-label's own mobile bump in every geoquiz game */
}

/* ============================================================ GUESS THE TEAM
   Reveal a club's ex-players one at a time (fewest games for that club
   first), guess the club via a text search + autocomplete dropdown. Text
   only, no club logos/badges anywhere (they're protected - direct
   instruction) - identity during play is deliberately withheld entirely, and
   only shown on the result screen via name + that club's own accent color
   (set inline per-episode by quiz.js, never hardcoded here).
   Score is INVERTED vs the other two games (lower slot position = better),
   but the visual language (card shapes, Anton numerals, accent/good/bad
   tokens) is the same shared system throughout. */
.gt-goal {
  font: 700 19px "Inter", sans-serif;
  line-height: 1.35;
  color: var(--text-dim);
  text-align: center;
  margin: 12px 0 18px;
}
.gt-goal em { color: var(--accent); font-style: normal; }

/* The "club card" - only used for the REVEALED club chip on the result
   screen now (.gt-club-reveal below) - filled solid in that club's own
   color. The live guessing controls (search input + Guess/Skip) used to sit
   in a matching boxed card above the player list too, but that extra card
   was removed (direct instruction, 2026-09-12: "dont place them in another
   card") - they now sit directly on the page, kept compact instead. */
.gt-club-card {
  background: var(--tile);
  border: 1.5px solid var(--panel-edge);
  border-radius: 14px;
  padding: 22px 16px;
  margin-bottom: 14px;
}

/* Search input full-width on its own row, Guess + Next Player in a row
   below it (direct instruction, 2026-09-13 - was a single 50/25/25 row
   before). */
.gt-search { position: relative; margin-bottom: 10px; }
.gt-search-input {
  width: 100%;
  /* 16px, not smaller - below that, iOS Safari auto-zooms the whole page in
     on focus with no reliable way to zoom back out again afterward (same
     issue + fix as geoquiz's own .lb-name-input) - found live on a real
     phone, 2026-09-13. Full-width now anyway, so 16px costs nothing here. */
  font: 400 16px "Inter", sans-serif;
  color: var(--text);
  /* Visibly darker than the page's own card panel (var(--tile)) - direct
     instruction, 2026-09-12 - so the input reads as a distinct control. */
  background: var(--bg);
  border: 1.5px solid var(--panel-edge);
  border-radius: 10px;
  padding: 0 14px;
  height: 48px;
  box-sizing: border-box;
}
.gt-search-input:focus { outline: none; border-color: var(--accent); }
.gt-suggestions {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 5;
  list-style: none;
  margin: 0;
  padding: 4px;
  max-height: 220px;
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--panel-edge);
  /* 10px, with the field above it, the history rows and the reveal chip. It
     sat at 12 through the pass and two notes below wrongly counted it as 10
     already; it was in fact the last 12px corner on these two pages. */
  border-radius: 10px;
  box-shadow: 0 12px 30px -10px rgba(0, 0, 0, 0.7);
}
.gt-suggestion {
  padding: 9px 10px;
  border-radius: 8px;
  font: 400 14px "Inter", sans-serif;
  color: var(--text-soft);
  cursor: pointer;
}
.gt-suggestion.active { background: var(--accent-wash); color: var(--text); }

.gt-club-buttons { display: flex; gap: 10px; margin-bottom: 14px; }
.gt-club-buttons .btn-primary,
/* No fill of its own. Whichever of these two buttons is live carries the
   accent, and quiz.js already decides which: updateGuessButtonState swaps
   .btn-primary and .btn-secondary between Guess and Next Player on every
   keystroke. So the accent belongs on .btn-primary, where it already is, and
   this rule is layout only.

   It used to paint EVERY .btn-secondary in this row accent, which cancelled
   that swap: the moment you typed a real club, Guess lit up and Next Player
   stayed exactly as blue, leaving two equally loud buttons at the one moment
   the choice between them matters. The note that justified it - "the two are
   never both live" - stopped being true as soon as a name was typed.

   Before you type, Next Player is still the obvious control: it carries
   .btn-primary then, while Guess is disabled. Nothing is lost.
   (direct instruction, 2026-10-03) */
.gt-club-buttons .btn-secondary {
  flex: 1;
  width: auto;
  margin-bottom: 0;
  padding: 12px 14px;
  font-size: 15px;
}
.gt-guess-btn:disabled { background: var(--control); color: var(--text-dim); cursor: default; }

.gt-history {
  list-style: none;
  margin: 0 0 6px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* Individually-bordered single-line cards. Padding trimmed back down a bit
   (direct instruction, 2026-09-12) while keeping the text sizes as they were
   - only the row's own height/breathing room shrinks. */
.gt-history-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 12px;
  background: var(--tile);
  border: 1px solid var(--panel-edge);
  border-radius: 10px;
}
/* Every revealed/interactive state uses the SAME accent/highlight border
   color now (direct instruction, 2026-09-12: "borders always highlight
   color") - wrong/won/active/missed no longer get their own red/green/blue
   distinction via color; only the missed row's DOTTED style (below) still
   sets it apart from a normal solid-border row. */
.gt-history-row.wrong { border-color: var(--accent); }
.gt-history-row.won { border-color: var(--accent); }
/* The currently-revealed, not-yet-resolved slot - awaiting a guess/skip,
   rendered inline in the same list rather than in a separate big card
   (2026-09-12). */
.gt-history-row.active { border-color: var(--accent); background: var(--accent-wash); }
/* Every one of the 10 slots is visible from the start of the round (direct
   instruction, 2026-09-12) - a slot not yet reached shows a muted "…"
   placeholder instead of disappearing, so the full-length list never
   reflows/jumps in height as the round progresses. */
.gt-history-row.upcoming { opacity: 0.5; }
.gt-history-row.upcoming .gt-history-name { color: var(--text-dim); font-weight: 400; }
.gt-history-pos {
  flex: 0 0 auto;
  width: 20px;
  text-align: center;
  font: 700 11px "Inter", sans-serif;
  color: var(--text-dim);
}
.gt-history-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 700 15px "Inter", sans-serif;
  color: var(--text);
}

/* Guess the Player only - a club's tenure years sits as a smaller, dimmer
   second line directly under its name, rather than competing with games/
   goals for horizontal room in an already-tight row (mobile width was the
   deciding factor for every other gt-history tuning pass on this
   component). .gt-history-main wraps BOTH lines as a flex:1 column; the
   nested .gt-history-name needs flex:none here (not the flex:1 it has as
   Guess the Club's own standalone row child above) so it sizes to its own
   content height instead of stretching to fill the column's cross axis. */
.gt-history-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.gt-history-main .gt-history-name { flex: none; }
.gt-history-years {
  font: 400 12px "Inter", sans-serif;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The player's still-ongoing stint ("since 2025") reads as a highlighted
   fact, not just another line of metadata - accent-colored + bold, same
   language the rest of the site already uses for "this is the live thing"
   (direct instruction, 2026-09-14). */
.gt-history-row.current .gt-history-years { color: var(--accent); font-weight: 700; }

/* Two stat groups (games/goals), each a big Anton number + a small dim
   label, side by side with a gap between them instead of a plain comma-
   separated text line - direct instruction, 2026-09-12: "make the display of
   games and goals a bit nicer, no comma, maybe use the other font". */
.gt-history-stats { flex: 0 0 auto; display: flex; gap: 12px; }
.gt-history-stat { display: inline-flex; align-items: baseline; gap: 3px; white-space: nowrap; }
/* Fixed-width, right-aligned - "GAMES"/"GOALS" is the same text every row, so
   once the NUMBER before it stops varying in width (1 digit vs 3 digits),
   the whole stat block is the same width every row, and both labels land at
   the exact same X position on every card (direct instruction, 2026-09-12).
   min-width covers up to 3 digits at this font size. */
.gt-history-stat-num { font: 400 17px "Anton", sans-serif; color: var(--text); min-width: 27px; text-align: right; }
.gt-history-stat-label {
  font: 700 9px "Inter", sans-serif;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-dim);
}
/* The auto-burned "invisible" slot after a wrong guess - never actually
   revealed to the player, shown only as a locked placeholder in the history
   list. Dotted + accent-colored border (direct instruction, 2026-09-12) -
   the dotted style is what sets it apart from every other (solid-border)
   row now that all border colors match. */
.gt-history-row.missed {
  border-style: dotted;
  border-color: var(--accent);
  background: transparent;
  color: var(--text-dim);
}
/* No font-weight here: Anton ships one weight and the scoped rule sets 400
   at (1,1,1), which beats this (0,3,0) - declaring 700 only looked like it
   did something. */
.gt-history-row.missed .gt-history-name { color: var(--text-dim); flex: 1; }

/* Result screen - the answer, revealed as a .gt-club-card (see above) filled
   solid in that club's own color (set inline per-episode by quiz.js, both
   background AND text color - many real club colors are very dark or very
   saturated (Juventus
   black, Chelsea navy, Inter Milan navy...), so using one as a plain
   foreground text color on this page's own dark background is illegible for
   most of the 54 eligible clubs, not just an edge case (found via local
   Playwright test, 2026-09-12: Juventus's reveal text was black-on-near-
   black). A solid fill with its OWN contrasting text (computed from that
   color's luminance, see quiz.js's pickTextColorFor) is legible regardless of
   how dark/light the specific club color is. */
.gt-club-reveal {
  text-align: center;
  font: 400 28px "Anton", sans-serif;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 1.2;
}
/* Guess the Player's own reveal chip - players don't have an inherent
   accent color the way clubs do, so this is a fixed accent fill (no
   per-episode inline JS color like Guess the Club's own .gt-club-reveal
   needs) rather than computing readable text per-color at runtime. */
/* Fill and text colour live in Guess the Player's own block below, which is
   the only page this id exists on - the accent fill this used to set was
   fully overridden there and stating it twice only invited them to drift. */

@media (max-width: 480px) {
  .gt-club-card { padding: 16px 12px; }
  .gt-search-input { height: 44px; }
  .gt-club-buttons { gap: 6px; }
  .gt-club-buttons .btn-primary,
  .gt-club-buttons .btn-secondary { padding: 10px 8px; font-size: 14px; }
  .gt-club-reveal { font-size: 22px; }
  .gt-history-row { padding: 6px 10px; }
  .gt-history-name { font-size: 13px; }
  .gt-history-years { font-size: 11px; }
  .gt-history-stat-num { font-size: 15px; min-width: 24px; }
  .gt-history-stats { gap: 8px; }
}

/* --- Order the Player (2026-09-19) --------------------------------------
   Ported from the geography site's own Order the Country (its sibling
   design, right down to the class names - .arrange-* everywhere - kept
   as-is rather than renamed, same as this game's own API endpoints/
   localStorage keys use "order" while the underlying data/scoring is
   still literally the arrange-style footrule-distance mechanic under a
   new name). Two real differences from the geography original: no flag
   icons (players don't have one - .arrange-card here is just a centered
   name, no .arrange-flag rule needed at all), and no highestPointM-style
   secondary "peak" line in the reveal value column (every metric here is
   a single plain number/currency figure, so .reveal-value-peak never
   gets used - .reveal-value-elev alone carries the whole value text). */
.arrange-endpoint-label {
  font: 400 12px "Inter", sans-serif;
  color: var(--text-dim);
  text-align: center;
  margin: 0;
}
.arrange-endpoint-label.top { margin-bottom: 4px; }
.arrange-endpoint-label.bottom { margin-top: 4px; margin-bottom: 18px; }

.arrange-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.arrange-row {
  display: flex;
  align-items: stretch;
  gap: 8px;
  position: relative;
  transition: transform 0.15s ease;
}
.arrange-card {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--tile);
  border: 1.5px solid var(--panel-edge);
  border-radius: 14px;
  padding: 10px;
  cursor: grab;
  touch-action: none;
}
.arrange-row.dragging .arrange-card {
  cursor: grabbing;
  border-color: var(--accent);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45);
}
.arrange-name {
  font: 700 18px "Inter", sans-serif;
  line-height: 22px;
  color: var(--text);
  text-align: center;
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
}
.arrange-arrow {
  width: 22.5px;
  border: 1px solid var(--panel-edge);
  border-radius: 12px;
  background: var(--tile);
  color: var(--text);
  font-size: 16px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.arrange-arrow:disabled { opacity: 0.25; cursor: default; }
@media (hover: hover) {
  .arrange-arrow:not(:disabled):hover { border-color: var(--accent); }
}
.arrange-arrow:not(:disabled):active { border-color: var(--accent); }

/* --- reveal: your order vs. correct order, plus the value ------------- */
.reveal-grid {
  display: grid;
  grid-template-columns: 1fr 1fr auto;
  column-gap: 8px;
  row-gap: 6px;
}
.reveal-head {
  text-align: left;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--panel-edge);
  margin-bottom: 4px;
}
.reveal-head-pct {
  margin: 0 0 2px;
  font: 400 15px "Inter", sans-serif;
  /* The bright grey, both columns (direct instruction, 2026-10-01). "Your
     Order" held the accent and "Correct Order" the quiet grey; the two are a
     pair and should read as one. The same change Rank the Player's
     .comparison-col-pct and both of Geography's equivalents took the same day,
     so the .muted variant that carried the second colour is gone from all
     four. */
  color: var(--text);
}
.reveal-head-title {
  margin: 0;
  font: 700 9px "Inter", sans-serif;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.reveal-head-value {
  margin: 0;
  font: 700 9px "Inter", sans-serif;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-dim);
  align-self: end;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--panel-edge);
  margin-bottom: 4px;
  text-align: right;
}

.reveal-row {
  display: flex;
  align-items: baseline;
  gap: 4px;
  min-width: 0;
}
.reveal-rank { font: 700 10px "Inter", sans-serif; color: var(--text-dim); flex-shrink: 0; }
.reveal-name {
  font: 600 10px "Inter", sans-serif;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
/* "Your Order" column only (direct instruction, 2026-09-25): right in
   THIS site's own blue accent, wrong in red. Geography's Order the Country
   does the same thing with its own --good, which is currently the same hex
   as its accent, so each site states a
   correct answer in its own colour rather than sharing one green - while the
   wrong red is shared, so "wrong" never changes meaning as a player moves
   between them. That red used to be hardcoded here as #ff496e; on
   2026-09-29 it became this site's --bad, which is the same value, so the
   cross-site match holds and the page no longer carries a literal.
   The "Best Picks" column stays neutral: it's the answer key, not a
   verdict. Both spans are recoloured so the "#n" rank carries the verdict
   too, not just the name. */
.reveal-row.reveal-correct .reveal-rank,
.reveal-row.reveal-correct .reveal-name { color: var(--accent); }
.reveal-row.reveal-wrong .reveal-rank,
.reveal-row.reveal-wrong .reveal-name { color: var(--bad); }
.reveal-value {
  text-align: right;
  white-space: nowrap;
  font-size: 11px;
  transform: translateY(-1px);
}
.reveal-value-elev {
  font: 700 11px "Inter", sans-serif;
  color: var(--text-dim);
  margin-left: 5px;
}

/* --- Build the Team (2026-09-19) ---------------------------------------
   Real design pass, ported from a Claude Design Canvas mockup built and
   iterated with the site owner directly (many rounds of pixel-level
   feedback - see [[buildtheteam-format-design]]). Two pieces: the picking
   screen (a goal statement - which moved into the page header on 2026-09-29
   and now names the metric FAMILIES rather than the round's own metric -
   one round's 4 candidates as 2-line cards, and a football pitch showing
   all of that formation's slots at once: filled/current/upcoming) and the
   reveal screen (ported near-verbatim from Rank the Player's own result panel -
   score/percentile/leaderboard/share+keep-playing - with a simplified
   2-column Your Team / Perfect Team comparison below the divider, no
   metric-name column since an episode only ever has one metric). */
.bt-round-position {
  font: 700 15px "Inter", sans-serif;
  color: var(--text);
  text-align: center;
  margin: 8px 0 14px;
}
.bt-candidates {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-bottom: 8px;
}
.bt-candidate {
  height: 64px;
  box-sizing: border-box;
  font: 700 15px "Inter", sans-serif;
  color: var(--text);
  background: var(--tile);
  border: 1.5px solid var(--panel-edge);
  border-radius: 14px;
  padding: 4px 8px;
  cursor: pointer;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
}
@media (hover: hover) {
  .bt-candidate:not(:disabled):hover { border-color: var(--accent); }
}
.bt-candidate:disabled { opacity: 0.5; cursor: default; }
.bt-candidate-line {
  line-height: 1.2;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --- pitch (picking screen) --------------------------------------------
   Field rotated 90 degrees (2026-09-19 design pass): goal-to-goal now runs
   left-to-right instead of top-to-bottom, and the field is shorter overall
   (height is 5:4 of width instead of the old portrait 3:4). aspect-ratio
   alone (no flex-grow - .card/.play-area aren't a fixed-height flex chain
   on this site, so flex-grow here would have nothing to grow into) - width
   comes from the card's own content width. It also runs 4px past the
   card's own padding on each side (a deliberate slight overhang, not a
   bug) via the negative margins below. */
.bt-pitch {
  position: relative;
  width: calc(100% + 8px);
  margin-left: -4px;
  margin-right: -4px;
  aspect-ratio: 5 / 4;
  container-type: inline-size; /* lets the marker text below size itself in cqw (see .bt-pitch-marker) */
}
.bt-pitch-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.bt-pitch-markers {
  position: absolute;
  inset: 0;
}
/* Marker cards SCALE WITH THE PITCH (2026-09-19, real-device report: "the
   bigger the screen, the bigger the field, but the cards stay the same size
   and overlap"). The design was tuned on a 390px-wide phone where the pitch
   renders 336 x 268.8px with 91.8 x 48px cards, so every dimension below is
   that px value as a fraction of the reference pitch: the box in plain
   percentages of the pitch (its containing block), the text in cqw
   (container-query units, .bt-pitch is the container - the px fonts before
   the @supports block are the fallback for browsers without them). left/top
   is the marker's CENTER (set by buildPitchLayout, also in percent), hence
   the translate. Small floor on the fonts so 320px phones stay legible. */
.bt-pitch-marker {
  position: absolute;
  width: 27.32%;   /* 91.8 / 336 */
  height: 17.86%;  /* 48 / 268.8 */
  transform: translate(-50%, -50%);
  cursor: pointer;
}
@media (hover: hover) {
  .bt-pitch-marker:hover .bt-pitch-card { border-color: var(--accent); }
}
.bt-pitch-card {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  background: var(--tile);
  border: 1.5px solid var(--panel-edge);
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 2px 4px;
}
@supports (font-size: 1cqw) {
  .bt-pitch-card { border-radius: 2.976cqw; padding: 0.595cqw 1.19cqw; gap: 0.893cqw; } /* 10 / 336, 2 / 336, 4 / 336, 3 / 336 */
  .bt-pitch-card-name { font-size: max(11px, 3.571cqw); } /* 12 / 336 */
}
.bt-pitch-card.current { background: var(--accent); border-color: var(--accent); }
.bt-pitch-card.filled { background: rgba(var(--accent-rgb), 0.6); border-color: var(--accent); }
.bt-pitch-card-name {
  font: 700 12px "Inter", sans-serif;
  color: var(--text-dim);
  text-align: center;
  line-height: 1.15;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bt-pitch-card.current .bt-pitch-card-name,
.bt-pitch-card.filled .bt-pitch-card-name { color: var(--on-accent); }

/* --- reveal: Your Team / Perfect Team comparison ------------------------ */
.bt-comparison-list { display: flex; flex-direction: column; }
.bt-comparison-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: end;
  margin-bottom: 8px;
}
.bt-comparison-header { margin-bottom: 10.7px; }
.bt-comparison-col-left { padding-right: 16px; }
.bt-comparison-col-right { padding-left: 4px; }
.bt-comparison-col-pct {
  margin: 0 0 2px;
  min-width: 0;
  text-align: center;
  font: 400 17px "Inter", sans-serif;
  /* The bright grey, both columns (direct instruction, 2026-10-01). Your own
     total held the accent and the episode's best the quiet grey; the two are a
     pair and should read as one. This was the last of the five summary screens
     still on the two-colour treatment - Rank the Player, Rank the Country,
     Order the Country and Order the Player all took the same change earlier
     the same day, and the .muted variant that carried the second colour is now
     gone from every one of them. */
  color: var(--text);
}
/* "YOUR TEAM" / "PERFECT TEAM" take the quiet grey (direct instruction,
   2026-10-02). Same value and same reasoning as Order the Player's own
   .reveal-head-title: these are labels for the columns, not results, so they
   sit behind the numbers and names they head. */
.bt-comparison-col-title {
  margin: 0;
  min-width: 0;
  text-align: center;
  font: 700 9px "Inter", sans-serif;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.bt-comparison-entry {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  line-height: 1.25;
}
.bt-comparison-name {
  display: block;
  text-align: center;
  font: 700 10px "Inter", sans-serif;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
/* "Your Team" only (direct instruction, 2026-10-02): the name is the verdict,
   accent when that pick was the best of its four, --bad when it was not. The
   "Perfect Team" column deliberately keeps --text - it is the answer key, not
   a judgement, the same split Order the Player's .reveal-row.reveal-correct
   makes against its own neutral right column. Both selectors are (0,0,3,0),
   so they cannot fight: .wrong and :not(.wrong) are mutually exclusive. */
.bt-comparison-entry.bt-comparison-col-left:not(.wrong) .bt-comparison-name { color: var(--accent); }
.bt-comparison-entry.bt-comparison-col-left.wrong .bt-comparison-name { color: var(--bad); }
.bt-comparison-value { font: 700 10px "Inter", sans-serif; color: var(--text-soft); white-space: nowrap; }

/* --- Per-game SEO copy (2026-09-28) ------------------------------------
   Search Console reports the football game pages as "Discovered - currently
   not indexed" and the geography ones as "Crawled - currently not indexed",
   i.e. Google has judged them thin. Every game page also shipped the SAME
   <h1>Football</h1>, so six pages competed on one meaningless heading.

   Fix, applied to Guess the Player and Guess the Club first: the page's real
   <h1> is now the content heading in the .game-seo block below the card, and
   #site-title is demoted to a <div>. That keeps quiz.js working untouched
   (it only ever sets #site-title's textContent) and keeps the header looking
   identical - but .site-header h1 in the shared region above no longer
   matches a <div>, so these three rules restate it by id. Written as
   additions here rather than as an edit to that shared region, so geography's
   own copy of it stays byte-comparable; the id selector matches an <h1> just
   as well, so nothing else was affected. As of 2026-10-01 the rollout is
   COMPLETE: all six football game pages AND this site's hub carry
   <div id="site-title">, as do all six geography games and the geography hub.
   TWO EXCEPTIONS as of 2026-10-06: /football/clubs/ and geography's
   /geography/countries/ carry <h1 id="site-title"> instead. Those two pages
   had no <h1> at all, and their wordmark ("Club Quizzes" / "Country
   Quizzes") is unique to the page rather than repeated across six, so it
   does not recreate the duplicate-heading problem this comment describes.
   Both selectors match them, which is why the styling is unchanged.
   The hub needed one extra rule (see .hub-wrap .site-header #site-title) because
   its wordmark is brighter than a game page's. */
.site-header #site-title {
  font: 400 40px/1 "Anton", sans-serif; /* was 42.29px - see .site-header h1 */
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin: 0;
  color: var(--text-soft);
}
.wrap:has(#play-area:not([hidden])) .site-header #site-title { font-size: 32px; margin-top: 8px; }

/* Sits BELOW the card, never above it - the game is what people came for,
   same call as the hub's own prose placement.

   A native <details>, collapsed by default (2026-09-28): a returning player
   doesn't need the rules in the way every visit, but the text still has to be
   there for crawlers. <details> keeps it in the HTML whether open or shut, so
   it is indexed either way - which is exactly why this must never become a
   "load more" that FETCHES the copy on click. Google indexes collapsed
   content; it cannot index content that isn't in the document.

   The page's <h1> lives inside the <summary>, so the heading stays visible
   when folded and only the prose hides. */
/* The only way out of a game that is not the logo, and it is always visible:
   start screen, DURING play, and on the result screen. It replaces an older
   link that lived inside the result panel and appeared only on the real daily
   summary, which meant a player who found a game too long had no exit at all -
   and the browser back button sends someone who arrived from a search result
   back to the search results, not into the site. So the exit already existed;
   it just pointed away.

   Ported from the geography repo the same day (2026-10-06) and identical
   there. Typography matches the "More about" summary directly below it so it
   reads as the same quiet tier of furniture; the one departure is the colour,
   because at --text-dim it carried exactly the weight of a fold toggle and
   would not have read as navigation. */
.hub-back {
  display: block;
  text-align: center;
  margin-top: 18px;
  color: var(--accent);
  font: 700 11px "Inter", sans-serif;
  letter-spacing: 0.04em;
  text-decoration: none;
}
.hub-back:hover { text-decoration: underline; }

/* No rule between the back link and the fold below it, and 18px either side of
   that link so the stack below the card is evenly spaced. Scoped to pages where
   .hub-back actually precedes .game-seo - the hub keeps its own divider, since
   nothing sits above it. Ported from geography, 2026-10-06. */
.hub-back + .game-seo { border-top: none; padding-top: 0; margin-top: 18px; }
.game-seo {
  /* No divider above the fold anywhere on the site (owner, 2026-10-06).
     The game pages lost it when the back link went in above them; the hubs
     and the portal follow. padding-top goes with it - it only existed to
     space the copy below the line. */
  margin-top: 24px;
  padding-top: 0;
  color: var(--text-soft);
  font-size: 13px;
  line-height: 1.6;
  text-align: left;
}
/* Deliberately quiet (direct instruction, 2026-09-28: "its SEO, its not
   something people care about"). Small, centered, --text-dim, with a matching
   chevron on EACH side so it reads as a symmetrical control rather than a
   heading. The game name is written in caps in the HTML, not via
   text-transform, so only that half is uppercase while "More about" stays
   sentence case. */
.game-seo > summary,
.reveal-fold > summary {
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font: 700 11px "Inter", sans-serif;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  /* Both needed to drop the default disclosure triangle - list-style covers
     Firefox and modern Chrome, the pseudo-element covers older WebKit. */
  list-style: none;
}
.game-seo > summary::-webkit-details-marker,
.reveal-fold > summary::-webkit-details-marker { display: none; }
.game-seo > summary:focus-visible,
.reveal-fold > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
/* Chevrons drawn from borders rather than a glyph, so they scale with the
   text's own colour via currentColor. Point down when shut, up when open. */
.game-seo > summary::before,
.game-seo > summary::after,
.reveal-fold > summary::before,
.reveal-fold > summary::after {
  content: "";
  flex: 0 0 auto;
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 0.15s ease;
}
.game-seo[open] > summary::before,
.game-seo[open] > summary::after,
.reveal-fold[open] > summary::before,
.reveal-fold[open] > summary::after { transform: translateY(1px) rotate(-135deg); }
.game-seo[open] > summary,
.reveal-fold[open] > summary { margin-bottom: 16px; }
@media (hover: hover) {
  .game-seo > summary:hover { color: var(--text-soft); }
}
@media (prefers-reduced-motion: reduce) {
  .game-seo > summary::before,
  .game-seo > summary::after { transition: none; }
}
/* The fold's own title. It sat INSIDE the fold as the page's real <h1>
   (direct instruction) until 2026-10-03, when the <h1> moved to the visible
   .start-title on the card and this became an <h2 class="seo-title"> keeping
   the identical styling - Google's title-link doc asks for the title text in
   the first VISIBLE <h1>, and this fold is shut by default. */
.game-seo h1,
.game-seo h2.seo-title {
  font: 400 20px "Anton", sans-serif;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text);
  margin: 0 0 10px;
}
/* Ported verbatim from the geography stylesheet 2026-10-01 - the explainer
   folds use the same markup on both sites, and football had the h1 rule but
   never the h2/list rules, so its headings fell back to browser defaults. */
/* Section heads inside the fold (2026-10-01). The prose grew subheadings so a
   player who does not get the game can find the rules, and so each answer is
   its own addressable block for search/LLM extraction. One step down the Anton
   ladder from the h1 (20 -> 16) and it stays 16 in the phone tier, where the
   h1 drops to 18 - 16 is the smallest size on that ladder. :first-of-type has
   no top margin because the h1 above it already supplies the gap. */
.game-seo h2:not(.seo-title) {
  font: 400 16px "Anton", sans-serif;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text);
  margin: 22px 0 8px;
}
/* On the HUBS the fold's title is still an <h1>, so :first-of-type finds the
   real first section head. On the GAME pages (2026-10-03) that title is an
   <h2.seo-title>, so the first section head is the SECOND h2 and is reached
   through the title instead - both arms set the same 18px. */
.game-seo h2:not(.seo-title):first-of-type,
.game-seo h2.seo-title + p + h2 { margin-top: 18px; }
.game-seo p { margin: 0 0 12px; }
/* Enumerable facts in the fold are a real list, not prose (2026-10-01): a
   model asked to name them copies a list verbatim and paraphrases a
   sentence. One column by default, because that fits any item of any length
   at any width - a list only goes to two columns when it opts in below. */
.game-seo ul {
  margin: 0 0 12px;
  padding: 0;
  list-style: none;
}
.game-seo li { margin: 0 0 3px; padding-left: 11px; position: relative; }
/* The bullet is drawn by ::before rather than list-style, because a native
   marker in the two-column variant hangs into the gutter instead of into
   the item's own box. */
.game-seo li::before {
  content: "";
  position: absolute;
  left: 3px;
  top: 0.62em;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--text-dim);
}
/* Opt-in two columns, for a list of EIGHT SHORT items (Match the Country's
   category labels, the only one so far). A GRID, not `columns: 2`: multicol
   splits the width evenly, which left the short left column with room it
   could not use while the longest item wrapped on the right. Column flow
   down 4 rows sizes each column to its own content instead.

   The 4 is the item count halved, so this class does not transfer to a list
   of a different length - Order the Country's six measures tried it and
   split 4 + 2, with two items wrapping. A longer or shorter list either
   stays one column or gets its own row count here. */
.game-seo p { margin: 0 0 12px; }
.game-seo p:last-child { margin-bottom: 0; }
.game-seo a { color: var(--accent); text-decoration: none; }
@media (hover: hover) {
  .game-seo a:hover { text-decoration: underline; }
}

/* Cross-topic links (2026-10-16, four topics): a link to another topic is in
   THAT topic's colour, set on the link itself so it works in any fold. The
   same four lines in all four stylesheets. */
.game-seo a.geo-link, .portal-about a.geo-link { --accent: #6ee7b7; }
.game-seo a.football-link, .portal-about a.football-link { --accent: #7ec6ff; }
.game-seo a.brain-link, .portal-about a.brain-link { --accent: #FF784F; }
.game-seo a.chess-link, .portal-about a.chess-link { --accent: #FF6172; }

@media (max-width: 480px) {
  .wrap:has(#play-area:not([hidden])) .site-header #site-title { font-size: 24px; }
  .game-seo { font-size: 12.5px; margin-top: 24px; padding-top: 0; }
  .game-seo h1,
  .game-seo h2.seo-title { font-size: 18px; }
  .game-seo > summary { font-size: 10.5px; }
}

/* ====================================== ALL SIX FOOTBALL GAMES
   One block. The first two are the same game reversed - identical .gt-*
   markup - and all six share the header, the card, the buttons, the result
   panel and the SEO block, so everything here applies to all of them unless
   a rule is marked otherwise. Rules that only suit one game are scoped to an
   id only that game has, further down; the .gt-* rules here simply find
   nothing to match on the pages without that markup.

   The scope is #play-area, which every game page has and the hub does not.
   It used to list one id per game - a 95-character selector repeated 67
   times, which every new game had to be threaded into by hand, and which was
   edited three times on 2026-09-29 alone. Specificity is unchanged at
   (1,0,1): :has() takes the weight of its most specific argument, and that
   is an id either way, so every fight already won against the shared rules
   stays won. A seventh game now joins by having a play area.

   Page-scoped overrides (2026-09-29). Everything above this line is shared -
   the header, .card, the buttons and the whole .gt-* block are used by other
   football games and, for the shared region, by geography as well - so
   anything tuned for THIS page only belongs down here, never up there.

   Each of those is scoped to an id only its own page carries -
   #gt-player-reveal, #gt-club-reveal, #duel-stack, #rank-grid, #arrange-list,
   #bt-pitch. No markup change is needed for any of them, so every tweak
   stays a CSS-only edit, which the dev server hot-swaps without reloading
   whatever round you are looking at.

   Specificity note: (2,1,1) here against (1,1,0) for the shared rules, so
   these win on weight and do not depend on source order. */
body:has(#play-area) .site-header #site-title {
  /* Picked by contrast ratio, not by eye: 16.98:1 against the page's pure
     black - the value asked for. Was var(--text-soft) #cfcfcf (13.48:1). */
  color: var(--text);
}

/* The goal line now lives in the SITE HEADER, directly under #site-title,
   not inside the card - it completes the sentence the title starts once a
   round is running ("GUESS THE PLAYER" / "from his club history"), so it
   belongs to the title rather than to the controls.

   Hidden until #play-area is showing: on the intro screen the title above
   still reads "Football", and a fragment beginning "from his" would have
   nothing to continue. .site-header is already text-align:center, so no
   centring is needed here.

   No margin-bottom - the gap down to the card is the header's own
   margin-bottom, which game mode already sets. */
body:has(#play-area) .gt-goal {
  display: none;
  /* The whole line in accent, not just the <em> on "club history". 11.41:1
     on the page's black. The em rule further up still sets the accent and
     font-style:normal, which is now only doing the italic reset - the colour
     half is redundant while this stays accent, but harmless, and it is what
     keeps the markup meaningful if this ever goes back to a grey line with
     one highlighted phrase. */
  color: var(--accent);
  /* Anton like the title, but sentence case rather than the uppercase every
     other Anton rule here uses (.start-title, .result-title, #site-title):
     caps directly under caps read as a stutter. */
  font: 400 20px/1.35 "Anton", sans-serif;
  text-transform: none;
  letter-spacing: 0.02em;
  /* 0, not 2px: #site-title above takes 2px more margin-top to sit closer to
     this line, and this gives the same 2px back so the subtitle itself stays
     exactly where it was. The pair moves together; the page below does not
     shift.

     The bottom carries the ENTIRE gap down to the card - the header's own
     margin-bottom is zeroed in game mode (below) so there is one source for
     it. 24px to match the card's inset on all four sides, minus 0.28em
     because this gap starts at TEXT, not at an edge: a line box does not end
     where its letters do, and at 20px/1.35 there is ~5.6px of dead space
     below the baseline. Without the subtraction the gap measures 24 and
     reads as ~30. The em resolves against this rule's own 20px, which is why
     the correction lives here rather than on .site-header. */
  margin: 0 0 calc(24px - 0.28em);
}
body:has(#play-area) .wrap:has(#play-area:not([hidden])) .gt-goal {
  display: block;
}

body:has(#play-area) .gt-search-input {
  /* One size for the whole control group, at both tiers. 16px rather than
     the buttons' inherited 15/14 because this is the floor iOS Safari
     imposes: under 16px it zooms the page in on focus and will not reliably
     zoom back out (hit on a real phone, 2026-09-13). The buttons are raised
     to meet the field rather than the field lowered to meet them. */
  font-size: 16px;
  /* The page background, which is pure black. The field then sits at the same
     level as the PAGE, one step below the card it is inset in (page -> card
     #101010 -> field back to the page), which reads as punched through. Its
     edge comes from the var(--panel-edge) border, not the fill, so nothing is
     lost by taking the fill all the way down. This was a literal #000 against
     a --bg of #0f0f0f until the token itself went black on 2026-09-29. */
  background: var(--bg);
  /* --text, which stepped down to #e7e7e7 in the same pass. The #f4f4f4 it
     replaced ran at 19.09:1 on black - past the point where halation shows,
     and this is 16px text you look at while typing, which is exactly the case
     it bites. #e7e7e7 gives 16.98:1, the ratio the page title runs at. */
  color: var(--text);
}
body:has(#play-area) .gt-suggestions {
  /* The page background, matching the field it drops out of - at --panel it
     was the same colour as the card, so the list read as a separate surface
     from its own input. Its own border and shadow still separate it from the
     card. */
  background: var(--bg);
}
body:has(#play-area) .gt-search-input::placeholder {
  /* Was never styled at all - the browser default (#757575) was showing
     through, which is not one of this site's colours and measured 4.16:1.
     --text-dim is the site's own token for quiet text and is what the years,
     the GAMES/GOALS labels and the row numbers already use, so the page has
     ONE grey for secondary text rather than two near-identical ones. 7.37:1
     on the black field. */
  color: var(--text-dim);
  opacity: 1; /* Firefox dims ::placeholder by default; without this the token
    would not land at the value it says. */
}

/* --- darker surfaces (2026-09-29) --------------------------------------
   The page read grey overall because its three stacked surfaces all sat in
   the #16-#2a band. Each is shifted down by roughly the same amount, so the
   relationships between them are preserved and only the overall cast
   changes. Redefining the TOKENS inside this page's scope rather than
   restating each rule: every use of --panel/--tile/--control on this page
   follows automatically, including ones not listed here.

   --panel-edge deliberately does NOT move. With the fills darker and the
   borders where they were, the borders carry more of the structure and
   everything reads MORE defined, not less. */
body:has(#play-area) {
  /* The three surfaces this block used to set are in :root now - see there.
     What stays is the control geometry, which IS particular to a play screen. */
  /* One height for the whole control group - the field and the two buttons
     under it are a single "type, then act" unit, and they were 6px apart on
     desktop and 7.3px on mobile. Small enough not to be nameable, big enough
     to make them read as unrelated parts. Matches what the field already
     was, so only the buttons move. */
  --control-h: 48px;
  /* One gap for the whole control group, used BOTH between the two buttons
     (horizontally) and between the field and the button row (vertically), so
     the group is spaced identically in both directions and the two can never
     drift apart. */
  --control-gap: 10px;
}
@media (max-width: 480px) {
  body:has(#play-area) { --control-h: 44px; --control-gap: 6px; }
}
body:has(#play-area) .gt-search-input,
body:has(#play-area) .gt-club-buttons .btn-primary,
body:has(#play-area) .gt-club-buttons .btn-secondary {
  height: var(--control-h);
}
/* The buttons are display:block with text-align:center, which centres their
   label horizontally but not vertically - with a height now set rather than
   derived from padding, the text would sit high without this. The revealed
   history rows are deliberately NOT given --control-h: they hold a club
   name, its years and two stats, so a fixed height would clip or be
   overridden the moment a long name wraps. They land on ~46px on their own,
   which is close enough that the card still reads as one rhythm. */
body:has(#play-area) .gt-club-buttons .btn-primary,
body:has(#play-area) .gt-club-buttons .btn-secondary {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* One look for every control you cannot press (direct instruction,
   2026-09-29), taken from Order the Player's own disabled arrow: a --control
   fill and a --text label, the whole thing at 25% opacity. Over this page's
   #101010 card that paints #141414 behind #464646 - far enough down that the
   button reads as absent rather than merely quiet, which is the distinction
   an earlier outline treatment here was reaching for.

   Ported as the CONSTRUCTION, not as those two hex values: opacity composites
   against whatever is actually behind the button, so it stays correct if a
   surface changes, and it keeps following --control and --text rather than
   freezing two literals that belong to no token.

   .btn-primary:disabled covers Guess-before-you-type, Start-while-loading,
   and Who Has More?'s continue button between duels. #submit-btn.not-ready
   is listed separately because Rank the Player's Submit is deliberately NOT
   disabled when the board is unfinished - it stays clickable so a tap can
   explain what is missing - but it should look just as unpressable. */
body:has(#play-area) .btn-primary:disabled,
body:has(#play-area) .btn-secondary:disabled,
body:has(#rank-grid) #submit-btn.not-ready {
  background: var(--control);
  border-color: var(--panel-edge);
  color: var(--text);
  opacity: 0.25;
  cursor: default;
}

/* The control group is spaced by --control-gap in BOTH directions - between
   the two buttons, and between the field and the button row - and the break
   below it is exactly twice that. So the group always sits half as far from
   itself as it does from the content, at any breakpoint, which is what makes
   it read as one "type, then act" unit rather than three evenly stacked
   blocks. Deriving the break with calc() rather than writing a number means
   that 1:2 relationship survives any later change to --control-gap.

   Resulting rhythm: 6 (between list rows) / 6 / 12 (mobile), and 10 / 20 on
   desktop, under an 18px gap below the goal line.

   The field-to-buttons gap has to be a margin on .gt-search rather than a
   flex gap, because the field and the button row are separate siblings, not
   two children of one container. */
body:has(#play-area) .gt-search {
  margin-bottom: var(--control-gap);
}
body:has(#play-area) .gt-club-buttons {
  gap: var(--control-gap);
  margin-bottom: calc(var(--control-gap) * 2);
}
/* The card's inset is the same all the way round the field: top padding set
   to match the card's own left/right padding (24px, 16px at the narrow
   tier), so the field sits equidistant from three edges instead of being
   closer to the top than to the sides.

   Scoped to the play state. The intro card overrides its own horizontal
   padding to 29px and is a different composition, so it keeps the shared
   top padding rather than being dragged along with this. */
body:has(#play-area) .card:has(#play-area:not([hidden])) {
  padding-top: 24px;
  /* Bottom matches top, so the card's inset is the same on all four sides.
     It was 14px of padding plus the list's own 6px margin = 20, against 16
     at the top. The list's margin is zeroed just below so this padding is
     the only thing setting the gap - otherwise the two would have to be
     kept in step by hand. */
  padding-bottom: 24px;
}
body:has(#play-area) .gt-history {
  margin-bottom: 0;
}
@media (max-width: 480px) {
  body:has(#play-area) .card:has(#play-area:not([hidden])) {
    padding-top: 16px;
    padding-bottom: 16px;
  }
  /* Tracks the 16px inset at this tier, same 0.28em text correction. */
  body:has(#play-area) .gt-goal { margin-bottom: calc(16px - 0.28em); }
}
/* The one tier the rule above never reached: .card drops to 12px sides here
   but the 480px block's 16px top and bottom carried over, so the button sat
   13px in from the sides and 17px from the bottom. Same inset all round, at
   every width (direct instruction, 2026-10-09). */
@media (max-width: 375px), (max-height: 700px) {
  body:has(#play-area) .card:has(#play-area:not([hidden])) {
    padding-top: 12px;
    padding-bottom: 12px;
  }
  body:has(#play-area) .gt-goal { margin-bottom: calc(12px - 0.28em); }
}


/* Bright text on this page all lands on --text, the value the page title, the
   goal line and the typed input already use. One bright value, one quiet one
   (--text-dim). This read "instead of the shared --text #f4f4f4" from when
   the token held that; it holds #e7e7e7 itself now, so the rule restates the
   inherited value rather than replacing it - kept because the :not()s below
   are what actually do the work.

   The :not()s are load-bearing. An .upcoming or .missed row deliberately
   dims its own name to --text-dim via .gt-history-row.upcoming
   .gt-history-name, which is (0,3,0); this selector carries an id inside
   :has() and so scores (1,4,1) and would otherwise win and light those rows
   back up. Excluding them here keeps the dimming that marks a slot as
   not-yet-reached or burned. */
body:has(#play-area) .gt-history-row:not(.upcoming):not(.missed) .gt-history-name,
body:has(#play-area) .gt-history-stat-num {
  color: var(--text);
}

/* Resolved rows stand at 1.25x the control height - 60px against the
   buttons' 48 on desktop, 55 against 44 on a phone - so the list reads as
   the substantial part of the screen rather than as more rows of the same
   weight as the controls above it.

   Now actually tied to --control-h, which is what the comment always claimed:
   it was two literals and a media query, and said 1.5x/66/72 besides. The
   calc gives exactly the 60 and 55 that shipped, so nothing moves - it just
   cannot drift from the control height any more, and the breakpoint goes.
   Guess the Club's own copy of this note had the 1.25x right.

   min-height, never height: these rows carry a club name that can wrap on a
   long one ("Borussia Monchengladbach"), and a fixed height would clip it.
   The row is already display:flex with align-items:center, so the extra
   space distributes around the content on its own.

   .upcoming is excluded deliberately - those are unreached placeholders and
   their shortness is the signal that nothing is there yet. */
body:has(#gt-player-reveal) .gt-history-row:not(.upcoming) {
  min-height: calc(var(--control-h) * 1.25);
}

/* Tightens the row's two lines toward each other by 1px each. Relative
   offsets rather than line-height or margins, so neither the row's height
   nor the left-alignment of each label under its value is disturbed - the
   text simply paints 1px off where it was laid out. Both halves of each line
   move together: name with values, years with labels. */
body:has(#play-area) .gt-history-name,
body:has(#play-area) .gt-history-stat-num {
  position: relative;
  top: 1px;
}
body:has(#play-area) .gt-history-years,
body:has(#play-area) .gt-history-stat-label {
  position: relative;
  top: -1px;
}

/* The club name is the answer - the thing the round is actually about - and
   it was set at 13px, a pixel SMALLER than the button labels above it. With
   the rows now at 1.25x --control-h there is room to give it its due. The years move with
   it so the pair keeps its relationship rather than the gap widening. */
body:has(#play-area) .gt-history-name {
  font-family: "Anton", sans-serif;
  /* Anton ships a single weight. The inherited 700 would make the browser
     synthesise a fake bold by smearing the outlines, which on a condensed
     face closes up the counters and looks muddy. */
  font-weight: 400;
  /* 16px, not the 14 the values beside it use: Anton is condensed, so it
     reads a size or so smaller than Inter at the same number, and the club
     name is the answer - it should carry the row. shrinkToFit() takes the
     element's own font-size as its ceiling, so this is the starting size a
     long name shrinks down FROM, not a fixed one. */
  font-size: 16px;
  /* Wider than the 0.02em used elsewhere for Anton - a condensed face set
     small closes up, and the extra tracking keeps club names legible. */
  letter-spacing: 0.05em;
}
body:has(#gt-player-reveal) .gt-history-years {
  font-size: 13px;
}

/* Restacks each stat so the row reads as two aligned lines rather than four
   separate clusters:

       Albacete Balompie      22      5
       1993 to 1995           games   goals

   The value joins the club name's line and matches it exactly (Inter 700
   14px, in place of Anton 17px); the label drops to the years' line and
   matches those (13px, sentence case). So each line of the row is one
   typographic voice instead of four.

   flex-direction: column turns each num+label pair from a side-by-side
   ("22 games") into a stacked column, and align-items: flex-start puts the
   label's left edge under its value's. The old min-width/text-align:right on
   the value existed to right-align digits so the labels lined up across
   rows; stacking makes that unnecessary - the label is wider than any value
   here, so the label now sets each column's width and therefore its own
   position, identically on every row. */
body:has(#gt-player-reveal) .gt-history-stat {
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
}
/* More air between the games and goals columns. Only the games column moves:
   .gt-history-stats is flex:0 0 auto at the end of a row whose main block is
   flex:1, so the block is anchored to the row's right edge - widening it
   pushes its left side leftwards and leaves goals exactly where it was. */
body:has(#play-area) .gt-history-stats {
  gap: 16px;
}
body:has(#play-area) .gt-history-stat-num {
  /* Matches the club name exactly - Anton 400 at 16px with the same tracking
     - so the row's top line is one typeface at one size rather than a name
     in one voice and its numbers in another. */
  font: 400 16px "Anton", sans-serif;
  letter-spacing: 0.05em;
  min-width: 0;
  text-align: left;
}
/* The slot number is worth keeping - it is the only content the unreached
   rows have, and it is effectively the score, since the game is won by
   spending as few clubs as possible. What it did not need was its own
   typographic voice: at Inter 700 11px it was the one thing in the row not
   speaking either of the row's two languages. Anton lines it up with the
   values on the same line, and it stays --text-dim so it marks position
   without competing with the club name.

   Anton at 12px, a step under the 16px values on the same line: same face,
   so it belongs to the row, but clearly subordinate - it marks the slot
   rather than reading as data.

   Narrower too. It was a 20px box plus the row's 10px flex gap - 40.7px of a
   313px row, 13%, for a single digit. 14px plus a 6px gap halves that and
   gives the name back the space. */
body:has(#play-area) .gt-history-pos {
  width: 14px;
  font: 400 12px "Anton", sans-serif;
  letter-spacing: 0.05em;
  /* 4px of space after the number, set here rather than on the row's flex
     `gap`, which serves every child and would have widened the main-to-stats
     gap too.

     An earlier note here said this margin was paired with a 4px reduction in
     the row's left padding, the two cancelling so the number moved closer to
     the edge and the name stayed put. The padding reduction was never made -
     the rule below restates the base 12px/10px exactly - so nothing
     cancelled: the number did not move and the name sits 4px further right
     than that note describes. Left as it renders, since that is what has
     shipped for weeks; the description is what was wrong. */
  margin-right: 4px;
}
body:has(#play-area) .gt-history-row {
  gap: 6px;
}

body:has(#play-area) .gt-history-stat-label {
  font-size: 13px; /* tracks .gt-history-years - they share a line */
  font-weight: 400; /* years are 400 too */
  text-transform: none;
  letter-spacing: 0;
}

/* One size for every button on a play screen, wherever it lives: the
   field's own 16px - see that rule for why 16 is the floor there. The
   shared stylesheet leaves .btn-primary at 15, which would have the three
   controls in the Guess games speaking at two sizes.

   The list grew on 2026-09-29. Measuring across the six games found the
   same control at three sizes: Order the Player's Submit had been given 16
   by hand with no phone step, and Who Has More?'s continue button sat on
   .btn-primary's base 15 by default rather than by decision. Both join
   here, so one rule governs the lot.

   Rank the Player's Submit is deliberately NOT in this list. Its board is
   12px throughout, so 16 read a third larger than everything above it; it
   keeps its own 13px and 34px height (direct instruction, 2026-09-29). */
body:has(#play-area) .gt-club-buttons .btn-primary,
body:has(#play-area) .gt-club-buttons .btn-secondary,
body:has(#duel-stack) #continue-btn,
body:has(#arrange-list) #submit-btn {
  font-size: 16px;
}
/* 1px down on phones. Safe on a button and not on the field: the 16px
   floor is an iOS auto-zoom-on-focus rule, and it only applies to form
   fields the user can focus and type into. A button never triggers it. */
@media (max-width: 480px) {
  body:has(#play-area) .gt-club-buttons .btn-primary,
  body:has(#play-area) .gt-club-buttons .btn-secondary,
  body:has(#duel-stack) #continue-btn,
  body:has(#arrange-list) #submit-btn {
    font-size: 15px;
  }
}

/* No per-tier type scaling. Every size in this section applies at both
   widths: the phone tier was briefly given its own smaller set (subtitle 16,
   club name 15, years 12, row number 11) on the reasoning that the card
   loses a quarter of its width there, but a single set reads fine and is one
   less thing to keep in step.

   That includes the title. The shared stylesheet sets it to 32px and then
   drops it to 24px on phones, which against the 20px subtitle left a ratio
   of 1.20 - the two were nearly the same size and read as one doubled
   heading rather than a title and its subtitle. 30px at BOTH widths gives
   1.50 and keeps the pair in proportion everywhere - see the #site-title
   rule below for how one declaration beats both media queries. */
/* The gap from the title down to the card is the header's own 15px game-mode
   margin (the shared rule near the top), exactly as on the geography site.
   Until 2026-10-10 this rule zeroed it, because the .gt-goal subtitle under
   the title carried the gap instead; the chess pages show no subtitle in game
   mode any more (owner), so the zeroing went with it. */
/* The "DAILY CHALLENGES" kicker belongs to the intro screen, where this page
   mirrors its own card on the hub. Once a round starts, .gt-goal takes that
   slot with the game's own subtitle, so the two never show together. */
body:has(#play-area) .wrap:has(#play-area:not([hidden])) #hub-kicker {
  display: none;
}
/* The SEO fold is hidden while a round is running, the same way and for the
   same reason as the kicker above it. Safe for search: a crawler never enters
   play mode, because play starts on a click, so it only ever renders the start
   screen where this is present. It MUST stay a CSS rule for that to hold.
   "Back to all football games" deliberately does NOT hide - that one exists
   precisely for the player who wants out mid-round. Ported from geography,
   2026-10-06. */
body:has(#play-area) .wrap:has(#play-area:not([hidden])) .game-seo {
  display: none;
}
/* Wider tracking on the card subtitles - 0.03em is tight for 10px uppercase,
   where the letterforms close up. Two selectors because the line appears in
   two places that share no scope: a game's own intro screen, and its card on
   the hub. Every football game and every football hub card now has it; the
   base .start-rules keeps 0.03em for the geography site, which shares this
   component by copy and has not had its pass.

   .hub-card, not a list of six #hub-card-* ids: that list had to be extended
   by hand for each game and a seventh card would have silently missed the
   tracking - the same hazard the shared game block was refactored away from
   on 2026-09-29.

   em, not px: hub cards are scaled with zoom at the 375-511px tier. A
   relative value tracks that; a fixed one would read proportionally wider on
   the hub than in the game.

   The same cards on quizebra.com's portal are in the geography repo and
   still run at 0.03em. */
body:has(#play-area) .start-rules,
.hub-card .start-rules {
  letter-spacing: 0.1em;
}
/* +10% on the in-game logo. The shared rule sets 42px here, 70% of the intro
   logo's 60px; this nudges that up without touching the intro screen's own
   logo, which still matches the hub exactly. (The 43/61.425 an earlier note
   quoted were the pre-2026-09-14 values.) */
body:has(#play-area) .wrap:has(#play-area:not([hidden])) .site-logo-icon {
  width: 47px; /* written out rather than left as a calc off a number that
    has since changed. */
}
/* Logo down 2px with nothing else moving. Done with relative positioning,
   not margins: the mark's margin-bottom COLLAPSES against #site-title's
   margin-top (the larger wins), so trading 2px from bottom to top does
   nothing at the bottom and pushes the whole header down by 2 instead.
   position:relative offsets the paint without touching layout. */
body:has(#play-area) .wrap:has(#play-area:not([hidden])) .site-logo-mark {
  position: relative;
  top: 2px;
}
/* 30px at both widths (see the section note above for why). Deliberately
   outside any media query: specificity beats media queries, so this one
   declaration overrides both the shared 32px and its 24px phone step. The
   tuples are (3,3,1) here against the shared rule's (2,3,0) - counting the
   [hidden] inside :not() at CLASS level, which is the trap documented further
   down this file. An earlier note said (3,2,1) against (2,2,0); the
   conclusion was right, the arithmetic was not. */
body:has(#play-area) .wrap:has(#play-area:not([hidden])) .site-header #site-title {
  font-size: 30px;
  /* 2px lower than the shared 8px, tightening the title to its subtitle. The
     subtitle takes the same 2px off its own margin (see .gt-goal below), so
     it does not move - only the gap above the title grows and the gap below
     it shrinks. */
  margin-top: 10px;
}

/* The name itself turns accent - nothing else. The inherited --accent-wash
   background does no work on a black list (8% accent composites to
   rgb(10,16,20), 1.1:1) and an accent edge bar was tried and dropped: it read
   as a stripe sitting beside a row that had not changed. Recolouring the one
   thing you are actually choosing is the clearest signal and adds no box,
   bar or fill to a list that is otherwise pure text on black. */
body:has(#play-area) .gt-suggestion.active {
  background: none;
  color: var(--accent);
}
/* Same treatment on hover. These rows already carry cursor:pointer but had no
   hover state at all, so the cursor promised something the row never
   acknowledged - and keyboard users could see which row they were about to
   commit while mouse users could not, for an identical action.

   Behind (hover: hover) like every other hover rule here: on a touch screen
   a hover style can stick after a tap and leave a row looking picked when it
   is not. If a keyboard-active row and a hovered row are both visible they
   look the same, which is correct - they mean the same thing - though only
   the keyboard one commits on Enter. */
@media (hover: hover) {
  body:has(#play-area) .gt-suggestion:hover {
    color: var(--accent);
  }
}


/* --- result screen ------------------------------------------------------
   The screen the round ends on was still on the shared defaults while the
   play screen moved onto this page's own system, so the two disagreed about
   inset, control height, type size and which greys exist. Brought into line.

   .result-panel sits INSIDE #play-area, so the card padding rule above
   already applies to it - its own 6px margin-top was stacking on top of that
   and making the first line sit 22px down against the play screen's 16. */
body:has(#play-area) .result-panel {
  margin-top: 0;
}

/* The end-of-round buttons are the play screen's controls by another name,
   so they take the same height and the same gap. They were 32px against 44,
   and their font-size was 13.4px - one of the fitted decimals from the old
   scale, measured rather than chosen.

   Height needs flex centring here as it does on the other buttons: these are
   <button> and <a> elements whose label would otherwise sit at the top of a
   box that is no longer sized by its padding. */
body:has(#play-area) .result-actions {
  gap: var(--control-gap);
}
/* Addressed through #result-actions, not .result-actions, and that is not a
   style preference. The shared rules these override are written as
   .result-actions:has(#share-btn:not([hidden])) .btn-primary - one id, and
   with the attribute selector inside :not() counting at class level, three
   class-weight components. A .result-actions form here scores (1,2,1) and
   LOSES to their (1,3,0) on the class column, so the font-size silently
   stayed at 13.4px. The id takes this to (2,1,1), which wins on the first
   column outright. */
body:has(#play-area) #result-actions .btn-primary,
body:has(#play-area) #result-actions .btn-secondary {
  height: var(--control-h);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 18px;
  font-size: 16px;
}

@media (max-width: 480px) {
  /* 14px, not 15, for the two-up row: Share beside "Play Another" on a daily
     result. At 15px the second label wrapped onto two lines inside its own
     button.

     An earlier note here said the row carries THREE controls in an extra
     round. It never does - every game sets shareBtn.hidden and
     practiceBackLink.hidden to opposites (rankplayer/js/quiz.js:1152, and the
     same pair in the other five), so it is share+next on a daily and
     next+back in an extra round, never all three. */
  body:has(#play-area) #result-actions .btn-primary,
  body:has(#play-area) #result-actions .btn-secondary {
    font-size: 14px;
  }
}

/* The extra-round pair stacks instead of sharing the row. "Back to today's
   results" is nowrap (see .practice-back-link) and needs 156px of ink at
   14px, while half a 320px card leaves 97px inside the padding - so the
   sentence ran out of its button and past the card's edge on all six games.
   Measured at 320px on a real result screen, not predicted.

   Same answer as #rank-actions further down, and for the same reason: the
   label cannot wrap, and a font-size tuned to one browser's Inter metrics is
   not a fix. Full width also means the two labels fit at the page's own 16px
   rather than the two-up tier's 14, so the override below puts that back.

   Only the extra-round pair stacks. A daily result shows Share beside "Play
   Another" - two short labels that fit the row at every width - and that one
   keeps its line.

   flex: none because .result-actions' children carry flex: 1; a column with
   a free-height container would read that as height. */

/* The two-up row keeps its line at every width, so on the narrowest phones it
   gives up its side padding instead. At 320 each half is 132px; 18px a side
   leaves 96 inside, and "Share Results" is 94 of it - 2.5px of slack, which
   is not enough to survive iOS rendering Inter slightly wider than Chromium
   does. At 8px the same label has 22px to spare.

   The padding is decoration on a centred label in a fixed-height box, so
   dropping it costs nothing visible; the row's own gap still separates the
   two halves. Applies to the stacked pair as well, where there was already
   room to spare either way. */
@media (max-width: 375px) {
  body:has(#play-area) #result-actions .btn-primary,
  body:has(#play-area) #result-actions .btn-secondary {
    padding: 0 8px;
  }
}

/* This page runs on two text values - #e7e7e7 bright, --text-dim quiet - and
   the result screen was introducing --text-soft #cfcfcf as a third, for the
   score label and the message under the answer. Both are secondary to the
   result itself, so they take the quiet one. The leaderboard rows below keep
   --text-soft deliberately: they are the same markup the hub card renders,
   and matching the hub matters more there. */
body:has(#play-area) .result-time-label,
body:has(#play-area) .result-sub {
  color: var(--text-dim);
}

/* Inverted from the shared rule's accent fill with dark text: the answer now
   sits on the same --tile surface the history rows use, with the accent
   carried by the name itself. That puts it in the same family as the rows
   that led up to it instead of being the one solid block of colour on the
   page, and it lets the card's var(--panel-edge) border read, which a
   near-white-on-accent fill swallowed. */
body:has(#gt-player-reveal) #gt-player-reveal {
  background: var(--tile);
  color: var(--accent);
}

/* Verdict and answer at one size, at BOTH widths. The shared rules give them
   four different values across the two tiers - .result-title 32/24 and
   .gt-club-reveal 28/22 - so matching them at one width left the verdict
   SMALLER than the name at the other. 28px everywhere, in line with the rest
   of this page, where every size is shared across tiers.

   Both are Anton and sit directly above one another, so they read as one
   block; the difference between them should be colour, not size. */
body:has(#play-area) .result-title,
body:has(#play-area) .gt-club-reveal {
  font-size: 28px;
}

/* Pulls "YOUR SCORE" up so its LETTERS start where the search field's top
   edge starts in game mode, rather than where its line box does. Both boxes
   already began at the card's 16px padding edge, but one shows a border there
   and the other shows text, whose glyphs sit 2.7px lower inside their line
   box (half-leading plus the gap between the font's ascent and its cap
   height, measured with canvas actualBoundingBoxAscent). Switching between
   the two screens showed the label sitting low.

   0.225em rather than -2.7px so it tracks the 12px font-size. */
body:has(#play-area) .result-time-label {
  margin-top: -0.225em;
}

/* Everything from the verdict down moves up 1px, tightening it to the score
   label above. The shared rule this overrides is
   .result-panel:has(#result-time-label:not([hidden])) .result-title, which
   scores (1,3,0) - the [hidden] inside :not() counts at class level - so a
   .result-panel form here would lose. #result-panel takes it to (2,1,1),
   winning on the id column. Same trap as the result buttons above. */
body:has(#play-area) #result-panel .result-title {
  margin-top: -5px;
}

/* The two Guess games only: the WON verdict ("Guessed at club #2!") drops to
   --text, the same grey as the page title above it (direct instruction,
   2026-10-03).

   Why only here: on these two screens the verdict sits directly above the
   answer - the club chip in its own brand colour, or the player's name in
   the accent - and an accent verdict made the panel two shouts in a row with
   nothing to look at first. Grey puts the verdict in the same voice as the
   title and leaves the answer as the only coloured thing on the screen.

   :not(.wrong) is load-bearing. quiz.js writes "result-title wrong" on a
   loss, and this selector carries an id inside :has() plus #result-panel, so
   without the exclusion it would score past .result-title.wrong (0,2,0) and
   turn "Not guessed" grey too. The loss stays --bad.

   The other four games keep the accent verdict: their result screens have no
   coloured answer under the title for it to compete with. */
body:has(#gt-club-reveal) #result-panel .result-title:not(.wrong),
body:has(#gt-player-reveal) #result-panel .result-title:not(.wrong) {
  color: var(--text);
}

/* --- consistency pass (2026-09-29) --------------------------------------
   Three things the game screen and the result screen still disagreed on, or
   that sat outside this page's own palette. Fixed at the token level where
   possible, so they cannot drift back one rule at a time.

   1. --text. The page settled on #e7e7e7 for bright text, but --text #f4f4f4
      was still reaching the "Keep Playing" label and the .gt-history-row
      container. Redefining the token catches both and anything added later,
      rather than chasing each rule; nothing on this page wants #f4f4f4.

   2. --on-accent. #0a0f0c was a fourth near-black, used nowhere but as the
      label on accent fills, and belonging to no other part of the page.
      Pure black instead - already the page background, the search field and
      the suggestion list, so it is a colour this page actually uses. On the
      accent it reads at 11.41:1, slightly better than the value it replaces. */
/* --text and --on-accent moved to :root - the whole site takes them now. */

/* 3. The reveal chip's corner. It shares --tile and --panel-edge with the
      history rows, so it reads as the same kind of object - but .gt-club-card
      is 14px where those rows are 10, which left several radii on one page
      (10 rows/field, 12 dropdown and buttons, 14 chip). 10px puts the chip in
      the family it already looks like it belongs to. */
body:has(#play-area) .gt-club-card {
  border-radius: 10px;
}

/* --- last outliers ------------------------------------------------------
   The game UI settled on a small set of values; everything below was what
   still sat outside it, all of it in the collapsed SEO block, which was
   styled in an earlier pass on its own terms.

   The sizes were the old fitted decimals - 12.5px body and 10.5px toggle,
   measured against something rather than chosen, and landing within a pixel
   of the 12px the game UI already uses for its own quietest labels. Both
   snapped onto 12. The shared rules set them inside a max-width:480px block,
   so these have to be there too or the wide tier keeps the shared 13px/11px.

   --text-soft #cfcfcf was the one place a third grey survived. It goes to
   --text-dim, the same quiet grey the years, labels, row numbers and score
   label use - this copy is the quietest thing on the page, so it belongs
   there rather than a step above. */
body:has(#play-area) .game-seo {
  color: var(--text-dim);
}
@media (max-width: 480px) {
  body:has(#play-area) .game-seo,
  body:has(#play-area) .game-seo > summary { font-size: 12px; }
}

/* The buttons, against 10px on the field, the history rows and the reveal
   chip. The suggestion list was named here as already being 10 and was not -
   it is fixed at its own rule now. The card keeps its own 20px: it is the
   container, not one of the things inside it. */
body:has(#play-area) .btn-primary,
body:has(#play-area) .btn-secondary,

/* The in-play "Back to today's results" on Order the Player and Build the
   Team. Those two put it on its own, outside any actions row, so neither the
   10px radius above nor the control sizing further up reached it - measured
   at a 12px corner, 15px label and 14px/22px padding, the only off-system
   control left on those pages in an extra round.

   Rank the Player keeps its own sizing: there the link is a child of
   #rank-actions, stacked under Submit, and that rule is later in the file at
   equal specificity, so its 13px/34px wins here. The two Guess games and Who
   Has More? have no in-play back link at all. */

/* --- Guess the Club only ------------------------------------------------
   Everything above is shared with Guess the Player. These four are where the
   two games genuinely differ.

   1. One line, not two. A row here is a player name plus games and goals -
      there is no equivalent of the other game's "1993 to 1995" second line,
      so the value/label pairs stay side by side rather than stacking. The
      FONTS still match: Anton 16 on the values, Inter 13 on the labels, set
      by the shared rules above.

   2. Shorter rows to match. A single-line row sits at exactly --control-h,
      the same height as the field and the buttons, which makes the list read
      as a stack of controls rather than of cards. The other game's two-line
      rows are 1.25x that. It also matters here that there are TEN rows, not
      eight: at the taller height the list alone would run past a phone
      screen (604px against 494 at this one). */
body:has(#gt-club-reveal) .gt-history-stat {
  flex-direction: row;
  align-items: baseline;
  gap: 4px;
}
body:has(#gt-club-reveal) .gt-history-row:not(.upcoming) {
  min-height: var(--control-h);
}

/* 3. The reveal chip keeps its club colour. quiz.js fills it per episode from
      that club's own brand colour and computes a readable text colour to go
      on top, so it cannot take the flat --tile treatment the other game's
      chip now uses - and the colour is the point of the reveal here. Only the
      size and corner come from the shared rules. */

/* 4. The unreached rows carry a muted "..." where the other game leaves the
      slot blank. Kept: with ten slots and no years line, the ellipsis is what
      stops an unreached row reading as an empty bar. */

/* One baseline for the whole row. With a single line of content, the slot
   number, the player name, the values and their labels should all sit on the
   same line - they were on four different ones, because centring aligns each
   child's BOX and their boxes are different heights (11px Inter number
   against 16px Anton name against 13px labels).

   The top:0 is not tidying. The shared block nudges the name and values down
   1px and the labels up 1px to close up Guess the Player's TWO lines; those
   are position:relative offsets, applied after layout, so they survive
   baseline alignment and pull three of the four items back off it. Cancelled
   here, where there is only one line to align.

   padding-top puts the aligned group back where the name already sat:
   baseline alignment parks the group at the top of the row's free space,
   which would have lifted the text ~4px. */
body:has(#gt-club-reveal) .gt-history-row {
  align-items: baseline;
  padding-top: 10px;
}
body:has(#gt-club-reveal) .gt-history-name,
body:has(#gt-club-reveal) .gt-history-stat-num,
body:has(#gt-club-reveal) .gt-history-stat-label {
  top: 0;
}

/* --- Who Has More? only --------------------------------------------------
   A duel, not a list, so less of the shared block reaches it - but the parts
   that were outside the palette are brought in here.

   The two duel cards keep their 92px height: unlike a list row there are only
   ever two of them and they are the thing you are looking at. Only the corner
   changes, onto the 10px every other surface uses.

   The timer track was #050505, a FIFTH near-black existing only here. Pure
   black is the page's own, already under the search fields on the other two
   games.

   The round pill was rgba(accent, 0.1) against --accent-wash's 0.08 - two
   tints a viewer cannot tell apart, and the same near-duplicate that was
   merged away on Guess the Player.

   The resolved-duel cards had their own dark text colours, #06210f on the
   green and #2a0509 on the red. Those were the on-good and on-bad pair to
   --on-accent's old #0a0f0c; now that one is #000000, these follow. */
body:has(#duel-stack) .duel-card {
  border-radius: 10px;
}

body:has(#duel-stack) .round-pill {
  background: var(--accent-wash);
  /* 12px, up from 10. The 10px was borrowed from .start-rules, which lives
     only on intro and hub cards - nothing on a play screen used it. 12px is
     this page's own smallest, shared with the score label and the SEO block.
     Anton was tried here and reverted: Inter keeps it reading as a label
     rather than competing with the Anton question below it.

     Two lines now - "ROUND" over the count - so the radius comes off the
     100px stadium shape, which only reads as a pill on a single line, onto
     the 10px every other surface here uses. line-height is tightened because
     the shared rule leaves it at normal, which on two lines opens a gap
     wider than the text itself. */
  font-size: 12px;
  line-height: 1.25;
  border-radius: 10px;
  padding: 5px 12px;
  /* The two lines are different widths and the pill is shrink-to-fit, so
     without this the narrower one sits flush left against the wider. It goes
     unnoticed at "1 OF 15", where the two are nearly the same width, and
     shows plainly from "10 OF 15" on - which is most of a run. */
  text-align: center;
  /* The shared rule uppercases this whole pill, which turned the count into
     "1 OF 15". The joining word reads better lower-case, so the transform is
     off here and the word "ROUND" is written in caps in quiz.js instead - the
     only part that should be shouted. */
  text-transform: none;
}
body:has(#duel-stack) .duel-card.correct,
body:has(#duel-stack) .duel-card.wrong,
body:has(#duel-stack) .duel-card.correct .name,
body:has(#duel-stack) .duel-card.correct .num,
body:has(#duel-stack) .duel-card.wrong .name,
body:has(#duel-stack) .duel-card.wrong .num {
  color: var(--on-accent);
}

/* The end-of-round recap onto the page's own sizes: its name was 11px Inter
   and its value 12px Anton, neither of which appears anywhere else here. They
   become the 13px/16px pair the other games' rows use for exactly this job -
   a label and the number it belongs to - and the tile takes the shared 10px
   corner. */
body:has(#duel-stack) .recap-entry {
  border-radius: 10px;
}
body:has(#duel-stack) .recap-name {
  font-size: 13px;
}
/* The value now matches its name exactly - same face, size and weight - so
   each entry reads as one phrase rather than a label with a number bolted on.
   It was Anton 12px against the name's Inter, chosen in 2026-09-13 to echo
   the live duel card's own .num; that echo is dropped deliberately here.

   The value carries no colour of its own: the verdict moved to the name on
   2026-10-02 (see .recap-name.higher), so the number stays the entry's own
   grey and the name does the talking. */
body:has(#duel-stack) .recap-value {
  font: 700 13px "Inter", sans-serif;
}

/* The in-card question, now sitting below the timer and above the cards. It
   repeats the header deliberately, so it is sized DOWN to the subtitle's 20px
   rather than the 27px it used to be as the card's headline - a reminder of
   what you are answering, not a second title. 27px was also a size used
   nowhere else on the site. One line, not the old two-line break. */
/* The question, the timer above it and the pill above that, all spaced on the
   page's own unit. They were uneven: the pill had 10px under it, the timer
   slot had NO margin at all so the question sat right on top of it, and the
   question had 36px below - a value used nowhere else. Now every gap in this
   stack is 2x --control-gap, the same break the other games use between
   blocks.

   28px on the question, up from the 20 it was given when it was only a
   reminder: it is what you are actually answering, and 28 is already the
   size the result screen's verdict and answer use. The 27px it had before
   this pass appeared nowhere else on the site.

   .duel-stack's own margin-top is zeroed so it does not collapse against the
   question's margin-bottom and quietly win - that is what produced the 36px
   gap under the question against 0 above it. */
body:has(#duel-stack) .metric-q {
  font-size: 28px;
  margin: 0 0 calc(var(--control-gap) * 2);
}
/* The gap under the pill matches the gap over it, so the pill sits in an
   even band of its own: above it is the card's top inset (24px, 16px at the
   narrow tier) plus the pill's own 2px top margin. control-gap * 2 is 20/12
   at those two tiers, so +6px lands on 26/18 - the same pair - and one rule
   tracks the breakpoint instead of a value repeated per tier. */
body:has(#duel-stack) .round-pill {
  margin-bottom: calc(var(--control-gap) * 2 + 6px);
}
body:has(#duel-stack) .duel-status-slot {
  margin-bottom: calc(var(--control-gap) * 2);
  /* The bar was aligned to the slot's BOTTOM, which was right when the
     countdown number sat above it inside the same slot. With the number
     hidden, all 22px of the remaining slot fell above the bar - 34px of gap
     over it against 12 under. Centring splits that evenly.

     The slot keeps its height rather than shrinking to the bar: .duel-status
     ("Correct!" / "Time's up") takes this same space when the duel resolves
     and needs ~36px, and the cards below must not jump when one replaces the
     other. */
  align-items: center;
}
body:has(#duel-stack) .duel-stack {
  margin-top: 0;
}

/* Keep the metric together when the question wraps. Left to itself the line
   broke wherever it ran out of room - "WHO HAS MORE LA LIGA / APPEARANCES?" -
   splitting the metric's own name across two lines. As an inline-block the
   metric is laid out as one unit: it stays on line one when the whole
   question fits, and moves to line two entire when it does not. It still
   wraps internally if a metric is on its own wider than the card, which is
   the only case where breaking it is unavoidable. */
body:has(#duel-stack) .metric-q em {
  display: inline-block;
}

/* The duel stack carried a 14px bottom margin of its own, which sat on top of
   the card's 16px padding and made the gap under the cards 30px against the
   16px either side of them. The card's padding is the only thing that should
   set that edge, as it is on every other screen.

   #continue-btn takes the gap instead, and only when it is there: during a
   live duel it is hidden, so the cards sit 16px off the card's bottom edge,
   matching their sides. */
body:has(#duel-stack) .duel-stack {
  margin-bottom: 0;
}
/* The last in-play button not on --control-h. It took its height from
   .btn-primary's padding instead, which made it 34px on a phone and 47.3px on
   desktop - against 44/48 for the Guess games' pair and Order the Player's
   Submit, which are the same kind of control on the same kind of screen.
   Flex centring for the same reason those carry it: the label would otherwise
   sit at the top of a box no longer sized by its padding. */
body:has(#duel-stack) #continue-btn {
  height: var(--control-h);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: calc(var(--control-gap) * 2);
}

/* 32px, originally derived from the countdown number (16) + 6 + the bar (10)
   when all three shared this slot. The number is display:none since later
   the same day (see the rule below), so the height is no longer that sum -
   it is now simply the space .duel-status ("Correct!" / "Time's up") needs
   when the duel resolves, which at 24px Anton is a little under 32.

   Kept fixed rather than shrunk to the bar for exactly that reason: the slot
   holds the clock during a duel and the verdict after it, and the cards below
   must not jump when one replaces the other. The bar is centred in it (see
   align-items above), which is what splits the leftover space evenly now that
   the number is not there to sit above it. */
body:has(#duel-stack) .duel-status-slot {
  height: 32px;
}

/* No countdown number: the bar alone reads better than a digit chasing its
   leading edge (2026-09-29). Hidden rather than deleted - quiz.js still
   writes its text and rides it along the bar, so leaving the element in
   place keeps that code untouched and the two in step if it ever returns. */
body:has(#duel-stack) #timer-num {
  display: none;
}

/* --- Rank the Player only -----------------------------------------------
   This page joined the shared scope above on 2026-09-29, so it already has
   the header, the card inset, the darker surfaces, the result panel, the
   button system and the 12px SEO block. What is left here is its own board:
   the player pool and the eight category buckets, which exist on no other
   page.

   Scoped with :has(#rank-grid) - an id this page has and no other does. Two
   of the classes below (.comparison-row and friends) are also used by Build
   the Team, which is why the scope matters rather than styling them bare.

   1. Anton on the CATEGORY names, and only those. The direction words stay
      Inter (direct instruction, 2026-09-29): "Most"/"Fewest" are the one
      part of a bucket that is a label rather than a name, and keeping them
      in the body face is what lets the competition read as the heading of
      its own tile. Player names were Anton for one pass and went back to
      Inter on 2026-09-29; the results table and every button were always
      Inter, in step with the other three games.

   2. One size. The board carried a breakpoint of its own (12px, 11px under
      480) where the rest of this page holds one size across both tiers.
      12px at both, for the names and the categories alike - the bottom rung
      of the site's Anton ladder, which runs 12/16/18/20/24/28/30.

   3. One radius. The board was on 12px against 10 everywhere else on this
      page after the join - the field, the buttons, the result chips. */
/* Through .pair-bucket deliberately: .category-label-stat is also what the
   results table's own category column renders, and that table stays Inter
   with every other button and label on the site. Covers the filled state too
   - there the label is nested one level deeper, inside .bucket-label. */
body:has(#rank-grid) .pair-bucket .category-label-stat {
  font-family: "Anton", sans-serif;
  /* Anton ships one weight; leaving the inherited 600/700 on would let the
     browser synthesise a fake bold and thicken the letterforms unevenly. */
  font-weight: 400;
  /* 0.05em, the same tracking Guess the Player and Guess the Club give their
     own Anton names - the note there applies here word for word: a condensed
     face set small closes up, and the extra tracking is what keeps it
     legible. Was 0.01em. */
  letter-spacing: 0.05em;
  /* The starting size a long label shrinks down FROM, not a fixed one - see
     shrinkCategoryLabelToFit, which steps it down until the line fits its
     bucket and stops at a 9px floor. */
  font-size: 12px;
}
/* Both together on purpose: a player's name must not change size when you
   move it from the pool into a bucket. Size only - the face comes from the
   base rules, which are Inter. */
body:has(#rank-grid) .ccard-name,
body:has(#rank-grid) .bucket-country-name {
  font-size: 12px;
}
body:has(#rank-grid) .ccard,
body:has(#rank-grid) .pair-bucket {
  border-radius: 10px;
}

/* 3. The board's greys. .bucket-label and .comparison-* were on --text-soft
      #cfcfcf, the third grey this page's own pass removed everywhere else -
      the same fix the other three games got. */
body:has(#rank-grid) .comparison-col-title,
body:has(#rank-grid) .comparison-rank {
  color: var(--text-dim);
}

/* 3b. The verdict on your own column (direct instruction, 2026-10-01: wrong
       in red, correct keeps the blue). Correct means your player IS the one
       the best-picks column uses for that category - a stricter thing than
       scoring well on it, since a category you did well in can still belong
       to another player in the one assignment that maximises the board.
       Only your column carries it; every row of the key would come out blue
       and say nothing.

       --accent, not --good: on this site the "right" colour is the blue, the
       same way Order the Player's own .reveal-row.reveal-correct reads. Geo's
       two equivalents use their mint for the same role. The red is shared.

       The NAME only, not the value under it (direct instruction,
       2026-10-01). Both were tinted when this went in earlier the same day.

       Scoped with body:has(#rank-grid) for specificity, not for style: the
       greys above are (1,1,1) because :has(#rank-grid) counts at ID level, so
       a plain .comparison-entry.wrong .comparison-rank at (0,3,0) loses the
       rank span and would have tinted only the name. */
body:has(#rank-grid) .comparison-entry.correct .comparison-country {
  color: var(--accent);
}
body:has(#rank-grid) .comparison-entry.wrong .comparison-country {
  color: var(--bad);
}

/* 4. The board on this page's own rhythm. Every gap on the play screen was
      its own number (9/22 in the pool, 10/8 between buckets, 18 over the
      button, and a second set at the narrow tier); they are now the same
      --control-gap the other three games space their controls with, so the
      board breathes like the rest of the site and tracks the breakpoint on
      its own. Resulting rhythm: 10 between cards and bucket rows, 20 from
      the pool down to the grid and from the grid down to Submit (6 / 12 on
      a phone). */
body:has(#rank-grid) .rank-pool {
  /* --control-gap minus 2 (direct instruction, 2026-09-29). The gap BETWEEN
     groups still uses the token; this is the gap WITHIN one, and the same
     6px that reads as tight between Guess the Club's 328px-wide rows reads
     as airy between these 80px cards - a channel is judged against what it
     separates, not in isolation. 4px on a phone, 8px on desktop. */
  gap: calc(var(--control-gap) - 2px);
  /* +2px (direct instruction, 2026-09-29). With the rules gone the CATEGORIES
     title sat 9px under the pool and 9px over its own grid - dead centre
     between the two, belonging to neither. The 2px tips it toward the grid it
     names, which is the only thing marking that boundary now. */
  margin-bottom: calc(var(--control-gap) * 2 + 2px);
  /* The two-line reservation has to use the same gap the grid does, or the
     pool's height stops matching the rows it holds. */
  --ccard-h: calc(var(--control-h) * 1.25);
  min-height: var(--ccard-h);
}
body:has(#rank-grid) .rank-pool:has(.ccard:nth-child(5)) {
  min-height: calc(var(--ccard-h) * 2 + var(--control-gap) - 2px);
}
body:has(#rank-grid) .pair-stack,
body:has(#rank-grid) .pair-module {
  /* Same reduction as the pool above, so the whole board is on one within-a-
     group gap - the buckets are 161px wide, between the pool cards and Guess
     the Club's rows, and they read the same way. */
  gap: calc(var(--control-gap) - 2px);
}
body:has(#rank-grid) #rank-actions {
  display: flex;
  /* A column, so Submit and the extra-round "Back to today's results" stack
     rather than splitting the row. They were side by side, and the label does
     not fit: it needs 145px of ink at 13px, while half a 390px row leaves 138
     inside the padding - so on every phone the sentence pushed into its own
     padding, and by 320px it was clipped. Measured ink, not guessed.

     Shrinking the type was the alternative and was measured out: the label
     only clears a 320px half somewhere below 11.5px, and a size tuned to one
     browser's Inter metrics is not a fix, since iOS renders it slightly wider.

     Stacking has no number to get wrong, needs no breakpoint, and is what
     Order the Player and Build the Team already do with this same link. In a
     daily round the link is hidden and Submit is the only child, so it fills
     the row exactly as before - the column changes nothing there. */
  flex-direction: column;
  gap: var(--control-gap);
  margin-top: calc(var(--control-gap) * 2);
}
/* The row disappears when it holds nothing - on the result screen quiz.js
   hides both children, and without this the row's own margin would still
   push the result panel down by 12px. :has() over a child that is NOT
   [hidden], so it needs no help from the JS. */
body:has(#rank-grid) #rank-actions:not(:has(> *:not([hidden]))) {
  display: none;
}
/* Both children are controls of the same kind, stacked, so they share one
   width, one height and one label size.

   flex: none because a column would otherwise read flex-grow as height and
   stretch them past the 34px below.

   Height needs flex centring here as it does on the other games' buttons:
   these are a <button> and an <a> whose label would otherwise sit at the top
   of a box no longer sized by its padding. */
body:has(#rank-grid) #rank-actions > * {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 0;
  border-radius: 10px;
  /* 13px and 34px, not the 16/44 the other games' play buttons use (direct
     instruction, 2026-09-29). Those sit beside 16px Anton names and a 16px
     input; this board is 12px throughout, so a 16px label read a third
     larger than everything above it, and 34px is the height that goes with
     13px type.

     The size was originally asked for as "the height of Who Has More?'s next
     round button", which then measured 34 on a phone. That button has since
     joined --control-h (44/48) with every other in-play control, so this
     pair no longer matches it and stands on the board's own 12px scale
     instead - which is the reason it was made smaller in the first place.

     One size for both children rather than the shared 16/44 on the link: the
     two now sit directly above one another, where a 44px 16px link under a
     34px 13px button would read as a different kind of thing. This rule is
     later in the file than the shared #practice-back-link-playing block and
     at equal specificity, so it wins - see that block's own comment. */
  height: 34px;
  padding: 0 18px;
  font-size: 13px;
}

/* 6. One hairline. The buckets drew a 1.5px border where the pool cards, the
      card itself and every control on the page draw 1px - at this size the
      heavier edge read as a different kind of object rather than a stronger
      one. */
body:has(#rank-grid) .pair-bucket {
  border-width: 1px;
}

/* 7. One size for a category, placed or not. .bucket-label pinned the label
      to 10px once a player was dropped in, while an empty bucket rendered it
      at the bucket's own 12px/11px - so a row visibly changed type size the
      moment you filled half of it. Inheriting instead means the two states
      differ only by the name appearing underneath.

      font-size alone, not the shorthand: the weight and family still come
      from the Anton rule above and from .category-label-dir's own Inter. */
body:has(#rank-grid) .bucket-label {
  font-size: inherit;
}

/* 8. Submit-before-you-have-placed-eight takes the shared unpressable look
      defined further up, so it is listed in that rule rather than styled
      here. Only the hover needs saying: without it the base
      #submit-btn.not-ready:hover would repaint the fill on a mouse-over and
      undo the effect. */
@media (hover: hover) {
  body:has(#rank-grid) #submit-btn.not-ready:hover {
    background: var(--control);
    filter: none;
  }
}

/* 9. The two lines of a wrapped name pushed 1px apart each - top line up,
      bottom line down (direct instruction, 2026-09-29). Done as line-height
      rather than two nudges: the card centres its name vertically, so
      growing the line box by 2px moves each line exactly 1px away from the
      middle and leaves a one-line name where it was.

      calc(1.2em + 2px) rather than a ratio: at 12px that would be 16.4/12 =
      1.3667, a fitted decimal that says nothing, while this says "the old
      1.2, plus 2px" and stays right if the size moves again. */
body:has(#rank-grid) .ccard-name {
  line-height: calc(1.2em + 2px);
}

/* 10. The reveal table onto the same rhythm as everything above it. Three
       values here predated this page's pass and followed nothing: the
       divider split 14px above / 20px below, and the header row sat on
       10.7px - a fitted decimal, measured against something rather than
       chosen.

       Now the page's own 1:2: --control-gap between rows, twice that under
       the header and on both sides of the divider, which makes the divider
       read as a break between two sections rather than as something that
       belongs to the table under it. 6/12 on a phone, 10/20 on desktop.

       Scoped, not edited in place, because #comparison-divider USED to be
       shared with Who Has More?'s #recap-divider; both are gone now, so the
       scoping is simply harmless rather than load-bearing. The .comparison-*
       CLASSES are this page's alone: Build the Team's table is .bt-comparison-*
       throughout, and an earlier note here claiming otherwise came from a grep
       that matched "bt-comparison-row" on the substring. The scoping is still
       right for the divider; for the classes it is simply harmless. */

body:has(#rank-grid) .comparison-row {
  margin-bottom: var(--control-gap);
}
body:has(#rank-grid) .comparison-header.comparison-row {
  margin-bottom: calc(var(--control-gap) * 2);
}
/* Restated, not inherited: the shared .comparison-row:last-child rule that
   zeroes this scores (0,2,0), and the scoped rule above scores (1,1,1), so
   the scoped one wins and the last row got a 6px margin back - which then sat
   on top of the card's own 16px padding and made the gap under the table 22.7
   against 16.7 everywhere else. The card's padding is the only thing that
   should set that edge, as on every other screen. */
body:has(#rank-grid) .comparison-row:last-child {
  margin-bottom: 0;
}

/* 11. The summary table's own type scale. It ran on 9px column titles, 10px
       for the category, the name and the rank, and a 17px percentage - four
       sizes, none of which appears anywhere else on the site. 12px is the
       smallest size the rest of this page uses, and it covers all three of
       the small ones; the percentage goes to 20px, the step the header
       subtitle already sits on.

       The category column keeps its shrink-to-fit (it now carries the
       flag + emoji the board shows), so 12px is the size it starts from,
       not a fixed one. */
body:has(#rank-grid) .comparison-col-title,
body:has(#rank-grid) .comparison-category,
body:has(#rank-grid) .comparison-country,
body:has(#rank-grid) .comparison-rank {
  font-size: 12px;
}
body:has(#rank-grid) .comparison-col-pct {
  font-size: 20px;
}

/* 11b. A rung smaller on a phone, exactly as Order the Player's own reveal
        does - everything below "See more daily football games", which here is
        #hub-link, then #comparison-divider, then #reveal-wrap. 11px is the
        next size below this table's 12 and one the site already uses
        (.ccard-name, .bucket-country-name); the percentages take 18, the next
        rung on the larger half of the ladder. Nothing above #hub-link moves,
        and .game-seo below keeps its own phone sizes. */
@media (max-width: 480px) {
  body:has(#rank-grid) .comparison-col-title,
  body:has(#rank-grid) .comparison-category,
  body:has(#rank-grid) .comparison-country,
  body:has(#rank-grid) .comparison-rank {
    font-size: 11px;
  }
  body:has(#rank-grid) .comparison-col-pct {
    font-size: 18px;
  }
}

/* 12. The two assignment columns, equal at any width. They were 1fr 1fr,
       which is minmax(AUTO, 1fr) - a track that refuses to go below its
       content's min-content width, and a .comparison-country is nowrap, so
       the column holding the longest name simply took more room. Measured at
       390px: 100.0px against 88.0px, i.e. "Your Assignment" and "Optimal
       Assignment" were not the same width and shifted with whichever names
       the day's set happened to contain.

       minmax(0, 1fr) lets both tracks be exactly half of what is left, and
       hands the overflow to shrinkToFit, which is what is meant to handle a
       long name here.

       The category column also stops being a fixed 145px - on a 320px phone
       that was 45% of the card, squeezing the two columns the screen exists
       to compare. A share instead, so all three scale together.

       That share drops from 1.25fr to 0.9fr on 2026-10-01: with the labels
       declaring their own two lines, the column is sized by "LEAGUE GAMES"
       rather than by "Champions League Games", so it no longer needs the
       largest share of the row - and the two columns the screen exists to
       compare get the difference. */
body:has(#rank-grid) .comparison-row {
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1fr) minmax(0, 1fr);
}

/* Align the three columns on their TEXT, not their boxes. .comparison-row
   bottom-aligns with align-items: end, which lines up the cells' bottom edges
   - and a cell's bottom edge is its last line BOX, whose glyphs sit inside it
   differently depending on the font-size. That is invisible while every cell
   is the same size and shows the moment shrinkCategoryLabelToFit drops a long
   category: measured at the 9px floor, the stat sat 1.75px below the value it
   is supposed to be level with, and the line above it 1.17px off the name.

   last baseline puts the final line of the category on the same baseline as
   the value beside it - 0.00px at both 11px and 10.25px, and 0.67px at the
   floor. The line above then lands by construction, because the stat's
   line-height is pinned in px just below.

   The three-line row (market_value) is the mirror image: it top-aligns so the
   name sits level with "Highest" rather than "Career-High" (direct
   instruction, 2026-10-01), so it takes FIRST baseline for the same reason.

   Both scoped through body:has(#rank-grid) for specificity - align-items: end
   sits on the shared .comparison-row at (0,1,0), and a lone class would tie
   with it rather than win. */
body:has(#rank-grid) .comparison-row {
  align-items: last baseline;
}
body:has(#rank-grid) .comparison-row-tall {
  align-items: first baseline;
}

/* 13. One line per name, truncated when it does not fit (direct instruction,
       2026-10-01). This reverses the wrapping added on 2026-09-29, which
       existed because the table could not hold everything at 12px - it needed
       422px of text across three columns at 390 and had 317. Two things paid
       that debt since: the category labels declare their own two lines, so
       that column is sized by "LEAGUE GAMES" rather than "Champions League
       Games", and the whole block drops to 11px on a phone. The space freed
       goes to the names, which is what the ellipsis then has to handle far
       less often.

       The category's stat goes back to nowrap WITH it: its line breaks are
       explicit <br>s now (see statLines in quiz.js), so nowrap pins each
       declared line and stops a third, arbitrary one appearing under it.

       The direction word was already nowrap - "Fewest" is short and breaking
       it off its arrow would read as two things. */
body:has(#rank-grid) .comparison-country {
  white-space: nowrap;
  /* max-width, and it is the part that actually makes the ellipsis work.
     .comparison-entry is a COLUMN flex container with align-items: center, so
     its children are sized to fit-content rather than stretched - the name's
     own box grew with the text and overflow had nothing to clip, leaving a
     long name painted across the next column (reported 2026-10-01; "Robert
     Lewandowski" is 112.2px in a 109.2px entry, so it was overlapping on real
     names, not only invented ones).

     min-width: 0 is the usual fix for a flex item that will not shrink and is
     no help here: that governs the MAIN axis, and this container's main axis
     is vertical. The cross axis is what needs capping.

     max-width rather than width: 100% so short names keep their own width and
     stay centred exactly as before - only a name that would overflow gets
     pulled back to the entry's content box and ellipsised. */
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
body:has(#rank-grid) .comparison-category .category-label-stat {
  white-space: nowrap;
  /* A line-height in PX, not the inherited 1.25 multiplier. The row
     bottom-aligns, so the label's LAST line is pinned level with the value and
     stays there; the lines above it are spaced by this line-height, while the
     name and value beside them are spaced by the entry's, which is fixed at
     the tier's own size. A multiplier here tracks the font-size, so the moment
     shrinkCategoryLabelToFit drops a long label the first line crept down away
     from the player's name: measured 0.17px out at 11px, 1.38 at 10.25, and
     2.50 at the 9px floor. In px the spacing stops moving and line one stays
     level however far the label is shrunk.

     15px is what 12 x 1.25 already computed to, so nothing changes at the
     default size - this only pins what was already there. */
  line-height: 15px;
}
/* The phone tier's own pin, 11 x 1.25. It has to sit AFTER the rule above
   rather than with the other phone overrides further up: the two selectors are
   identical, so source order is the only thing separating them, and from up
   there it lost and every label on a phone was spaced for 12px type. */
@media (max-width: 480px) {
  body:has(#rank-grid) .comparison-category .category-label-stat {
    line-height: 13.75px;
  }
}
/* .result-panel is text-align:center, which the category cell inherits. It
   never showed while the line was nowrap - a single line has nothing to
   centre against - but the moment it wraps, "Bundesliga Titles" breaks to a
   centred "Titles" under "Bundesliga", against a left-aligned "Most" above
   it. The column is left-aligned by intent (see .comparison-category
   .category-label's own align-items rule); this is the text half of it. */
body:has(#rank-grid) .comparison-category {
  text-align: left;
}

/* 14. The nudge that holds the two assignment clusters apart was 16px of
       padding on the left column and 4px on the right - 20px taken out of
       two columns that are 74.9px wide on a 320px phone, i.e. a quarter of
       the space the names have to fit into. It came down to 8 and 4, which
       kept the clusters visibly separate and handed 8px back to each name.

       Even at 6 and 6 since 2026-10-01. The total gap is identical - 12px on
       top of the row's own 6px - so nothing moves apart, but the two columns
       now have the SAME content width. They did not: 136.4px against 140.4px
       on desktop, which was invisible while a long name wrapped and became a
       visible fault once names started truncating, because the same name
       would render whole in Best Picks and cut in Your Picks. Measured with
       the eight longest names in the set in both columns, that asymmetry alone
       accounted for 4 of the 8 clipped names on desktop; at 6/6 only
       "Pierre-Emerick Aubameyang" and "Karl-Heinz Rummenigge" still clip, and
       both columns agree on every name. On a phone the split is cosmetic -
       103.2px either way, and names that long clip regardless - but the
       symmetry is the point. */
body:has(#rank-grid) .comparison-row > *:nth-child(2) {
  padding-right: 6px;
}
body:has(#rank-grid) .comparison-row > *:nth-child(3) {
  padding-left: 6px;
}

/* 15. The one rule this page still draws - above the reveal table - is
       quieter than --panel-edge, which is a token for the EDGE of an
       object - a card, a tile, a control - where it has to read as that
       object's boundary. A rule divides one surface, so it should sit just
       off it rather than draw a border across it. Measured against this
       page's card at #101010 (level 16): --panel-edge lands 26 levels above
       it, --control 16, --tile only 7, which is close enough to vanish.

       --hairline was the obvious candidate, being the site's own token for
       footer and SEO dividers, but it is 10% white and composites to level
       40 on this background - no quieter than what it would replace.
       --control is already on this page as the secondary button's fill and
       the back link's background.

       The board carried two of these for a while, between the players and
       the categories and under the grid; they came out on 2026-09-29 and
       the section titles carry the separation on their own.

       Scoped rather than applied to #comparison-divider globally: Build the
       Team and Who Has More? draw the same element and were left on
       --panel-edge. That was the right call while this was the only page
       past its pass; now that all six are done it is simply an unreconciled
       difference - the two rules this page sets here (14/20 margins and this
       colour) are the only places that divider differs between games. */

body:has(#rank-grid) #play-area:has(#rank-pool[hidden]) .rank-section-title {
  display: none;
}

/* 16. "Players" and "Categories" over the two halves of the board (direct
       instruction, 2026-09-29). Anton, as asked.

       16px rather than the board's own 12: at 12 an Anton label would weigh
       exactly as much as the category names under it and stop reading as a
       heading. 16 is the next rung on the site's Anton ladder - the size the
       other games set their club and player names at.

       Uppercase with 0.1em of tracking, the same treatment .start-rules gets
       on this page's intro and hub card, and --text-dim so a label naming a
       section stays quieter than the section itself.

       One small gap below (--control-gap), so each title belongs to the
       block under it rather than floating between the two. */
body:has(#rank-grid) .rank-section-title {
  font: 400 16px "Anton", sans-serif;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  /* The page's bright text, the same --text the title above carries (direct
     instruction, 2026-09-29), not the quiet grey. These name the two halves
     of the board, and with the rules gone they are the only thing marking
     the boundary, so they carry the weight rather than sitting back. */
  color: var(--text);
  text-align: center;
  /* Optical, not box, spacing. A line box does not end where the letters do:
     at 16px Anton the box is 24px tall and holds 14px of ink, leaving 4.67px
     dead above and 5.33px below (measured with a baseline probe plus canvas
     actualBoundingBox). Left alone that read as 21.4px over the first title
     against the card's own 16.7px inset, and 11.3px under it against a 6px
     gap - which is the "too much space" you can see.

     Both corrections subtract exactly that dead space, so what you MEASURE
     between the letters and their neighbours is the page's own rhythm: the
     card inset above the first title, --control-gap * 2 below the divider
     above the second, and --control-gap under each.

     em, so they track the font-size rather than needing a refit if 16 moves.
     Same technique as .gt-goal's own margin further up this file. */
  margin: -0.292em 0 calc(var(--control-gap) - 0.333em);
  /* Up 3px (direct instruction, 2026-09-29), which also happens to centre
     CATEGORIES exactly in its own band: it had 12px of optical space above
     it and 6 below, and 3 is the number that makes those 9 and 9. PLAYERS
     moves the same amount for consistency and lands on 13.67 / 9, closer to
     even than the 16.67 / 6 it had.

     position:relative, so this moves the paint only - the pool, the grid and
     everything under them stay exactly where the margins above put them.
     Same reason the logo nudge further up this file uses it, and px rather
     than em to match that one: this is a nudge, not a proportion. */
  position: relative;
  top: -3px;
}

/* 17. The emoji sat low and read a size too big beside the text. Measured
       against Anton at 12px with canvas actualBoundingBox (Segoe UI Emoji,
       Windows/Chrome), as a fraction of font-size:

         capital letters   0.867 above the baseline, 0.008 below
         trophy/shirt/
         money/shield/
         globe             0.867 above,              0.142 below
         a real flag       0.517 above,              0.017 below

       So an emoji is ~16% taller than the caps and hangs 0.142em under the
       baseline - 1.7px at 12px, which is the overhang you can see under a
       trophy. A flag does neither, which is why flags already looked right
       and are left alone (see appendGlyph, which gives them no span).

       Two corrections, and both are needed: 0.86em makes the emoji's ink
       exactly cap height, and the lift puts its bottom on the baseline.
       Size alone would leave it sitting low; the lift alone would leave it
       overshooting the cap line.

       top, not vertical-align: a relative offset is applied after layout, so
       it moves the paint without growing the line box and nothing below
       shifts. Same reason the logo nudge and the row nudges here use it.

       -0.142em resolves against this span's OWN font-size, so it is exactly
       the emoji's own descent - the number above, not a value fitted by eye.
       That also means it stays correct if the size changes.

       Caveat: these are one emoji font's metrics. Apple Color Emoji and Noto
       have their own, so this is exact on Windows and approximate on a
       phone - in the right direction either way, since every emoji font
       hangs below the baseline. */
body:has(#rank-grid) .cat-emoji {
  font-size: 0.86em;
  position: relative;
  top: -0.142em;
}

/* --- Order the Player only ----------------------------------------------
   This page joined the shared scope above on 2026-09-29, so it already has
   the header, the card inset, the darker surfaces, the result panel, the
   button system and the 12px SEO block. What is left here is its own board:
   eight draggable name rows, each flanked by a pair of move buttons.

   Scoped with :has(#arrange-list) - an id this page has and no other does.

   1. The rows read like Guess the Club's (direct instruction, 2026-09-29):
      an Anton name on a row exactly --control-h tall, at the page's one
      10px radius and one 1px hairline. They were 18px Inter on a 50px card
      with a 14px radius and a 1.5px border - three values that existed
      nowhere else on the site.

      16px rather than 18: Anton is condensed, so it reads about a size
      smaller than Inter at the same number, and 16 is what the other games
      set their own list names at. Tracking matches them too - see
      .gt-history-name's note on why a condensed face set small needs it. */
body:has(#arrange-list) .arrange-card {
  height: var(--control-h);
  padding: 0 10px;
  border-width: 1px;
  border-radius: 10px;
}
body:has(#arrange-list) .arrange-name {
  font: 400 16px "Anton", sans-serif;
  letter-spacing: 0.05em;
  line-height: 1;
  /* line-height: 1 above plus the overflow: hidden this inherits from
     .arrange-name was shaving the top off a diacritic that sits on a CAPITAL.
     At 16px the caron in Cech reaches 18px above the baseline while the box
     top is only 15px, so 3px came off and what was left read as a flat bar -
     the name looked like "Petr Cech" (owner spotted it, 2026-10-09). The font
     was never the problem: Anton carries the glyph and so does the subset.
     11 of the 341 names are affected, among them Sesko, Ozil, Skriniar,
     Alvarez, Niguez and Militao, all with the accent written properly in
     episodes.json.
     The padding extends the clip box; the equal negative margin takes it back
     out of the layout, so the row does not move. 4px covers the worst case
     (3px at both 16 and 12, 1.5px at the 9px shrink floor). */
  padding-top: 4px;
  margin-top: -4px;
}

/* 2. The move buttons take the same radius and the same neutral fill every
      other control on the site uses. --control, not --tile: a tile is
      something you read, a control is something you press, and these are
      the only pressable things in the row. 24px wide, replacing a fitted
      22.5, and the row's own gap comes off --control-gap so the three parts
      are spaced like any other control group. */
body:has(#arrange-list) .arrange-row {
  gap: var(--control-gap);
}
body:has(#arrange-list) .arrange-arrow {
  width: 24px;
  border-radius: 10px;
  background: var(--control);
  font-size: 13px;
  /* A <button> does not inherit font-family - the UA stylesheet sets its own,
     which is why these two arrows were the only Arial on the site. Nothing to
     do with the glyphs: Inter carries U+25B2/U+25BC perfectly well (checked by
     rendering both to a canvas against a deliberately absent family). Every
     other button here already does this; .arrange-arrow was simply missed. */
  font-family: inherit;
}

/* 3. The endpoint labels keep their wording and sit closer to the list they
      bracket - 4px was arbitrary, --control-gap is the page's own. The
      bottom one's 18px below it is the gap down to Submit, which the button
      row now sets itself. */
body:has(#arrange-list) .arrange-endpoint-label.top {
  margin-bottom: var(--control-gap);
}
body:has(#arrange-list) .arrange-endpoint-label.bottom {
  margin-top: var(--control-gap);
  margin-bottom: 0;
}

/* 4. Submit is a play-screen control like Guess and Next Club, so it takes
      the control height and sits calc(--control-gap * 2) below the board,
      the same break every other page here puts between a group and what
      follows it. It had no margin of its own and leaned on the endpoint
      label's 18px. */
body:has(#arrange-list) #submit-btn {
  height: var(--control-h);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: calc(var(--control-gap) * 2);
}

/* 5. No card inset of its own. #game:has(.arrange-list) dropped the card to
      10px of side padding at the narrow tier, from when the rows needed every
      pixel; with the arrows at 24px and the name centred they no longer do.
      That override was first replaced with 16px to stop this page being inset
      differently from the others, which only moved the outlier: the tier's
      shared value is .card { padding: 10px 12px }, so 16 was as wrong as 10,
      and .arrange-list exists on the intro screen too - at (2,1,1) it beat
      .card:has(#start-panel:not([hidden])) (1,2,0) and took 13px off the
      intro card's 29px inset, which no other game loses.

      So both overrides are gone and the shared 12px applies. 12 is also more
      room than the 16 the rows were measured fitting in, not less. */

/* 6. The reveal grid's own type scale. It ran on 9px column titles, 10px for
      the rank and the name, 11px for the value and a 15px percentage - four
      sizes that appear nowhere else, the same state Rank the Player's
      comparison table was in before its pass. 12px is the smallest size the
      rest of this page uses and covers all four small ones; the percentage
      takes 20px, the step the header subtitle already sits on.
 */
body:has(#arrange-list) .reveal-head-title,
body:has(#arrange-list) .reveal-head-value,
body:has(#arrange-list) .reveal-rank,
body:has(#arrange-list) .reveal-name,
body:has(#arrange-list) .reveal-value-elev {
  font-size: 12px;
}
body:has(#arrange-list) .reveal-head-pct {
  font-size: 20px;
}

/* 6b. The metric column sizes itself to its content, and that is now the
       right answer because quiz.js declares where the head breaks (see
       leagueHeadLines / headLines there). Its max-content is the widest LINE
       rather than the whole label, so the column - and the rule under it -
       stops exactly where the text does, and everything left over goes to the
       two order columns. Only one metric shows per round, so the narrow ones
       hand that space back: CAREER / RED CARDS needs 70.8px of head where
       PREMIER / LEAGUE GAMES needs 97.3.

       This column was `auto` originally too, and it was wrong then: with the
       label on one line its max-content was the whole of "Champions League
       Goals", 175.3px, which left the name columns 80.3px each on a 390px
       phone while a column heading set the width. A 104px cap fixed the
       symptom on 2026-10-01 and charged the narrow metrics for it; declaring
       the break fixes the cause, so the cap is gone and so is the phone tier's
       own narrower copy of it.

       The values in that column are nowrap and contribute their own
       max-content, which is what keeps "24 red cards" (73.5) readable under a
       70.8px head. .reveal-head-value already carried align-self: end, so the
       last line sits where a single line would and the extra grows upward. */
/* 6c. The whole comparison block drops a rung on a phone (direct instruction,
       2026-10-01: everything below "See more daily football games", which in
       the markup is #hub-link - then #comparison-divider, then #reveal-wrap,
       which is all of this).

       12px -> 11px for the headings, names, ranks and values: the next size
       below the 12 this page sets, and one the site already uses
       (.ccard-name, .bucket-country-name, .gt-history-*). Below that the
       ladder runs 10.5, 10, 9.5, 9, so there is room to go further if this
       still reads large.

       20px -> 18px for the two percentages, the next rung down on the larger
       half of the ladder (.arrange-name, .game-seo h1 both sit on 18).

       Nothing above #hub-link moves - the score, its leaderboard and the two
       buttons keep the sizes they have. .game-seo below already has its own
       phone sizes and is left to them. */
@media (max-width: 480px) {
  body:has(#arrange-list) .reveal-head-title,
  body:has(#arrange-list) .reveal-head-value,
  body:has(#arrange-list) .reveal-rank,
  body:has(#arrange-list) .reveal-name,
  body:has(#arrange-list) .reveal-value,
  body:has(#arrange-list) .reveal-value-elev {
    font-size: 11px;
  }
  body:has(#arrange-list) .reveal-head-pct {
    font-size: 18px;
  }
}

/* 7. The correct column's rank numbers and every value take the same bright
      grey the player names do (direct instruction, 2026-09-29), instead of
      the quiet --text-dim they shared with the column headings. The answer
      key and the numbers behind it are content, not labels.

      The :not() pair is load-bearing, not decoration. .reveal-rank appears
      in BOTH columns, and "your order" states its verdict on the rank as
      well as the name - red when wrong, accent when right. A bare
      "body:has(#arrange-list) .reveal-rank" scores (1,1,1) against those
      rules' (0,3,0) and wins on the first column, which would have wiped
      the verdict off every rank in the left column. Excluding the two
      marked classes leaves this applying only to the unmarked rows, which
      are exactly the correct-order entries. */
body:has(#arrange-list) .reveal-row:not(.reveal-correct):not(.reveal-wrong) .reveal-rank,
body:has(#arrange-list) .reveal-value-elev {
  color: var(--text);
}

/* --- Build the Team only ------------------------------------------------
   This page joined the shared scope above on 2026-09-29, so it already has
   the header, the card inset, the darker surfaces, the result panel, the
   button system and the 12px SEO block. What is left is its own screen: a
   position prompt, four candidate buttons and the pitch.

   Scoped with :has(#bt-pitch) - an id this page has and no other does.

   The PITCH's composition is deliberately untouched (direct instruction,
   2026-09-29): its geometry, marker positions and container-query sizing
   came out of a mockup and many rounds of feedback, and only surfaces,
   radii and type follow the system here.

   1. Anton on both sets of names - the four candidates and the names on the
      pitch cards (direct instruction). 16px on the candidates, matching the
      other games' list names; the pitch cards keep their container-query
      size, which is what holds them inside a marker that scales with the
      pitch. */
body:has(#bt-pitch) .bt-candidate-line,
body:has(#bt-pitch) .bt-pitch-card-name {
  font-family: "Anton", sans-serif;
  /* Anton ships one weight; the inherited 700 would have the browser
     synthesise a fake bold and thicken the letterforms unevenly. */
  font-weight: 400;
  /* Same tracking the other games give their own Anton names - a condensed
     face set small closes up without it. */
  letter-spacing: 0.05em;
}
body:has(#bt-pitch) .bt-candidate-line {
  font-size: 16px;
}
/* The position names up 1px (direct instruction, 2026-09-29), with a ceiling.
   Restated inside @supports as well as out: the original sizes these with a
   container query so they track the pitch's own width, and a plain scoped
   rule here would win on specificity and throw that away. 13/336 is the same
   ratio the original used at 12, with its floor moved up by one to match.

   The ceiling is the point. 3.869cqw passes the candidates' fixed 16px once
   the pitch is wider than ~414px, which it is on any desktop window - at
   1100px the pitch is 439px and these rendered 16.97px, LARGER than the four
   names you are choosing between. The +1px made that crossover happen
   sooner, so it needed a cap rather than a smaller ratio: min() holds them
   one step under the candidates however wide the pitch gets, while max()
   keeps the small-screen floor. Phone: 13.03. Desktop: 15. */
body:has(#bt-pitch) .bt-pitch-card-name {
  font-size: 13px;
}
@supports (font-size: 1cqw) {
  body:has(#bt-pitch) .bt-pitch-card-name { font-size: min(15px, max(12px, 3.869cqw)); }
}

/* 2. One radius and one hairline. The candidates were on a 14px radius and
      a 1.5px border, and the pitch cards on a container-query radius that
      computed to 10.0189px - a value fitted to the pitch's width rather
      than chosen. Both land on the page's 10px and 1px.

      The pitch card's radius has to be restated inside the same @supports
      block the original used, or that rule's 2.976cqw wins on source order
      wherever container queries are supported, which is everywhere that
      matters. */
body:has(#bt-pitch) .bt-candidate {
  border-radius: 10px;
  border-width: 1px;
}
body:has(#bt-pitch) .bt-pitch-card {
  border-width: 1px;
}
@supports (font-size: 1cqw) {
  body:has(#bt-pitch) .bt-pitch-card { border-radius: 10px; }
}

/* 3. The position prompt takes the page's quiet grey and its smallest size:
      it names what to do, which is a label, not the content. It was 15px in
      --text, the same weight as the candidate names it introduces. */
body:has(#bt-pitch) .bt-round-position {
  font: 700 12px "Inter", sans-serif;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0 0 var(--control-gap);
}

/* 4. The candidate grid on the page's own rhythm - --control-gap between the
      four, twice that down to the pitch, which is the break between one
      group and the next everywhere else here. */
body:has(#bt-pitch) .bt-candidates {
  gap: var(--control-gap);
  margin-bottom: calc(var(--control-gap) * 2);
}

/* 5. The reveal table, same pass the other two games' tables had: 10.7px was
      a fitted decimal, and 9/10 appear nowhere else on the site. 12px is the
      smallest size the rest of the page uses; the percentage takes the 20px
      the header subtitle sits on.

      The wrong state's #ff8a8a - a FOURTH red - and the pitch cards' #0f0f0f
      were fixed at their own declarations rather than overridden from here,
      so the literals are gone from the file rather than merely outranked. */
body:has(#bt-pitch) .bt-comparison-col-title,
body:has(#bt-pitch) .bt-comparison-name,
body:has(#bt-pitch) .bt-comparison-value {
  font-size: 12px;
}
body:has(#bt-pitch) .bt-comparison-col-pct {
  font-size: 20px;
}
body:has(#bt-pitch) .bt-comparison-header {
  margin-bottom: calc(var(--control-gap) * 2);
}

/* The gate panel, brought back into line with the surfaces pass (2026-09-29).

   Its fill was --bg #0f0f0f, chosen when the card was #161616 and the two
   were six levels apart. Inside this scope the card is --panel #101010, one
   level away, so the "solid panel" its own comment describes stopped reading
   as a panel at all and only the number showed. --tile is the token for a
   raised surface inside a card - the same one the duel cards it stands in
   for use - so the gate now reads as the block it replaces.

   And its margins go to 0, matching the #duel-stack they were written to
   match: that stack was zeroed in this same block, which left the gate 14px
   taller than what follows it and made the whole card jump the moment the
   countdown ended. Measured before: 432.26px during the gate, 418.26px after. */
body:has(#duel-stack) .gate-panel {
  background: var(--tile);
  margin-top: 0;
  margin-bottom: 0;
  /* The panel stands in for the pair of duel cards, which went to 10px with
     everything else on this page; the shared rule still draws it at 14, the
     last 14px corner on any play screen. */
  border-radius: 10px;
}

/* --- Four-option multiple choice -------------------------------------------
   Ported from geoquiz/css/style.css, which has carried these rules since Name
   the Flag. Football never had a multiple-choice game, so the club quizzes are
   the first use here. --control-gap and @keyframes shake already exist in this
   file, so only the grid and the buttons came across. Keep the two files in
   step: if one changes, change the other. */
.options {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: calc(var(--control-gap) * 2);
}
.opt-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font: 400 20px "Anton", sans-serif;
  line-height: 1.2;
  letter-spacing: 0.01em;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--panel-edge);
  border-radius: 12px;
  padding: 18px 14px;
  min-height: 100px;
  cursor: pointer;
  transition: transform 0.12s ease, background 0.12s ease, border-color 0.12s ease;
}
/* hover:hover excludes touch - without it a tap can leave the hover state stuck
   on mobile until the next tap elsewhere. Same reason as geoquiz's own copy. */
@media (hover: hover) {
  .opt-btn:hover:not(:disabled) { border-color: var(--accent); transform: translateY(-1px); }
}
.opt-btn:active:not(:disabled) { transform: translateY(0); }
.opt-btn:disabled { cursor: default; opacity: 0.55; }
/* No .correct / .wrong styling here on purpose: the quiz shows no verdict
   between questions. You pick, it moves on, and the score is the whole payoff
   at the end. @keyframes shake stays in the file - the duel game still uses it
   at :1229 - but nothing in a club quiz triggers it. */

/* Geoquiz does not need this: its options are country names. One Liverpool
   option ran to 107 characters, which a two-column grid of 20px Anton cannot
   hold at phone width. quiz.js adds .options-long when any option in a question
   passes 40 characters. One column, smaller face, left-aligned because centred
   prose of that length is hard to scan.

   Anton, not Inter: the short tiles are Anton, and switching family mid-quiz
   made the long rounds look like a different game. Anton is condensed, so the
   same column holds more per line and the face can run larger than the 15px
   Inter it replaces without the block growing. */
.options.options-long { grid-template-columns: 1fr; gap: 8px; }
.options.options-long .opt-btn {
  font: 400 17px/1.3 "Anton", sans-serif;
  letter-spacing: 0.01em;
  /* Centred, like every other tile and like the question above them. This used
     to set text-align: left and justify-content: flex-start, on the reasoning
     that centred prose of this length is hard to scan. That reasoning predates
     text-wrap: balance below, which evens the lines out; with it, the one-column
     round no longer reads as a different screen from the other fourteen. */
  min-height: 0;
  padding: 14px 16px;
}

/* ===========================================================================
   CLUB FAN QUIZZES  (/football/<club>/)
   ===========================================================================
   A different shape from the twelve daily games, so it gets its own block
   rather than bending theirs. Pages are GENERATED from
   tools/clubquiz_template.html, so every club inherits all of this.
   =========================================================================== */

/* Club colours come from the site's OWN data, data/clubs_canonical.json in the
   football video project, which already stores a colour for 728 clubs. Do not
   invent one and do not paste a brand hex off the web; look it up there.

   Each club needs TWO values, because the stored brand colour is a saturated
   mid-tone that works as a fill and fails as text:
     --club-fill  the stored colour, used where it is a FILL against black,
                  always with white text (Liverpool 5.01:1, Barcelona 7.83:1)
     --accent-rgb the same hue and saturation lightened until it clears 4.5:1
                  as TEXT on black, because --accent is a text colour 47 times
                  in this file, some of it at 10-11px

   --accent and --accent-wash must be RESTATED, not just --accent-rgb: :root
   declares them as rgb(var(--accent-rgb)) and a custom property is substituted
   at the element that declares it, so descendants otherwise inherit the
   already-resolved blue.

   Stored / derived, for the clubs looked up so far:
     Liverpool   #E00020 -> #EA0021 (4.52:1)
     Barcelona   #A50044 -> #E7005F (4.56:1)
     Bayern      #DC052D -> #EB0530 (4.60:1)
     Arsenal     #DC0006 -> #EB0006 (4.54:1)
     Real Madrid #FFFFFF -> clears as text already, use it directly */
.clubquiz-liverpool {
  --accent-rgb: 234, 0, 33;
  --accent: rgb(var(--accent-rgb));
  --accent-wash: rgba(var(--accent-rgb), 0.08);
  --accent-dim: #7a1526;
  --club-fill: #E00020;
}
.clubquiz-barcelona {
  --accent-rgb: 231, 0, 95;
  --accent: rgb(var(--accent-rgb));
  --accent-wash: rgba(var(--accent-rgb), 0.08);
  --accent-dim: #5c0026;
  --club-fill: #A50044;
}
.clubquiz-arsenal {
  --accent-rgb: 235, 0, 6;
  --accent: rgb(var(--accent-rgb));
  --accent-wash: rgba(var(--accent-rgb), 0.08);
  --accent-dim: #7a0003;
  --club-fill: #DC0006;
}
/* The one club whose stored colour is not a hue. White needs no lightening to
   clear 4.5:1 as text on black, so --accent-rgb IS the brand value here, and
   --club-ink flips the Play More button's text to near-black because white
   type on a white fill is an invisible button. */
.clubquiz-realmadrid {
  --accent-rgb: 255, 255, 255;
  --accent: rgb(var(--accent-rgb));
  --accent-wash: rgba(var(--accent-rgb), 0.08);
  --accent-dim: #8c8c8c;
  --club-fill: #FFFFFF;
  --club-ink: #0b0b0b;
}
/* --- header -----------------------------------------------------------------
   The club name carries the accent and the kicker sits under it in the quiet
   grey, which is the reverse of the daily games, where the topic name is plain
   and the kicker is accented. On a club page the club IS the subject.

   One spacing rhythm, --cq-gap, between name, kicker and card. The logo keeps
   its own fixed margin above, so a longer club name scaled down to fit makes
   the block shorter and everything below moves up together. */
/* Two gaps run the whole page. --cq-gap-tight is the hair of air that keeps a
   subtitle attached to the thing it describes: the kicker under the club name,
   the rules line under the title (that one plus 2px, since tall Anton over small
   Inter caps needs slightly more than the header pair). --cq-gap-block is every
   real break: header to card, card edge to title, rules to button, button to
   card edge. It is 30px because the shared .card rule already insets the intro
   panel by 29px plus its 1px border, and that was the fixed quantity to match. */
.clubquiz {
  --cq-gap-tight: 2px;
  --cq-gap-block: 30px;
  /* These three are the shared header's own intro values, restated here only so
     the play-state pins further down have one place to read them from.

     --cq-name-size has a second job: quiz.js reads it off document.body as its
     "has the stylesheet applied yet" sentinel, because WebKit runs the deferred
     script before a pending sheet lands. Rename it there in the same commit, or
     the club name stops being fitted to the card. */
  --cq-name-size: 40px;
  --cq-logo-size: 60px;
  --cq-logo-gap: 10px;
  /* The question and the label above it are the only left-aligned prose on the
     card, so they read tighter against the edge than the centered rows do. Two
     pixels each side, on top of the card's own padding. Deliberately NOT applied
     to the option tiles: those are the thing the inset is measured against, and
     insetting them too would just move the whole block. */
  --cq-text-inset: 2px;
}
.clubquiz .site-header { margin-bottom: var(--cq-gap-block); }
/* Addressed through .clubquiz .site-header #site-title, not .cq-club: the base
   rule at :2109 carries an id, so a bare class loses to it. inline-block is
   load-bearing too - as a block the element fills the header and scrollWidth
   reports the BOX, so the fit logic scaled against 358px of empty space
   instead of the width of the word. */
/* body.clubquiz:has(...) is (2,2,1), which it has to be: the rule at :2300 is
   (2,1,1) and sets this to --text for every page with a #play-area, including
   this one. The file documents its own weight there; this is the next step up. */
body.clubquiz:has(#play-area) .site-header #site-title,
.clubquiz .site-header #site-title {
  font-size: var(--cq-name-size);
  color: var(--accent);
  display: inline-block;
  vertical-align: top;      /* without this the inline-block sits on a baseline
    and the line box adds a descender's worth of space under the name, which
    shows up as the two gaps being 13px and 10px instead of equal */
  white-space: nowrap;      /* quiz.js scales it down rather than wrapping */
  margin: 0;
}
/* Equal air above and below: --cq-gap sits between the club name and this, and
   the same value sits between this and the card (via .site-header's own
   margin-bottom). The extra 2px is the nudge down the owner asked for. */
.clubquiz .site-header .cq-kicker {
  color: var(--text-dim);
  display: block;
  margin: var(--cq-gap-tight) 0 0;
  padding: 0;
}

/* --- intro card -------------------------------------------------------------
   The title is a sentence, not a short game name. .start-title is Anton with
   white-space: nowrap, fitted per game to the content column and documented as
   never wrapping; a sentence in it is simply cut off. .cq-title wraps, and
   quiz.js scales it down if it would outrun the button below it. */
.cq-title {
  font: 400 25px/1.15 "Anton", sans-serif;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  text-wrap: balance;
  /* --text, not --text-dim: tried the quieter grey and the whole card receded
     too far. The club's own name inside the sentence carries the colour, so the
     contrast between the two is what does the work, not dimming the sentence. */
  color: var(--text);
  /* The rules line belongs to the title above it, the same way the kicker
     belongs to the club name, so it hugs it and the larger gap goes underneath,
     before the button. Two px looser than the header pair: the title is tall
     uppercase Anton and the rules line is small Inter caps, so the two need
     slightly more air than the header's pair to read as grouped rather than
     collided. */
  margin: 0 0 calc(var(--cq-gap-tight) + 2px);
}
/* The club's own name inside the title carries the club colour, so the card
   echoes the header without repeating it. build_clubquiz.py wraps the
   short_name where it appears in the title sentence. */
.cq-title .cq-name { color: var(--accent); }

/* The air above the button matches the air below it. The card's own bottom
   inset is the fixed quantity (29px of padding plus its 1px border = 30px to
   the visible edge), so this is set to match rather than the other way round. */
.clubquiz .start-panel .start-rules { margin: 0 0 var(--cq-gap-block); }
/* The air above the title matches the air below the button. The shared .card
   rule leaves only 10px on top, which is right for a game card whose first
   element is a short name and wrong for one that opens on a sentence. Minus
   the 1px border, so the visible distances match. */
.clubquiz .card:has(#start-panel:not([hidden])) {
  padding-top: calc(var(--cq-gap-block) - 1px);
}
/* .start-panel carries its own 10px top padding, which sat on top of the card's
   and made the gap 40px against the button's 30px. The card owns that inset
   here, so the panel contributes none. */
.clubquiz .start-panel { padding-top: 0; }

/* The header does not change when play starts. Three shared rules shrink
   #site-title once #play-area shows (32px at :2116, 24px at the 480px tier,
   30px at :2782) and a fourth hides the kicker beside it. All four are right for
   the daily games, whose header carries the live goal line, and wrong here,
   where the club name IS the page. Nothing is needed to keep the kicker: that
   hide rule keys off #hub-kicker, an id the club template deliberately omits.

   body.clubquiz:has(...) is (3,4,1), which it has to be: the :2782 rule is
   (3,3,1) and a plain .clubquiz chain would lose to it. The inline font-size
   quiz.js sets would mask the size anyway, but margin-top is not inline and the
   cascade should not depend on the script having run. */
body.clubquiz:has(#play-area) .wrap:has(#play-area:not([hidden])) .site-header #site-title {
  font-size: var(--cq-name-size);
  margin-top: 0;
}
/* The zebra keeps its intro size and place. The shared rules shrink it to 47px,
   pull the mark up 18px and nudge it 2px down again, which together is what
   lifts the daily games' header out of the way of a timed round. */
body.clubquiz:has(#play-area) .wrap:has(#play-area:not([hidden])) .site-logo-icon {
  width: var(--cq-logo-size);
}
body.clubquiz:has(#play-area) .wrap:has(#play-area:not([hidden])) .site-logo-mark {
  position: static;          /* drops the shared 2px relative nudge with it */
  margin: 0 auto var(--cq-logo-gap);
}
/* ...and the air under the header, which the shared rule takes to 0 to bring a
   timed card up. With these four pinned, the card's top edge lands at the same y
   in both states as a consequence, rather than by being set twice. */
body.clubquiz:has(#play-area) .wrap:has(#play-area:not([hidden])) .site-header {
  margin-bottom: var(--cq-gap-block);
}

/* --- play area --------------------------------------------------------------
   One rhythm on the container rather than a margin per child. */
#round-view { display: flex; flex-direction: column; gap: 14px; }
#round-view #progress-label { margin: 0; padding-inline: var(--cq-text-inset); }
#round-view .options { margin-top: 0; }

/* Scorelines and seasons stay whole; quiz.js wraps each one in this. */
.nb { white-space: nowrap; }
/* The option's text lives in one span so the button keeps a single flex item
   no matter how many .nb spans the string needs. */
.opt-btn > .opt-text {
  display: inline;
  /* As a flex item this blockifies, which is what lets balance apply to it.
     Even line lengths matter most on the one-column round, where an option runs
     to three lines, but it costs nothing on the short tiles. */
  text-wrap: balance;
}
.cq-question {
  font: 600 19px/1.35 "Inter", system-ui, sans-serif;
  color: var(--text);
  margin: 0;
  padding-inline: var(--cq-text-inset);
  /* Exactly three line boxes: 3 x the 1.35 line-height. The reserve keeps the
     option grid at a constant y from question to question, which is the whole
     point of it, and three lines is now the hard ceiling (see
     tools/check_question_lines.py), so reserving the old 5.11 lines just left
     two lines of dead air above the tiles. Set in em so it follows the font
     down to 17.5px at the 480px tier without a second value. */
  min-height: 4.05em;
  /* Block, not flex. It was flex only to top-align the text inside the
     reserved height, which block does by itself, and a flex container would
     have turned every text node and .nb span into its own flex item laid out
     in a row that never wraps. */
  display: block;
  /* balance, not pretty. pretty only tidies the LAST line; it still fills every
     line greedily, so a long word that will not fit leaves a hole on the right
     and a stub below. Measured at 390px across the fifteen Liverpool questions:
     pretty left a mean of 182px of empty space and up to 263px, with Q1 running
     299 / 326 / 73. balance evens them to 217 / 246 / 231 and brings the mean to
     119px. Line counts are identical at 430, 414, 390, 375 and 360, so it
     cannot push a question past the three-line rule.

     Not justify, which stretches word spacing to fill lines 1 and 2 but leaves
     the same 73px stub on the last line: rivers of white space between words,
     and the thing that looked wrong still there. Not hyphens: auto either,
     which measured no change at all. */
  text-wrap: balance;
}

/* --- result -----------------------------------------------------------------*/
.cq-band {
  font: 400 22px/1.15 "Anton", sans-serif;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  /* The brighter grey (#e7e7e7), not the club colour: the score above it is
     already in the accent, and two accent lines stacked made neither of them
     the thing you look at first. Same reasoning, and the same token, as
     .cq-title. --text-soft and --text-dim are both #999999 here, so --text is
     the only one of the three that reads as bright. */
  color: var(--text);
  margin: 2px 0;
}
/* The question block is centred, like everything else on this card.

   It was left-aligned, and with the greedy default wrap that was right: lines
   of wildly different length start at wildly different places when centred, and
   the short last line floats in the middle. text-wrap: balance above changed the
   arithmetic. Measured across the fifteen Liverpool questions at 390px and
   360px, centring now moves each line's start by 23px on average and 43px at
   worst, against a left-aligned block whose right edge has holes of 119px on
   average and 165px at worst. Centring does not remove the raggedness, it splits
   it evenly between two edges instead of piling it on one.

   #progress-label is a single line, so it has no wobble at all. */
body.clubquiz:has(#play-area) #round-view { text-align: center; }

/* The fifteen questions, listed inside the collapsed explainer.

   They are there because none of them reached a crawler otherwise:
   renderQuestion() is only reachable from a click handler, and Google renders
   JavaScript but does not click, so no question text ever entered the DOM for
   any crawler. Every AI crawler is worse off still, since none of them run JS
   at all. This block took the page from 474 crawlable words to 897.

   Styled quiet on purpose, like the rest of .game-seo: this is reference
   material for someone who opened the explainer, not a second copy of the
   game. The options sit under their question in --text-dim so the questions
   stay scannable, and the correct answer is not marked anywhere. */
.game-seo .cq-qlist {
  margin: 0 0 18px;
  padding-left: 1.5em;
}
.game-seo .cq-qlist li {
  margin-bottom: 10px;
  padding-left: 0;        /* the shared .game-seo li inset is for its bullet */
  position: static;
}
/* .game-seo li draws its own bullet with ::before, because a native marker
   hangs into the gutter in that block's two-column variant. These items are
   numbered, so they would get both a number and a dot. */
.game-seo .cq-qlist li::before { content: none; }
.game-seo .cq-qlist li::marker { color: var(--text-dim); }
/* The question takes --text and the options keep the block's own --text-soft.
   The first attempt put the options on --text-dim, which separates them from
   nothing: --text-soft and --text-dim are the same #999999 in this file, so
   the two lines rendered identically. The contrast has to come from making the
   question brighter, not the options quieter. */
.game-seo .cq-qlist-q { color: var(--text); }
.game-seo .cq-qlist-opts {
  display: block;
  margin-top: 2px;
  font-size: 12.5px;
}

/* The result screen's own vertical rhythm.

   The shared .result-sub sits under a short Inter line in the daily games; here
   it sits under a 22px Anton band, and the daily rules pulled it up INTO the
   band's descenders. Measured: the band's ink ran 245..278 while the subtitle
   started at 266, an overlap of nearly 12px.

   Addressed by id, not class: the shared rules that set these margins are
   body:has(#play-area) #result-panel .result-title at (2,1,1) and friends, so a
   class chain here would lose. #result-score and #result-band put
   this at (3,1,1), the same arithmetic the file documents for the header
   at :2782. */
body.clubquiz:has(#play-area) #result-panel #result-score { margin: 2px 0 0; }
body.clubquiz:has(#play-area) #result-panel #result-band {
  /* Bottom 12.85px, which PAINTS as 17px: the card's own 16px padding plus its
     1px border, so the air above the buttons matches the air below them down to
     the card's visible edge.

     Not 17px flat, which is what the cut #result-blurb used. That line was
     13px Inter whose line box ended where its letters did. This is 22px Anton
     in all caps, whose line box hangs 4.15px below the baseline (3px font
     descent plus half-leading) with no descender ever drawn into it. Measured
     with canvas font metrics, not with a Range, because a Range returns the
     line box and would have left the band looking 4px too far from the
     buttons.

     Settled against the RENDERED PIXELS, not against font metrics, which were
     wrong here twice: a Range returns the line box (7.85px below the letters),
     and canvas measureText on the raw string counts the descender of the "g" in
     "nights" against letters this element never draws, since it is
     text-transform: uppercase. Even measuring the uppercased string, Anton's
     reported fontBoundingBoxAscent puts the baseline 4px above where the caps
     actually land.

     So the value comes from scanning the screenshot for the lowest row of lit
     pixels in the band and the first row of the button beneath it:
     tools/../scratchpad pixel_gap.py. 13.86px of margin painted as exactly 17px
     of air, matching the 16px padding plus 1px border below the buttons, and
     the owner then asked for 4px more: capitals have no descenders, so an
     equal measured gap reads tight against a button's flat edge. 17.86px now
     paints as 21px above and leaves 17px below. In all caps every band has the
     same descent, so one value holds for all five.

     Both halves move together whenever the phrase shifts on its own, or the
     rest of the card gets dragged with it. The phrase has come down 4px from
     where that note was written: 2px of it arrives from #result-score's own new
     margin above, the other 2px from this top going -2 -> 0. The bottom then
     went back up by 4 to carry the buttons down with it. */
  margin: 0 0 19.86px;
}
/* "of 15" hangs off the right of the score without taking part in centring it.
   The number stays exactly where it sat when it was alone on the line, which is
   the point: it is the result, and the denominator is a footnote. Hence
   position: absolute on the suffix and position: relative on the number, rather
   than two inline spans, which would have shifted the number left by half the
   suffix's width. */
#result-score .cq-score-num { position: relative; display: inline-block; }
#result-score .cq-score-of {
  position: absolute;
  /* 0.22em is 6.16px of air off the digit's right edge, plus the 2px nudge
     asked for after looking at it. */
  left: calc(100% + 0.22em + 2px);
  /* 6px above the number's own bottom edge. Flush bottoms measured equal but
     read as sitting low: the digit is 28px and the footnote 11.2px, so a shared
     baseline hangs the small text off the bottom of a much taller glyph rather
     than beside it. Raised on request after looking at it. */
  bottom: 6px;
  font-size: 0.4em;
  letter-spacing: 0.02em;
  color: var(--text-dim);
  white-space: nowrap;
}

/* Play More carries the accent, not Share. This page is reached cold from
   search by someone who may not know the site exists, and the whole reason it
   exists is to turn that visitor into someone who plays the daily games - the
   one POST at the end of quiz.js is there to measure exactly that. A share
   brings another visitor to this same single page; Play More is what turns one
   into a user of the site. The daily games keep their own arrangement, where
   Share takes the accent (see :564); this is the club pages only.

   The club's exact brand colour, on the one surface where it is a fill against
   black rather than text. White text clears 5.01:1 on Liverpool's #E00020,
   7.83:1 on Barcelona's #A50044 and 5.26:1 on Arsenal's #DC0006. Real Madrid's
   fill IS white, where white text would be invisible, so the ink comes from
   --club-ink, which that club alone overrides to near-black.
   #more-btn is an <a>, not a <button>, so it inherits neither the daily rule
   nor the removed underline. */
.result-actions #more-btn {
  background: var(--club-fill);
  color: var(--club-ink, #ffffff);
  text-decoration: none;
}
@media (hover: hover) {
  .result-actions #more-btn:hover { background: var(--club-fill); filter: brightness(1.12); }
}
/* Share drops to the neutral control fill: still plainly a button, no longer
   the loudest thing on a screen whose job is the one next to it. */
.clubquiz #share-btn:not([hidden]) { background: var(--control); color: var(--text); }
@media (hover: hover) {
  .clubquiz #share-btn:not([hidden]):hover {
    background: var(--control-hover); filter: none;
  }
}

/* House breakpoints are 480 and 375; an earlier pass of this block invented
   560 and 400, which were the only two of their kind in the file. */
@media (max-width: 480px) {
  .options { gap: 16px; }
  .opt-btn { padding: 16px 10px; font-size: 20px; min-height: 100px; }
  .options.options-long { gap: 8px; }
  .options.options-long .opt-btn { min-height: 0; padding: 13px 14px; }
}
/* (max-height: 700px) as well as the width, which is what Name the Flag's own
   tier uses (geoquiz/css/style.css @media (max-width: 375px),
   (max-height: 700px)). Without the height arm this tier never fired on a real
   phone: a 390x664 viewport is wider than 375, so the club quiz kept the 480
   tier's 16px gaps and 100px tiles while the flag game had already stepped down
   to 8px and 80px. That is the whole difference the owner saw between the two. */
@media (max-width: 375px), (max-height: 700px) {
  .options { gap: 8px; }
  .opt-btn { padding: 10px 8px; font-size: 15px; min-height: 80px; }
  .options.options-long .opt-btn { padding: 12px 13px; }
  .cq-title { font-size: 22px; }
  .cq-question { font-size: 17.5px; }
  #round-view { gap: 12px; }
}

/* ---- result-screen buttons, ported from the geography repo (2026-10-06) ----
   Identical there. Both sites' result screens were the same shape before this
   and should stay so; fix a problem in one and port it rather than letting
   the two drift. */
/* The share button sits BELOW the two-up row rather than in it, full width and
   in the accent. It is the only action on the result screen that brings a NEW
   person to the site; everything else just moves someone already here. */
.result-share { margin-top: 10px; }
/* The other-game button is an <a>, so it needs the underline removed that a
   <button> never had. Pure black rather than --control, with the label in the
   accent: it has to read as the quieter of the two without disappearing, and
   --control sat too close to the card's own --panel to separate them. */
#other-game-btn {
  /* One child since 2026-10-07 - the "Try" verb is gone - so centring it is the
     whole job. This was a 1fr auto 1fr grid whose only purpose was to hold the
     name in the middle track while a variable-width verb sat in the left one,
     and before that a hidden ::after copy of the verb balancing it on the right.
     Both are unnecessary now. */
  display: flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
  text-decoration: none;
  color: var(--accent);
  background: var(--bg);
  /* --accent-dim, the same border the player's OWN leaderboard row carries:
     accent, but stepped back. A full --accent outline made this the loudest
     thing on the screen, louder than the share button it is meant to sit
     under. Kept at 1.5px so it matches the weight of One More Round beside
     it - the pair differs in colour, not in thickness. */
  border: 1.5px solid var(--accent-dim);
}
/* Both buttons in this row are outlined, in their own colour, so the pair reads
   as one control split in two. The text here is --text-dim rather than --text:
   this button was .btn-primary until the share button took the accent off it,
   and at full --text it competed with the accent-outlined button beside it
   instead of sitting behind it. .btn-secondary sets border: none, so these need
   the ID to win. */
/* The game name is the whole label. */
.other-game-name {
  /* line-height 1 so the 18px Anton does not inflate the button past the
     15px Inter one beside it - a flex row takes the height of its tallest
     child, and the name's default line box was that child. */
  font: 400 1em/1 "Anton", sans-serif;
  letter-spacing: 0.02em;
}


.result-actions #next-btn {
  white-space: nowrap;
  /* --text, not --text-dim (owner, 2026-10-06): the quiet grey made
     this read as disabled beside the accent-bordered button next to
     it. It is still the secondary of the two - that is carried by the
     neutral border, not by dimming the label. */
  color: var(--text);
  /* Back to --panel-edge after a try at --text (owner, 2026-10-06):
     a full #e7e7e7 edge made this secondary button louder than the
     accent-bordered primary beside it. The label stays --text. */
  border: 1.5px solid var(--panel-edge);
}
/* Exactly half each, whatever the labels say. flex: 1 alone grows from the
   CONTENT width, so the longer label took the wider button and the pair moved
   around as the game name changed. flex-basis 0 fixes the split, and
   min-width: 0 is what actually lets a flex item shrink below its content -
   without it the text would push the box back out instead of being shrunk to
   fit by fitOneLineBtn. */
.result-actions #other-game-btn,
.result-actions #next-btn {
  flex: 1 1 0;
  min-width: 0;
  /* 8px of side padding instead of the shared 22px (owner: 4px 2026-10-06,
     raised to 8px 2026-10-07). These two are fixed at half the row each, so
     every pixel of padding is a pixel the label has to give up - at 22px a
     long game name was being shrunk hard for space that was only ever going
     to be empty. The vertical padding is untouched, so the buttons keep their
     height. fitOneLineBtn reads this value at runtime rather than hardcoding
     it, so the shrink-to-fit follows the number automatically. */
  padding-left: 8px;
  padding-right: 8px;
}

/* The comparison, folded shut. Borrows the "More about" toggle's whole look -
   both chevrons, the sizing, the open-state rotation, the same --text-dim - by
   joining those selector lists above, so the two folds on a page read as one
   kind of control. 18px above and below, matching the band everything else
   below the card sits in. Ported from geography, 2026-10-06. */
/* #recap-divider is gone too (2026-10-07), for the same reason and one day
   later: Who Has More?'s duel recap now sits inside its own shut
   <details class="reveal-fold">, so there is nothing left to divide. It had
   outlived #comparison-divider only because the two shared a rule and
   deleting that took this with it by accident; this removal is deliberate. */

/* #comparison-divider is gone (2026-10-06). The <hr> it styled separated the
   result buttons from the comparison below them; that now sits inside a
   <details class="reveal-fold"> shut by default, so there is nothing to
   divide. Spacing comes from .reveal-fold's own margin. Ported from
   geography. */

.reveal-fold { margin-top: 18px; }
.reveal-fold > summary { margin-top: 0; }
.card:has(.reveal-fold) { padding-bottom: 18px; }

/* ---- football-only: beating this repo's own play-state block ----------------
   Football styles the result row through
   `body:has(#play-area) #result-actions .btn-secondary` (2,1,1), which geography
   has no equivalent of. Everything ported above lost to it: the grid never
   applied, so the game name was not centred and the verb was not baseline
   aligned, and the 4px side padding stayed at 18px. #result-panel lives INSIDE
   #play-area, so these rules are in force on the result screen too. */
body:has(#play-area) #result-actions #other-game-btn {
  /* Restates the centred row from the base rule, which this (2,1,1) selector
     would otherwise beat. The align-content that used to be here went with the
     grid: a flex row with align-items: center has no spare cross-axis space to
     mis-place, which is what pinned the label to the top of the fixed-height
     button before. */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 8px;
}
body:has(#play-area) #result-actions #next-btn { padding: 0 8px; }
/* The share button left #result-actions, so it stopped getting that block's
   height and no longer matched the pair above it. */
body:has(#play-area) .result-panel .result-share {
  height: var(--control-h);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 18px;
  font-size: 16px;
}
/* The practice-state restore selects .btn-primary; #next-btn is .btn-secondary
   now, so it was left a size behind the link under it. */
/* And the card's own play-state padding (1,2,1) beat .card:has(.reveal-fold),
   so the 18px below the fold never applied. */
body:has(#play-area) .card:has(.reveal-fold) { padding-bottom: 18px; }

/* ---- the wide card closing the hub grid ------------------------------------
   Mirrors the geography hub's Country Quizzes card. Its width is the row the two
   232px cards make, written as that arithmetic rather than 480 so its edges stay
   on the columns' outer edges if either number moves. 12px padding rather than
   18, since it holds two lines rather than five. */
.hub-card.hub-card-wide {
  width: calc(232px * 2 + 16px);
  max-width: 100%;
  /* 4px top and bottom since 2026-10-07, which puts both wide cards at 40px -
     exactly twice .card's own 20px corner, so the short sides are half circles. The
     content is a 26px Anton line in one and a 29px name box in the other, so
     the height had to come out of the padding. Sides stay 12. */
  /* 14px top and bottom: the card is 1.5x its old height (40px -> 60px), which
     has to come from the padding - the content is a single 26px Anton line and
     cannot grow. Sides stay 12. */
  padding: 14px 12px;
}
/* The subtitle is gone from this card (2026-10-07). The only thing then left
   making the gap below the title bigger than the gap above it was the title's
   own 2px bottom margin - the top gap is the card's 12px padding and nothing
   else. Dropping it squares the two and takes another 2px off the height.

   Its own rule, not folded into the .hub-card-wide .start-title block below:
   football's copy of that block is shared with .clubindex .hub-card
   .start-title, which still has a subtitle under it and needs the margin. */
.hub-card-wide .start-title { margin-bottom: 0; }
.hub-card-wide .start-panel {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}
.hub-card-wide .cq-text { flex: 1 1 auto; }
/* Two crests a side. 6px in from the card's own 12px padding so they line up
   with the CONTENT of the cards above, which carry 18px. 4px down because the
   title is 26px Anton at line-height 1.15, so its cap top sits below its line
   box top. */
.cq-crests {
  flex: 0 0 auto;
  display: flex;
  gap: 5px;
  margin: 4px 6px 0;
}
/* The mask carries the shape, background-color carries the colour, so the ink is
   var(--accent) exactly and the anti-aliased edges come through as partial alpha.
   One height for all four; aspect-ratio is set per crest inline, which is what
   gives each its own width. 20px is the title's cap height at 26px Anton - the
   letters, not the line box. */
.cq-crest {
  display: block;
  height: 20px;
  width: auto;
  background-color: var(--accent);
  -webkit-mask: var(--crest) no-repeat center / contain;
  mask: var(--crest) no-repeat center / contain;
}
/* Accent on the left running to --text on the right, on this card and on the
   club cards' own titles. width: fit-content is load-bearing: the heading is a
   block inside a flex item, so without it the gradient paints across the whole
   column and the accent end lands in empty space beside the centred text. */
.hub-card-wide .start-title,
.clubindex .hub-card .start-title {
  width: fit-content;
  margin-inline: auto;
  /* Both stops are overridable per card, defaulting to the shared
     accent-to-text pair. Real Madrid's accent IS white, so the default
     painted white onto near-white with no transition to see. That card
     runs --text (the light grey) into its own white accent instead, so it
     reads left-to-right like every other card but brightens rather than
     dimming - the one card whose accent is lighter than --text.
     (Owner, 2026-10-06: light grey left, full white right.) */
  background: linear-gradient(to right, var(--title-from, var(--accent)), var(--title-to, var(--text)));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
@media (min-width: 375px) and (max-width: 511px) {
  .hub-card.hub-card-wide {
    zoom: 1;
    width: calc(232px * 2 * 0.7759 + 6px);
  }
}
/* Below 375px the grid is ONE column, so there is no two-card row to span and
   this card matches its siblings. 375 is measured, not derived: the arithmetic
   said 398, but the wrap's own negative margins at that tier widen the row and
   two cards keep fitting down to 375.
   No zoom override here: the card takes .hub-card's own.

   MUST stay last - .cq-crests sets display: flex at equal specificity, so placed
   earlier this loses on source order and the crests never hide. */
@media (max-width: 374px) {
  .hub-card.hub-card-wide { width: 232px; }
  .cq-crests { display: none; }
}

/* Result-row type scale (owner, 2026-10-07), identical on both sites.
   Phone: game name 17, One More Round 14, Challenge a Friend 15.
   Desktop: 19 / 15 / 16. Before this geography ran 18/15/15 at EVERY width
   with no breakpoint, and football ran 16.8/14/16 below 480 and 19.2/16/16
   above - a divergence neither repo recorded a reason for.

   The game name's size is set on the BUTTON, not on .other-game-name, and
   the name is 1em of it. That is load-bearing rather than stylistic:
   fitOneLineBtn shrinks the button's own font-size to keep a long name
   inside its 8px side padding, and only a relative size on the child
   follows it down. An absolute size on the name would ignore the fit and
   run straight through the padding again.

   Specificity and position: football styles this row through
   body:has(#play-area) #result-actions .btn-secondary, which is (2,1,1).
   These match that weight and sit at the END of the file, so source order
   decides. Geography has no competing rule and is unaffected by the extra
   weight. Moving this block earlier would silently restore the old sizes
   on football only. */
body:has(#play-area) .result-actions #other-game-btn { font-size: 19px; }
body:has(#play-area) .result-actions #next-btn { font-size: 15px; }
body:has(#play-area) #share-btn { font-size: 16px; }
@media (max-width: 480px) {
  body:has(#play-area) .result-actions #other-game-btn { font-size: 17px; }
  body:has(#play-area) .result-actions #next-btn { font-size: 14px; }
  body:has(#play-area) #share-btn { font-size: 15px; }
}

/* The daily-whiz card at the top of each hub. Everything about its box comes
   from .hub-card-wide; this is only what goes inside it.

   The label and the name sit on ONE line, centred as a pair, which is why this
   overrides .hub-card-wide .start-panel's space-between (that spreads the wide
   card's flag groups to the edges, which is the opposite of what is wanted
   here). baseline, not centre, so the small label sits on the same line as the
   much larger name instead of floating in the middle of it.

   flex-wrap lets the name drop below the label on a one-column phone, where
   both will not fit across. The card grows a line there; clipping someone's
   name would be worse. */
/* The card is only as wide as what is in it, and sits on its own row. The
   wrapper is what guarantees that row: .hub-grid wraps, and a card narrower than
   the full width can otherwise pull a 232px game card up beside it. */
.whiz-row {
  flex: 0 0 100%;
  /* Plain block, not a centring flex row: the card is positioned by a measured
     margin-left (see placeWhizCard in the hub's own script), which centring
     would fight. */
  display: block;
  /* Half the grid's own row gap below this card, not the full 16px every other
     pair gets: it is a 40px strip rather than a 194px card, and the full gap
     left it floating away from the games it introduces. Written as a negative
     margin because the gap belongs to .hub-grid and applies to every row. */
  margin-bottom: -9px;
}
.card.hub-card.whiz-card {
  /* Starting width only - the script sets an explicit one once it can measure
     the label (see placeWhizCard). Both beat .hub-card's own 232px. */
  width: fit-content;
  max-width: 100%;
  /* 7px on the left so the globe clears the pill's own curve by a hair, against
     12px on the right where a straight-edged name box sits. */
  /* 5px at the top against 4px at the bottom: the content sits 1px lower in the
     card than centred, which is where it reads right against the heading above. */
  padding: 5px 12px 4px 7px;
  /* 2px, against .hub-card's own 3px. The game cards are five times this one's
     height, and the same offset that reads as a crisp edge on them reads as a
     heavy slab on a 40px pill. */
  box-shadow: 2px 2px 0 0 var(--accent);
  /* .hub-card zooms to 0.7759 between 375 and 511px. .hub-card-wide used to hold
     this card at 1; now that it no longer carries that class, it has to say so
     itself or the whole card shrinks on those phones. */
  zoom: 1;
}
.whiz-card .start-panel {
  /* Grid, not the flex row .hub-card-wide .start-panel sets up - see the note
     above about why two equal columns are what pins the name box to the centre.
     Same specificity as that rule, so this block has to stay after it. */
  display: grid;
  /* The label takes what it needs; the name box takes everything left over, so
     the width the card gains on the right lands in the box rather than beside
     it. Its LEFT edge is still set by the label plus the gap, which is what
     keeps it on the right game card's edge. */
  grid-template-columns: auto 1fr;
  align-items: center;
  /* The GRID's own gap. It is what sets the distance between the category and
     the name box, and the card is positioned so those two land on the game
     cards' own column edges - so this has to stay equal to .hub-grid's gap. */
  column-gap: 16px;
  row-gap: 0;
  /* Holds the height while the name is still empty, before the endpoint
     answers. On the panel because min-height does nothing on an inline. */
  min-height: 30px;
}
.whiz-label {
  justify-self: end;
  text-align: right;
  /* No padding here: the column gap above IS the separation, and padding would
     pull the text off the game card's edge it is meant to line up with. */
  font: 400 14px/1.2 "Anton", sans-serif;
  letter-spacing: 0.02em;
  /* Grey by default - only the key word carries the accent, see .whiz-key.
     Same --text the wordmark at the top of the page uses (#site-title). */
  color: var(--text);
  white-space: nowrap;
}
.whiz-name {
  /* stretch, not start: the name box is the flexible track now, so it has to
     fill it. justify-self: start would shrink it back to its own content and
     leave the width the card gained sitting empty beside it. */
  justify-self: stretch;
  max-width: 100%;
  /* The #1 leaderboard row's own box: pure black against the card's --panel,
     one --panel-edge hairline, the same 10px corner.

     4px of vertical padding, not the row's own 6px: 16px Inter plus 6px top and
     bottom and two borders comes to 33px, which is taller than the whole content
     band this card has. 4px lands at 29px and fits. */
  padding: 4px 10px;
  background: var(--bg);
  border: 1px solid var(--panel-edge);
  border-radius: 10px;
  /* Inter 400, the same face and weight .lb-name uses for the #1 rows inside the
     game cards, at 14px (direct instruction, 2026-10-07). Those rows are 12px in
     CSS but the cards around them are zoomed to 0.7759 on a phone, so they land
     near 9px there while this card is never zoomed - 14px keeps the name clearly
     the loudest thing on the hub without the gap the 16px it started at opened
     up at that tier. */
  font: 400 14px/1.2 "Inter", sans-serif;
  color: var(--text);
  /* A name longer than the half-card is cut rather than wrapped, so the card
     holds 56px for any name. The 15-character input cap makes this close to
     unreachable in practice; it is here so the layout cannot break, not because
     it is expected. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* The box is now wider than the name, so the name sits in the middle. */
  text-align: center;
}
/* 375-511px: the grid zooms its cards to 180px and tightens its own gap to 6px.
   The card's own column-gap used to be matched to it here; that went when the
   panel became a single column, where there is no gap to set. */
@media (min-width: 375px) and (max-width: 511px) {
  /* The grid's gap is 6px on this tier, so half of it is 3px. */
  .whiz-row { margin-bottom: -4px; }
}
/* The two emoji that used to flank this label are gone (2026-10-09): the
   drawn mascots below replaced them, and nothing on either site emits
   .whiz-e1/.whiz-e2 any more. Their recolour filter, measured against this
   site's own accent, went with them. */
/* width: auto is load-bearing, not tidying. The <img> carries width="118" so
   the row cannot reflow when the file lands, and that attribute is a
   presentational hint equivalent to width: 118px - which, on the absolutely
   positioned box below, wins over the intrinsic ratio and renders the drawing
   118px wide at 40px tall. An explicit auto is what hands the width back to
   the ratio. This cost the football hub a squashed wizard once already. */
.whiz-mascot {
  width: auto;
}
.whiz-key { color: var(--accent); }
/* The whiz card as shipped (2026-10-07): no chrome of its own, and the name
   stacked under the category rather than beside it.

   The chrome is made INVISIBLE rather than removed - transparent fill, border
   and no shadow, with the border still 1px wide - so the box keeps its exact
   geometry. transparent and not the page's own colour, which cannot then drift
   apart from it.

   --whiz-layout is what placeWhizCard reads to stand down: on "stack" it
   leaves the width and margins to this stylesheet, because the side-by-side
   layout needed an inline width and margin-left and inline styles would beat
   everything below.
   What it is NOT any more is a working switch. Flipping it would give a broken
   hybrid: the stacked rules further down (the panel's single minmax(0, 1fr)
   track, the name's own width, and the absolutely positioned mascots) all win
   on specificity or source order regardless of this value. The side-by-side
   rules and the measuring code in both hub scripts are kept as history, not as
   a path anyone can take. */
.card.hub-card.whiz-card {
  --whiz-layout: stack;
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  width: fit-content;
  margin-inline: auto;
}
.whiz-card .start-panel {
  grid-template-columns: auto;
  justify-items: center;
  /* Tighter than the 16px column gap the side-by-side layout borrows from the
     grid: that gap existed to land on the game cards' edges, and stacked there
     is nothing to land on. */
  row-gap: 4px;
  column-gap: 0;
  min-height: 0;
}
/* justify-self too, not just text-align: the base rule pins the label to the
   END of its column for the side-by-side layout, and stacked that leaves a
   short label floating to the right of the name box under it. It did not show
   while the label carried an emoji on each side and was the wider of the two;
   with the mascots moved outside it, it does. */
.whiz-label { text-align: center; justify-self: center; }
/* 4px shorter than the #1 rows it otherwise matches: same 12px Inter and same
   side padding, 4px vertical instead of their 6px, so the box is 26px against
   their 30px. */
.whiz-name { font: 400 12px "Inter", sans-serif; padding: 4px 10px; }
/* 4px off the gap between the hub kicker ("DAILY CHALLENGES") and the whiz card
   below it, scoped to the hub so no other page's header moves. The header's own
   margin is 16px; this card is a thin strip rather than a full card, and the
   full gap left it adrift from the heading it belongs to. */
.hub-wrap .site-header { margin-bottom: 10px; }
/* Inside a topic card the whiz sits under that card's "DAILY CHALLENGES" line,
   centred on the card and hugging its own content. The -4px top pulls it closer
   to that line: .topic-mini-header's own 8px margin is sized for the game cards
   that used to follow it, and left this strip adrift from the heading it
   belongs to. */
/* Portal: the two mascots are PINNED to the edges of the game cards below, so
   they stay put whatever the day's name or the label ("DAILY FOOTYWHIZ" vs
   "FOOTY-GOAT") happens to be. The left one's left edge is the left edge of the
   game cards, the right one's right edge is their right edge, and the label and
   name box take the space between them, centred (direct instruction,
   2026-10-09). The card is made as wide as the game cards (100%) and its side
   padding and side border are dropped here only, so nothing stands between the
   mascots and those edges; top and bottom keep their 1px so the strip's height
   is unchanged.
   The drawings are 40px tall and sit 1px above the name box's bottom edge -
   bottom: 5px against this card's 4px of bottom padding, since a positioned
   child measures from the padding box. Both numbers were tuned on the page
   across two passes - 14px shorter and 4px up, then 9px taller and 3px down -
   so they are what the owner saw and approved, not a derivation. The aspect
   ratio is untouched; width is still auto. */
/* Football's accent on its own card, the same way .topic-card-football sets it
   on the topic card below. --accent-rgb has to come too, or anything derived
   from it still resolves to mint at :root. */
.whiz-card-football {
  --accent: var(--accent-football);
  --accent-rgb: 126, 198, 255;
}

/* Everything from here is the whiz card as it was tuned on the portal, and
   it is deliberately NOT scoped to that page: this hub carries the
   same card and were asked to look the same (direct instruction,
   2026-10-09). Kept byte-identical to the geography repo's copy
   apart from this note; change them together. */
.card.hub-card.whiz-card {
  width: 100%;
  box-sizing: border-box;
  padding-inline: 0;
  border-inline-width: 0;
  /* The mascots are taken OUT of the flow below, so this is what the panel
     alone is centred by. */
  justify-content: center;
  position: relative;
  /* Held on its own compositor layer, always - this is about the hover lift,
     not about speed.
     The lift is a 2px translate over 0.12s, so the card passes through
     fractional offsets (0.33, 1.41, 1.88 ... measured). Text can be drawn at
     those, but EMOJI cannot: they are bitmaps and snap to whole pixels, so the
     name - which for a default is almost entirely emoji - jumped in two or
     three steps while the rest of the card glided, and then everything shifted
     once more at the end as the card dropped its temporary layer and the type
     was re-rasterised. Two movements and a shimmer, which is exactly what it
     looked like.
     A layer that is already there cannot be set up or torn down mid-gesture,
     so the glyphs are rasterised once and simply moved. translate3d rather
     than will-change: it is the same promotion without asking the browser to
     keep guessing about future changes. */
  transform: translate3d(0, 0, 0);
}
@media (hover: hover) {
  /* The same 2px lift a.hub-card:hover gives every card, restated in 3D so it
     stays on the layer above instead of swapping the card back to a 2D
     transform for the duration of the hover. */
  a.card.hub-card.whiz-card:hover { transform: translate3d(0, -2px, 0); }
}
/* The name box never gives up width to the mascots (direct instruction,
   2026-10-09). It used to: the three of them shared one flex row, so growing
   the drawings squeezed the box until a minted default name clipped to
   "qui (zebra) (globe) ...". Taking the drawings out of the flow means the
   panel always has the card's full width, every name renders in full, and
   where a long one reaches far enough to meet a drawing the drawing simply
   sits on top of it - which is what was asked for, and reads fine because the
   name is centred and the overlap is at its ends. */
/* minmax(0, 1fr), not the stacked layout's own auto: an auto track sizes to
   its widest item's content, so a name longer than the card stretched the
   track and pushed everything - including the title above it - off to one
   side. Pinned to the card's width, the name's box stays put and only its
   text spills, evenly, which is the whole point. min-width: 0 on the item
   keeps it from re-imposing its own content width as a floor. */
.whiz-card .start-panel {
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
  grid-template-columns: minmax(0, 1fr);
}
.whiz-card .whiz-name { min-width: 0; }
.whiz-card .whiz-mascot {
  position: absolute;
  /* Over the name box, not under it. Absolutely positioned elements already
     paint above in-flow content, so this is belt and braces against anything
     later giving the panel a stacking context of its own. */
  z-index: 2;
  height: calc(16.8px + 4px + 26px - 1.8px - 5px);
  /* 5px, not 1px: an absolutely positioned child is placed against its
     container's PADDING box, and the card carries 4px of bottom padding, so
     4 + 1 puts the drawing the same 1px above the name box's bottom edge that
     the margin did while it was in the flow. */
  bottom: 5px;
}
/* The #1 name loses its box and is just text (direct instruction,
   2026-10-09). Only the two things that DRAW the box go: the fill and the
   border's colour. The border itself stays 1px and the padding stays put, so
   the row keeps its exact 26px and nothing that was tuned against it moves -
   not the panel's height, not the card's, and not the mascots that are placed
   1px above this box's bottom edge. The same trick the whiz card itself uses
   to be chromeless a few rules above. */
.whiz-card .whiz-name {
  background: transparent;
  border-color: transparent;
  /* The name is shown in FULL, whatever its length, and the mascots sit over
     its ends where it reaches them (direct instruction, 2026-10-09).
     The base rule ellipsises at the box's width, which was fine while the card
     was as wide as a column of game cards; the hubs then pinned it to 147px and
     the ellipsis came back. Measured at that width: a realistic 15-character
     name needs 128px and fits, but the widest the rename field actually allows
     - 15 capital Ws, and the field's cap is 15 UTF-16 units, so a person really
     can type it - needs 203px and lost 57px of itself. The API's own cap is 20
     units, which is ten emoji at 187px.
     The box keeps the card's width and only its CONTENT is allowed out, which
     is the part that matters: sizing the BOX to the name instead grew the
     panel's grid track to 203px, so the overflow went out one side only and
     took the title's centring with it. A centred nowrap line that is wider
     than its box spills equally on both sides, which is what this wants.
     Safe at every width down to 320px on all three pages: the worst case
     spans 203px centred, stays on screen, and never scrolls the page. */
  overflow: visible;
  text-overflow: clip;
  /* Sized to the name and centred in the track, which is what makes a long one
     hang out EVENLY on both sides. text-align cannot do it: an inline line
     wider than its line box overflows the end edge only, so a centred name
     still ran off to the right alone. This only works because the track above
     is minmax(0, 1fr) and so ignores content - with the stacked layout's own
     auto track, a box sized this way grew the panel instead. */
  width: max-content;
  max-width: none;
  justify-self: center;
}
/* Both shifted down, the title by 12px and the name by 7px (direct
   instruction, 2026-10-09, settled by eye over three passes). Nudged with
   position rather than with margin or
   padding on purpose: those would grow the panel, which would grow the card,
   which would move the mascots - they are placed against the card's bottom
   edge - and push the game cards below it down too. A relative offset moves
   only what is drawn, so the two amounts are exactly the two amounts asked
   for and nothing else shifts with them. */
.whiz-card .whiz-label { position: relative; top: 12px; }
.whiz-card .whiz-name { position: relative; top: 7px; }
/* Left and right edges, with the left pair pushed 4px further out (direct
   instruction, 2026-10-09: 6px out, then 2px back). :first-of-type and
   :last-of-type rather than nth-child
   because the panel sits between them. */
.whiz-card .whiz-mascot:first-of-type { left: -4px; }
/* On a hub the card is a frozen 147px wide (see the rule just above) and the
   drawings hang 22px out past its edges on each side (direct instruction,
   2026-10-09 - the card was briefly sized to each page's own title, which made
   the two hubs disagree, so football's width was frozen and geography adopted
   it). The portal has its own, smaller overhang, where they line up with the
   game cards instead. .whiz-row is the hubs' own wrapper; the portal has none. */
/* 147px is FOOTBALL's own title ink, frozen (direct instruction, 2026-10-09:
   keep football's spacing, give geography the same). Both hubs use it, so the
   drawings sit in the same place on each whatever the page is called - which
   sizing to each title could not do, GEOGRAPHY being 30px the wider word.
   Needs the full .card.hub-card.whiz-card to outrank that rule's own width:
   100% further up. */
.whiz-row .card.hub-card.whiz-card { width: 147px; }
.whiz-row .whiz-card .whiz-mascot:first-of-type { left: -22px; }
.whiz-row .whiz-card .whiz-mascot:last-of-type { right: -22px; }
.whiz-card .whiz-mascot:last-of-type { right: 0; }
/* 4px off the gap between the hub kicker ("DAILY CHALLENGES") and the whiz card
   below it, scoped to the hub so no other page's header moves. The header's own
   margin is 16px; this card is a thin strip rather than a full card, and the
   full gap left it adrift from the heading it belongs to. */
.hub-wrap .site-header { margin-bottom: 10px; }

/* ---- the whiz archive page ------------------------------------------------
   A heading, the all-time holder, and a table. The names reuse .whiz-name, so a
   winner looks the same here as on the hub that sent you.                    */
.whiz-archive-page .wrap { max-width: 480px; }
/* Dates line up down the column: the comma and the year sit at the same x on
   every row (direct instruction, 2026-10-09). Two things were moving them.
   tabular-nums fixes the first - Inter's default figures are proportional
   here, so "07" measured 18.55px against "30"'s 19.81 and the comma shifted
   with the day. The second is the month, and no numeric feature touches
   letters: the twelve abbreviations run from Jul at 22.17px to May at 31.47,
   9.3px of drift. So the month gets a fixed slot wide enough for the widest of
   them, and everything after it starts from the same place.
   2.15em is May's 31.47px at this 15px size, plus a little. In em so it
   follows the type if the date is ever resized; re-measure if the FACE
   changes, since these widths are Inter's.
   The cost, and it is the unavoidable one: the gap between month and day is no
   longer constant - it is widest after Jul and tightest after May. A column
   can have an even gap or an aligned comma, not both, without going
   monospaced. */
.wa-mon { display: inline-block; width: 2.15em; }
/* The wizard replaced the zebra wordmark above the title (direct instruction,
   2026-10-09). Height rather than width, because the two images have different
   shapes and it is the vertical space above the title that was being held. */
/* 70.4px is 64 plus 10% (direct instruction, 2026-10-09). It grows DOWNWARD:
   the header stacks, so the image's top stays where it is and the title and
   everything under it move down by the 6.4px the drawing gained. */
/* ...and nudged 2px left (direct instruction, 2026-10-09). A transform, so it
   moves what is drawn and nothing else: the image's own box stays where it is,
   so the header keeps its width and the title under it does not shift. */
.whiz-archive-page .wa-logo { width: auto; height: 70.4px; transform: translateX(-2px); }
/* Both gaps are tuned for this page only (direct instruction, 2026-10-09: 4px
   tighter above the title, 8px looser below it). Scoped, because the two rules
   that set them - .site-logo-mark's own margin and .hub-wrap .site-header's -
   are shared with the hub and the game pages, which are not being changed.
   The lower gap is set on the CARD rather than on the header: the header's
   bottom margin and the card's top margin collapse into one, so the larger of
   the two wins and raising the card's own is enough. */
.whiz-archive-page .site-logo-mark { margin-bottom: 6px; }
.whiz-archive-page .wa-goat.card.hub-card { margin-top: 24px; }

/* The GOAT sits in a card of its own, and it is a real game card from the hub
   rather than a second copy of one: the section wears .card.hub-card, so the
   panel fill, the 1px --text border, the 20px corner and the crisp 3px accent
   shadow all come from the same rules the six game cards use. Only the things
   this block needs differently are set here (direct instruction, 2026-10-09). */
/* .wa-goat.card.hub-card, not plain .wa-goat: .card.hub-card sets padding: 18px
   with two classes, which outranks a single-class selector however far down the
   file it sits. The padding below was silently doing nothing until this was
   raised to match. */
.wa-goat.card.hub-card {
  /* 24px below, matching the 24px above it (direct instruction, 2026-10-09).
     The 2px top here is dead: a later, more specific rule up the file
     (.whiz-archive-page .wa-goat.card.hub-card) sets margin-top: 24px, which is
     where the top gap actually comes from - it has to be a separate rule to
     beat the page header's own collapsing bottom margin. */
  margin: 2px auto 24px;
  text-align: center;
  /* .card's own 10px 24px 24px is sized for a card with a title and a list in
     it; this one holds three short centred lines. The sides match the top, so
     the gap between the name's box and this card's edge is the same 14px as
     the gap above the title (direct instruction, 2026-10-09). */
  padding: 14px 14px 16px;
  /* Shrink-wrapped, not .hub-card's fixed 232px. This is what makes the rest
     of it work without measuring anything: the card is as wide as its widest
     child, the name's box below stretches to fill it, and so the name box is
     exactly goat-to-goat wide while the name is short and pushes both itself
     and this card wider as soon as it is not. */
  width: fit-content;
  /* No lift under this one (direct instruction, 2026-10-09). It keeps the fill,
     the border and the corner that make it a game card; it just does not sit
     above the page the way the six clickable ones do - which is right, since
     this one is not a link. */
  box-shadow: none;
}
/* GEO and the hyphen in the page's grey, WHIZ and GOAT in the accent, which is
   the title above this card split the same way. .whiz-key carries the accent,
   so the colour is stated once in the markup and never duplicated here. */
.wa-goat-label {
  margin: 0 0 8px;
  font: 400 19px "Anton", sans-serif;
  letter-spacing: 0.04em;
  color: var(--text);
}
/* A goat each side of the FOOTYWHIZ-GOAT line (direct instruction, 2026-10-09 -
   they were on the title, then on the name, then gone, and are now back on the
   title). In their own colours: no filter, unlike the accent-tinted pair the
   hubs use.
   The size and lift are the values this page already had fitted to this exact
   line - 18.1px against 19px Anton, raised 3.25px so the goat's ink sits on
   the same band as the capitals rather than hanging below them. */
.wa-goat-emoji {
  display: inline-block;
  /* Inner side only. A margin on the OUTER side of each goat would widen the
     title line past the goats themselves, and the name's box below is sized to
     that line - so the box would sit a few px proud of them on each side
     instead of lining up with them, which is the whole requirement. */
  margin: 0;
  font-size: 18.1px;
  vertical-align: baseline;
  transform: translateY(-3.25px);
}
.wa-goat-label .wa-goat-emoji:first-child { margin-right: 7px; }
.wa-goat-label .wa-goat-emoji:last-child { margin-left: 7px; }
/* A plain block, no longer a flex row: a flex row sized the name's box to its
   content, and the box has to STRETCH to the card's width instead - that is
   what gives it the goat-to-goat minimum. */
.wa-goat-row { margin: 0; }
/* The GOAT's name, celebrated (direct instruction, 2026-10-09 - option A of
   three mocked up). It keeps a box, but the box is the accent's own now: a
   faint wash, an accent hairline, and the name itself in the accent at half
   again the size. What it is no longer is .whiz-name's neutral dark field with
   a grey edge - the same treatment every leaderboard row gets, which made the
   one name on the page that is a record read like an input.
   The box's own properties are restated here, but NOT its overflow, ellipsis
   or max-width: those are inherited from .whiz-name on purpose, and they are
   what trims a GOAT name that outgrows the card rather than letting it escape. */
.wa-goat-name {
  display: block;
  /* Fills the card's content box, which is itself the widest of the title row
     and this name - so the box is never narrower than the goats above it, and
     never too narrow for the name inside it. The name is centred within it by
     .whiz-name's own text-align. */
  width: auto;
  font: 600 22px/1.25 "Inter", sans-serif;
  color: var(--accent);
  background: rgba(var(--accent-rgb), 0.1);
  border: 1px solid rgba(var(--accent-rgb), 0.45);
  border-radius: 12px;
  padding: 6px 16px;
}
.wa-goat-wins { margin: 8px 0 0; font: 400 11px "Inter", sans-serif; color: var(--text-dim); }

/* The roll, as an ordinary table. Left aligned throughout, including the player
   counts: a right-aligned number column would be the only thing on the page not
   starting at the same edge as everything above it. */
/* A grid, and plain divs rather than table elements: `1fr auto 1fr` puts the
   name column dead centre of the page whatever is in it, and sizes it to its
   content so a long name is never squeezed.

   The table ELEMENTS had to go. A <td> in this grid would not fill its track
   however it was asked - justify-self: stretch computed as stretch and still
   rendered at content width, and width: 100% did nothing either - so the last
   cell sat at 16px of a 108px column and every row's border stopped short of
   the page edge. The ARIA roles on the markup carry the semantics; they were
   needed regardless, since display: contents drops a table's implicit ones. */
.wa-table {
  display: grid;
  /* minmax(0, auto) on the name column, not a bare auto: an auto track
     cannot shrink below its content, so the widest name the field allows
     (203px) pushed the whole table off a 320px phone. With a 0 floor the
     track gives way and .whiz-name's own ellipsis takes over, which is
     what it is there for. Nothing changes wherever there is room. */
  grid-template-columns: 1fr minmax(0, auto) 1fr;
  width: 100%;
  /* Dates and counts sit in columns, so their digits have to be one width. */
  font-variant-numeric: tabular-nums;
}
.wa-table [role="rowgroup"], .wa-table [role="row"] { display: contents; }
.wa-table [role="columnheader"], .wa-table [role="cell"] {
  display: flex;
  align-items: center;
  border-bottom: 1px solid var(--panel-edge);
}
.wa-table [role="columnheader"] {
  padding: 0 8px 7px;
  /* The headers keep the dimmer grey; the rows below them are brighter, so
     the labels sit back and the data reads first. */
  font: 700 12px "Inter", sans-serif;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap;
}
/* The middle header is the only one allowed to wrap. "DAILY FOOTYWHIZZES" is
   158px of nowrap text, which together with DATE and PLAYERS overflowed a
   320px phone - the grid's auto track cannot shrink below a cell it is
   forbidden to break. DATE and PLAYERS stay nowrap: they are short, and a
   two-line "PLAYERS" would be worse than anything this fixes. */
.wa-table [role="columnheader"]:nth-child(2) { white-space: normal; text-align: center; }
.wa-table [role="cell"] { padding: 7px 8px; }
/* The counts hug the right edge, the names sit centred, the dates start at
   the left - the reading order of the three. */
.wa-table > [role="rowgroup"] > [role="row"] > :nth-child(2) { justify-content: center; }
.wa-table > [role="rowgroup"] > [role="row"] > :last-child { justify-content: flex-end; }
.wa-table [role="row"]:last-child > [role="cell"] { border-bottom: 0; }
.wa-table [role="cell"]:first-child { white-space: nowrap; }
/* The counts sit 1px further in than the cell padding alone would put them
   (direct instruction, 2026-10-09). Data cells only - the PLAYERS header
   keeps the plain 8px, so the digits land a hair inside the label rather
   than flush under it, which is the optical correction being asked for. */
.wa-table > [role="rowgroup"] > [role="row"] > [role="cell"]:last-child { padding-right: 9px; }
.wa-table [role="cell"]:nth-child(2) .whiz-name { font-size: 14px; }
/* font-variant-numeric sits in THIS rule, not with the alignment comment
   above: the `font` shorthand resets it, and this rule comes later. It was
   declared up there first and silently did nothing. */
.wa-date {
  font: 700 15px "Inter", sans-serif;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
/* The one right-aligned column: a count reads better against a fixed edge,
   and it is the last column so nothing sits after it to look ragged. */
.wa-players { text-align: right; }
/* Same shorthand trap as .wa-date above: .wa-table asks for tabular figures
   for the whole table and this rule quietly undid it for the one column
   that is right-aligned, where it matters most. */
.wa-players {
  font: 400 15px "Inter", sans-serif;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
/* The roll takes the SAME sizes the game pages use for their own leaderboards
   on a phone (direct instruction, 2026-10-09: "make it similar to the
   geography games"). Those rows are 12px Inter for the name and the time and
   11px for the rank, at every width and with no breakpoint of their own; this
   page was running 15px dates and counts, which read heavier than the GOAT
   card above them.
   Phone only, because desktop was already right. 511px is the site's existing
   phone tier, the same boundary .hub-card's zoom uses, not a new number.
   It has to sit after .wa-date and .wa-players: a media query adds no
   specificity of its own, and both of those set their size with a `font`
   shorthand, so from anywhere above they simply lost. */
@media (max-width: 511px) {
  .wa-date, .wa-players { font-size: 12px; }
  /* The same selector the 14px rule above uses, not a shorter one: that rule
     scores four class-level parts and a plain .wa-roll .whiz-name scores
     two, so it lost no matter where it sat. */
  .wa-table [role="cell"]:nth-child(2) .whiz-name { font-size: 12px; }
  /* The headers take the cells' tighter side padding too, or the column
     edges go ragged: the data dropped to 6px here and the labels above it
     would have stayed at 8, putting DATE 2px inside its own dates. */
  .wa-table [role="columnheader"] { font-size: 11px; padding: 0 6px 7px; }
  .wa-table [role="cell"] { padding: 6px 6px; }
  .wa-table > [role="rowgroup"] > [role="row"] > [role="cell"]:last-child { padding-right: 7px; }
}

.wa-empty, .wa-back {
  margin: 18px 0 0;
  text-align: center;
  font: 400 12px "Inter", sans-serif;
  color: var(--text-dim);
}
.wa-back a { color: var(--accent); }
@media (max-width: 374px) {
  .wa-goat-label { font-size: 17px; }
}
/* The name box's height is PINNED, not left to its content: it otherwise came
   from whatever emoji the day's winner happened to have in their name, so the
   card changed height as the winner changed. 26px is what 14px Inter plus 4px
   padding and a 1px border comes to. */
.whiz-card .whiz-name {
  height: 26px;
  /* An explicit line box, not `normal`. With `normal` the line grows to fit the
     tallest glyph, and a name's EMOJI are taller than its letters - so the text's
     baseline moved depending on which emoji the day's winner happened to have,
     and the names sat at different heights on the three hubs. Pinned, the letters
     land in the same place whatever is beside them; the emoji overflow the line
     box, which they did anyway.
     16px is the content box: 26 less 8 of padding and 2 of border. */
  line-height: 16px;
  /* Even top and bottom. The line-height above is what holds the text in the same
     place across the three hubs; this only decides how high in the box it sits. */
  padding: 4px 10px;
}
/* 375-511px: the game cards are zoomed to 0.7759, so their 26px titles render at
   about 20px - but this card is held at zoom: 1 (its width is computed for it),
   so its title stayed a true 26px and read noticeably bigger than the six above
   it on most phones. Written as the arithmetic rather than 20.17px so it stays
   true if either number moves. */
@media (min-width: 375px) and (max-width: 511px) {
  .hub-card-wide .start-title { font-size: calc(26px * 0.7759); }
}
/* The rename field's placeholder ("Type your name"), shown only to a player who
   has never typed one. Two things, both following rules this site already set
   itself elsewhere:

   Held at 16px even while the input grows to 20px on focus - and it IS focused
   from the first frame for exactly these players. At 20px the prompt needs about
   158px against roughly 145px of content box on a 360px phone, so it was being
   cut mid-word. Same trick .fc-guess-input::placeholder already uses.

   And given a colour: unstyled it falls back to the browser's #757575, which is
   not one of this site's greys and is the exact problem .gt-search-input's own
   placeholder rule was written to fix. --text-dim is the token for quiet text. */
.lb-name-input::placeholder {
  font-size: 16px;
  color: var(--text-dim);
  opacity: 1;
}

/* ===========================================================================
   MATE HUNT (chess site, 2026-10-10)
   The board is the ChessPuzzleGames video board, ported from the youtube-chess
   repo's board_theme.json / Board.tsx (commit c70c79e): one 135deg gradient
   coral -> burgundy under 64 flat square veils, so the board shifts colour
   from top left to bottom right; last-move orange at 55 %; mate flash; piece
   drop shadows; and the RIM RULE - a black piece on a dark square gets a thin
   rim in the light-square colour so it does not sink in. Every length is a
   ratio of the board, in container-query units (1cqw = 1 % of the board's
   width), so the look is identical at any size: the video's 800px board had
   100px squares, 6px piece inset, 18px radius, 22px coordinates, 2px rim.
   Pieces are the video's hybrid set (Chessnut + a cburnett bishop), painted
   as background images, never text glyphs - the Unicode chess symbols render
   as EMOJI on iOS and Android.
   ======================================================================== */
/* One line over the board: the move number on the left in the accent, in
   what used to be the status line's type, and the "Give up" button on the
   right (owner, 2026-10-10). The status itself ("Your move", "Check!", "The
   computer is thinking") is no longer shown; it stays in the DOM, visually
   hidden, so its aria-live announcements still reach a screen reader. The
   board already shows a check by tinting the king. */
.mh-topline {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin: 0 0 10px;
}
.mh-status {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
.mh-moves {
  margin: 0;
  font-family: "Anton", sans-serif;
  font-size: 18px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  min-height: 1.3em;
}
/* Holds the board and the verdict over it. The container for the cqw units
   the verdict uses; the board is its own container for its squares. */
.mh-board-wrap {
  position: relative;
  container-type: inline-size;
}
/* The verdict: the board's own last move and flash stay visible under a
   dark veil, the reason in large letters on top - the same job as Guess the
   Country's "Correct!" / "Time's Up" over its map, and the same colours:
   accent for a mate, the bad red for being mated, plain text otherwise. */
.mh-end {
  position: absolute;
  inset: 0;
  z-index: 6;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.5cqw;
  padding: 4cqw;
  background: rgba(0, 0, 0, 0.5);
  text-align: center;
  cursor: pointer;
}
.mh-end[hidden] { display: none; }
.mh-end-title {
  margin: 0;
  font: 400 12cqw/1.05 "Anton", sans-serif;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  /* Every verdict in the accent with a 1px black border outside the letters
     (owner, 2026-10-10). paint-order draws the stroke UNDER the fill, so only
     its outer half shows: a 2px stroke reads as exactly 1px outside. */
  color: var(--accent);
  -webkit-text-stroke: 2px #000;
  paint-order: stroke fill;
  text-shadow: 0 2cqw 4cqw rgba(0, 0, 0, 0.6);
}
.mh-end-sub {
  margin: 0;
  font: 400 4cqw/1.3 "Inter", sans-serif;
  color: #ffe3ea;
  /* 0.5px outside: a 1px stroke under the fill, as on the title. */
  -webkit-text-stroke: 1px #000;
  paint-order: stroke fill;
  text-shadow: 0 1cqw 2cqw rgba(0, 0, 0, 0.6);
}
.mh-end-sub:empty { display: none; }
.mh-board {
  container-type: inline-size;
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  grid-template-rows: repeat(8, 1fr);
  width: 100%;
  aspect-ratio: 1;
  /* Square corners (owner, 2026-10-10); the video's 18/800 radius is gone. */
  border-radius: 0;
  overflow: hidden;
  background: linear-gradient(135deg, #ff6a4e 0%, #8e0f3c 100%);
  /* 0 40px 99px at 800px, plus the 1px hairline */
  box-shadow: 0 5cqw 12.4cqw rgba(0, 0, 0, 0.69), 0 0 0 1px rgba(255, 255, 255, 0.18);
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.mh-sq {
  position: relative;
  display: block;
  padding: 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  min-width: 0;
  min-height: 0;
  overflow: visible; /* the piece shadow and the mate glow spill over the edge */
  cursor: pointer;
  font: inherit;
  color: inherit;
  appearance: none;
  background: none;
  touch-action: manipulation;
}
/* The veils: the gradient shows through them. */
.mh-sq.light { background: rgba(255, 255, 255, 0.62); }
.mh-sq.dark { background: rgba(60, 0, 24, 0.32); }
/* Flat squares over the veil: last move and the selected piece in the
   video's orange, check and mate in its mate red. ::before so the piece
   (a child) and the coordinates stay above them. */
.mh-sq::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
}
.mh-sq.last::before { background: #ffa22e; opacity: 0.55; }
.mh-sq.sel::before { background: #ffa22e; opacity: 0.55; }
.mh-sq.check::before { background: #ff3b5c; opacity: 0.45; }
/* A wrong move in Mate in One/Two, held on the board before it is taken back. */
.mh-sq.wrong::before { background: var(--bad); opacity: 0.6; }
/* ...and the piece that made it shakes on its square once it has landed. */
.mh-sq.wrong-move .mh-piece { animation: mi-shake 0.32s ease-in-out; }
@keyframes mi-shake {
  0%, 100% { translate: 0 0; }
  20% { translate: -6% 0; }
  40% { translate: 6% 0; }
  60% { translate: -4% 0; }
  80% { translate: 4% 0; }
}
/* The mated king: the video's flash, held. 40/100 of a square of glow. */
.mh-sq.mate::before { background: #ff3b5c; opacity: 0.75; box-shadow: 0 0 5cqw #ff3b5c; }
/* Lifted above its neighbours, or later siblings paint over the glow. */
.mh-sq.mate { z-index: 2; }
/* Legal targets are not in the video (it has no interaction): a burgundy dot
   on a light square, a light dot on a dark one (the burgundy vanished there,
   owner 2026-10-10), and a ring for a capture in the same two colours. */
.mh-sq.target::after {
  content: "";
  position: absolute;
  inset: 37%;
  border-radius: 50%;
  background: rgba(142, 15, 60, 0.42);
  pointer-events: none;
  z-index: 2;
}
.mh-sq.target.capture::after {
  inset: 5%;
  background: none;
  border: 0.5cqw solid rgba(142, 15, 60, 0.42);
}
.mh-sq.dark.target::after { background: rgba(255, 227, 234, 0.55); }
.mh-sq.dark.target.capture::after { background: none; border-color: rgba(255, 227, 234, 0.55); }
.mh-sq:focus-visible { outline: 0.4cqw solid #fff6ee; outline-offset: -0.4cqw; z-index: 3; }
/* Coordinates: 22px/100px of a square, weight 600, 85 %, the video's two
   colours. Rank numbers up the left file, file letters along the bottom rank. */
.mh-coord {
  position: absolute;
  font: 600 2.75cqw/1 "Inter", sans-serif;
  opacity: 0.85;
  pointer-events: none;
  z-index: 2;
}
.mh-coord.rank { left: 0.75cqw; top: 0.5cqw; }
.mh-coord.file { right: 0.875cqw; bottom: 0.25cqw; }
.mh-sq.light .mh-coord { color: #a0522d; }
.mh-sq.dark .mh-coord { color: #ffe3ea; }
/* Pieces: inset 6/100 of a square, the video's two-layer drop shadow. */
.mh-piece {
  position: absolute;
  inset: 6%;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  pointer-events: none;
  z-index: 1;
  filter: drop-shadow(0 1cqw 1cqw rgba(0, 0, 0, 0.5)) drop-shadow(0 0.125cqw 0.125cqw rgba(0, 0, 0, 0.35));
}
/* A move slides (2026-10-10): the piece is drawn in its NEW square at once,
   offset back to where it came from, and glides home - fast start, soft
   landing, the video's cubic-bezier(0.2, 0, 0, 1) - lifting to 1.1 with the
   video's larger shadow at the midpoint. The `translate` and `scale`
   properties are used rather than `transform` so the slide and the lift can
   run as a transition and an animation side by side. A captured piece fades
   under the arriving one. ~220ms: a slide you notice, never one you wait for. */
/* --slide-ms: the puzzle games slow this down for the solution they show after
   a lost puzzle (setSlideSpeed in their quiz.js); 220ms everywhere else. */
.mh-piece.moving {
  transition: translate var(--slide-ms, 220ms) cubic-bezier(0.2, 0, 0, 1);
  animation: mh-lift var(--slide-ms, 220ms) ease-in-out;
  z-index: 4;
}
@keyframes mh-lift {
  0% { scale: 1; }
  50% { scale: 1.1; filter: drop-shadow(0 2.75cqw 2.25cqw rgba(0, 0, 0, 0.45)) drop-shadow(0 0.25cqw 0.25cqw rgba(0, 0, 0, 0.3)); }
  100% { scale: 1; }
}
.mh-piece.captured {
  animation: mh-fade 200ms ease forwards;
  z-index: 0;
}
@keyframes mh-fade { to { opacity: 0; } }
/* The square the mover lands on sits above its neighbours while it slides in,
   or the squares in between paint over the piece on its way. */
.mh-sq.landing { z-index: 3; }
@media (prefers-reduced-motion: reduce) {
  .mh-piece.moving { transition: none; animation: none; }
  .mh-piece.captured { animation: none; display: none; }
}
/* THE RIM RULE: only a black piece on a dark square, in front of the shadow. */
.mh-sq.dark .mh-piece.b {
  filter: drop-shadow(0 0 0.25cqw rgba(255, 196, 185, 0.85)) drop-shadow(0 0 0.25cqw rgba(255, 196, 185, 0.85))
          drop-shadow(0 1cqw 1cqw rgba(0, 0, 0, 0.5)) drop-shadow(0 0.125cqw 0.125cqw rgba(0, 0, 0, 0.35));
}
.mh-piece.wK { background-image: url(../matehunt/assets/pieces/wK.svg); }
.mh-piece.wQ { background-image: url(../matehunt/assets/pieces/wQ.svg); }
.mh-piece.wR { background-image: url(../matehunt/assets/pieces/wR.svg); }
.mh-piece.wB { background-image: url(../matehunt/assets/pieces/wB.svg); }
.mh-piece.wN { background-image: url(../matehunt/assets/pieces/wN.svg); }
.mh-piece.wP { background-image: url(../matehunt/assets/pieces/wP.svg); }
.mh-piece.bK { background-image: url(../matehunt/assets/pieces/bK.svg); }
.mh-piece.bQ { background-image: url(../matehunt/assets/pieces/bQ.svg); }
.mh-piece.bR { background-image: url(../matehunt/assets/pieces/bR.svg); }
.mh-piece.bB { background-image: url(../matehunt/assets/pieces/bB.svg); }
.mh-piece.bN { background-image: url(../matehunt/assets/pieces/bN.svg); }
.mh-piece.bP { background-image: url(../matehunt/assets/pieces/bP.svg); }

/* Sits in the top line beside the move number, so it is compact: the
   shared .btn-secondary is a full-width 14px-padded block. */
.mh-resign {
  min-width: 0;
  width: auto;
  margin: 0;
  padding: 8px 14px;
  font-size: 13px;
}
.mh-resign.armed { color: var(--bad); border-color: var(--bad); }

/* --- Mate in One / Mate in Two (2026-10-10) --------------------------------
   Same board as Mate Hunt; these add the timed-round pieces. */
/* Three columns: Give up left, the side-to-move pill centered, the puzzle
   number right. Equal outer columns keep the pill truly centered over the
   board whatever the two sides' widths. */
.mi-topline {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
}
/* The number and the pill share one type: Anton 18px, line-height 1, so their
   text sits on the same line (owner, 2026-10-10). */
.mi-topline .mh-moves { justify-self: end; line-height: 1; min-height: 0; }
/* Juicers' and Horde's goal, centered on the card (owner, 2026-10-11): Anton,
   normal capitals, in the bright text so the accent stays the counter's. Three
   columns as on Mate in One, so the middle one is truly centered. Goal and
   counter share one size that scales down on narrow phones rather than wrap. */
.mh-goalline {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
}
.mh-goalline .mh-resign { justify-self: start; }
.mh-goalline .mh-moves { justify-self: end; line-height: 1; min-height: 0; font-size: min(18px, 4vw); }
.mh-goal {
  margin: 0;
  font: 400 min(18px, 4vw)/1 "Anton", sans-serif;
  letter-spacing: 0.5px;
  /* All caps (owner, 2026-10-11, after a short spell in normal capitals). */
  text-transform: uppercase;
  color: var(--text);
  white-space: nowrap;
  text-align: center;
}
/* Find the Country's give-up button (geoquiz .fc-giveup-btn), ported: a small
   square with "Give / up" stacked in 9px type, solid red with "Sure?" while
   armed. */
/* body:has(#play-area) in front: the shared "body:has(#play-area) .btn-secondary"
   rule (row, baseline) is (1,1,1) and outranked a plain .mi-topline .mh-resign,
   which left the label at the top of the button. */
body:has(#play-area) .mh-topline .mh-resign {
  justify-self: start;
  width: 32px;
  /* The pill's height (10 + 18 + 10): both are centered in the row, so their
     tops and bottoms line up exactly (owner, 2026-10-10). Change both together. */
  height: 38px;
  margin: 0;
  padding: 0;
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: 9px;
  line-height: 1.15;
}
/* Half a pixel up: "Give / up" measured 6.5px above the ink, 5.5px below. */
body:has(#play-area) .mh-topline .mh-resign > span { position: relative; top: -0.5px; }
body:has(#play-area) .mh-topline .mh-resign.armed { background: var(--bad); color: var(--text); border-color: transparent; }
/* Hidden keeps its grid cell: the pill is invisible until the clock starts,
   and the line must not reflow when it appears. */
.mi-topline .mi-to-move[hidden] { display: block !important; visibility: hidden; }
/* Whose move it is, as a pill in that side's color (owner, 2026-10-10, option
   1 of three): black text on white for White, white text and outline on black
   for Black, so the side reads before the words do. */
.mi-to-move {
  margin: 0;
  font: 400 18px/1 "Anton", sans-serif; /* exactly the puzzle number's type */
  letter-spacing: 0.5px;
  text-transform: uppercase;
  white-space: nowrap;
  /* 10 over and under. Measured on screen (full-page pixels, not an element
     crop, which rounds the half-pixel box edges): this gives 11.5px of space
     above the letters and 11.5px below (owner asked twice for more on top;
     9 over measured 10.5 / 11.5). The box is centered in the row, so its text
     stays level with "1/3" without any offset. */
  padding: 10px 18px;
  border-radius: 999px;
}
.mi-to-move.w { background: #f4f4f4; color: #000; }
.mi-to-move.b { background: #000; color: #f4f4f4; box-shadow: inset 0 0 0 1.5px #f4f4f4; }
/* The timer under the board. Its number rides ABOVE the track (#timer-num is
   bottom: 100% + 6px), so the gap below the board has to leave it room. */
.mi-timer { margin-top: 34px; }
/* "All Done!" at the top of the result screen: Guess the Country's
   #round-label type (geoquiz/css/style.css), which shows the same words there. */
.mi-all-done {
  margin: 0 0 14px;
  font: 400 26px "Anton", sans-serif;
  line-height: 1;
  color: var(--text);
  letter-spacing: 0.03em;
  text-transform: uppercase;
}
/* --- reveal: par line and the move list ------------------------------- */
/* The round's material ("14 pawns vs 3 bishops"), first line of the game
   record. It used to be the header's subtitle during play; the header shows
   no subtitle in game mode any more (owner, 2026-10-10). */
.mh-reveal-material {
  margin: 0 0 6px;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-dim);
}
.mh-reveal-par { margin: 10px 0 8px; font-size: 14px; color: var(--text-dim); }
.mh-reveal-par b { color: var(--text); }
.mh-movelist {
  margin: 0;
  padding-left: 0;
  list-style: none;
  counter-reset: mv;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
  gap: 2px 10px;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.mh-movelist li { counter-increment: mv; display: flex; gap: 6px; }
.mh-movelist li::before { content: counter(mv) "."; color: var(--text-dim); min-width: 2.2em; text-align: right; }
.mh-move-w { min-width: 3.4em; color: var(--text); }
.mh-move-b { color: var(--text-dim); }
.mh-credits { font-size: 12px; }

/* "NEXT GAME" over the other-game button (owner, 2026-10-13: the game name
   alone did not read as the next game to play). A caption above the button in
   the dim grey; the button itself keeps its size. Drawn as ::before so no game
   page needs a markup change, in px because fitOneLineBtn shrinks the
   button's own font-size. The row gets the room above it. Identical on all
   four sites. */
#other-game-btn { position: relative; }
#other-game-btn::before {
  content: "NEXT GAME";
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% + 6px);
  font: 700 10px/1 "Inter", sans-serif;
  letter-spacing: 0.14em;
  color: var(--text-dim);
  text-align: center;
  pointer-events: none;
}
.result-actions:has(#other-game-btn:not([hidden])) { padding-top: 16px; }

/* Topic hubs (owner, 2026-10-17): the Daily Whiz wizards 1.1x (40 -> 44px)
   and standing ON the first row of game cards, like on the portal. --wz-drop is
   the gap between the wizards' feet and the card top before this change: the
   grid gap minus the row's negative margin, plus the 5.7px the feet sat above
   the strip's bottom edge. Label and name move down with them, to the same
   distance above the card as on the portal. On hover: the portal's magic. */
.whiz-row { --wz-drop: 12.7px; }
@media (min-width: 375px) and (max-width: 511px) {
  .whiz-row { --wz-drop: 7.7px; }
}
.whiz-row .whiz-card .whiz-mascot { height: 44px; bottom: calc(5px - var(--wz-drop)); }
.whiz-row .whiz-card .whiz-label { top: calc(11.8px + var(--wz-drop)); }
.whiz-row .whiz-card .whiz-name { top: calc(2.4px + var(--wz-drop)); }
.whiz-row .whiz-mascot { transform-origin: 50% 100%; transition: filter 0.2s ease; }
.whiz-row .whiz-card::before,
.whiz-row .whiz-card::after {
  content: "\1F30D";
  position: absolute;
  z-index: 3;
  font: 400 10px/1 "Inter", sans-serif;
  color: var(--accent);
  opacity: 0;
  pointer-events: none;
  text-shadow: 0 0 6px var(--accent);
}
.whiz-row .whiz-card::before { left: -6px; top: calc(var(--wz-drop) - 4px); }
.whiz-row .whiz-card::after { right: -12px; top: calc(var(--wz-drop) - 8px); font-size: 8px; }
@media (hover: hover) {
  .whiz-row .whiz-card:hover .whiz-mascot { filter: brightness(1.15) drop-shadow(0 0 5px rgba(var(--accent-rgb), 0.9)); }
  .whiz-row .whiz-card:hover .whiz-mascot:first-of-type { animation: qzWave 0.6s ease-in-out; }
  .whiz-row .whiz-card:hover .whiz-mascot:last-of-type { animation: qzWave 0.6s ease-in-out reverse; }
  .whiz-row .whiz-card:hover::before { animation: qzTwinkle 0.9s ease-out 0.1s; }
  .whiz-row .whiz-card:hover::after { animation: qzTwinkle 0.9s ease-out 0.3s; }
}
@keyframes qzWave { 0%, 100% { rotate: 0deg; } 30% { rotate: -7deg; } 70% { rotate: 4deg; } }
@keyframes qzTwinkle { 0% { opacity: 0; transform: translateY(4px) scale(0.4); } 40% { opacity: 1; transform: translateY(0) scale(1.2); } 100% { opacity: 0; transform: translateY(-6px) scale(0.6); } }
@media (prefers-reduced-motion: reduce) {
  .whiz-row .whiz-mascot, .whiz-row .whiz-card::before, .whiz-row .whiz-card::after { animation: none !important; }
}
