/* tiles.css — True Highlights tile-grid listing (P1). Loaded only behind `?ui=tiles`
 * (docs/design/tile-grid/PLAN.md §1). Everything here is namespaced `.th-…`; any rule that
 * overrides a LEGACY (classic-UI) selector is scoped under `html[data-ui="tiles"]` so this file
 * is a no-op for every visitor still on the classic listing.
 *
 * Colours come from /lib/tokens.css wherever a token already exists (--bg/--card/--ink/--muted/
 * --line/--control-line/--accent/--brand/--focus-ring/…) so light/dark/wild theming is automatic.
 * A handful of new tokens the tile grid needs and tokens.css doesn't define (tile ground, date
 * block, tooltip, league-menu, crest plate, sizing) are declared below as --th-* custom
 * properties, re-themed the same three ways tokens.css itself uses (bare :root default, the
 * no-JS prefers-color-scheme fallback, and explicit [data-theme="dark"/"wild"]).
 *
 * Fonts are the site's own self-hosted Anton/Oswald/Barlow (@font-face in styles.css) — never
 * Google Fonts. No rule here ever targets `.player-panel`, `#yt-slot`, `iframe`, or any selector
 * that could reach a YouTube frame (the player stage is Phase 2; this file has nothing to say
 * about it either way).
 */

/* ============================================================================================
   tokens
   ============================================================================================ */
:root {
  --th-tile: var(--card2);
  --th-tile-neutral: var(--control-line);
  --th-tint: 40%;
  --th-tint-hover: 66%;
  --th-date-bg: var(--ink);
  --th-date-ink: var(--bg);
  --th-tip-bg: var(--ink);
  --th-tip-ink: var(--bg);
  --th-tip-accent: var(--accent);
  --th-menu-bg: var(--ink);
  --th-menu-ink: var(--bg);
  --th-plate: var(--c-grey-50, #f4f1ea);   /* crests assume a light plate in every theme */
  --th-crest-filter: opacity(1);
  --th-grey-crest: .42;
  --th-lift: 0 12px 22px -14px rgba(25, 26, 30, .62);
  --th-tw: 148px;
  --th-th: 74px;
  /* The date column used to be a whole tile wide, which is a lot of empty ground on a desktop row
     (owner, 2026-09-15: "even at this width have the dates take up less horizontal space"). Its
     own token now, stepped with the tile-size setting below. */
  --th-datew: 104px;
  --th-gap: 10px;
  --th-header-h: 64px;   /* kept in sync with .site-header's real height by tiles.js (resize + boot) */
  --th-display: 'Anton', 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --th-label: 'Oswald', 'Barlow', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --th-body: 'Barlow', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --th-tint: 34%; --th-tint-hover: 56%;
    --th-crest-filter: drop-shadow(0 0 1px rgba(255, 255, 255, .6));
    --th-grey-crest: .34;
    --th-lift: 0 12px 24px -12px rgba(0, 0, 0, .85);
  }
}
:root[data-theme="dark"] {
  --th-tint: 34%; --th-tint-hover: 56%;
  --th-crest-filter: drop-shadow(0 0 1px rgba(255, 255, 255, .6));
  --th-grey-crest: .34;
  --th-lift: 0 12px 24px -12px rgba(0, 0, 0, .85);
}
:root[data-theme="wild"] {
  --th-tint: 38%; --th-tint-hover: 62%;
  --th-crest-filter: drop-shadow(0 0 1px rgba(255, 255, 255, .5));
  --th-grey-crest: .4;
  --th-lift: 4px 4px 0 var(--attention);
  --th-menu-bg: var(--brand); --th-menu-ink: var(--brand-ink);
  --th-date-bg: var(--attention); --th-date-ink: var(--bg);
  --th-tip-accent: var(--attention);
}

/* ============================================================================================
   hide legacy chrome that the new header/footer replace (brand block stays as-is — untouched)
   ============================================================================================ */
html[data-ui="tiles"] #site-nav .lp-bar,
html[data-ui="tiles"] .help-chip,
html[data-ui="tiles"] .theme-quick,
html[data-ui="tiles"] .account-chip,
html[data-ui="tiles"] .site-footer,
html[data-ui="tiles"] .kick-ball,
html[data-ui="tiles"] .kick-ball-2 {
  display: none;
}
/* Full-bleed listing column (16px gutters at every width) instead of the classic 780px column. */
html[data-ui="tiles"] #app {
  max-width: none;
  margin: 0;
  padding: 18px 16px 40px;
  min-height: 100vh;
}
/* Room for the fixed mobile footer goes on BODY, not #app: `.seo-index` (the somaticmovement
   tagline + the league index navs) sits after #app in flow, so padding on #app alone still left
   the last ~63px of the page under the bar. --th-foot-h is 0 unless the footer is actually fixed
   (tiles.js only publishes a height when getComputedStyle says position:fixed), so desktop is
   untouched. */
html[data-ui="tiles"] body { padding-bottom: var(--th-foot-h, 0px); }

/* ============================================================================================
   header layout — brand left, controls right, at BOTH densities
   ============================================================================================ */
/* Owner, 2026-09-15: "even in noncondensed view, have the logo be left and picker be more right,
   to the left of my games." So the resting header uses the same arrangement the condensed one
   does, and the condense becomes a pure density change — the wordmark and tagline drop out, the
   crest shrinks, nothing moves anywhere else.
   flex-wrap + a flex-basis on the nav does the responsive part without a breakpoint to guess at:
   while the masthead lockup and the whole control row fit on one line they share it, and the
   moment they don't the nav wraps to its own line — which is exactly today's stacked phone header,
   only with the brand left-aligned instead of centred. */
html[data-ui="tiles"] .site-header {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px clamp(10px, 2vw, 18px);
  text-align: left;
}
html[data-ui="tiles"] .site-header .masthead-top { flex: none; }
/* "No Spoilers" starts under the T of True, not under the crest (owner, 2026-09-16: "left aligned
   with the T in true" — between where it sat and the centred placement of the logo asset). The
   tagline is a full-width sibling of .brand, so its ink starts at the masthead's left edge; the
   wordmark starts one crest + one brand gap further in. Measured: the tagline was 42.2px left of
   the T at 1280 and 22.2px at 375 — exactly `--logo-h + gap - 14px` (its own scrawl nudge) in both
   cases, so the translate simply carries the whole offset. Tiles only: classic keeps the 14px the
   intro's clip-path math is written against (styles.css ~247). */
html[data-ui="tiles"] .tagline { transform: translateX(calc(var(--logo-h) + 10px)) rotate(-1deg); }
html[data-ui="tiles"] .site-header #site-nav { flex: 1 1 420px; min-width: 0; }
/* The picker crosses the row to sit against the tools: its own auto margin eats the free space,
   and the tools drop theirs so the two stay one gap apart rather than being split to the edges. */
html[data-ui="tiles"] .th-switch { margin-left: auto; }
html[data-ui="tiles"] .th-tools { margin-left: 0; }

/* ============================================================================================
   condensed header (scrolled) — crest only, on one row with the controls
   ============================================================================================ */
/* Owner, 2026-09-15: "with masthead condense, just show the logo … move the logo to the upper left
   and the league picker to left of My Games." So the scrolled state isn't the classic UI's "the
   masthead is gone" — it's a compact bar: the crest at the left, then the league picker sitting
   directly against the tools on the right. tiles only; classic keeps collapsing the whole block.
   The masthead-top collapse is UNDONE here (classic animates its max-height to 0) and the pieces
   that actually go are the wordmark and the tagline — the crest is what stays. That means the
   height change lands in one step rather than easing; the band offset follows it either way,
   because tiles.js's trackHeaderHeight() follows the flip frame by frame. */
html[data-ui="tiles"] .site-header.condensed .masthead-top {
  max-height: none; opacity: 1; transform: none; margin: 0;
}
html[data-ui="tiles"] .site-header.condensed .brand { gap: 0; }
html[data-ui="tiles"] .site-header.condensed .wm,
html[data-ui="tiles"] .site-header.condensed .tagline { display: none; }
html[data-ui="tiles"] .site-header.condensed .site-logo { height: 34px; }
html[data-ui="tiles"] .site-header.condensed #site-nav { flex: 1 1 auto; min-width: 0; }
html[data-ui="tiles"] .site-header.condensed .th-bar { padding-block: 0; }
/* Group-by-league would leave a second row hanging under a bar that exists to be small; it comes
   back the moment the reader scrolls up to the full masthead. */
html[data-ui="tiles"] .site-header.condensed .th-group-btn { display: none; }

/* ============================================================================================
   header controls (mounted into #site-nav by tiles.js)
   ============================================================================================ */
.th-bar { display: flex; align-items: center; gap: clamp(10px, 2vw, 22px); flex-wrap: wrap; padding-block: 10px 6px; }
/* A column: the league button, then the group pill directly beneath it. The pill's left edge is the
   button's by construction — align-items:stretch makes the button span the column, so wherever the
   switcher sits (right of the tools on desktop, the gutter on phones) the two share one left edge.
   The pill opts out of the stretch so it keeps its own width. */
.th-switch { position: relative; display: flex; flex-direction: column; align-items: stretch; gap: 4px; }
.th-league-btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 12px 6px 4px; border: 0; background: none; color: var(--ink); cursor: pointer;
  font: 400 26px/1 var(--th-display); letter-spacing: .01em; text-transform: uppercase;
}
.th-league-btn svg { width: 15px; height: 15px; flex: none; transition: rotate .18s ease; }
.th-league-btn[aria-expanded="true"] { background: var(--th-menu-bg); color: var(--th-menu-ink); }
.th-league-btn[aria-expanded="true"] svg { rotate: 180deg; }
/* Holds window.TH.ui.leaguePicker's borrowed classic grid (TRU-20, owner request 2026-09-15) — the
   classic .lp-* rules (styles.css) draw every tile, group heading and crest; this rule ONLY
   positions/sizes the dropdown box itself. Chrome (background/border/radius/padding) mirrors
   classic's own .lp-panel (styles.css) rather than reusing that class directly: .lp-panel carries
   #site-nav-scoped sizing rules of its own (max-width/max-height, and a position:fixed full-screen
   sheet under 520px) tuned for classic's in-flow, full-width panel — sharing the class here would
   pull those in too and fight this rule.
   ANCHORED TO THE VIEWPORT (position: fixed), not .th-switch (2026-09-15 revision — a .th-switch-
   relative version shipped first, per-breakpoint anchor+width tuned against getBoundingClientRect()
   measurements, but .th-switch's own box is shrink-wrapped to the button alone, so however carefully
   the width was tuned per breakpoint it was always a NARROW dropdown wedged into whatever margin
   happened to be left beside the button (175-380px, 1-4 tiles/row) — never the wide classic grid the
   owner asked for. Anchoring to the viewport instead removes the button's position from the equation
   entirely: width is min(900px, 100vw - 32px) regardless of where .th-switch sits, so the grid packs
   as many of classic's own tile columns as the SCREEN allows (8/row at 900px, matching classic's own
   #site-nav .lp-comp/.lp-grid sizing — untouched here, inherited exactly as classic has it).
   .th-league-menu remains a DOM child of .th-switch either way — position only changes layout/
   containing-block, not the DOM tree — so the outside-click handler's `e.target.closest('.th-switch')`
   (tiles.js) still resolves true for a click anywhere inside this fixed-position panel. No JS change
   needed.
   top/right key off --th-header-h (kept live by tiles.js's trackHeaderHeight, both densities) so the
   panel stays glued to the header's actual bottom edge whether it's full-height or condensed; right:
   16px matches the header's own side gutter so the panel's edge lines up under the tools row.
   max-height reserves --th-header-h above AND --th-foot-h below (the pinned mobile footer, 0 when
   absent) plus 24px of breathing room, so the last group never hides behind fixed chrome the way an
   unreserved height would. 100dvh with a 100vh fallback line: dvh accounts for a mobile browser's
   collapsing address bar, vh alone can overshoot a phone's real visible height. z-index 1000 clears
   the fixed .th-foot (990) and stays under the explorer/settings sheets (1300). */
.th-league-menu {
  position: fixed; top: calc(var(--th-header-h) + 6px); right: 16px; left: auto; z-index: 1000;
  width: min(900px, calc(100vw - 32px));
  max-height: calc(100vh - var(--th-header-h) - var(--th-foot-h, 0px) - 24px);
  max-height: calc(100dvh - var(--th-header-h) - var(--th-foot-h, 0px) - 24px);
  overflow-y: auto;
  padding: 14px 14px 4px; border-radius: 16px;
  background: var(--card); color: var(--ink); border: 1px solid var(--line);
  box-shadow: 0 22px 34px -20px rgba(0, 0, 0, .6);
}
.th-league-menu[hidden] { display: none; }
/* Classic's phone tile formula (`min(78px, (100vw - 54px) / 4)`) budgets 54px of chrome for its own
   full-bleed sheet. This panel keeps 16px margins and 14px padding a side — 60px — so at 375px the
   fourth 78px tile no longer fit and rows wrapped to three with ~60px idle (measured). Same idea,
   this panel's real chrome: four per row again at 375 (70px tiles) and 320 (56px); the extra 4px keeps sub-pixel rounding from tipping a row to three. */
@media (max-width: 640px) {
  /* #site-nav is load-bearing: classic sets --lp-tile on `#site-nav .lp-grid`, and this panel
     lives inside #site-nav, so a class-only selector loses to the ID and the tiles stay 78px. */
  #site-nav .th-league-menu .lp-grid { --lp-tile: min(78px, calc((100vw - 94px) / 4)); }
}
.th-lg {
  width: 24px; height: 24px; flex: none; border-radius: 4px;
  /* Both tracks must be CAPPED. An implicit grid track is max-content, so a square or portrait
     league logo grew the row past this box's fixed height and `overflow: hidden` cropped it —
     measured in the group-by-league marks: Serie A rendered 34x41 inside a 34x27 box (14px of the
     wordmark gone), Ligue 1 34x46, La Liga 34x34. minmax(0, 1fr) pins the tracks to the box, so
     the image's height:100% resolves against the box and object-fit letterboxes it instead. */
  display: grid; grid-template: minmax(0, 1fr) / minmax(0, 1fr); place-items: center;
  background-color: var(--th-plate); overflow: hidden;
}
/* .th-lg-all (the old flat menu's "All leagues" icon swatch) retired alongside rebuildLeagueMenu's
   old body (TRU-20, 2026-09-15) — .th-lg itself stays: .th-mark (the per-tile competition badge)
   still uses it, untouched by this change. */
.th-lg-img { width: 100%; height: 100%; object-fit: contain; padding: 2px; }
.th-wordmark { width: 100%; height: 100%; }
.th-wm-a, .th-wm-b { fill: #191a1e; font: 700 1em var(--th-label); text-transform: uppercase; text-anchor: middle; }
.th-wm-rule { stroke: #191a1e; stroke-width: 2; opacity: .5; }
.th-wm-badge { fill: #191a1e; }
.th-wm-badge-txt { fill: var(--th-plate); font: 700 1em var(--th-label); text-anchor: middle; }

.th-tools { margin-left: auto; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.th-pill {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px; height: 36px; padding: 0 13px;
  border: 1.5px solid var(--control-line); border-radius: 999px; background: var(--card); color: var(--ink); cursor: pointer;
  font: 600 13px/1 var(--th-label); letter-spacing: .03em;
}
.th-pill svg { width: 15px; height: 15px; flex: none; }
.th-pill[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.th-icon-btn {
  width: 36px; height: 36px; display: grid; place-items: center; flex: none;
  border: 1.5px solid var(--control-line); border-radius: 50%; background: var(--card); color: var(--ink); cursor: pointer;
}
.th-icon-btn svg { width: 17px; height: 17px; }
.th-pill:hover, .th-icon-btn:hover { border-color: var(--ink); }
/* Search field (Phase 3, PLAN.md §8 Q4 / owner brief 2026-09-15) — hidden by default (the `hidden`
   attribute in tiles.js's markup), sits between the two pills and the search trigger so tab order
   stays linear. Heights mirror .th-pill/.th-icon-btn at each breakpoint below (36/32/30). */
.th-search-wrap { display: inline-flex; align-items: center; gap: 4px; min-width: 0; }
.th-search-wrap[hidden] { display: none; }
/* Owner, 2026-09-16: "when user selects the search, make the search bar wider." The field is only
   ever on screen while search is open (the wrap carries `hidden` otherwise), so its resting width IS
   its selected width — no separate focus state to maintain. 240px cut the placeholder off mid-word
   ("Search teams or competi…"); the clamp now starts above that and scales with the viewport. */
.th-search-input {
  height: 36px; min-width: 0; width: clamp(220px, 32vw, 460px);
  padding: 0 10px; border: 1.5px solid var(--control-line); border-radius: 999px;
  background: var(--card); color: var(--ink); font: 500 14px/1 var(--th-body);
}
.th-search-input::placeholder { color: var(--muted); }
.th-search-input:focus-visible { outline: 2px solid var(--focus-ring, var(--accent)); outline-offset: 1px; }
/* The browser's own magnifier/×-to-clear chrome on type=search duplicates our own controls. */
.th-search-input::-webkit-search-decoration,
.th-search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.th-search-clear {
  width: 28px; height: 28px; flex: none; display: grid; place-items: center;
  border: 0; border-radius: 50%; background: none; color: var(--muted); cursor: pointer;
}
.th-search-clear svg { width: 13px; height: 13px; }
.th-search-clear:hover { color: var(--ink); background: var(--card2); }
/* "Group by league" — owner, 2026-09-15: it only does anything in the All leagues view, so it
   lives under that selector instead of in Settings. Its own line under .th-bar, left-aligned with
   the league name above it (the -1px matches .th-league-btn's optical left edge). */
/* Follows the picker: with the switcher pushed over to the tools, a left-aligned pill sat orphaned
   under the brand instead of under the control it belongs to (owner, 2026-09-15). */
.th-group-btn {
  /* +4px so the pill's left edge meets the "A" of ALL LEAGUES rather than the button's border box:
     .th-league-btn carries 4px of left padding at this width. The phone block zeroes it, where the
     button has none. */
  align-self: flex-start; margin-left: 4px;
  display: inline-flex; align-items: center; gap: 7px; height: 30px; padding: 0 12px;
  /* Square-ish, not a pill (owner, 2026-09-15) — it reads as a control on the grid, like the date
     bands and league marks below it, rather than as one more round tool from the row above. */
  border: 1.5px solid var(--control-line); border-radius: 2px; background: var(--card); color: var(--muted);
  font: 600 11.5px/1 var(--th-label); letter-spacing: .06em; text-transform: uppercase; cursor: pointer;
}
.th-group-btn[hidden] { display: none; }
.th-group-btn svg { width: 14px; height: 14px; flex: none; }
.th-group-btn:hover { border-color: var(--ink); color: var(--ink); }
.th-group-btn[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

/* My Games "your teams" chip row */
.th-note { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding-block: 12px 0; }
.th-note strong { font: 600 12px/1 var(--th-label); letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.th-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.th-chip {
  display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px 0 5px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--card); color: var(--ink); cursor: pointer;
  font: 500 13px/1 var(--th-body);
}
.th-chip:hover { border-color: var(--control-line); }
.th-chip-crest { width: 18px; height: 18px; border-radius: 50%; object-fit: contain; background: var(--th-plate); }
.th-chip-x { opacity: .6; font-size: 13px; }

.th-soon-bar {
  display: flex; align-items: center; gap: 12px; width: 100%; margin-top: 14px; padding: 11px 14px;
  border: 1.5px solid var(--line); border-radius: 6px; background: var(--card); color: var(--ink); cursor: pointer; text-align: left;
}
.th-soon-bar .th-k { font: 600 13px/1 var(--th-label); letter-spacing: .08em; text-transform: uppercase; }
.th-soon-bar .th-v { font: 500 13px/1 var(--th-body); color: var(--muted); }
.th-soon-bar svg { width: 13px; height: 13px; margin-left: auto; transition: rotate .18s ease; }
.th-soon-bar[aria-expanded="true"] svg { rotate: 90deg; }

/* ============================================================================================
   day rows
   ============================================================================================ */
/* Section spacing lives INSIDE each day as bottom padding, not as a gap between them. A sticky
   band is released when its containing block's bottom reaches it; with a flex gap that happened
   while the next section — and its band — were still a gap-width short of the header, so at every
   day boundary the outgoing band left ~44px before the incoming one arrived, with a bare tile
   showing under no label (verifier, 2026-09-15, run 1 B3). With the space inside the section the
   two bands hand off at the same instant. Same visual rhythm as before. */
.th-days { display: flex; flex-direction: column; gap: 0; padding-block: 18px 0; }
.th-day { display: grid; grid-template-columns: var(--th-datew) minmax(0, 1fr); gap: var(--th-gap); align-items: start; }
.th-date {
  position: sticky; top: var(--th-header-h); z-index: 5; margin: 0; height: var(--th-th);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  border-radius: 4px; background: var(--th-date-bg); color: var(--th-date-ink);
}
.th-date .th-dow { font: 600 10.5px/1 var(--th-label); letter-spacing: .16em; text-transform: uppercase; opacity: .78; }
/* Sized off the column, not fixed, so "SEP 14" still fits once the track narrows. */
.th-date .th-dm { font: 400 28px/.95 var(--th-display); font-size: clamp(19px, calc(var(--th-datew) * .215), 28px); letter-spacing: .01em; text-transform: uppercase; }
.th-date.is-today .th-dow { opacity: 1; color: var(--th-tip-accent); }
/* Coming up's "Highlights soon" group: an outlined block, so it never reads as a calendar date. */
.th-day.is-soon-group .th-date { background: var(--card); color: var(--ink); box-shadow: inset 0 0 0 1.5px var(--ink); }
.th-day.is-soon-group .th-date .th-dm { font-size: 24px; }
/* The section's bottom spacing is padding on THIS wrapper, so it sits inside .th-day's content
   box — a sticky band is constrained to its containing block's content box, so padding on .th-day
   itself released the band that many px early and reopened the gap (measured 33px on phones). */
.th-lines { display: flex; flex-direction: column; gap: var(--th-gap); min-width: 0; padding-bottom: 24px; }
.th-line { display: flex; align-items: flex-start; gap: var(--th-gap); min-width: 0; }
.th-mark {
  flex: none; width: var(--th-th); height: var(--th-th); margin: 0; padding: 6px 4px 5px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
  border: 1.5px solid var(--ink); border-radius: 4px; background: var(--card);
}
/* Proportional, not fixed: the mark is a --th-th square, so a 34x27 crest and an 8.5px label left
   an XL mark looking mostly empty. Percentages resolve against the mark's own definite box. */
.th-mark .th-lg { width: 62%; height: 46%; border-radius: 3px; }
.th-mark .th-nm { max-width: 100%; font: 600 8.5px/1.05 var(--th-label); font-size: max(8.5px, calc(var(--th-th) * .095)); letter-spacing: .04em; text-align: center; text-transform: uppercase; overflow-wrap: anywhere; color: var(--ink); }
.th-tiles { list-style: none; margin: 0; padding: 0; flex: 1; min-width: 0; display: flex; flex-wrap: wrap; gap: var(--th-gap); }
.th-tiles li { display: block; }
.th-empty { margin: 0; padding: 40px 0; font: 500 15px/1.4 var(--th-body); color: var(--muted); overflow-wrap: anywhere; }
/* Search results (Phase 3) — reuses .th-days/.th-empty above unchanged; only the count line is new. */
.th-search-results { padding-top: 4px; }
.th-search-summary { margin: 0 2px 4px; font: 600 12.5px/1.3 var(--th-label); letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.th-search-results .th-empty { padding-top: 24px; }
/* Coming up's truncation note (owner cap, 2026-09-15 — PLAN §8 Q4): "Showing 20 of 34" etc. */
.th-coming-more { margin: 4px 2px 0; font: 500 12.5px/1.3 var(--th-body); color: var(--muted); }
.th-show-earlier {
  display: block; margin: 20px auto 4px; padding: 10px 20px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--card2); color: var(--ink); font: 600 13px/1 var(--th-label); letter-spacing: .04em; cursor: pointer;
}

/* ============================================================================================
   the tile — two crests on team-colour halves, split by a diagonal; no clip/overlay rule here
   or anywhere in this file ever targets a video frame (the stage is Phase 2 and lives elsewhere)
   ============================================================================================ */
.th-tile {
  --th-mix: var(--th-tint);
  position: relative; display: block; width: var(--th-tw); height: var(--th-th); padding: 0;
  border: 0; border-radius: 6px; overflow: hidden; background: var(--bg); color: var(--ink); cursor: pointer;
  transition: transform .16s ease, box-shadow .16s ease;
  -webkit-tap-highlight-color: transparent;
}
.th-half { position: absolute; inset: 0; transition: background-color .18s ease; }
.th-half.h { clip-path: polygon(0 0, 57% 0, 45% 100%, 0 100%); background-color: color-mix(in oklab, var(--hc) var(--th-mix), var(--th-tile)); }
.th-half.a { clip-path: polygon(calc(57% + 3px) 0, 100% 0, 100% 100%, calc(45% + 3px) 100%); background-color: color-mix(in oklab, var(--ac) var(--th-mix), var(--th-tile)); }
.th-cr {
  position: absolute; top: 50%; width: 34%; height: 62%; transform: translate(-50%, -50%);
  object-fit: contain; background-color: var(--th-plate); border-radius: 3px;
  filter: var(--th-crest-filter);
  transition: opacity .18s ease;
}
.th-cr.h { left: 25.5%; } .th-cr.a { left: 75.5%; }
.th-cr.th-mono { display: grid; place-items: center; font: 400 15px/1 var(--th-display); font-size: clamp(11px, calc(var(--th-th) * .2), 22px); color: var(--ink); opacity: .72; background: var(--th-tile); }
.th-code {
  display: none; position: absolute; transform: translateX(-50%);
  font: 600 9.5px/1 var(--th-label); letter-spacing: .06em; color: var(--ink);
  /* Scales with the tile-size token like .th-mark .th-nm does, so the code stays in proportion
     to the crest above it at every size instead of shrinking into a 9.5px speck on xl tiles. */
  font-size: clamp(9.5px, calc(var(--th-th) * .15), 16px);
}
.th-code.h { left: 25.5%; } .th-code.a { left: 75.5%; }
.th-tile.is-available:hover, .th-tile.is-available:focus-visible { --th-mix: var(--th-tint-hover); transform: translateY(-2px); box-shadow: var(--th-lift); }
.th-tile.is-soon, .th-tile.is-future, .th-tile.is-ppd { cursor: help; }
.th-tile.is-soon .th-half, .th-tile.is-future .th-half, .th-tile.is-ppd .th-half { background-color: var(--th-tile); }
.th-tile.is-soon .th-cr, .th-tile.is-future .th-cr, .th-tile.is-ppd .th-cr { filter: grayscale(1) var(--th-crest-filter); opacity: var(--th-grey-crest); }
.th-tag {
  position: absolute; left: 50%; bottom: 6px; transform: translateX(-50%); padding: 3px 6px 2px; border-radius: 3px;
  background: var(--ink); color: var(--bg); font: 600 9.5px/1 var(--th-label); letter-spacing: .06em; text-transform: uppercase; white-space: nowrap;
  max-width: calc(100% - 10px); overflow: hidden; text-overflow: ellipsis;
}
.th-tag.soon { background: var(--card); color: var(--ink); box-shadow: inset 0 0 0 1px var(--control-line); }
.th-tag.ppd { background: var(--brand); color: var(--brand-ink); }
/* Team codes on (pref: tileCodes) — set on <html> by tiles.js */
html.th-codes .th-code { display: block; }
/* Crest + code read as ONE centred group, not two things with a gutter between them (owner,
   2026-09-16: "less space between logo and team names"). Measured on the 74px default tile the
   old 42%-tall crest ended at 67% and the bottom-anchored code started at ~80% — a ~10px hole.
   Top-anchoring the code at 74% pins that gap at ~3% of tile height (2-5px across sm..xl) and
   hands the reclaimed room back to the crest: 42% -> 54% tall, 34% -> 40% wide. The 40% width
   still clears the diagonal, whose x runs 57% (top) -> 45% (bottom): at the crest's lowest
   point (70%) it sits at 48.6%, outside the home crest's 45.5% right edge and the away crest's
   55.5% left edge. */
html.th-codes .th-cr { top: 43%; height: 54%; width: 40%; }
html.th-codes .th-code { top: 74%; bottom: auto; }
html.th-codes .th-tag { top: 4px; bottom: auto; }
/* Team colours off (pref: tileColors) — collapse every half to the neutral tile ground; hover gets
   an ink ring instead of a colour lift so "available" is still legible without colour. */
html.th-nocolor .th-tile .th-half { background-color: var(--th-tile); }
html.th-nocolor .th-tile.is-available:hover::after, html.th-nocolor .th-tile.is-available:focus-visible::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 2px var(--ink);
}
.th-tile.th-add {
  display: grid; place-content: center; justify-items: center; gap: 4px;
  background: transparent; border: 1.5px dashed var(--control-line); color: var(--muted);
}
.th-tile.th-add .th-plus { font: 400 26px/.8 var(--th-display); }
.th-tile.th-add .th-al { font: 600 9.5px/1 var(--th-label); letter-spacing: .09em; text-transform: uppercase; }
.th-tile.th-add:hover { border-color: var(--ink); color: var(--ink); }

/* ============================================================================================
   tooltip
   ============================================================================================ */
.th-tip {
  position: fixed; left: 0; top: 0; z-index: 45; max-width: 280px; padding: 9px 11px; border-radius: 5px;
  background: var(--th-tip-bg); color: var(--th-tip-ink); pointer-events: none; box-shadow: 0 14px 28px -16px rgba(0, 0, 0, .55);
}
.th-tip[hidden] { display: none; }
.th-tip b { display: block; font: 600 13.5px/1.15 var(--th-label); letter-spacing: .02em; text-transform: uppercase; text-wrap: balance; }
.th-tip b i { font-style: normal; opacity: .55; padding-inline: 1px; }
.th-tip span { display: block; margin-top: 4px; font: 500 12px/1.25 var(--th-body); opacity: .8; }
.th-tip em { display: block; margin-top: 6px; font: 600 10.5px/1 var(--th-label); font-style: normal; letter-spacing: .08em; text-transform: uppercase; opacity: .7; }

/* ============================================================================================
   settings dialog
   ============================================================================================ */
.th-settings {
  width: min(560px, calc(100vw - 24px)); max-height: calc(100dvh - 24px); padding: 0; overflow-y: auto;
  border: 2px solid var(--ink); border-radius: 10px; background: var(--card); color: var(--ink);
  box-shadow: 0 30px 60px -30px rgba(0, 0, 0, .65);
}
.th-settings::backdrop { background: rgba(20, 20, 15, .55); }
.th-set-card { display: grid; padding: 16px 20px 18px; }
.th-set-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-bottom: 8px; border-bottom: 4px solid var(--ink); }
.th-set-head h2 { margin: 0; font: 400 32px/1 var(--th-display); letter-spacing: .01em; text-transform: uppercase; }
.th-set-x { width: 36px; height: 36px; display: grid; place-items: center; border: 0; border-radius: 50%; background: none; color: var(--ink); cursor: pointer; }
.th-set-x svg { width: 20px; height: 20px; }
.th-set-sec h3 { margin: 18px 0 4px; font: 600 11.5px/1 var(--th-label); letter-spacing: .13em; text-transform: uppercase; color: var(--muted); }
.th-sw, .th-set-row { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 16px; padding: 11px 0; border-top: 1px solid var(--line); }
.th-sw { cursor: pointer; }
.th-sw b, .th-set-row b { display: block; font: 600 15px/1.2 var(--th-label); letter-spacing: .02em; }
.th-sw small, .th-set-row small { display: block; margin-top: 3px; font: 500 13px/1.35 var(--th-body); color: var(--muted); }
.th-sw input { position: absolute; right: 0; width: 44px; height: 26px; margin: 0; opacity: 0; cursor: pointer; }
.th-sw-ui { position: relative; width: 44px; height: 26px; border-radius: 999px; background: var(--control-line); transition: background-color .15s ease; }
.th-sw-ui::after { content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: var(--card); box-shadow: 0 1px 2px rgba(0, 0, 0, .3); transition: transform .15s ease; }
.th-sw input:checked + .th-sw-ui { background: var(--accent); }
.th-sw input:checked + .th-sw-ui::after { transform: translateX(18px); }
.th-sw input:focus-visible + .th-sw-ui { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
.th-set-row select {
  padding: 7px 10px; border: 1.5px solid var(--control-line); border-radius: 6px; background: var(--bg); color: var(--ink); font: 500 14px/1 var(--th-body);
}
.th-set-btn { height: 32px; padding: 0 14px; border: 1.5px solid var(--control-line); border-radius: 999px; background: var(--bg); color: var(--ink); font: 600 12.5px/1 var(--th-label); letter-spacing: .03em; cursor: pointer; }
.th-set-btn:disabled { opacity: .45; cursor: default; }
.th-seg { display: inline-flex; padding: 3px; border: 1.5px solid var(--control-line); border-radius: 999px; background: var(--bg); }
.th-seg label { position: relative; }
.th-seg input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
.th-seg span { display: block; padding: 6px 12px; border-radius: 999px; font: 600 12.5px/1 var(--th-label); letter-spacing: .04em; text-transform: uppercase; color: var(--ink); }
.th-seg input:checked + span { background: var(--ink); color: var(--bg); }
.th-seg input:focus-visible + span { outline: 3px solid var(--focus-ring); outline-offset: 1px; }
.th-set-foot { display: flex; align-items: center; justify-content: flex-end; gap: 12px; margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--line); }
.th-done { height: 38px; padding: 0 20px; border: 0; border-radius: 999px; background: var(--ink); color: var(--bg); font: 600 14px/1 var(--th-label); letter-spacing: .05em; text-transform: uppercase; cursor: pointer; }
.th-sample-row { align-items: center; }
/* The preview follows the chosen tile size too, scaled down a little so XL still fits the row. */
.th-sample-wrap { --th-tw: 132px; --th-th: 64px; }
html[data-tile-size="sm"] .th-sample-wrap { --th-tw: 112px; --th-th: 56px; }
html[data-tile-size="lg"] .th-sample-wrap { --th-tw: 156px; --th-th: 78px; }
html[data-tile-size="xl"] .th-sample-wrap { --th-tw: 176px; --th-th: 88px; }

/* ============================================================================================
   in-flow footer (built by tiles.js; the classic fixed .site-footer is hidden above)
   ============================================================================================ */
/* Pinned to the bottom of the screen at EVERY width (owner, 2026-09-15: "make sure the footer is
   sticky on all widths") — these are the links a reader reaches for without wanting to scroll a
   day's worth of tiles to find them, and Settings among them. The list scrolls UNDER it, so the
   background is opaque and <body> reserves --th-foot-h (measured by tiles.js).
   z-index 990 puts the bar ABOVE the stage (980) so it stays put while a match plays, and below
   the explorer sheet (1300/1301), which is a whole surface and owns the screen. The one exception
   is enforced in tiles.js: .is-under-video hides the bar whenever the player frame would reach
   under it, because nothing may sit in front of an embed (docs/SPOILER-SAFETY.md, Constraint 1). */
.th-foot {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 990;
  margin-top: 0; padding: 0 clamp(16px, 4vw, 40px) env(safe-area-inset-bottom, 0px);
  border-top: 1px solid var(--line);
  background: var(--bg); box-shadow: 0 -10px 20px -16px rgba(0, 0, 0, .8);
}
/* visibility, not display: the bar keeps its box so tiles.js can still measure it and work out
   when the frame has moved clear again. */
.th-foot.is-under-video { visibility: hidden; pointer-events: none; }
/* "Free. No ad tracking." renders in BOTH footer rows; the second is the privacy link, the useful
   one. A pinned bar can't spend a row's height on the duplicate, at any width. */
.th-foot-fine { display: none; }
.th-foot-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 20px; padding-block: 8px; }
.th-foot-row nav { display: flex; flex-wrap: wrap; gap: 6px 18px; }
.th-foot-link {
  padding: 0; border: 0; background: none; color: var(--ink); cursor: pointer;
  font: 600 12.5px/1 var(--th-label); letter-spacing: .08em; text-transform: uppercase; text-decoration: none;
}
.th-foot-link:hover { color: var(--accent); }
.th-foot-fine { font: 500 12.5px/1.3 var(--th-body); color: var(--muted); }
/* Label + support CTA hug the left as a pair; Feedback takes the whole remaining run (owner,
   2026-09-15). The auto margin, not space-between, so the first two stay one gap apart. */
.th-foot-support { border-top: 1px solid var(--line); justify-content: flex-start; gap: 8px 14px; }
.th-foot-support-label { font: 500 12.5px/1.3 var(--th-body); color: var(--muted); text-decoration: none; }
.th-foot-feedback { margin-left: auto; padding: 0; border: 0; background: none; color: var(--ink); cursor: pointer; font: 600 12.5px/1 var(--th-label); letter-spacing: .04em; }
.th-foot-support-cta { color: var(--brand); font: 600 12.5px/1 var(--th-label); text-decoration: none; }
.th-foot-support-cta:hover { text-decoration: underline; }
/* Theme menu — the footer's "Theme" opens this instead of the whole Settings sheet (owner,
   2026-09-15). Parked on <body> and positioned by tiles.js, so the fixed footer's own stacking
   context can't clip it; z-index clears the bar (990) and the stage (980) but stays under the
   explorer sheet (1300). Same ink-on-menu treatment as the league switcher. */
.th-theme-menu {
  position: fixed; z-index: 1000; min-width: 168px;
  display: grid; gap: 1px; padding: 6px; border-radius: 6px;
  background: var(--th-menu-bg); color: var(--th-menu-ink);
  box-shadow: 0 22px 34px -20px rgba(0, 0, 0, .6);
}
.th-theme-menu[hidden] { display: none; }
.th-theme-menu button {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 10px;
  border: 0; border-radius: 4px; background: none; color: inherit; text-align: left; cursor: pointer;
  font: 600 13px/1.15 var(--th-label); letter-spacing: .05em; text-transform: uppercase;
}
.th-theme-menu button:hover, .th-theme-menu button:focus-visible { background: color-mix(in oklab, var(--th-menu-ink) 14%, transparent); }
.th-theme-menu button[aria-checked="true"] { box-shadow: inset 3px 0 0 var(--brand-red); }
/* The add-teams chip is an action, not a removable favourite — outlined so it reads as one. */
.th-chip-add { padding: 0 12px; border-style: dashed; border-color: var(--control-line); color: var(--muted); font-weight: 600; }
.th-chip-add:hover { border-style: solid; border-color: var(--ink); color: var(--ink); }

/* ============================================================================================
   responsive — desktop tile size step, then the phone layout (sticky date band + 3-col grid)
   ============================================================================================ */
/* Tile size (owner request, 2026-09-15, TRU-20) — Small/Medium/Large/XL, default Large. Only the
   two size tokens change, so every rule that reads --th-tw/--th-th (tiles, the date block, league
   marks) scales together; the phone column count is set alongside them in the ≤640px block below.
   The stage's own Up next strip sets its own tokens (stage.css). */
html[data-tile-size="sm"] { --th-tw: 116px; --th-th: 58px; --th-datew: 92px; }
html[data-tile-size="lg"] { --th-tw: 188px; --th-th: 94px; --th-datew: 116px; }
html[data-tile-size="xl"] { --th-tw: 240px; --th-th: 120px; --th-datew: 128px; }

@media (max-width: 1000px) {
  :root { --th-tw: 132px; --th-th: 66px; --th-datew: 96px; }
  html[data-tile-size="sm"] { --th-tw: 108px; --th-th: 54px; --th-datew: 88px; }
  html[data-tile-size="lg"] { --th-tw: 168px; --th-th: 84px; --th-datew: 108px; }
  html[data-tile-size="xl"] { --th-tw: 208px; --th-th: 104px; --th-datew: 118px; }
  .th-date .th-dm { font-size: 25px; }
  .th-pill { width: 36px; padding: 0; }
  .th-pill .th-lbl { display: none; }
}
@media (max-width: 640px) {
  :root { --th-gap: 8px; }
  /* Give the top back some of the room the grid wants (owner, 2026-09-15: "we're taking up too
     much space at the top on narrow viewports"). Side padding is untouched — only the vertical
     run, which was 18px above and below the whole header. */
  /* Side padding drops to the grid's own 16px gutter too, so the league name, the toggle under it
     and the tiles below all start on one line — and the row gets 8px back for the tools. */
  /* Edge-to-edge (index.html's viewport-fit=cover): this rule out-specifies styles.css's
     .site-header on phones, so the status-bar strip has to be bought back HERE too — the
     styles.css inset alone never lands at this width. 0px off-device, as everywhere else. */
  html[data-ui="tiles"] .site-header { padding: calc(10px + env(safe-area-inset-top, 0px)) 16px 4px; }
  /* Condense the masthead itself at this width (owner, 2026-09-15). The brand lockup was 63px of
     a 152px header at 375px, above a full-bleed grid that wants the room. Same lockup, scaled as
     one — crest, wordmark and tagline step down together, so nothing changes about the mark but
     its size. Tiles-only (html[data-ui="tiles"]), so the classic masthead is untouched; and it's
     --logo-h rather than .site-logo's height, because --ball-lane is derived from that token
     (styles.css "masthead metrics") and the intro balls have to follow the smaller crest. */
  html[data-ui="tiles"] .site-header { --logo-h: 30px; }
  html[data-ui="tiles"] .brand { font-size: 1.16rem; gap: 6px; }
  html[data-ui="tiles"] .tagline { font-size: 1.05rem; margin-top: -5px; transform: translateX(calc(var(--logo-h) + 6px)) rotate(-1deg); }
  .th-bar { row-gap: 6px; padding-block: 6px 4px; }
  /* 32px, not 34: five tools plus the league name have to share one row here now. */
  .th-pill, .th-icon-btn { width: 32px; height: 32px; }
  /* Calendar stays reachable on phones (owner decision, 2026-09-15, TRU-20 — PLAN §8 Q3
     resolved): it used to be hidden here. It's already icon-only at this width via the
     ≤1000px .th-pill .th-lbl rule above, so no further CSS is needed to make it compact. */
  /* The switcher shares the tools' row here rather than taking a line of its own (owner,
     2026-09-15: "we're taking up too much space at the top on narrow viewports … have the league
     selector be in line with the option buttons on the right"). nowrap keeps it there; the
     switcher is the flexible one — it truncates rather than pushing the tools off. -4px pulls it
     from .site-header's 20px padding onto the grid's own 16px gutter, so the league name lines up
     with the date bands and tiles below it. text-align guards against .site-header's own
     `text-align: center`, which the switcher would otherwise inherit. */
  .th-bar { flex-wrap: nowrap; column-gap: 8px; }
  /* Pre-condense, the selector drops BELOW the icon row (owner, 2026-09-16) — the brand owns the
     first line, the tools the second, and ALL LEAGUES gets a full line of its own at the gutter,
     where it lines up with the date bands and tiles below it and needs no truncation. The
     condensed bar is untouched: there the three share one 48px row, which is the point of it. */
  /* TWO COLUMNS, not three stacked rows (owner, 2026-09-16): the brand takes the vertical space on
     the left, the tools and the selector stack on the right. `flex-wrap: wrap` with the nav sized to
     its content means no breakpoint has to be guessed — where both columns fit they share a line,
     and where they don't (≈320px) the nav drops below the brand on its own, still right-aligned. */
  html[data-ui="tiles"] .site-header:not(.condensed) { flex-wrap: wrap; align-items: flex-start; }
  /* min-content basis, not auto: a wrapping flex container places items by their HYPOTHETICAL size
     before it flexes them, so an `auto` basis (the bar's ~330px max-content) sent the nav to its own
     line instead of letting it compress beside the brand. min-content is the tools' own width, which
     is both the smallest the column can be and the value that decides the wrap honestly — it fits at
     375 and doesn't at 320, which is exactly where the fallback should kick in. */
  html[data-ui="tiles"] .site-header:not(.condensed) #site-nav { flex: 0 1 min-content; margin-left: auto; }
  /* …except while search is open. min-content is the TOOLS' width, so the two-column header capped
     the field at a few characters ("Search tea…" at 375) no matter how the row inside it flexed —
     .th-tools.th-searching's flex-grow below can only divide the column it is given. For the
     duration of a search the nav is the row, so the field gets the brand's width too (measured
     138px -> 331px at 375). The brand keeps its own column; only the nav's basis changes. */
  html[data-ui="tiles"] .site-header:not(.condensed):has(.th-tools.th-searching) #site-nav { flex: 1 1 100%; }
  html[data-ui="tiles"] .site-header:not(.condensed) .th-bar { flex-wrap: wrap; row-gap: 4px; justify-content: flex-end; }
  html[data-ui="tiles"] .site-header:not(.condensed) .th-tools { order: 1; margin-left: auto; }
  /* basis 100% so the selector spans the nav column and starts on the tools' own left edge. */
  html[data-ui="tiles"] .site-header:not(.condensed) .th-switch { order: 2; flex-basis: 100%; margin-left: 0; }
  /* Room again, so the league name goes back up from the 19px it needed while sharing the row. */
  html[data-ui="tiles"] .site-header:not(.condensed) .th-league-btn { font-size: 22px; }
  /* The condensed crest has to come out of the same row as the picker and five tools. */
  html[data-ui="tiles"] .site-header.condensed { gap: 8px; }
  html[data-ui="tiles"] .site-header.condensed .site-logo { height: 28px; }
  /* display:flex so the button is a flex ITEM that shrinks with its switcher. Without it the
     switcher shrank under compression and the button simply overflowed it — measured at 320px,
     the chevron ran 6px under the first tool button. (max-width:100% is the wrong lever here: it
     makes the switcher's max-content contribution circular and collapses it to nothing.) */
  .th-switch { display: flex; flex: 0 1 auto; min-width: 0; text-align: left; margin-left: auto; }
  .th-league-btn { display: flex; flex: 0 1 auto; min-width: 0; font-size: 19px; padding: 4px 6px 3px 0; gap: 5px; }
  .th-league-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .th-tools { flex: none; gap: 5px; flex-wrap: nowrap; }
  /* Search open (owner brief 2026-09-15: "it can take the bar's full width for the duration,
     pushing the tools aside"). The other pills/icon-buttons vanish so the field — which needs real
     typing room on a phone — can grow into their space; the search trigger itself stays (re-tapping
     it is how you close). min-width:0 on the row and the input is required for the flex-grow to
     actually shrink/grow rather than overflow the nowrap .th-bar. */
  .th-tools.th-searching { flex: 1 1 auto; min-width: 0; }
  .th-tools.th-searching .th-pill,
  .th-tools.th-searching #th-alerts-btn,
  .th-tools.th-searching #th-settings-btn { display: none; }
  .th-tools.th-searching .th-search-wrap { flex: 1 1 auto; min-width: 0; }
  .th-search-input, .th-search-clear { height: 32px; }
  .th-tools.th-searching .th-search-input { width: 100%; }
  .th-group-btn { height: 28px; margin-left: 0; }
  .th-days { gap: 0; padding-top: 12px; }
  .th-day { display: block; }
  /* 14px above = the band's old bottom margin (a margin on the sticky element counts against its
     sticky range and released it early); 18px below = the old section gap. Both inside the box. */
  .th-lines { padding: 14px 0 18px; }
  .th-date {
    /* The band is sticky and the cards run UNDER it, so it needs both: real room at rest, and an
       edge that reads as a layer while they pass (owner, 2026-09-15: "fix the space between the
       sticky date and the scrolling cards on mobile"). The old 9px gap put the first row almost
       against the band, and the shadow was too faint at -12px spread to separate anything. */
    top: var(--th-header-h); height: auto; margin: 0; padding: 9px 12px 8px; border-radius: 0 0 5px 5px;
    flex-direction: row; align-items: baseline; justify-content: flex-start; gap: 9px;
    box-shadow: 0 10px 14px -8px rgba(0, 0, 0, .55);
  }
  .th-date .th-dm { font-size: 22px; }
  .th-date .th-dow { font-size: 11px; }
  .th-line { display: block; }
  .th-line + .th-line { margin-top: 12px; }
  .th-mark {
    width: auto; height: auto; margin-bottom: 8px; padding: 0 0 6px; flex-direction: row; justify-content: flex-start; gap: 8px;
    border: 0; border-bottom: 1.5px solid var(--line); border-radius: 0; background: none;
  }
  .th-mark .th-lg { width: 22px; height: 20px; }
  .th-mark .th-nm { font-size: 12.5px; letter-spacing: .06em; text-align: left; }
  /* Phones lay tiles out on a column grid, so tile size picks the column count instead of a width. */
  .th-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
  html[data-tile-size="sm"] .th-tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  html[data-tile-size="lg"] .th-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  html[data-tile-size="xl"] .th-tiles { grid-template-columns: minmax(0, 1fr); }
  .th-tiles .th-tile { width: 100%; height: auto; aspect-ratio: 2 / 1; }
  .th-tag { bottom: 4px; padding: 2px 4px 1px; font-size: 8.5px; }
  html.th-codes .th-tag { top: 3px; }
  .th-settings .th-set-card { padding: 12px 14px; }
  .th-set-head h2 { font-size: 26px; }
  /* Four size options don't leave a phone row enough width for the label, which then wraps to five
     lines — stack the segmented control under its label instead. */
  .th-set-row-seg { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .th-foot { padding-inline: 16px; }
  .th-foot-row { gap: 4px 14px; }
  .th-foot-row nav { gap: 4px 14px; }
}
/* Under 375px the condensed crest costs the league name its last readable inches: measured at 360px
   the label ellipsised to 78px and at 320px to 52px, with the crest holding 36px of the row. The
   name is the useful half — the crest is still right there the moment the reader scrolls up to the
   full masthead — so on these widths the condensed bar goes back to controls only. */
@media (max-width: 374px) {
  html[data-ui="tiles"] .site-header.condensed .masthead-top { display: none; }
}
/* Search open (Phase 3) needs the same trade at 375-640px, where the rule above doesn't reach yet.
   Measured: growing .th-search-input to fill the row (the .th-searching rules above) makes .th-bar
   want its own full ~343px row — more than fits next to the condensed crest — so .site-header's
   OWN flex-wrap (not overflow) stacked crest-above-bar instead of sharing one line, nearly doubling
   the condensed header's height (48px -> 88px at 375px) even though nothing scrolled horizontally.
   Dropping the crest the moment search opens keeps the bar alone on its one compact row, same as
   the ≤374px case already does unconditionally. */
@media (max-width: 640px) {
  html[data-ui="tiles"] .site-header.condensed:has(.th-tools.th-searching) .masthead-top { display: none; }
}
/* The narrowest phones still in use (320px): the league name and five tools need 304px of a 288px
   row at the sizes above, so both step down one more notch rather than the name ellipsizing. */
@media (max-width: 340px) {
  .th-league-btn { font-size: 17px; }
  .th-pill, .th-icon-btn { width: 30px; height: 30px; }
  .th-tools { gap: 4px; }
  .th-search-input, .th-search-clear { height: 30px; }
}
@media (prefers-reduced-motion: reduce) {
  .th-tile, .th-half, .th-league-btn svg, .th-soon-bar svg { transition: none; }
  .th-tile.is-available:hover, .th-tile.is-available:focus-visible { transform: none; }
}

/* ============================================================================================
   wild theme flourish — matches the acid-bar treatment styles.css already gives .date-header
   ============================================================================================ */
:root[data-theme="wild"] .th-date {
  transform: rotate(-0.6deg);
  box-shadow: 3px 3px 0 var(--brand);
}
@media (prefers-reduced-motion: reduce) {
  :root[data-theme="wild"] .th-date { transform: none; }
}
