/* p3rf.css — the shared COMPONENT layer of the platform design system.
 *
 * WHAT THIS IS
 * ------------
 * core/static/design/ holds the design system proper: tailwind.css (the
 * utility layer cycling's 63 templates are written in), custom.css (the P3
 * tokens, type and chrome), the self-hosted faces and Font Awesome. This file
 * sits on top of it and defines the COMPONENT vocabulary that pack templates
 * written in plain HTML use — .hero, .card, .stat-grid, .btn, .badge, tables,
 * plus the shell's own header/footer/palette classes.
 *
 * WHY IT EXISTS
 * -------------
 * The design merge moved hockey onto the platform shell but left its 1 073-line
 * stylesheet behind, referenced by nothing. All 23 hockey pages kept emitting
 * .hero / .card / .stat-card / .badge and rendered with no rule behind any of
 * them. This file is that stylesheet, rebuilt on the P3 tokens: same class
 * names, so not one page template had to change, but the colours, type, radii
 * and shadows now come from the same source cycling renders from.
 *
 * THE BRIDGE BELOW IS THE WHOLE TRICK. Every component rule underneath reads
 * var(--ink), var(--card-bg), var(--green) and so on. Pointing those at the
 * design system's tokens restyles the entire component set at once — and
 * because the design system already ships dark values for every token under
 * html.dark / .dark-mode, dark mode follows for free. Do not hard-code a
 * colour below; add or re-point a token here instead.
 *
 * ACCENT. --accent-color is overridden per-sport by the shell from
 * PACK.accent, so hockey's teal and cycling's indigo both flow through these
 * components without a per-sport stylesheet. That is why the bridge sends the
 * old --indigo / --green brand roles at the accent rather than at fixed hues.
 * Semantic colour (--good/--bad) and data encoding (--data-a/--data-b) stay
 * separate on purpose: recolouring a sport must never silently change the
 * meaning of a green delta or a chart series.
 */

:root {
  /* ---- bridge: chrome & surfaces -> design-system tokens -------------- */
  --bg:        var(--bg-primary);
  --card-bg:   var(--bg-card);
  --border:    var(--border-color);
  --row-alt:   var(--bg-tertiary);
  --ink:       var(--text-primary);
  --ink-muted: var(--text-muted);
  --link:      var(--accent-color);
  --white:     #ffffff;
  --radius:    var(--radius-lg);
  --shadow:    var(--shadow-lg);

  /* ---- bridge: brand roles -> the pack accent ------------------------- */
  /* These were hockey's fixed emerald/indigo. As brand roles (eyebrows,
     pills, hero numbers, active states) they belong to whichever sport is
     rendering. */
  --green:       var(--accent-color);
  --green-dark:  var(--accent-hover);
  --indigo:      var(--accent-color);
  --indigo-dark: var(--accent-hover);

  /* ---- semantic + medal colours (NOT the accent) ---------------------- */
  --good:   #0f8f6b;
  --bad:    #c0392b;
  --gold:   #b8860b;  --gold-bg:   rgba(246, 195, 66, 0.22);
  --silver: #6b7280;  --silver-bg: rgba(148, 163, 184, 0.25);
  --bronze: #9a5b30;  --bronze-bg: rgba(205, 133, 63, 0.22);

  /* ---- the dark band a hero sits on ---------------------------------- */
  /* The P3 slate ramp, so a hero and the footer are the same black. */
  --navy-950: var(--slate-950);
  --navy-900: var(--slate-900);
  --navy-800: var(--slate-800);
  --navy-700: var(--slate-700);

  /* ---- rink / chart geometry (hockey's shot maps) --------------------- */
  --chart-halo: #ffffff;
  --chart-halo-inner: var(--slate-950);
  --rink-line: #b9c2d8;
  --rink-edge: #8f9ab6;
  --rink-blue: #5b7fd1;
  --rink-red:  #c0392b;
}

/* Tokens with no design-system equivalent need their own dark values. The
   bridged ones above must NOT be repeated here — custom.css already redefines
   --bg-card, --text-primary and friends under this same selector, and
   restating them would pin the dark theme to a stale copy. */
html.dark, .dark-mode {
  /* #0f8f6b / #c0392b sit around 3:1 against the dark card and fail the 4.5:1
     text threshold, so the dark theme gets lighter variants. */
  --good:   #3ddca4;
  --bad:    #ff7b6b;
  --gold:   #f6c342;
  --silver: #cbd3e1;
  --bronze: #e0975f;
  --chart-halo: var(--slate-950);
  --chart-halo-inner: #f2f5ff;
  --rink-line: #39435f;
  --rink-edge: #4b5675;
}



/* No global reset here: tailwind.css owns preflight and custom.css owns the
   body ground and type. This file is the COMPONENT layer only. */

/* Inside a page body, an <a> is chrome-coloured unless it is body copy or a
   table cell (the two rules below). The shell's own header/footer links are
   outside .page, so they keep their own styling. :not(.btn) is required: at
   equal specificity `.page a` (0,1,1) beats `.btn-gray`/`.btn-green` (0,1,0),
   so an <a class="btn btn-gray"> rendered dark text on its own dark fill —
   invisible in either theme. Each .btn-* variant already sets its own color;
   this rule must not fight it. */
.page a:not(.btn) { color: inherit; text-decoration: none; }

/* Links inside body copy and tables need to look like links. The blanket
   `color: inherit; text-decoration: none` above is right for nav items, cards
   and whole-row targets, but it also made every player and team name in a
   table indistinguishable from plain text. */
.page tbody a:not(.badge):not(.sweater-link),
.page .card-header a,
.page p a { color: var(--link); }
.page tbody a:not(.badge):not(.sweater-link):hover,
.page .card-header a:hover,
.page p a:hover { text-decoration: underline; }
.inline-link { color: var(--link); }
.inline-link:hover { text-decoration: underline; }
/* On the dark hero, the body link colour has too little contrast — the hero
   band is dark regardless of the page's own light/dark theme, so this reads
   --accent-on-dark rather than the theme-following --green/--indigo. */
.hero-link { color: var(--accent-on-dark); text-decoration: underline; }
.hero-link:hover { color: #fff; }

:where(a, button, select, input, [tabindex]):focus-visible {
  outline: 2px solid var(--indigo);
  outline-offset: 2px;
  border-radius: 4px;
}

.delta-good { color: var(--good); font-weight: 700; }
.delta-bad { color: var(--bad); font-weight: 700; }

/* Both sit on the hero band, which is always the dark navy gradient — same
   --accent-on-dark reasoning as .hero-link above. */
.eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--accent-on-dark);
  display: flex;
  align-items: center;
  gap: 0.6em;
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  border: 1px solid color-mix(in srgb, var(--accent-on-dark) 50%, transparent);
  color: var(--accent-on-dark);
  padding: 0.25em 0.9em;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
}

.season-filter-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--ink-muted);
  padding: 0.25em 0.8em;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  cursor: pointer;
}
.season-filter-btn.active {
  background: var(--navy-800);
  border-color: var(--navy-800);
  color: #fff;
}

/* ---------- Game page tabs ---------- */
.game-tabs {
  display: flex;
  gap: 0.4rem;
  margin-bottom: 1.4rem;
  border-bottom: 1px solid var(--border);
}
.game-tab-btn, .record-tab-btn {
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  padding: 0.7rem 0.9rem;
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--ink-muted);
  cursor: pointer;
}
.game-tab-btn:hover, .record-tab-btn:hover { color: var(--ink); }
.game-tab-btn.active, .record-tab-btn.active {
  color: var(--ink);
  border-bottom-color: var(--indigo);
}

/* Custom hover tooltip (see base.html's data-tip handler) — replaces the
   browser's native title="" tooltip on SVG charts. */
th[data-tip], .stat-card .label[data-tip] { cursor: help; }
.chart-tooltip {
  position: fixed;
  z-index: 1000;
  pointer-events: none;
  background: var(--navy-950);
  color: #fff;
  padding: 0.5em 0.75em;
  border-radius: 8px;
  font-size: 0.78rem;
  font-weight: 600;
  line-height: 1.4;
  max-width: 260px;
  box-shadow: var(--shadow);
}

/* ---------- Hero ---------- */
.hero {
  background: linear-gradient(160deg, var(--navy-900), var(--navy-800));
  color: var(--white);
  padding: 2.6rem 1.5rem 4.2rem;
  position: relative;
}
.hero-inner { max-width: 1280px; margin: 0 auto; }
.hero-season-picker {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.hero-season-picker label {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #b9c0d4;
}
.hero-season-picker select {
  border: 1px solid rgba(255, 255, 255, 0.3);
  background: rgba(255, 255, 255, 0.1);
  color: var(--white);
  border-radius: 10px;
  padding: 0.5rem 0.8rem;
  font-weight: 700;
  font-size: 0.9rem;
  cursor: pointer;
}
.hero-season-picker select option { color: var(--ink); }
.hero h1 {
  font-size: 2.1rem;
  font-weight: 900;
  letter-spacing: -0.02em;
  margin: 0.5rem 0 0.4rem;
  display: flex;
  align-items: center;
  gap: 0.55rem;
}
.hero p.sub {
  color: #b9c0d4;
  font-size: 1rem;
  max-width: 620px;
  margin: 0;
}
.hero-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
}
.zigzag {
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 26px;
  background: var(--bg);
  clip-path: polygon(
    0% 100%, 0% 40%, 4% 0%, 8% 40%, 12% 0%, 16% 40%, 20% 0%, 24% 40%,
    28% 0%, 32% 40%, 36% 0%, 40% 40%, 44% 0%, 48% 40%, 52% 0%, 56% 40%,
    60% 0%, 64% 40%, 68% 0%, 72% 40%, 76% 0%, 80% 40%, 84% 0%, 88% 40%,
    92% 0%, 96% 40%, 100% 0%, 100% 100%
  );
}

/* ---------- Layout ---------- */
.container { max-width: 1280px; margin: 0 auto; padding: 2.2rem 1.5rem 1.5rem; }
.section-title {
  font-size: 0.78rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-muted);
  margin: 0 0 0.9rem;
}

/* ---------- Stat cards (home) ---------- */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
  margin-top: -2.6rem;
  position: relative;
  z-index: 2;
}
/* Column counts live here rather than in inline styles: an inline
   grid-template-columns outranks the responsive media query below and would
   pin phones to six columns. */
.stat-grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
.stat-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.stat-grid.cols-5 { grid-template-columns: repeat(5, 1fr); }
.stat-grid.cols-6 { grid-template-columns: repeat(6, 1fr); }
.stat-grid.cols-7 { grid-template-columns: repeat(7, 1fr); }
.stat-grid.tucked { margin-top: -2.6rem; }
.stat-grid.flush { margin-top: 0; }
.stat-card {
  background: var(--navy-900);
  border: 1px solid var(--navy-700);
  border-radius: var(--radius);
  padding: 1.3rem 1.4rem;
  color: var(--white);
  box-shadow: var(--shadow);
}
.stat-card .num {
  font-size: 2rem;
  font-weight: 900;
  /* .stat-card's own background is always the dark navy band, in both
     themes — --green (the theme-following accent bridge) put the raw light-
     theme accent on that dark ground, which for anything darker than
     roughly #6B7280 drops under 2:1. --accent-on-dark is always the
     lightened shade regardless of the page's own theme. */
  color: var(--accent-on-dark);
  letter-spacing: -0.02em;
}
.stat-card .label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #99a1ba;
  margin-top: 0.3rem;
}
/* .num is sized for a short number/score (e.g. "82", "+15") — a phrase like
   "Clinched Playoff Spot" wrapped onto multiple lines at that size, and since
   a stat-grid row shares height across every card, it inflated the whole row.
   Use this on a .num holding a short status phrase instead. */
.stat-card .num.status-text {
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: normal;
  line-height: 1.25;
}
.stat-card-team { display: flex; align-items: center; justify-content: space-between; gap: 0.8rem; }
.stat-card-logo { width: 40px; height: 40px; object-fit: contain; flex-shrink: 0; }
.stat-card .label-team { display: flex; align-items: center; justify-content: center; gap: 0.35rem; }
.stat-card .label-team img { width: 16px; height: 16px; object-fit: contain; }

/* "Best of the Night" performer tiles — a small category label, the
   player's face with their team's logo badged on it, the big number, then
   the name last (name sits below the number so the number stays the thing
   your eye lands on first, but at a size big enough to actually read who
   it is without opening the modal). */
.stat-card-performer {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.5rem;
}
.performer-title {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #99a1ba;
}
.performer-media {
  position: relative;
  width: 64px;
  height: 64px;
}
.performer-face {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--navy-800);
  border: 2px solid var(--navy-700);
}
.performer-face-placeholder {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: var(--navy-800);
  border: 2px solid var(--navy-700);
}
.performer-team-logo {
  position: absolute;
  right: -4px;
  bottom: -4px;
  width: 26px;
  height: 26px;
  object-fit: contain;
  background: var(--navy-900);
  border-radius: 50%;
  padding: 3px;
  border: 2px solid var(--navy-900);
}
.stat-card-performer .num {
  font-size: 1.8rem;
  margin-top: 0.1rem;
}
.performer-name {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--white);
  line-height: 1.25;
}
.performer-name .meta { font-size: 0.82rem; font-weight: 500; color: #99a1ba; }
@media (max-width: 560px) {
  .performer-face, .performer-face-placeholder { width: 52px; height: 52px; }
  .performer-media { width: 52px; height: 52px; }
  .stat-card-performer .num { font-size: 1.5rem; }
}
/* Fixed, not theme-following — .stat-card's background is always the dark
   navy band in both themes, so a plain orange reads fine either way. */
.flame-hot { color: #ff7a35; }

/* ---------- Cards ---------- */
.card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  /* auto, not hidden: every cell is white-space:nowrap, so a wide table
     (standings runs 13 columns) has to be scrollable on a narrow screen
     rather than clipped off the edge of the card. */
  overflow-x: auto;
}
.card + .card { margin-top: 1.5rem; }
.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.1rem 1.3rem;
  border-bottom: 1px solid var(--border);
}
.card-header h3 {
  margin: 0;
  font-size: 1rem;
  font-weight: 800;
}
.card-header .meta {
  font-size: 0.78rem;
  color: var(--ink-muted);
}

/* ---------- Tables ---------- */
.page table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.page thead th {
  text-align: left;
  text-transform: uppercase;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  color: var(--ink-muted);
  font-weight: 800;
  padding: 0.7rem 0.9rem;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
thead th.num, td.num { text-align: right; }
.page tbody td {
  padding: 0.7rem 0.9rem;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
/* Long prose cells (play-by-play descriptions) have to wrap. Left as nowrap
   they forced the whole card sideways instead of the text using the width it
   already had. */
tbody td.wrap, tbody td:last-child:not(.num) { white-space: normal; }
[data-panel="pbp"] tbody td:last-child { white-space: normal; min-width: 18rem; }
.page tbody tr:nth-child(even) { background: var(--row-alt); }
.page tbody tr:hover { background: #eef2ff; }
tbody tr:last-child td { border-bottom: none; }

/* Whole-row/card navigation targets (see base.html's delegated click/keydown
   handler) — cursor and a visible focus ring so keyboard users can see
   which row is about to activate on Enter/Space. */
[data-href] { cursor: pointer; }
[data-href]:focus-visible { outline: 2px solid var(--indigo); outline-offset: -2px; }

.player-suggest {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 20;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow);
  overflow: hidden;
}
.player-suggest-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0.9rem;
  cursor: pointer;
  font-size: 0.88rem;
  color: var(--ink);
}
.player-suggest-item img { width: 20px; height: 20px; object-fit: contain; }
.player-suggest-item .pos { color: var(--ink-muted); font-weight: 700; font-size: 0.78rem; }
.player-suggest-item.active, .player-suggest-item:hover { background: var(--hover-bg, rgba(120,130,255,0.12)); }
.player-suggest-empty { padding: 0.6rem 0.9rem; color: var(--ink-muted); font-size: 0.85rem; }

.team-cell { display: flex; align-items: center; gap: 0.6rem; font-weight: 700; }
.team-cell img { width: 24px; height: 24px; object-fit: contain; }
/* A player face between the team logo and the name — round and object-fit:
   cover, unlike the square contain-fit team logo right next to it. */
.team-cell img.team-cell-face { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
/* Games list: Home/Score/Away as their own columns (not one free-flowing
   "Matchup" cell) so a longer team name can't shove the score sideways.
   Team names are normal weight here; only the winning side gets bold.
   table-layout: fixed plus two unwidthed "gap" columns (one between Date
   and Home, one after Away) is what centers the Home/Score/Away block in
   the space to the right of Date — a fixed-layout table splits leftover
   width equally between columns that have no declared width, so both gaps
   grow/shrink together and stay equal instead of the block hugging one
   edge. */
.games-table { table-layout: fixed; }
.games-table-date { width: 7.5rem; }
.games-table-home { width: 16rem; text-align: right; }
.games-table-home .team-cell { justify-content: flex-end; }
/* The header label reads left-to-right like every other column heading
   (Date, Score, Away) — only the body cells beneath it right-align to
   hug the score. */
.games-table thead .games-table-home { text-align: right; }
.games-table-score { width: 6rem; text-align: center; }
.games-table-away { width: 16rem; text-align: left; }
.game-team { font-weight: 400; }
.game-team.winner { font-weight: 900; }
.game-score.winner { font-weight: 900; }
.rank { color: var(--ink-muted); font-weight: 700; width: 1.6rem; display: inline-block; }
/* Standings table's # column — a plain table cell, not the inline-block
   .rank span above (that one's sized for a flex/list row and squeezes a
   <td> down to ~0 content width once tbody td's own padding is subtracted,
   which knocked the numbers out of alignment with the "#" header). */
td.rank-col { color: var(--ink-muted); font-weight: 700; }

.badge {
  display: inline-flex;
  align-items: center;
  font-size: 0.68rem;
  font-weight: 800;
  padding: 0.18em 0.55em;
  border-radius: 999px;
  letter-spacing: 0.03em;
  /* A badge is a one-line pill everywhere it's used. Without this, a
     multi-word label (e.g. standings' "Clinched Presidents' Trophy") sitting
     in a table's last cell inherits that cell's white-space:normal (set
     below so prose cells like play-by-play descriptions can wrap), and the
     browser shrinks the whole column to fit the wrapped pill — throwing off
     that row's height and the table's column widths generally. */
  white-space: nowrap;
}
.champion-line {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-weight: 800;
  color: var(--gold);
}
.badge-green { background: rgba(46, 230, 166, 0.15); color: var(--green-dark); }
.badge-indigo { background: rgba(106, 111, 240, 0.14); color: var(--indigo-dark); }
.badge-gray { background: #eef0f6; color: var(--ink-muted); }
.badge-red { background: #fde8e8; color: #c0392b; }
html.dark .badge-red { background: rgba(255, 123, 107, 0.18); color: var(--bad); }

/* ---------- Franchise history (/history) ---------- */
.franchise-card { margin-bottom: 1.5rem; }
.franchise-card .card-header h3 { display: flex; align-items: center; gap: 0.5rem; }
.franchise-card .card-header h3 img { width: 26px; height: 26px; object-fit: contain; }
.franchise-timeline { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; }
.franchise-era {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  text-decoration: none;
  color: inherit;
  text-align: center;
  min-width: 130px;
}
.franchise-era:hover { border-color: var(--indigo); }
.franchise-era img { width: 28px; height: 28px; object-fit: contain; }
.franchise-era .era-name { font-weight: 700; font-size: 0.85rem; }
.franchise-era .era-years { color: var(--ink-muted); font-size: 0.72rem; }
.franchise-transition {
  display: flex;
  flex-direction: column;
  align-items: center;
  color: var(--ink-muted);
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  white-space: nowrap;
}
.franchise-transition .arrow { font-size: 1.1rem; line-height: 1; color: var(--ink-muted); }

/* Sortable column headers */
.sort-link { display: inline-flex; align-items: center; gap: 0.25em; color: inherit; }
.sort-link:hover { color: var(--ink); text-decoration: underline; }
.sort-link.is-active { color: var(--ink); }
.sort-arrow { font-size: 0.9em; line-height: 1; }

.rank-note {
  display: inline-block;
  margin-left: 0.35em;
  font-size: 0.9em;
  font-weight: 700;
  /* Fixed light grey, not var(--ink-muted) — every caller sits inside a
     .stat-card, whose background is always the dark navy band regardless of
     theme (see .stat-card .num above). The light theme's --ink-muted is
     tuned for a light card and reads near-invisible on that dark ground. */
  color: #c7cce0;
  text-transform: none;
  letter-spacing: 0;
}
.rank-note.is-good { color: var(--good); }
.rank-note.is-bad { color: var(--bad); }

/* Win/loss badges on a team's results */
.result-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.1rem;
  padding: 0.16em 0.45em;
  border-radius: 6px;
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.03em;
}
.result-w { background: rgba(46, 230, 166, 0.18); color: var(--good); }
.result-l { background: rgba(192, 57, 43, 0.13); color: var(--bad); }
.result-otl { background: rgba(106, 111, 240, 0.16); color: var(--indigo-dark); }
html.dark .result-otl { color: #a7abff; }
.home-away {
  display: inline-block;
  width: 1.4rem;
  color: var(--ink-muted);
  font-weight: 700;
  font-size: 0.8rem;
}
.ot-flag {
  margin-left: 0.35em;
  font-size: 0.65rem;
  font-weight: 800;
  color: var(--ink-muted);
  vertical-align: super;
}

/* Filter rows above a table or chart */
.filter-bar {
  display: flex;
  gap: 0.7rem;
  margin-bottom: 1.4rem;
  flex-wrap: wrap;
  align-items: center;
}
.shot-filter {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
  padding: 0 1.3rem 0.2rem;
}

/* Half-rink shot charts, side by side — 2 normally (Goals/All Shots), 3 when
   Blocked joins them, 4 on the player page once Assists does too. Explicit
   per-count classes rather than auto-fit: auto-fit sizes columns off the
   container alone, so 2 real panels in a wide card would stretch to fill it
   at the same width a 4-panel row uses, instead of matching cols to the
   panel count actually present. */
.rink-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.1rem;
  padding: 1.1rem 1.3rem;
}
.rink-pair.trio { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.rink-pair.quad { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1100px) {
  .rink-pair.quad { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.rink-panel { margin: 0; }
.rink-panel svg {
  width: 100%;
  height: auto;
  display: block;
  background: var(--row-alt);
  border-radius: 10px;
}
.rink-panel figcaption {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
}
.rink-title { font-weight: 800; font-size: 0.95rem; }
.rink-count {
  font-weight: 800;
  font-size: 0.95rem;
  color: var(--green-dark);
}
html.dark .rink-count { color: var(--green); }
.rink-sub {
  flex: 1 0 100%;
  font-size: 0.75rem;
  line-height: 1.3;
  /* Reserves 2 lines' worth of height regardless of how long this panel's
     own caption is — with panels of unequal caption length otherwise
     wrapping to different line counts, the shorter ones' SVGs sat higher
     than a longer one's in the same row (e.g. the 3/4-panel rows, where the
     narrower columns wrap sooner). */
  min-height: 2.6em;
  color: var(--ink-muted);
}
/* Heatmap cells: one hue per panel (indigo for shot volume, green for
   goals — same pairing the old scatter dots used), monotone light->dark by
   varying fill-opacity over a flat surface, which is mathematically the
   same as mixing the hue toward the surface colour. Cells are direct
   children of the <svg>; the rink's own shapes live inside its
   <g class="rink">, so a child combinator separates them without needing
   Selectors Level 4 :not(). */
.rink-panel svg > rect.heatmap-cell.is-shots { fill: var(--indigo); }
.rink-panel svg > rect.heatmap-cell.is-goals { fill: var(--green-dark); }
html.dark .rink-panel svg > rect.heatmap-cell.is-goals { fill: var(--green); }
.rink-panel svg > rect.heatmap-cell.is-blocked { fill: var(--gold); }
.rink-panel svg > rect.heatmap-cell.is-assists { fill: var(--silver); }
.heatmap-legend {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.5rem;
  font-size: 0.7rem;
  color: var(--ink-muted);
}
.heatmap-legend .swatch {
  width: 64px;
  height: 8px;
  border-radius: 4px;
  background: linear-gradient(to right, transparent, var(--indigo));
}
.heatmap-legend .swatch.is-goals { background: linear-gradient(to right, transparent, var(--green-dark)); }
html.dark .heatmap-legend .swatch.is-goals { background: linear-gradient(to right, transparent, var(--green)); }
.heatmap-legend .swatch.is-blocked { background: linear-gradient(to right, transparent, var(--gold)); }
.heatmap-legend .swatch.is-assists { background: linear-gradient(to right, transparent, var(--silver)); }
@media (max-width: 720px) {
  .rink-pair, .rink-pair.trio, .rink-pair.quad { grid-template-columns: 1fr; }
}

/* ---------- Shot-type-against bars (team/player "weaknesses" panel) ---------- */
.shot-type-bars { padding: 0.4rem 1.3rem 1.3rem; }
.shot-type-bar-row {
  display: grid;
  grid-template-columns: 110px 1fr 52px 56px;
  align-items: center;
  gap: 0.7rem;
  padding: 0.35rem 0;
}
.shot-type-bar-label {
  font-size: 0.82rem;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.shot-type-bar-track {
  height: 10px;
  border-radius: var(--radius-full);
  background: var(--row-alt);
  overflow: hidden;
}
.shot-type-bar-fill {
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--bad);
  opacity: 0.55;
}
.shot-type-bar-fill.is-worst { opacity: 0.9; }
.shot-type-bar-value {
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--ink);
  text-align: right;
}
.shot-type-bar-detail {
  font-size: 0.72rem;
  color: var(--ink-muted);
  text-align: right;
}
@media (max-width: 560px) {
  .shot-type-bar-row { grid-template-columns: 84px 1fr 44px; }
  .shot-type-bar-detail { display: none; }
}

/* ---------- Team grid ---------- */
.team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 0.9rem;
}
.team-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.1rem;
  display: flex;
  align-items: center;
  gap: 0.9rem;
  box-shadow: var(--shadow);
}
.team-card img { width: 42px; height: 42px; object-fit: contain; }
/* Every other title on the site (h1-h6, card-header h3) inherits the global
   --font-display rule; this is a plain div so it never did, and fell back to
   body copy (Geist) instead of the display face every other heading uses. */
.team-card .name { font-family: var(--font-display); font-weight: 800; font-size: 0.95rem; }
.team-card .record { font-size: 0.78rem; color: var(--ink-muted); margin-top: 0.15rem; }

/* ---------- Notices ---------- */
.notice {
  background: rgba(106, 111, 240, 0.08);
  border: 1px solid rgba(106, 111, 240, 0.25);
  border-radius: var(--radius);
  padding: 0.9rem 1.1rem;
  font-size: 0.88rem;
  color: var(--ink);
  margin-bottom: 1.2rem;
}
html.dark .notice { background: rgba(106, 111, 240, 0.16); }

/* ---------- Records page ---------- */
.record-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 0.9rem;
}
.record-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.2rem 1.3rem;
  box-shadow: var(--shadow);
  cursor: pointer;
}
.record-card:hover { border-color: var(--indigo); }
.record-card .label {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-muted);
}
.record-card .value {
  font-size: 1.7rem;
  font-weight: 900;
  margin: 0.25rem 0;
  color: var(--indigo-dark);
}
html.dark .record-card .value { color: var(--indigo); }
.record-card .detail { font-size: 0.82rem; color: var(--ink-muted); }

/* Record tiles open this <dialog> with the top-10 list; it renders in the
   browser's top layer via showModal(), so position in the DOM doesn't
   affect stacking — only these two rules place it visually. */
dialog.record-dialog {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0;
  background: var(--card-bg);
  color: var(--ink);
  box-shadow: var(--shadow);
  max-width: 480px;
  width: calc(100% - 2rem);
}
dialog.record-dialog::backdrop { background: rgba(6, 10, 20, 0.55); }
.record-dialog-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.2rem;
  border-bottom: 1px solid var(--border);
}
.record-dialog-header h3 { margin: 0; font-size: 1rem; }
.dialog-close {
  background: none;
  border: none;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  color: var(--ink-muted);
  padding: 0.1rem 0.4rem;
}
.dialog-close:hover { color: var(--ink); }
.record-top10 {
  list-style: none;
  margin: 0;
  padding: 0.5rem;
  max-height: 60vh;
  overflow-y: auto;
}
.record-top10 li {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  padding: 0.6rem 0.7rem;
  border-radius: 8px;
  cursor: pointer;
}
.record-top10 li:hover { background: var(--row-alt); }
.record-top10 .rank { font-weight: 800; color: var(--ink-muted); width: 1.4em; flex-shrink: 0; }
.record-top10 .value { font-weight: 800; min-width: 5em; }
.record-top10 .detail { color: var(--ink-muted); font-size: 0.85rem; }

/* ---------- Leaderboard (Records page + a team's Records tab) ----------
   A filter form picks one stat; this is the ranked result underneath it —
   full-width, primary page content, not the compact list record-top10 was
   built for inside a small dialog. */
.leaderboard { margin-top: 1.1rem; }
.leaderboard-row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.6rem 0.9rem;
  border-radius: 10px;
  cursor: pointer;
}
.leaderboard-row:nth-child(even) { background: var(--row-alt); }
.leaderboard-row:hover { background: #eef2ff; }
html.dark .leaderboard-row:hover { background: #1a2340; }
.leaderboard-row:focus-visible { outline: 2px solid var(--indigo); outline-offset: -2px; }

.leaderboard-rank {
  flex-shrink: 0;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 0.8rem;
  background: var(--bg);
  color: var(--ink-muted);
}
.leaderboard-rank.rank-1 { background: var(--gold-bg); color: var(--gold); }
.leaderboard-rank.rank-2 { background: var(--silver-bg); color: var(--silver); }
.leaderboard-rank.rank-3 { background: var(--bronze-bg); color: var(--bronze); }

.leaderboard-logo { width: 1.8rem; height: 1.8rem; object-fit: contain; flex-shrink: 0; }

.leaderboard-main {
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.leaderboard-value { font-weight: 800; }
.leaderboard-detail {
  color: var(--ink-muted);
  font-size: 0.85rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The #1 row is the actual record, not just another list entry. */
.leaderboard-row.is-first {
  background: linear-gradient(135deg, rgba(106, 111, 240, 0.12), rgba(46, 230, 166, 0.08));
  padding: 0.9rem 1rem;
  margin-bottom: 0.3rem;
}
.leaderboard-row.is-first .leaderboard-rank { width: 2.4rem; height: 2.4rem; font-size: 1rem; }
.leaderboard-row.is-first .leaderboard-logo { width: 2.5rem; height: 2.5rem; }
.leaderboard-row.is-first .leaderboard-value { font-size: 1.35rem; color: var(--indigo-dark); }
html.dark .leaderboard-row.is-first .leaderboard-value { color: var(--indigo); }
.leaderboard-row.is-first .leaderboard-detail { font-size: 0.88rem; }

.leaderboard-empty { color: var(--ink-muted); margin-top: 1rem; }

.division-title {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-weight: 800;
  font-size: 0.95rem;
  margin: 1.6rem 0 0.7rem;
}
.division-title .conf {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--indigo-dark);
  background: rgba(106, 111, 240, 0.12);
  padding: 0.2em 0.6em;
  border-radius: 999px;
}

/* ---------- Buttons / links ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.7rem 1.3rem;
  border-radius: 10px;
  font-weight: 800;
  font-size: 0.85rem;
  border: none;
  cursor: pointer;
}
/* The primary button. The ink was #06331f, a near-black green chosen to sit
   on the old fixed emerald; now that the fill follows the pack accent it has
   to be white, or hockey's teal and football's ochre get dark-green text. */
.btn-green { background: var(--accent-color); color: #fff; }
.btn-green:hover { background: var(--accent-hover); }

/* Two grounds, two treatments. A hero is always the dark slate band, so an
   outline button there is white-on-transparent; anywhere else it has to read
   on the page ground instead. Splitting them is what keeps the "Browse Teams"
   hero button and the "Sign out" button on a white card both legible. */
.btn-outline {
  background: transparent;
  border: 1px solid var(--border-strong);
  color: var(--text-primary);
}
.btn-outline:hover { background: var(--accent-soft); border-color: var(--accent-color); color: var(--accent-color); }
.hero .btn-outline,
.hub-hero .btn-outline {
  border-color: rgba(255, 255, 255, 0.25);
  color: var(--white);
}
.hero .btn-outline:hover,
.hub-hero .btn-outline:hover {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.5);
  color: var(--white);
}
.btn-sm { padding: 0.4rem 0.8rem; font-size: 0.72rem; }
.btn-indigo { background: var(--indigo); color: #fff; }
.btn-gray { background: var(--navy-800); color: #fff; }
.btn-red { background: #fde8e8; color: #c0392b; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

.field-label {
  display: block;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--ink-muted);
  margin-bottom: 0.3rem;
}
.field-input {
  width: 100%;
  padding: 0.55rem 0.7rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--ink);
  font-size: 0.9rem;
}
.field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 0.9rem;
}
/* The Records page's stat pickers ("Best Goal Differential, One Season")
   run noticeably longer than most field-grid labels, which clip at 160px. */
.field-grid.wide { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }

/* Fantasy layouts: board + sidebar, and the skater/goalie scoring pair.
   Both collapse to a single column on narrow screens (see media queries). */
.draft-layout {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 1.5rem;
  align-items: start;
}
.scoring-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
}

/* ---------- Team detail ---------- */
.team-header {
  display: flex;
  align-items: center;
  gap: 1.2rem;
}
/* A white chip behind the mark, not a swapped SVG variant: the hero band is
   always dark navy, and several team marks (Lightning, Sabres, Maple Leafs)
   are drawn in a navy/black that otherwise all but disappears into it. A
   white backing keeps every team's logo readable there regardless of its
   own brand color. */
.team-header img {
  width: 64px;
  height: 64px;
  object-fit: contain;
  background: #fff;
  border-radius: 50%;
  padding: 8px;
  box-sizing: border-box;
}
.player-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 0.75rem;
}
.player-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.8rem;
  display: flex;
  gap: 0.7rem;
  align-items: center;
}
.player-card img {
  width: 46px; height: 46px; border-radius: 8px; object-fit: cover;
  background: var(--row-alt);
}
.player-card .num {
  font-weight: 900;
  color: var(--indigo-dark);
  font-size: 1.05rem;
  width: 1.6rem;
}
/* Sweater-number links (team roster cards, a player's team-history table)
   look like plain text otherwise — a permanent dotted underline is the
   common "there's more here" hint, so it doesn't take a hover to notice
   the number is clickable at all. */
.sweater-link {
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}
.player-card a.num:hover { text-decoration: underline solid; }
/* Same gap as .team-card .name above — a plain div never picks up the
   global h1-h6 --font-display rule on its own. */
.player-card .name { font-family: var(--font-display); font-weight: 700; font-size: 0.88rem; }
.player-card .pos { font-size: 0.72rem; color: var(--ink-muted); margin-top: 0.15rem; }

.roster-group-title {
  font-weight: 800;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-muted);
  margin: 1.4rem 0 0.7rem;
}

.footer {
  text-align: center;
  color: var(--ink-muted);
  font-size: 0.78rem;
  padding: 1rem 1.5rem 1.5rem;
}

/* ---------- Dark mode ---------- */
html.dark {
  --bg: #0b0e17;
  --card-bg: #131a2b;
  --border: #232c44;
  --row-alt: #101627;
  --ink: #eef0f8;
  --ink-muted: #8a91ab;
  --link: #9ba0ff;
  /* #0f8f6b / #c0392b sit around 3:1 against the dark card and fail the 4.5:1
     text threshold, so the dark theme gets lighter variants rather than
     reusing the light ones. */
  --good: #3ddCa4;
  --bad: #ff7b6b;
  --gold: #f6c342;
  --silver: #cbd3e1;
  --bronze: #e0975f;
  --chart-halo: #0b0e17;
  --chart-halo-inner: #f2f5ff;
  --rink-line: #39435f;
  --rink-edge: #4b5675;
}
html.dark tbody tr:hover { background: #1a2340; }
html.dark .badge-indigo { background: rgba(106, 111, 240, 0.22); }
html.dark .badge-green { background: rgba(46, 230, 166, 0.18); }
html.dark .badge-gray { background: #1c2338; color: #9aa2bd; }
html.dark .zigzag { background: var(--bg); }

@media (max-width: 900px) {
  .stat-grid,
  .stat-grid.cols-3,
  .stat-grid.cols-5,
  .stat-grid.cols-6,
  .stat-grid.cols-7 { grid-template-columns: repeat(2, 1fr); }

  .hero { padding: 2rem 1rem 3.6rem; }
  .hero h1 { font-size: 1.6rem; }
  .container { padding: 1.8rem 1rem 3rem; }
  .draft-layout,
  .scoring-columns { grid-template-columns: 1fr; }

  /* Game/team/player tab bars (3-4 labels, e.g. "Play by Play") are wider
     than a phone screen with no wrap defined, which forced the whole page
     to scroll sideways just to reach the last tab. Scroll the tab strip
     itself instead, same pattern the shell nav uses. */
  .game-tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .game-tabs::-webkit-scrollbar { display: none; }
  .game-tab-btn { flex: 0 0 auto; white-space: nowrap; }

  /* Card headers pair a title with meta text and, on a few pages, a row of
     filter buttons — on a phone that combination is wider than the card,
     so let it wrap to a second line rather than clipping/scrolling. */
  .card-header { flex-wrap: wrap; row-gap: 0.5rem; }
}

@media (max-width: 560px) {
  .stat-grid,
  .stat-grid.cols-3,
  .stat-grid.cols-5,
  .stat-grid.cols-6,
  .stat-grid.cols-7 { grid-template-columns: repeat(2, 1fr); }
  .stat-card { padding: 1rem; }
  .stat-card .num { font-size: 1.5rem; }
}

.field-hint {
  margin: 0.35rem 0 0;
  font-size: 0.75rem;
  line-height: 1.45;
  color: var(--ink-muted);
}

/* =====================================================================
   SHELL CHROME — header, footer, palette.
   Everything here is the platform's own, not a pack's. The header's
   structural classes (.sticky-nav, .nav-link-item) come from custom.css so
   the hub, hockey and cycling share one header treatment; these fill the
   gaps for the controls the shell adds.
   ===================================================================== */

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--accent-color);
  color: #fff;
  padding: 0.6rem 1rem;
  font-weight: 700;
  border-radius: 0 0 var(--radius-md) 0;
}
.skip:focus { left: 0; }

/* The sport you are in, next to the logo. Clicking it opens the switcher;
   the entries go to each sport's OWN home. */
.nav-sport-chip,
.nav-mobile-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.65rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-color);
  background: var(--accent-soft);
  color: var(--accent-color);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: -0.01em;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out);
}
.nav-sport-chip:hover,
.nav-mobile-trigger:hover { background: var(--accent-ring); }
.nav-mobile-trigger {
  background: var(--bg-tertiary);
  color: var(--text-secondary);
}

.nav-sport-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  flex: 0 0 auto;
}
.nav-sport-current { color: var(--accent-color) !important; background: var(--accent-soft); }

.nav-mobile-sep {
  padding: 0.5rem 0.75rem 0.25rem;
  font-size: 9px;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--text-muted);
  border-top: 1px solid var(--border-color);
  margin-top: 0.25rem;
}

.nav-search-btn,
.nav-icon-btn,
.nav-account {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border: 1px solid var(--border-color);
  background: var(--bg-tertiary);
  color: var(--text-secondary);
  border-radius: var(--radius-md);
  font-size: 11px;
  font-weight: 700;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.nav-search-btn { padding: 0.35rem 0.5rem; border-radius: var(--radius-full); }
.nav-icon-btn   { padding: 0.45rem; min-width: 34px; }
.nav-account {
  padding: 0.35rem 0.7rem;
  background: var(--accent-soft);
  border-color: var(--accent-ring);
  color: var(--accent-color);
}
.nav-search-btn:hover,
.nav-icon-btn:hover { background: var(--accent-soft); color: var(--accent-color); }
.nav-account:hover  { background: var(--accent-ring); }

@media (min-width: 1536px) { .nav-search-btn { width: 12rem; justify-content: flex-start; padding-left: 0.85rem; } }

/* A dropdown opened by tap rather than hover — toggleHeaderDropdown adds
   this, matching the behaviour cycling's header already had. */
.active-dropdown {
  opacity: 1 !important;
  transform: scale(1) !important;
  pointer-events: auto !important;
}

.flash {
  border-radius: var(--radius-lg);
  padding: 0.75rem 1rem;
  font-size: var(--fs-sm);
  font-weight: 600;
  border: 1px solid;
}
.flash-ok  { background: var(--success-soft); border-color: var(--success); color: var(--good); }
.flash-err { background: var(--danger-soft);  border-color: var(--danger);  color: var(--bad); }

/* ---- global search palette (Ctrl-K) -------------------------------- */
.palette {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(2, 6, 23, 0.55);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 12vh 1rem 1rem;
}
.palette[hidden] { display: none; }
.palette-box {
  width: min(640px, 100%);
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-xl);
  overflow: hidden;
}
.palette-box input {
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--border-color);
  background: transparent;
  color: var(--text-primary);
  padding: 1rem 1.2rem;
  font-size: var(--fs-md);
  font-family: var(--font-body);
}
.palette-box input:focus { outline: none; }
.palette-results { max-height: 50vh; overflow-y: auto; }
.palette-results a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.7rem 1.2rem;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-primary);
  text-decoration: none;
  border-bottom: 1px solid var(--hairline);
}
.palette-results a:hover,
.palette-results a.is-active { background: var(--accent-soft); color: var(--accent-color); }
.palette-results .sp {
  font-size: var(--fs-2xs);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--text-muted);
  flex: 0 0 auto;
}
.palette-hint {
  margin: 0;
  padding: 0.6rem 1.2rem;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  background: var(--bg-tertiary);
}

/* =====================================================================
   PAGE SHELL
   .page is the content column every pack template renders inside, and the
   scope the component rules above are written against.
   ===================================================================== */
.page {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2.2rem 1.5rem 3rem;
}
.page > .stat-grid:first-child { margin-top: -2.6rem; }
@media (max-width: 900px) { .page { padding: 1.8rem 1rem 2.5rem; } }

/* A handful of wide tables (hockey's team/skater/goalie advanced stats,
   19-21 columns) ran wider than .page's 1280px column and scrolled inside
   it even when the browser window itself had spare width either side of
   that column. This breaks the card out to the viewport edge (minus
   .page's own gutter) so overflow-x only kicks in once the WINDOW runs
   out of room, not the reading column. The 50%-50vw offset is relative to
   the card's own containing block width, so it lands at the same gutter
   regardless of how deep .page nests it. */
.card-breakout {
  width: calc(100vw - 3rem);
  margin-left: calc(50% - 50vw + 1.5rem);
  margin-right: calc(50% - 50vw + 1.5rem);
}
@media (max-width: 900px) {
  .card-breakout {
    width: calc(100vw - 2rem);
    margin-left: calc(50% - 50vw + 1rem);
    margin-right: calc(50% - 50vw + 1rem);
  }
}

/* =====================================================================
   HUB — the platform home at "/". Same components as a sport page, so the
   hub does not drift into a fourth visual language.
   ===================================================================== */
.hub-hero {
  background: linear-gradient(160deg, var(--slate-900), var(--slate-800));
  color: #fff;
  /* Clears the zigzag, which is 26px of clipped band at the bottom edge. */
  padding: 4rem 1.5rem 5rem;
  position: relative;
}
.hub-hero-in { max-width: 1280px; margin: 0 auto; }
.hub-hero h1 {
  font-family: var(--font-display);
  font-size: clamp(2rem, 5vw, 3rem);
  font-weight: 900;
  letter-spacing: var(--tracking-tight);
  margin: 0.6rem 0 0.8rem;
}
.hub-lede { color: #b9c0d4; font-size: var(--fs-md); max-width: 46rem; margin: 0; }
/* The cards sit fully BELOW the zigzag, not straddling it. Overlapping them
   into the dark band leaves the sawtooth cutting across white cards, which
   reads as a rendering fault rather than a flourish — hockey's stat cards can
   tuck into its hero because they are dark on dark. */
.hub-grid-wrap { max-width: 1280px; margin: 0 auto; padding: 2.5rem 1.5rem 4rem; position: relative; z-index: 2; }
@media (max-width: 900px) {
  .hub-hero { padding: 2.5rem 1rem 4rem; }
  .hub-grid-wrap { padding: 2rem 1rem 3rem; }
}
.hub-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1.25rem;
}
.sport-card {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-top: 3px solid var(--sport-accent, var(--accent-color));
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 1.5rem;
  text-decoration: none;
  color: inherit;
  transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}
.sport-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-xl); }
.sport-card h2 {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: 800;
  margin: 0;
  color: var(--text-primary);
}
.sport-card p { margin: 0; font-size: var(--fs-sm); line-height: var(--lh-normal); color: var(--text-secondary); }
.sport-tag {
  align-self: flex-start;
  font-size: var(--fs-2xs);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--sport-accent, var(--accent-color));
}
.sport-stat {
  margin-top: auto;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-color);
  font-family: var(--font-mono);
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--text-primary);
}
.sport-stat small {
  display: block;
  font-family: var(--font-body);
  font-size: var(--fs-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--text-muted);
}

/* ---- error pages ---------------------------------------------------- */
.err { max-width: 40rem; margin: 0 auto; padding: 5rem 1.5rem; text-align: center; }
.err-code {
  font-family: var(--font-display);
  font-size: 5rem;
  font-weight: 900;
  line-height: 1;
  color: var(--accent-color);
}
.err h1 { font-size: var(--fs-xl); font-weight: 800; margin: 0.5rem 0 0.75rem; color: var(--text-primary); }
/* :not(.err-code) matters — .err p is one step more specific than .err-code,
   so an unqualified rule here silently reset the big number to body copy. */
.err p:not(.err-code) { color: var(--text-secondary); font-size: var(--fs-base); margin: 0 0 1.5rem; }
.err-actions { display: flex; gap: 0.75rem; justify-content: center; flex-wrap: wrap; }

/* ---- account / login forms ------------------------------------------ */
/* .field-grid / .field-label / .field-input are defined with the rest of the
   form controls above; only the focus ring is added here. */
.field-input:focus { outline: 2px solid var(--accent-ring); outline-offset: 1px; border-color: var(--accent-color); }
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 0.5rem 1.5rem; font-size: var(--fs-sm); }
.kv dt { font-weight: 700; color: var(--text-muted); }
.kv dd { margin: 0; color: var(--text-primary); }

/* ---- odds and ends the shell templates use -------------------------- */
.narrow { max-width: 46rem; }
.wide   { max-width: 1440px; }
.muted  { color: var(--text-muted); font-size: var(--fs-sm); }
.empty  { color: var(--text-muted); font-size: var(--fs-sm); text-align: center; padding: 3rem 1rem; }
.kicker {
  margin: 0;
  font-size: var(--fs-2xs);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: var(--tracking-kicker);
  color: var(--accent-color);
}
.hub-hero .kicker { color: var(--accent-300); }

/* A card whose content is prose or a form rather than a table. The base
   .card has no padding on purpose — a table has to reach its edges. */
.card-pad { padding: 1.3rem; }
.card-pad > :first-child { margin-top: 0; }
.card-pad > :last-child  { margin-bottom: 0; }
.field-stack { display: flex; flex-direction: column; gap: 1rem; }

.hero-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 1rem; }
.chip-sm {
  display: inline-flex;
  align-items: center;
  padding: 0.2em 0.7em;
  border-radius: var(--radius-full);
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(255, 255, 255, 0.1);
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: #fff;
}

/* .hero-in / .hero-sub were the shell's own names for what hockey calls
   .hero-inner / .sub. Aliased rather than renamed so neither side breaks. */
.hero-in  { max-width: 1280px; margin: 0 auto; }
.hero-sub { color: #b9c0d4; font-size: var(--fs-md); max-width: 620px; margin: 0; }
