/* ---------- Themes ----------
   Nineteen self-contained palettes (colors + font family — no network
   fonts, everything falls back through system font stacks so the app
   stays fully offline-capable). Bare :root is "Crimson Felt", the
   original look and the default; the rest override via
   :root[data-theme="..."], which JS sets on <html> and persists to
   localStorage. Each sets its own color-scheme (dark or light) so native
   form controls/scrollbars render correctly either way.

   Five themes (stainless-steel, copper-shine, opal-shimmer, rock-and-
   roll, flowery-forest) additionally override --body-surface/
   --panel-surface/--btn-primary-surface/--modal-surface (defined once
   at bare :root, defaulting to exactly what body/.panel/.btn-primary/
   .confirm-modal-card already painted) with real multi-stop gradients -
   brushed metal, iridescence, concert spotlights, or dappled forest
   light - instead of just evocatively-named flat colors. */

:root {
  --bg: #1a0a0a;
  --bg-panel: #241010;
  --bg-card: #2e1414;
  --felt: #5c0f14;
  --felt-light: #8a1a1f;
  --accent: #e8b923;
  --accent-dark: #b8890f;
  --danger: #ff5252;
  --danger-dark: #c62828;
  --info: #5aa9e6;
  --info-dark: #3d7cb0;
  --text: #f6efe9;
  --text-dim: #c9a2a2;
  --border: #4a1e1e;
  --bg-glow: #3a1212;
  --radius: 14px;
  --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* Text color for content sitting directly on an --accent or
     --felt-light background (buttons, toggles, badges) — dark by
     default, flipped to light per-theme below wherever that specific
     background color is too dark for dark text to read. */
  --on-accent: #241a00;
  --on-felt-light: #06170f;
  /* Graph card background — a faint dark wash by default. Overridden
     per light theme below with a very light tint of that theme's own
     accent color instead, so it never reads as flat neutral gray the
     way a black-alpha wash over a white panel would. */
  --graph-bg: rgba(0, 0, 0, 0.25);
  /* Four surfaces a "material" theme (stainless-steel, copper-shine,
     opal-shimmer) overrides with a real multi-stop gradient instead of
     a flat color, for an actual brushed-metal/iridescent reflection
     rather than just picking metallic-sounding colors. Every other
     theme leaves these at their default, which exactly matches what
     body/.panel/.btn-primary/.confirm-modal-card already painted before
     this existed - zero visual change for them. */
  --body-surface: radial-gradient(ellipse at top, var(--bg-glow) 0%, var(--bg) 60%);
  --panel-surface: var(--bg-panel);
  --btn-primary-surface: var(--accent);
  --modal-surface: linear-gradient(180deg, var(--felt-light) 0%, var(--felt) 60%, var(--bg-glow) 100%);
  color-scheme: dark;
}

:root[data-theme="emerald-rail"] {
  --bg: #071a10;
  --bg-panel: #0d2618;
  --bg-card: #113420;
  --felt: #0f5c33;
  --felt-light: #1a8a4f;
  --accent: #d4af37;
  --accent-dark: #a8862a;
  --danger: #ff6b5e;
  --danger-dark: #c94f3f;
  --info: #5aa9e6;
  --info-dark: #3d7cb0;
  --text: #f2f0e6;
  --text-dim: #a8c9b5;
  --border: #1e4a30;
  --bg-glow: #123a22;
  --font-family: "Trebuchet MS", "Verdana", "Segoe UI", sans-serif;
}

:root[data-theme="neon-arcade"] {
  --bg: #0a0a12;
  --bg-panel: #12121e;
  --bg-card: #181828;
  --felt: #1a1a3a;
  --felt-light: #2f2f6b;
  --accent: #00e5ff;
  --accent-dark: #00a8bf;
  --danger: #ff2f92;
  --danger-dark: #c4206e;
  --info: #7c4dff;
  --info-dark: #5a35c4;
  --text: #eafcff;
  --text-dim: #8fa3c9;
  --border: #2a2a5a;
  --bg-glow: #22224a;
  --on-felt-light: #eafcff;
  --font-family: "SF Mono", "Consolas", "Menlo", "Courier New", monospace;
}

:root[data-theme="midnight-ivory"] {
  --bg: #0e1218;
  --bg-panel: #161c26;
  --bg-card: #1c2430;
  --felt: #1f2d3d;
  --felt-light: #2f4258;
  --accent: #c9a768;
  --accent-dark: #9c8149;
  --danger: #d9534f;
  --danger-dark: #a83f3c;
  --info: #6fa8dc;
  --info-dark: #4f80b0;
  --text: #f2ede3;
  --text-dim: #a8a296;
  --border: #2c3644;
  --bg-glow: #263547;
  --on-felt-light: #f2ede3;
  --font-family: Georgia, "Iowan Old Style", "Times New Roman", serif;
}

:root[data-theme="sunset-chalk"] {
  --bg: #1a0f08;
  --bg-panel: #26160c;
  --bg-card: #301c10;
  --felt: #7a3b12;
  --felt-light: #b0591a;
  --accent: #ffb703;
  --accent-dark: #cc9202;
  --danger: #ef476f;
  --danger-dark: #c23557;
  --info: #06a77d;
  --info-dark: #04805f;
  --text: #fff3e0;
  --text-dim: #d9b391;
  --border: #4a2c14;
  --bg-glow: #4a2810;
  --on-felt-light: #fff3e0;
  --font-family: "Century Gothic", Futura, "Trebuchet MS", sans-serif;
}

:root[data-theme="obsidian-break"] {
  --bg: #08090a;
  --bg-panel: #101214;
  --bg-card: #16181b;
  --felt: #1c1f23;
  --felt-light: #2a2e33;
  --accent: #7fd4ff;
  --accent-dark: #4fa8cc;
  --danger: #ff5c5c;
  --danger-dark: #c73e3e;
  --info: #8f9bab;
  --info-dark: #6b7684;
  --text: #eef1f4;
  --text-dim: #9aa3ad;
  --border: #2a2e33;
  --bg-glow: #1c1f23;
  --on-felt-light: #eef1f4;
  --font-family: "Helvetica Neue", Arial, "Segoe UI", sans-serif;
  color-scheme: dark;
}

:root[data-theme="sunset-blaze"] {
  --bg: #1a0e1f;
  --bg-panel: #251530;
  --bg-card: #2f1a3d;
  --felt: #7a2a4a;
  --felt-light: #b0446a;
  --accent: #ff7a45;
  --accent-dark: #cc5f34;
  --danger: #ff4d6d;
  --danger-dark: #c93a56;
  --info: #5b8def;
  --info-dark: #3f68b8;
  --text: #fff0e6;
  --text-dim: #d9a8b8;
  --border: #4a2540;
  --bg-glow: #3d1a35;
  --on-accent: #2a1206;
  --on-felt-light: #fff0e6;
  --font-family: Optima, Candara, "Segoe UI", sans-serif;
}

:root[data-theme="red-and-pink"] {
  --bg: #1c0510;
  --bg-panel: #2a0818;
  --bg-card: #380b20;
  --felt: #8c0f3a;
  --felt-light: #c2185b;
  --accent: #ff4081;
  --accent-dark: #c60055;
  --danger: #ff1744;
  --danger-dark: #c4001d;
  --info: #4fc3f7;
  --info-dark: #0288d1;
  --text: #ffe6ef;
  --text-dim: #e08aa8;
  --border: #5c1030;
  --bg-glow: #4a0e28;
  --on-accent: #fff0f5;
  --on-felt-light: #fff0f5;
  --font-family: Futura, "Century Gothic", "Trebuchet MS", sans-serif;
}

:root[data-theme="gold-mine"] {
  --bg: #140d06;
  --bg-panel: #1f150a;
  --bg-card: #2a1c0d;
  --felt: #4a3218;
  --felt-light: #6b4a22;
  --accent: #d4a017;
  --accent-dark: #a67d12;
  --danger: #c0392b;
  --danger-dark: #922b21;
  --info: #4a90a4;
  --info-dark: #34677a;
  --text: #f5e6c8;
  --text-dim: #b39b72;
  --border: #3d2a14;
  --bg-glow: #33220f;
  --on-accent: #221604;
  --on-felt-light: #f5e6c8;
  --font-family: Copperplate, Rockwell, Georgia, serif;
}

:root[data-theme="rock-and-roll"] {
  --bg: #0a0a0a;
  --bg-panel: #141414;
  --bg-card: #1c1c1c;
  --felt: #2b2b2b;
  --felt-light: #3d3d3d;
  --accent: #e10600;
  --accent-dark: #a80400;
  --danger: #ff3b3b;
  --danger-dark: #c92a2a;
  --info: #d9d9d9;
  --info-dark: #a6a6a6;
  --text: #f5f5f5;
  --text-dim: #b0b0b0;
  --border: #3d3d3d;
  --bg-glow: #1c1c1c;
  --on-accent: #fff5f5;
  --on-felt-light: #f5f5f5;
  --font-family: Impact, "Arial Black", Arial, sans-serif;
  /* Concert-stage spotlights - overlapping colored light cones beaming
     down from the rig onto a dark stage floor, instead of a flat black
     backdrop. */
  --body-surface: radial-gradient(circle at 18% 0%, rgba(255, 255, 255, 0.32) 0%, rgba(255, 255, 255, 0) 24%),
    radial-gradient(circle at 50% -8%, rgba(225, 6, 0, 0.42) 0%, rgba(225, 6, 0, 0) 30%),
    radial-gradient(circle at 82% 0%, rgba(124, 77, 255, 0.32) 0%, rgba(124, 77, 255, 0) 26%),
    radial-gradient(circle at 35% 100%, rgba(0, 180, 255, 0.16) 0%, rgba(0, 180, 255, 0) 35%),
    radial-gradient(ellipse at top, #2b2b2b 0%, #0a0a0a 60%);
  --panel-surface: radial-gradient(circle at 25% 0%, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0) 42%),
    radial-gradient(circle at 85% 100%, rgba(225, 6, 0, 0.14) 0%, rgba(225, 6, 0, 0) 45%),
    linear-gradient(var(--bg-panel), var(--bg-panel));
  --btn-primary-surface: radial-gradient(circle at 30% 20%, rgba(255, 255, 255, 0.55) 0%, rgba(255, 255, 255, 0) 45%),
    linear-gradient(145deg, #ff3b3b 0%, #e10600 50%, #7a0300 100%);
  --modal-surface: radial-gradient(circle at 50% 0%, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 40%),
    radial-gradient(circle at 12% 100%, rgba(124, 77, 255, 0.16) 0%, rgba(124, 77, 255, 0) 45%),
    radial-gradient(circle at 88% 100%, rgba(225, 6, 0, 0.16) 0%, rgba(225, 6, 0, 0) 45%),
    linear-gradient(180deg, #3d3d3d 0%, #2b2b2b 60%, #1c1c1c 100%);
}

:root[data-theme="daybreak-chalk"] {
  --bg: #f5f1e8;
  --bg-panel: #ffffff;
  --bg-card: #ffffff;
  --felt: #e8dcc0;
  --felt-light: #f3ead2;
  --accent: #c1440e;
  --accent-dark: #963309;
  --danger: #d33a3a;
  --danger-dark: #a52c2c;
  --info: #2a6f97;
  --info-dark: #1d4f6d;
  --text: #2b241a;
  --text-dim: #7a6f5c;
  --border: #d8cba8;
  --bg-glow: #efe3c4;
  --on-accent: #fff8ef;
  --graph-bg: rgba(193, 68, 14, 0.08);
  --font-family: "Avenir Next", "Century Gothic", "Trebuchet MS", sans-serif;
  color-scheme: light;
}

:root[data-theme="pearl-lounge"] {
  --bg: #f4f2f6;
  --bg-panel: #ffffff;
  --bg-card: #ffffff;
  --felt: #e4dde8;
  --felt-light: #eee8f2;
  --accent: #6b2d5c;
  --accent-dark: #4d1f42;
  --danger: #b3364f;
  --danger-dark: #8a2740;
  --info: #4a5a8f;
  --info-dark: #34406b;
  --text: #2a2430;
  --text-dim: #7d7284;
  --border: #d9d0e0;
  --bg-glow: #ece5f0;
  --on-accent: #fbf8fc;
  --graph-bg: rgba(107, 45, 92, 0.08);
  --font-family: "Palatino Linotype", "Book Antiqua", Georgia, serif;
  color-scheme: light;
}

:root[data-theme="sunrise-glow"] {
  --bg: #fff3ea;
  --bg-panel: #ffffff;
  --bg-card: #fff8f2;
  --felt: #ffd9b8;
  --felt-light: #ffe8cf;
  --accent: #ff7e5f;
  --accent-dark: #cc6349;
  --danger: #e0524f;
  --danger-dark: #ad3d3b;
  --info: #4a90c2;
  --info-dark: #356b91;
  --text: #3a2a20;
  --text-dim: #8a6f5c;
  --border: #f0c9a0;
  --bg-glow: #ffe4c4;
  --on-accent: #fff8ef;
  --on-felt-light: #3a2a20;
  --graph-bg: rgba(255, 126, 95, 0.08);
  --font-family: Avenir, "Century Gothic", "Trebuchet MS", sans-serif;
  color-scheme: light;
}

:root[data-theme="stainless-steel"] {
  --bg: #c7ced3;
  --bg-panel: #e6e9eb;
  --bg-card: #f4f6f7;
  --felt: #9aa4ac;
  --felt-light: #c7ced3;
  --accent: #3b6e8f;
  --accent-dark: #24455a;
  --danger: #b83232;
  --danger-dark: #8c2525;
  --info: #5a7d99;
  --info-dark: #3f5a70;
  --text: #1c2226;
  --text-dim: #4c5860;
  --border: #8b969e;
  --bg-glow: #eef1f2;
  --on-accent: #f4f8fa;
  --on-felt-light: #1c2226;
  --graph-bg: rgba(59, 110, 143, 0.08);
  --font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  color-scheme: light;
  /* Diagonal brushed-steel streaks - alternating light/dark bands like
     light raking across a photographed sheet of stainless, rather than
     a flat "metallic-sounding" gray. */
  --body-surface: linear-gradient(
    115deg,
    #aab3ba 0%,
    #e4e8eb 12%,
    #ffffff 22%,
    #c2cad0 32%,
    #8f9aa2 44%,
    #d8dde1 56%,
    #ffffff 68%,
    #b2bac0 80%,
    #e8ecef 92%,
    #ced5da 100%
  );
  --panel-surface: linear-gradient(135deg, #f4f6f7 0%, #ffffff 16%, #c9d0d5 36%, #eef1f3 56%, #ffffff 74%, #b7bfc5 100%);
  --btn-primary-surface: linear-gradient(150deg, #1c4258 0%, #3b6e8f 25%, #a9d3ea 50%, #3b6e8f 75%, #1c4258 100%);
  --modal-surface: linear-gradient(160deg, #f4f6f7 0%, #ffffff 16%, #c2cad0 40%, #eef1f3 60%, #ffffff 80%, #aab3ba 100%);
}

:root[data-theme="copper-shine"] {
  --bg: #4a2c14;
  --bg-panel: #5c3a1c;
  --bg-card: #6b4423;
  --felt: #8a5a2e;
  --felt-light: #a56f3a;
  --accent: #d99a55;
  --accent-dark: #a97a3f;
  --danger: #d9432e;
  --danger-dark: #a3311f;
  --info: #6fb8b0;
  --info-dark: #4f8f88;
  --text: #fbe8d8;
  --text-dim: #f4e3cc;
  --border: #7a4e28;
  --bg-glow: #6b4423;
  --on-accent: #2a1608;
  --on-felt-light: #2a1608;
  --graph-bg: rgba(217, 154, 85, 0.1);
  /* Verdana's wide letterforms and tall x-height read far more clearly
     than a classic serif over a warm, busy gradient background. */
  --font-family: Verdana, Tahoma, "Segoe UI", sans-serif;
  color-scheme: dark;
  /* Warm copper glow - gentle highlight bands over a mid-tone patina
     base. --text/--text-dim sit directly on body-surface (the header's
     ghost buttons and title have no background of their own) and on
     panel-surface/modal-surface (regular panel/card content), so every
     stop here is capped at #8a5a2e or darker - light enough to still
     read as a warm copper shimmer, dark enough to keep text legible
     everywhere the gradient shows through (checked against WCAG's 4.5:1
     minimum). The brighter highlight band is reserved for
     btn-primary-surface, where the text sitting on it (--on-accent) is
     dark, not light, so it can safely go all the way up to a bright
     highlight without the same problem. */
  --body-surface: linear-gradient(
    120deg,
    #5c3a1c 0%,
    #7a4e28 16%,
    #8a5a2e 28%,
    #6b4423 42%,
    #855530 56%,
    #7a4e28 70%,
    #8a5a2e 84%,
    #5c3a1c 100%
  );
  --panel-surface: linear-gradient(140deg, #6b4423 0%, #8a5a2e 28%, #7a4e28 52%, #855530 76%, #5c3a1c 100%);
  --btn-primary-surface: linear-gradient(150deg, #a56f3a 0%, #c99356 25%, #e6b878 50%, #c99356 75%, #a56f3a 100%);
  --modal-surface: linear-gradient(160deg, #6b4423 0%, #8a5a2e 24%, #7a4e28 48%, #855530 72%, #5c3a1c 100%);
}

/* --text/--text-dim were re-tuned against body/panel/modal-surface
   above (see the comment there), but semantic text colors that also
   render directly on those same gradients - --accent (player-name
   links), --danger, --info - can't be individually re-tuned without
   either clashing with the theme's own hue or losing their meaning
   across every other theme. A soft dark halo, inherited onto every
   piece of text in this theme instead, gives all of them a consistent
   legibility boost regardless of which exact gradient stop sits behind
   them. Elements with their own text-shadow (e.g. .stat-value) already
   override this locally, so it only fills the gap. */
:root[data-theme="copper-shine"] body {
  text-shadow: 0 1px 3px rgba(20, 10, 2, 0.65);
}

:root[data-theme="opal-shimmer"] {
  --bg: #f3f0f5;
  --bg-panel: #faf8fb;
  --bg-card: #ffffff;
  --felt: #e0d8ea;
  --felt-light: #f0e8f5;
  --accent: #8a4fb8;
  --accent-dark: #6b3a94;
  --danger: #d9587a;
  --danger-dark: #a83f5c;
  --info: #3f8fae;
  --info-dark: #2c6a84;
  --text: #2e2438;
  --text-dim: #6d6080;
  --border: #ddd0e6;
  --bg-glow: #efe6f5;
  --on-accent: #fdfaff;
  --on-felt-light: #2e2438;
  --graph-bg: rgba(138, 79, 184, 0.08);
  --font-family: "Palatino Linotype", "Book Antiqua", Georgia, serif;
  color-scheme: light;
  /* A soft pearly base with a diagonal sweep of pastel hues - the closest
     a flat CSS gradient gets to opal's shifting rainbow play-of-color.
     The button additionally animates its own gradient position (see the
     opal-shimmer-shift keyframes below) for real movement/shimmer. */
  --body-surface: linear-gradient(
    120deg,
    #e8f5f0 0%,
    #f5e8f0 14%,
    #e8ecf7 28%,
    #fdf3e0 42%,
    #f0e0f5 56%,
    #e0f0ef 70%,
    #f7e8ec 84%,
    #eef0f9 100%
  );
  --panel-surface: linear-gradient(135deg, #ffffff 0%, #eef7f2 16%, #f7ecf5 32%, #ecf0fb 48%, #fdf6e8 64%, #f3e8f7 80%, #ffffff 100%);
  --btn-primary-surface: linear-gradient(150deg, #6b3d8c 0%, #a874c7 20%, #5fa39d 40%, #b487c2 60%, #5f7cc2 80%, #6b3d8c 100%);
  --modal-surface: linear-gradient(160deg, #ffffff 0%, #f0f7f2 16%, #f7ecf5 34%, #ecf0fb 52%, #fdf6e8 70%, #f3e8f7 88%, #ffffff 100%);
}

@media (prefers-reduced-motion: no-preference) {
  :root[data-theme="opal-shimmer"] .btn-primary {
    background-size: 250% 250%;
    animation: opal-shimmer-shift 9s ease-in-out infinite;
  }
}

@keyframes opal-shimmer-shift {
  0%,
  100% {
    background-position: 0% 50%;
  }
  50% {
    background-position: 100% 50%;
  }
}

:root[data-theme="flowery-forest"] {
  --bg: #f6f8f0;
  --bg-panel: #fbfcf7;
  --bg-card: #ffffff;
  --felt: #dce8d0;
  --felt-light: #ecf3e2;
  --accent: #7a9d5c;
  --accent-dark: #5c7a42;
  --danger: #d97a7a;
  --danger-dark: #b05a5a;
  --info: #7aa8c4;
  --info-dark: #5a86a3;
  --text: #33402a;
  --text-dim: #71805f;
  --border: #d4e0c5;
  --bg-glow: #eef4e5;
  --on-accent: #f8fbf3;
  --on-felt-light: #33402a;
  --graph-bg: rgba(122, 157, 92, 0.08);
  --font-family: Garamond, Baskerville, Georgia, serif;
  color-scheme: light;
  /* Soft blooms of leaf-green and flower-blush over a pale canopy-light
     wash - delicate rather than literal branch/leaf line art, since a
     flat gradient can't draw shapes, but the same dappled-light feeling. */
  --body-surface: radial-gradient(circle at 15% 8%, rgba(163, 201, 124, 0.35) 0%, rgba(163, 201, 124, 0) 30%),
    radial-gradient(circle at 85% 14%, rgba(255, 214, 224, 0.4) 0%, rgba(255, 214, 224, 0) 25%),
    radial-gradient(circle at 22% 86%, rgba(200, 225, 170, 0.35) 0%, rgba(200, 225, 170, 0) 32%),
    radial-gradient(circle at 78% 92%, rgba(255, 232, 214, 0.3) 0%, rgba(255, 232, 214, 0) 28%),
    radial-gradient(circle at 50% 50%, rgba(236, 243, 226, 0.5) 0%, rgba(236, 243, 226, 0) 60%),
    linear-gradient(180deg, #eef4e5 0%, #f6f8f0 100%);
  --panel-surface: radial-gradient(circle at 8% 0%, rgba(163, 201, 124, 0.18) 0%, rgba(163, 201, 124, 0) 38%),
    radial-gradient(circle at 92% 100%, rgba(255, 214, 224, 0.22) 0%, rgba(255, 214, 224, 0) 42%),
    linear-gradient(var(--bg-panel), var(--bg-panel));
  --btn-primary-surface: radial-gradient(circle at 30% 20%, rgba(255, 255, 255, 0.5) 0%, rgba(255, 255, 255, 0) 45%),
    linear-gradient(145deg, #9dc47a 0%, #7a9d5c 55%, #5c7a42 100%);
  --modal-surface: radial-gradient(circle at 12% 0%, rgba(163, 201, 124, 0.25) 0%, rgba(163, 201, 124, 0) 42%),
    radial-gradient(circle at 88% 100%, rgba(255, 214, 224, 0.28) 0%, rgba(255, 214, 224, 0) 44%),
    linear-gradient(160deg, #ffffff 0%, #f3f8ed 50%, #eef4e5 100%);
}

:root[data-theme="blackout-contrast"] {
  --bg: #000000;
  --bg-panel: #0a0a0a;
  --bg-card: #000000;
  --felt: #000000;
  --felt-light: #1a1a1a;
  --accent: #ffee00;
  --accent-dark: #ccbe00;
  --danger: #ff1744;
  --danger-dark: #c4001d;
  --info: #00e5ff;
  --info-dark: #00acc1;
  --text: #ffffff;
  --text-dim: #cccccc;
  --border: #595959;
  --bg-glow: #000000;
  --on-felt-light: #ffffff;
  --font-family: "Arial Black", Arial, "Helvetica Neue", sans-serif;
  color-scheme: dark;
}

:root[data-theme="paper-contrast"] {
  --bg: #ffffff;
  --bg-panel: #ffffff;
  --bg-card: #ffffff;
  --felt: #ffffff;
  --felt-light: #f0f0f0;
  --accent: #0047ab;
  --accent-dark: #002d6b;
  --danger: #d90000;
  --danger-dark: #a30000;
  --info: #005f99;
  --info-dark: #003f66;
  --text: #000000;
  --text-dim: #4d4d4d;
  --border: #000000;
  --bg-glow: #f0f0f0;
  --on-accent: #ffffff;
  --graph-bg: rgba(0, 71, 171, 0.08);
  --font-family: "Arial Black", Arial, "Helvetica Neue", sans-serif;
  color-scheme: light;
}

* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

/* min-height, not height: a themed body background (see --body-surface)
   is painted once across body's own box at its natural size - if body
   were capped at exactly one viewport tall while real page content
   (the common case - rosters, game history, brackets) runs taller, the
   background would need to repeat down the extra length, and a
   multi-stop/diagonal gradient's top and bottom edges rarely match,
   producing a visible seam at the repeat boundary. Letting body grow
   with its content instead means there's only ever one full-length
   paint, so nothing needs to tile. */
html, body {
  min-height: 100%;
}

body {
  margin: 0;
  background: var(--body-surface);
  color: var(--text);
  font-family: var(--font-family);
  -webkit-font-smoothing: antialiased;
  touch-action: manipulation;
  overscroll-behavior-y: contain;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

#app {
  max-width: 900px;
  margin: 0 auto;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.hidden {
  display: none !important;
}

/* ---------- Header ---------- */

.app-header {
  padding: max(16px, env(safe-area-inset-top)) 16px 8px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.theme-select-row,
.language-select-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
  color: var(--text-dim);
  background: var(--bg-panel);
  padding: 6px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  min-width: 0;
}

.theme-select-row select,
.language-select-row select {
  font-size: 0.78rem;
  padding: 4px 8px;
  max-width: 120px;
  min-width: 0;
  background: var(--bg-panel);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
}

@media (max-width: 480px) {
  .theme-select-row label,
  .language-select-row label {
    display: none;
  }

  .theme-select-row select,
  .language-select-row select {
    max-width: 30vw;
  }
}

.all-players-nav-btn {
  font-size: 0.85rem;
  padding: 8px 14px;
}

.app-header h1 {
  margin: 0;
  font-size: 1.6rem;
  letter-spacing: 0.02em;
}

.focus-toggle-btn {
  display: block;
  margin: 4px auto 12px;
  font-size: 0.85rem;
  padding: 8px 14px;
}

#app.focus-mode .panel {
  display: none;
}

/* Focus Mode is a clean, read-from-across-the-room scoreboard - the
   logo/title, nav buttons, and theme/language/no-stats setup controls
   have no place in it, and with all of those gone the header itself
   has nothing left to show. */
#app.focus-mode .app-header {
  display: none;
}

#app.focus-mode .no-stats-row-inline {
  display: none;
}

#app.focus-mode #scoreboard-panel {
  padding-top: 8px;
}

/* Focus Mode is meant to be read from across the room, so the live score
   numbers scale up - up to 3x their normal size - the moment a card has
   the room to spare. Sized off the card's own width (container query
   units) rather than a fixed multiplier or a player-count check, so it
   naturally scales back down on its own once enough players are on
   screen that each card gets narrower - no js layout math needed. */
#app.focus-mode .player-panel,
#app.focus-mode .member-card {
  container-type: inline-size;
}

#app.focus-mode .stat-value {
  font-size: clamp(3rem, 24cqw, 9rem);
}

#app.focus-mode .stat-value.small {
  font-size: clamp(1.8rem, 14cqw, 5.4rem);
}

/* Names need to read from across the room same as the score itself -
   same container-query scaling as .stat-value above, just a smaller
   share of the card's width since the score is still the main event. */
#app.focus-mode .player-name {
  font-size: clamp(1.8rem, 13cqw, 4.5rem);
}

#app.focus-mode .member-name {
  font-size: clamp(1.3rem, 9cqw, 3rem);
}

/* Same across-the-room readability as the score itself while in Focus
   Mode - #scoreboard-panel has no container-query width to key off of
   (unlike the per-card rules above), so this scales off viewport width
   instead. */
#app.focus-mode .shot-counter-widget {
  font-size: clamp(3rem, 11vw, 5.5rem);
}

/* The keypad shortcut number needs the same across-the-room readability
   as everything else in Focus Mode - scales with the card same as
   .stat-value above, floored well above the normal-mode 24px minimum. */
#app.focus-mode .keypad-number-badge {
  width: clamp(42px, 12cqw, 64px);
  height: clamp(42px, 12cqw, 64px);
  font-size: clamp(24px, 8cqw, 40px);
  top: 10px;
  left: 10px;
}

#app.focus-mode .player-panel:has(.keypad-number-badge:not(:empty)),
#app.focus-mode .member-card:has(.keypad-number-badge:not(:empty)) {
  padding-top: 76px;
}

.no-stats-row-inline {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin: 0 auto 12px;
  font-size: 0.85rem;
  color: var(--text-dim);
  cursor: pointer;
}

.no-stats-row-inline input {
  cursor: pointer;
}

.no-stats-row-inline:has(input:checked) {
  color: var(--accent);
  font-weight: 600;
}

.focus-players-wrap {
  display: none;
}

#app.focus-mode .focus-players-wrap {
  display: block;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 14px;
  margin: 0 auto 10px;
  max-width: 480px;
}

.focus-players-wrap .roster-list {
  margin-top: 10px;
}

.brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
}

.brand-logo {
  width: 52px;
  height: 52px;
  flex-shrink: 0;
}

.logo-badge-ring {
  fill: none;
  stroke: var(--accent);
  stroke-width: 3;
}

.logo-tail-1 {
  fill: var(--felt-light);
}

.logo-tail-2 {
  fill: var(--accent-dark);
}

.logo-tail-3,
.logo-body,
.logo-head {
  fill: var(--accent);
}

.logo-comb,
.logo-wattle {
  fill: var(--felt-light);
}

.logo-beak,
.logo-eye {
  fill: #200606;
}

.brand-text {
  text-align: left;
}

.brand-slogan {
  margin: 2px 0 0;
  font-size: 0.8rem;
  font-style: italic;
  font-weight: 600;
  color: var(--accent);
  letter-spacing: 0.02em;
}

/* ---------- Buttons ---------- */

button {
  font: inherit;
  border: none;
  border-radius: 10px;
  padding: 12px 16px;
  cursor: pointer;
  user-select: none;
  touch-action: manipulation;
  transition: transform 0.06s ease, filter 0.15s ease;
}

button:active {
  transform: scale(0.96);
}

.btn-primary {
  background: var(--btn-primary-surface);
  color: var(--on-accent);
  font-weight: 700;
}

.btn-primary:hover { filter: brightness(1.08); }

.btn-ghost {
  background: transparent;
  color: var(--text-dim);
  border: 1px solid var(--border);
}

.btn-ghost:hover {
  color: var(--text);
  border-color: var(--text-dim);
}

.btn-danger {
  background: var(--danger);
  color: #fff;
  font-weight: 600;
}

.btn-danger:hover { filter: brightness(1.1); }

.full-reset-block {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px dashed var(--danger);
}

.btn-full-reset {
  background: #1a0000;
  color: #ff6b6b;
  border: 2px solid var(--danger);
}

.btn-full-reset:hover {
  background: var(--danger);
  color: #fff;
  filter: none;
}

button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  transform: none;
}

/* ---------- Panels ---------- */

.panel {
  background: var(--panel-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin: 8px 16px;
  /* #app is a column flexbox, so every panel is a flex item with a
     default min-width of "auto" (i.e. its content's own min-content
     width) - wide unconstrained content (like a many-round bracket
     tree) can silently push the whole page wider than the viewport
     that way, bypassing any overflow-x:auto set on something inside
     the panel entirely, since the overflow never happens there in the
     first place. This lets the panel actually shrink to the viewport,
     so that inner scrolling can do its job. Same reasoning applies on
     the vertical axis: a panel's default min-height is likewise its
     own content's min-content height, which can similarly stop the
     page from growing/scrolling past a panel whose content has grown
     taller than the viewport (Focus Mode's Team view, with several
     member cards stacked at its across-the-room font sizes, is exactly
     that case) instead of just flowing past it normally. */
  min-width: 0;
  min-height: 0;
}

.panel h2 {
  margin: 0 0 12px;
  font-size: 1.05rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.collapsible-panel .panel-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  background: none;
  border: none;
  padding: 0;
  margin: 0 0 12px;
  cursor: pointer;
  font: inherit;
  color: inherit;
  text-align: left;
}

.collapsible-panel .panel-toggle h2 {
  margin: 0;
}

.panel-chevron {
  display: inline-flex;
  color: var(--text-dim);
  line-height: 1;
  flex-shrink: 0;
  margin-left: 12px;
  transition: transform 0.2s ease;
}

.panel-chevron svg {
  display: block;
}

.collapsible-panel.collapsed .panel-toggle {
  margin-bottom: 0;
}

/* Bigger and visually bolder while a panel is closed — a collapsed
   panel's chevron is the only hint that there's more inside, so it
   needs to read clearly at a glance, not just rotate. */
.collapsible-panel.collapsed .panel-chevron {
  transform: rotate(-90deg) scale(1.45);
}

.collapsible-panel.collapsed .panel-body {
  display: none;
}

.panel-summary {
  display: none;
  color: var(--text-dim);
  font-size: 0.85rem;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.collapsible-panel.collapsed .panel-summary {
  display: block;
  cursor: pointer;
}

.row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}

.row:last-child {
  margin-bottom: 0;
}

.row label {
  color: var(--text-dim);
  font-size: 0.85rem;
}

input[type="text"], input[type="number"], input[type="email"], input[type="tel"], select {
  font: inherit;
  background: var(--bg-card);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 10px;
  padding: 12px;
  min-height: 44px;
}

input[type="text"], input[type="email"], input[type="tel"] {
  flex: 1;
  min-width: 140px;
  font-size: 1.05rem;
}

/* A phone/email field that isn't empty but also isn't a real, complete
   phone number or email address (see isValidPhoneNumber/isValidEmail) -
   checked live as it's typed, not just when something tries to save
   it. */
input.field-invalid {
  border-color: var(--danger);
  box-shadow: 0 0 0 1px var(--danger);
}

.day-notes-textarea {
  font: inherit;
  background: var(--bg-card);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 10px;
  padding: 12px;
  width: 100%;
  resize: vertical;
  margin-bottom: 10px;
}

select {
  flex: 1;
  min-width: 140px;
}

input[type="number"] {
  width: 80px;
}

.mode-row label {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 140px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  cursor: pointer;
}

.mode-row input[type="radio"] {
  accent-color: var(--accent);
}

.checkbox-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  cursor: pointer;
  color: var(--text-dim);
}

.checkbox-row input[type="checkbox"] {
  width: 20px;
  height: 20px;
  accent-color: var(--accent);
  flex: 0 0 auto;
}

/* ---------- Game rotation ---------- */

.rotation-list {
  list-style: none;
  margin: 0 0 10px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.rotation-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 10px;
}

.rotation-position {
  font-weight: 800;
  color: var(--accent);
  width: 22px;
  text-align: center;
  flex: 0 0 auto;
}

.rotation-name {
  flex: 1 1 auto;
  min-width: 70px;
  font-weight: 600;
}

.rotation-target-input {
  flex: 0 0 auto;
  width: 60px;
  text-align: center;
}

.rotation-unit-select {
  flex: 0 0 auto;
  width: 80px;
}

.rotation-controls {
  display: flex;
  gap: 4px;
}

.rotation-controls button {
  width: 34px;
  height: 34px;
  padding: 0;
  font-size: 0.9rem;
  background: var(--bg-panel);
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: 8px;
}

.rotation-status {
  color: var(--text-dim);
  font-size: 0.85rem;
  margin: 4px 0 0;
}

.rotation-status.is-warning {
  color: var(--danger);
  font-weight: 600;
}

.rotation-status strong {
  color: var(--accent);
}

/* ---------- Roster ---------- */

.roster-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.roster-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
}

.roster-row.is-playing {
  border-color: var(--felt-light);
}

.roster-name {
  flex: 1;
  min-width: 90px;
  font-weight: 600;
}

.player-name-translated,
.player-name-real {
  font-size: 0.8em;
  font-weight: 400;
  opacity: 0.65;
  margin-left: 4px;
}

.player-name-edit-btn {
  background: none;
  border: none;
  padding: 0 4px;
  font-size: 0.75em;
  cursor: pointer;
  opacity: 0.6;
}

.player-name-edit-btn:hover {
  opacity: 1;
}

/* A flat 30%-black wash read fine on the original dark felt themes,
   but on any light theme it turns into a muddy mid-gray chip that
   --accent text can't reliably read against (and even on a dark theme,
   a fixed black doesn't necessarily suit an unrelated hue like Opal's
   purple or Copper's warm brown). A tint of the theme's own accent
   color into its own panel color instead always stays light on light
   themes and dark on dark ones, the same relationship --accent-as-text
   already relies on everywhere else it's used (player-name links,
   leaderboard rows, etc.) without a contrast problem. */
.rating-badge {
  display: inline-block;
  margin-left: 7px;
  padding: 2px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 18%, var(--bg-panel));
  border: 1px solid var(--border);
  color: var(--accent);
  font-size: 0.95rem;
  font-weight: 700;
  vertical-align: middle;
  font-variant-numeric: tabular-nums;
}

/* A persistent club/squad tag (see buildClubTeamBadge) - deliberately
   styled in --info rather than --accent so it reads as a distinct kind
   of badge from the rating right next to it. Dashed and dimmer when
   unset, as an "add one" invitation rather than looking like a bug. */
.club-team-badge {
  display: inline-block;
  margin-left: 7px;
  padding: 2px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--info) 18%, var(--bg-panel));
  border: 1px solid var(--border);
  color: var(--info);
  font-size: 0.85rem;
  font-weight: 700;
  vertical-align: middle;
}

.club-team-badge.is-unset {
  background: transparent;
  border-style: dashed;
  color: var(--text-dim);
  font-weight: 500;
  opacity: 0.8;
}

.btn-playing {
  background: var(--bg-panel);
  color: var(--text-dim);
  border: 1px solid var(--border);
  font-size: 0.78rem;
  padding: 8px 12px;
}

.btn-playing.is-on {
  background: var(--felt-light);
  color: var(--on-felt-light);
  border-color: var(--felt-light);
  font-weight: 700;
}

.team-toggle {
  display: flex;
  gap: 4px;
}

.team-toggle button {
  width: 34px;
  height: 34px;
  padding: 0;
  font-weight: 700;
  background: var(--bg-panel);
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: 8px;
}

.team-toggle button.is-selected {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}

.roster-remove {
  background: transparent;
  color: var(--text-dim);
  border-radius: 50%;
  width: 32px;
  height: 32px;
  padding: 0;
  font-size: 1rem;
}

.roster-remove:hover {
  color: var(--danger);
}

.roster-stats-btn {
  background: transparent;
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: 50%;
  width: 32px;
  height: 32px;
  padding: 0;
  font-size: 0.95rem;
}

.roster-stats-btn:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.roster-edit-rating-btn {
  background: transparent;
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: 50%;
  width: 32px;
  height: 32px;
  padding: 0;
  font-size: 0.85rem;
}

.roster-edit-rating-btn:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.empty-hint {
  color: var(--text-dim);
  font-size: 0.9rem;
  padding: 4px 2px;
}

/* ---------- Win toast ---------- */

.win-toast {
  margin: 8px 16px;
  text-align: center;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 800;
  font-size: 1rem;
  padding: 12px;
  border-radius: 12px;
}

/* Same message, but for anywhere #app itself is hidden (All Players,
   Tournament, Player, Contact Sheet) - .win-toast lives inside #app and
   sits inline in its layout, so it's invisible whenever a full-screen
   page is showing instead. This is a fixed overlay, always on top,
   shown/hidden by the same showToast() based on which one is visible. */
.page-toast {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1400;
  max-width: min(92vw, 480px);
  text-align: center;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 800;
  font-size: 1rem;
  padding: 12px 18px;
  border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

/* ---------- Shot counter (all game types) ---------- */

/* Lives in the score-keeping area itself (#scoreboard-panel, right
   under the Now Playing/Duration line) rather than floating over the
   page, so it's part of the normal game view instead of a separate
   overlay someone has to notice - and, since #scoreboard-panel is
   exempt from Focus Mode's panel-hiding, it shows up there too with no
   extra wiring (see the #app.focus-mode override below for its
   across-the-room sizing there). Controlled by the numeric keypad (see
   handleKeypadShortcut), a tap/click (pause), or either Show/Hide
   Timer control (the Game Setup panel's, or the small persistent
   .shot-counter-visibility-toggle button right here in the scoreboard
   - see tickShotCounter, which keeps that one visible even while this
   widget itself is hidden, so hiding it never strands anyone without
   a way back). Sized with clamp() so it stays legible without
   overflowing a phone-width viewport. */
.shot-counter-widget {
  display: block;
  width: fit-content;
  margin: 4px auto 14px;
  background: rgba(0, 0, 0, 0.6);
  border: 2px solid rgba(255, 255, 255, 0.22);
  color: var(--text);
  padding: clamp(10px, 2.6vw, 20px) clamp(24px, 6vw, 44px);
  border-radius: 999px;
  font-size: clamp(2.4rem, 9vw, 3.8rem);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.03em;
  text-align: center;
  cursor: pointer;
}

/* Quick-access companion to the widget above - unlike it, this stays
   visible whenever the shot counter is enabled at all, regardless of
   the widget's own hidden state, so there's always a one-tap way back
   without digging into Game Setup. */
.shot-counter-visibility-toggle {
  display: block;
  width: 36px;
  height: 36px;
  margin: 4px auto 14px;
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--text-dim);
  cursor: pointer;
}

.shot-counter-visibility-toggle:hover {
  background: rgba(0, 0, 0, 0.5);
  color: var(--accent);
}

/* ---------- Standings ---------- */

#standings-panel h2:not(:first-child) {
  margin-top: 16px;
}

.standings-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.standings-row {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
}

.standings-row.is-reached {
  border-color: var(--accent);
}

.standings-row-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}

.standings-name {
  font-weight: 600;
  font-size: 0.92rem;
}

.standings-status {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  vertical-align: middle;
  background: var(--bg-panel);
  color: var(--text-dim);
  border: 1px solid var(--border);
}

.standings-status.is-playing {
  background: var(--felt-light);
  color: var(--on-felt-light);
  border-color: var(--felt-light);
  font-weight: 800;
}

.standings-count {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--accent);
  white-space: nowrap;
}

.standings-bar-track {
  height: 8px;
  border-radius: 999px;
  background: var(--bg-panel);
  overflow: hidden;
}

.standings-bar-fill {
  height: 100%;
  background: var(--felt-light);
  border-radius: 999px;
}

.is-reached .standings-bar-fill {
  background: var(--accent);
}

/* ---------- Scoreboard ---------- */

.scoreboard {
  flex: 1;
  /* A flex item's default min-height is "auto" (its own content's
     min-content size) - same class of bug already found and fixed once
     this session for .panel's horizontal equivalent (min-width). In
     Focus Mode's Team view specifically, member cards can grow quite
     tall (see the container-query font scaling below), and #app is
     itself a column flex container - without this, WebKit can refuse
     to let the page grow/scroll past #app's own box to reveal the rest
     of a tall team roster instead of just growing normally past one
     screen's height. */
  min-height: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 10px;
  padding: 8px 16px 20px;
  align-items: start;
}

.scoreboard.scoreboard-teams {
  grid-template-columns: 1fr 1fr;
}

@media (max-width: 620px) {
  .scoreboard.scoreboard-teams {
    grid-template-columns: 1fr;
  }
}

/* Phone view: two compact columns instead of one tall stack, so a
   typical roster fits without scrolling. Team mode stays single-column
   (its panels hold two nested member cards each, too tight to halve). */
@media (max-width: 480px) {
  .scoreboard:not(.scoreboard-teams) {
    grid-template-columns: repeat(auto-fit, minmax(142px, 1fr));
    gap: 8px;
    padding: 6px 10px 14px;
  }

  .player-panel {
    padding: 10px;
    gap: 6px;
    border-radius: 10px;
  }

  .player-name {
    font-size: 0.92rem;
  }

  .stat-mini {
    font-size: 11px;
  }

  .stat-label {
    font-size: 0.62rem;
  }

  .stat-value {
    font-size: 2.1rem;
  }

  .ball-controls {
    gap: 6px;
  }

  .btn-ball {
    min-height: 46px;
    font-size: 1.25rem;
    border-radius: 8px;
  }
}

.player-panel {
  position: relative;
  background: linear-gradient(180deg, var(--felt) 0%, var(--bg-glow) 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

/* ---------- Keyboard/keypad scoring shortcut ---------- */

.keypad-number-badge {
  position: absolute;
  top: 8px;
  left: 8px;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  border: 2px solid var(--accent);
  color: var(--accent);
  font-size: 24px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 1;
}

.keypad-number-badge:empty {
  display: none;
}

/* The badge sits absolutely positioned at top:8/left:8 at 42x42, so its
   bottom edge reaches 50px down from the card's top edge - when a real
   number is showing, push the whole card's content below that instead of
   letting the centered name text start right under (and behind) it. */
.player-panel:has(.keypad-number-badge:not(:empty)),
.member-card:has(.keypad-number-badge:not(:empty)) {
  padding-top: 52px;
}

.is-keypad-selected {
  box-shadow: 0 0 0 4px var(--accent) inset;
  background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 22%, var(--felt)) 0%, var(--bg-glow) 100%);
}

.is-keypad-selected .keypad-number-badge {
  background: var(--accent);
  color: var(--bg);
  border-color: var(--accent);
  transform: scale(1.1);
}

.player-name {
  font-size: 1.15rem;
  font-weight: 700;
  text-align: center;
  word-break: break-word;
}

.stat-mini {
  font-size: 14px;
  color: var(--text-dim);
  text-align: center;
}

.stat-mini.stat-mini-tourney {
  font-size: 24px;
  font-weight: 700;
}

.stat-mini strong {
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.stat-mini .flag {
  margin-left: 4px;
}

.stat-block {
  width: 100%;
  text-align: center;
}

.stat-label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-dim);
}

.stat-value {
  font-size: 3rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  color: var(--accent);
  text-shadow: 0 2px 5px rgba(0, 0, 0, 0.6);
}

.fair-race-note {
  text-align: center;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--text-dim);
  margin-top: 2px;
}

/* "🔥 N in a row" - shown only for whichever player currently has an
   active run (see buildRunStreakBadge). Bold and warm-colored so it
   reads at table distance, matching the app's across-the-room sizing. */
.run-streak-badge {
  display: block;
  width: max-content;
  max-width: 100%;
  text-align: center;
  font-size: 1.1rem;
  font-weight: 800;
  color: #fff;
  background: linear-gradient(135deg, #ff9a3c, #ff5e3a);
  border-radius: 999px;
  padding: 6px 16px;
  margin: 8px auto 0;
  animation: milestone-pop 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.member-card .run-streak-badge {
  font-size: 0.85rem;
  padding: 4px 12px;
  margin-top: 6px;
}

.stat-value.small {
  font-size: 1.8rem;
}

.stat-value .flag {
  margin-left: 0.15em;
  font-size: 0.55em;
  vertical-align: middle;
}

.ball-controls {
  display: flex;
  gap: 10px;
  width: 100%;
}

.btn-ball {
  flex: 1;
  min-height: 64px;
  font-size: 1.6rem;
  font-weight: 800;
  border-radius: 12px;
}

.btn-ball.plus {
  background: var(--felt-light);
  color: var(--on-felt-light);
}

.btn-ball.minus {
  background: var(--bg-card);
  color: var(--text);
  border: 1px solid var(--border);
}

/* ---------- Quick Counter mode ---------- */

.quick-counter-name-row {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
}

.quick-counter-name-input {
  flex: 1;
  min-width: 0;
  text-align: center;
  font-size: 1.05rem;
  font-weight: 700;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 8px;
  color: var(--text);
}

.quick-counter-name-input:focus {
  outline: none;
  border-color: var(--accent);
}

.quick-counter-remove-player {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--danger);
  background: rgba(200, 60, 60, 0.15);
  color: var(--danger);
  font-size: 1.1rem;
  font-weight: 800;
  line-height: 1;
  cursor: pointer;
}

/* Quick Counter's player count is unpredictable (one tap can add or
   remove a card mid-session) and usually small, so - unlike the regular
   scoreboard's grid, which is meant to spread a full roster evenly -
   this stays a flex row of fixed-ish-width cards that wrap and stay
   centered as a group, rather than being stretched across the whole
   width or left stranded at the edge by the grid's own extra empty
   columns (which the full-width rows below kept "alive" even with only
   one or two actual player cards to show). */
.scoreboard.scoreboard-quick {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
}

.scoreboard.scoreboard-quick .quick-counter-panel {
  flex: 0 1 230px;
}

@media (max-width: 480px) {
  .scoreboard.scoreboard-quick .quick-counter-panel {
    flex-basis: 142px;
  }
}

.quick-counter-gametype-row {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
}

.quick-counter-gametype-row label {
  flex-shrink: 0;
  color: var(--text-dim);
}

.quick-counter-gametype-select {
  flex: 1;
  min-width: 140px;
}

.quick-counter-add-row {
  width: 100%;
  display: flex;
  gap: 10px;
  background: var(--bg-card);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 14px;
}

.quick-counter-add-input {
  flex: 1;
  min-width: 0;
}

.quick-counter-add-btn {
  flex-shrink: 0;
}

.quick-counter-load-row {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.quick-counter-load-select {
  flex: 1;
  min-width: 140px;
}

.quick-counter-load-btn {
  flex-shrink: 0;
}

.quick-counter-reset-btn {
  flex-shrink: 0;
  border-color: var(--danger);
  color: var(--danger);
}

/* ---------- Team scoreboard ---------- */

.team-panel {
  background: linear-gradient(180deg, var(--felt) 0%, var(--bg-glow) 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.team-name {
  font-size: 1rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
  text-align: center;
}

/* The club team's own name (see buildSharedTeamBanner) is the headline
   once two teammates sharing it are actually playing together - sized
   to read from well across a room, the same "big enough to matter"
   scale as .stat-value, not just a slightly-bolder label. */
.shared-team-banner {
  font-size: clamp(1.4rem, 5.5vw, 2.4rem);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--accent);
  text-align: center;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
  line-height: 1.1;
}

/* Same idea, applied to a tournament match side's own name (see
   buildTournamentSidePanel) instead of a separate banner element - it's
   already the one thing shown there, so it just needs to be bigger
   rather than duplicated. */
.player-name.shared-team-name {
  font-size: clamp(1.2rem, 4.5vw, 2rem);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

/* The individual member names listed under a tournament team's big name
   (see appendEntrantIdentity) - still secondary to the team name, but
   needs to be genuinely readable rather than fine print now that it
   sits under a large headline. */
.player-name.shared-team-name .tournament-match-team-members {
  font-size: 1.05rem;
  color: var(--text);
}

.team-needs-opponent-warning {
  width: 100%;
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid var(--danger);
  border-radius: 8px;
  padding: 8px 10px;
  color: var(--danger);
  font-size: 0.85rem;
  font-weight: 700;
  text-align: center;
}

.team-members {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.member-card {
  position: relative;
  width: 100%;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.member-name {
  font-size: 1.3rem;
  font-weight: 700;
  text-align: center;
}

.member-card .ball-controls {
  gap: 8px;
}

.member-card .btn-ball {
  min-height: 48px;
  font-size: 1.3rem;
}

/* ---------- History ---------- */

.history-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 260px;
  overflow-y: auto;
}

.history-list li {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  font-size: 0.88rem;
  color: var(--text-dim);
}

.history-list li strong {
  color: var(--text);
}

.history-date {
  display: block;
  font-size: 0.72rem;
  color: var(--text-dim);
  opacity: 0.8;
  margin-bottom: 2px;
}

.history-duration {
  display: block;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--accent);
  margin-bottom: 4px;
  font-variant-numeric: tabular-nums;
}

.history-winner {
  color: var(--accent);
}

.history-rating-delta {
  font-weight: 700;
  margin-left: 2px;
}

.history-rating-delta.is-up {
  color: #6fcf6f;
}

.history-rating-delta.is-down {
  color: var(--danger);
}

.history-skunk {
  font-weight: 700;
  color: var(--accent);
}

.history-race-win-item {
  border-color: var(--accent) !important;
  background: linear-gradient(180deg, rgba(232, 178, 60, 0.16) 0%, var(--bg-card) 70%) !important;
}

.history-race-banner {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px dashed rgba(232, 178, 60, 0.4);
  color: var(--accent);
  font-weight: 700;
  font-size: 0.92rem;
}

/* ---------- Per-game win popup ---------- */

.gamewin-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
}

/* Both the per-game win popup and the tournament-win popup are meant to be
   read from across the room, not squinted at up close — computer/tablet
   viewports get noticeably larger type via clamp() (the vw-based middle
   term settles near the upper bound on a roomy screen, backing off toward
   the lower bound only when the viewport genuinely can't fit it, e.g. a
   long player-name list). Phones get a different treatment entirely below:
   the dialog fills the whole screen instead of shrinking to a small
   centered card. max-height + overflow-y is the hard backstop for
   whatever clamp() can't shrink away, so the dialog never grows taller
   than the viewport. */
.gamewin-card {
  background: linear-gradient(180deg, var(--felt-light) 0%, var(--felt) 60%, var(--bg-glow) 100%);
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  max-width: min(94vw, 640px);
  max-height: 92vh;
  width: 100%;
  overflow-y: auto;
  padding: clamp(32px, 6vw, 56px) clamp(28px, 5vw, 48px);
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  animation: milestone-pop 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.gamewin-emoji {
  font-size: clamp(2.6rem, 7vw, 5.6rem);
  margin-bottom: 8px;
}

.gamewin-card h2 {
  margin: 0 0 10px;
  color: var(--accent);
  font-size: clamp(1.5rem, 4.5vw, 3.4rem);
}

.gamewin-card p {
  margin: 0 0 22px;
  font-size: clamp(1.08rem, 3vw, 2.3rem);
  font-weight: 600;
}

.gamewin-details {
  text-align: left;
  margin-bottom: 20px;
  max-height: 48vh;
  overflow-y: auto;
}

.gamewin-details .player-stats-row {
  background: rgba(0, 0, 0, 0.3);
}

.gamewin-card .btn-primary {
  width: 100%;
}

.gamewin-quick-actions {
  display: flex;
  gap: 10px;
  margin-bottom: 14px;
}

.gamewin-quick-actions .btn {
  flex: 1;
}

/* Read-only badge (see buildSkunkIndicator/updateGamewinSkunkIndicator)
   - hidden by default, shown the instant the game is known to be a
   skunk (immediately for points/balls-unit wins, or live as soon as
   Balls Left on the Table hits 7 for a rack-mode win). */
.gamewin-skunk-indicator {
  text-align: center;
  background: rgba(232, 178, 60, 0.16);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  color: var(--accent);
  font-weight: 800;
  font-size: 1.05rem;
  padding: 8px 12px;
  margin-top: 8px;
}

/* "Count this win for the team" checkbox (see buildCountsForTeamRow) -
   only shown for a solo win by someone with a persistent club team. */
.gamewin-counts-for-team-row {
  cursor: pointer;
  margin-top: 8px;
}

.gamewin-counts-for-team-row input[type="checkbox"] {
  width: 22px;
  height: 22px;
  accent-color: var(--accent);
  flex: 0 0 auto;
}

/* ---------- Milestone celebration popup ---------- */

.milestone-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
}

.milestone-card {
  background: linear-gradient(180deg, var(--felt-light) 0%, var(--felt) 60%, var(--bg-glow) 100%);
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  max-width: min(94vw, 640px);
  max-height: 92vh;
  width: 100%;
  overflow-y: auto;
  padding: clamp(20px, 3vw, 28px) clamp(18px, 3vw, 24px);
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  animation: milestone-pop 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
}

@keyframes milestone-pop {
  from {
    transform: scale(0.85);
    opacity: 0;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}

/* Deliberately much smaller than the gamewin card's win-flash text below
   (which only ever shows a line or two) - the milestone card can carry a
   full player list plus a multi-step game rotation, and at the old
   viewport-scaled sizes that content alone taller than most screens,
   forcing a scroll to see the Undo/Nice buttons at all. */
.milestone-emoji {
  font-size: clamp(1.8rem, 5vw, 2.6rem);
  margin-bottom: 6px;
}

.milestone-card h2 {
  margin: 0 0 8px;
  color: var(--accent);
  font-size: clamp(1.3rem, 3vw, 1.7rem);
}

.milestone-card p {
  margin: 0 0 14px;
  font-size: clamp(1rem, 2vw, 1.25rem);
  font-weight: 600;
}

.milestone-details {
  text-align: left;
  margin-bottom: 14px;
  max-height: 48vh;
  overflow-y: auto;
}

.milestone-details .player-stats-row {
  background: rgba(0, 0, 0, 0.3);
}

.milestone-card .btn-primary {
  width: 100%;
}

.gamewin-card .player-stats-row .label {
  font-size: clamp(0.9rem, 2vw, 1.8rem);
}

.milestone-card .player-stats-row .label {
  font-size: clamp(0.85rem, 1.4vw, 1rem);
}

.gamewin-card .player-stats-row .value {
  font-size: clamp(1.1rem, 2.4vw, 2.2rem);
}

.milestone-card .player-stats-row .value {
  font-size: clamp(0.95rem, 1.6vw, 1.15rem);
}

.gamewin-card .value-list {
  font-size: clamp(0.95rem, 2.2vw, 1.9rem);
}

.milestone-card .value-list {
  font-size: clamp(0.9rem, 1.5vw, 1.05rem);
}

.gamewin-card .btn-primary,
.gamewin-card .gamewin-quick-actions .btn {
  font-size: clamp(1.05rem, 2.6vw, 2.1rem);
  padding: clamp(12px, 2vw, 22px);
}

.milestone-card .btn-primary,
.milestone-card .gamewin-quick-actions .btn {
  font-size: clamp(0.95rem, 1.6vw, 1.15rem);
  padding: clamp(10px, 1.2vw, 14px);
}

/* Phones: fill the whole screen instead of shrinking to a small centered
   card - there's no "far away" to read from on a phone, so more space for
   the content matters more than a floating dialog aesthetic. */
@media (max-width: 640px) {
  .gamewin-overlay,
  .milestone-overlay,
  .force-reset-overlay,
  .onhill-overlay {
    padding: 0;
  }

  .gamewin-card,
  .milestone-card,
  .force-reset-card,
  .onhill-card {
    max-width: 100vw;
    width: 100vw;
    height: 100vh;
    max-height: 100vh;
    border-width: 0;
    border-radius: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
}

.balls-left-row {
  align-items: center;
}

.balls-left-stepper {
  display: flex;
  align-items: center;
  gap: 8px;
}

.balls-left-btn {
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  border-radius: 50%;
  padding: 0;
  font-size: 1.1rem;
  font-weight: 800;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-card);
  color: var(--text);
  border: 1px solid var(--border);
}

.balls-left-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

.balls-left-value {
  min-width: 3ch;
  text-align: center;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.balls-left-input {
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  padding: 4px 6px;
  min-width: 9ch;
  -moz-appearance: textfield;
}

.balls-left-input::-webkit-outer-spin-button,
.balls-left-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.balls-left-input:focus {
  outline: none;
  border-color: var(--accent);
}

.gamewin-card .balls-left-row .label,
.milestone-card .balls-left-row .label {
  font-size: clamp(0.9rem, 2vw, 1.6rem);
}

.gamewin-card .balls-left-value,
.milestone-card .balls-left-value {
  font-size: clamp(1rem, 2.2vw, 1.8rem);
  min-width: 11ch;
}

.gamewin-card .balls-left-btn,
.milestone-card .balls-left-btn {
  width: clamp(30px, 5vw, 46px);
  height: clamp(30px, 5vw, 46px);
  font-size: clamp(1.1rem, 2.4vw, 1.8rem);
}

.player-game-log-balls-left {
  color: var(--text-dim);
}

/* ---------- Forced-reset confirmation popup ---------- */

.force-reset-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1300;
}

.force-reset-card {
  background: linear-gradient(180deg, var(--felt-light) 0%, var(--felt) 60%, var(--bg-glow) 100%);
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  max-width: min(94vw, 640px);
  max-height: 92vh;
  width: 100%;
  overflow-y: auto;
  padding: clamp(32px, 6vw, 56px) clamp(28px, 5vw, 48px);
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  animation: milestone-pop 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.force-reset-emoji {
  font-size: clamp(2.6rem, 7vw, 5.6rem);
  margin-bottom: 8px;
}

.force-reset-card h2 {
  margin: 0 0 10px;
  color: var(--accent);
  font-size: clamp(1.5rem, 4.5vw, 3.4rem);
}

.force-reset-card p {
  margin: 0 0 22px;
  font-size: clamp(1.08rem, 3vw, 2.3rem);
  font-weight: 600;
}

.force-reset-card .btn-primary {
  width: 100%;
  font-size: clamp(1.05rem, 2.6vw, 2.1rem);
  padding: clamp(12px, 2vw, 22px);
}

/* ---------- On-the-hill warning popup ---------- */

.onhill-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
}

/* Bigger than the shared gamewin/milestone/force-reset baseline on
   purpose - this one's a time-pressured "step up NOW" warning meant to be
   readable at a glance from across the table, and it auto-dismisses
   itself (see announceOnHill's 5s timer) so there's no cost to going
   large. */
.onhill-card {
  background: linear-gradient(180deg, var(--felt) 0%, var(--bg-glow) 100%);
  border: 2px solid var(--danger);
  border-radius: var(--radius);
  max-width: min(96vw, 900px);
  max-height: 92vh;
  /* svh ("small" viewport height) is the height guaranteed visible even
     with a mobile browser's address/toolbar chrome on screen - plain vh
     can be based on the chrome-collapsed viewport instead, so content
     sized against it can end up taller than what's actually visible
     until the user scrolls. Declared after the vh fallback so browsers
     without svh support just keep using that; ones that do support it
     override with the safer value. */
  max-height: calc(92svh - 8px);
  width: 100%;
  overflow-y: auto;
  padding: clamp(36px, 7vw, 64px) clamp(30px, 6vw, 56px);
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  animation: onhill-shake 0.4s ease;
}

@keyframes onhill-shake {
  0% { transform: scale(0.85) rotate(0deg); opacity: 0; }
  40% { transform: scale(1.03) rotate(-1deg); opacity: 1; }
  60% { transform: rotate(1deg); }
  100% { transform: scale(1) rotate(0deg); }
}

/* --onhill-scale (1 by default) is set by fitOnHillCardToViewport() -
   a long player-name list at this card's already-large baseline sizes
   can overflow even the svh-based max-height above on a short/narrow
   screen no matter how the clamp()s below are tuned (clamp only ever
   sees viewport size, never how much text there actually is), so once
   painted, JS measures the real overflow and shrinks every font on the
   card by the same factor until it actually fits (floored so text
   never goes illegibly small; scrolls the small remainder if it does). */
.onhill-emoji {
  font-size: calc(clamp(3.4rem, 10vw, 8rem) * var(--onhill-scale, 1));
  margin-bottom: 10px;
}

.onhill-card h2 {
  margin: 0 0 12px;
  color: var(--danger);
  font-size: calc(clamp(2rem, 7vw, 5rem) * var(--onhill-scale, 1));
}

.onhill-card p {
  margin: 0 0 26px;
  font-size: calc(clamp(1.3rem, 4.5vw, 3.2rem) * var(--onhill-scale, 1));
  font-weight: 600;
}

.onhill-card .btn-primary {
  width: 100%;
  background: var(--danger);
  color: #fff;
  font-size: calc(clamp(1.3rem, 3.2vw, 2.6rem) * var(--onhill-scale, 1));
  padding: clamp(14px, 2.4vw, 28px);
}

/* ---------- Game-change popup ---------- */

.gamechange-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
}

.gamechange-card {
  background: linear-gradient(180deg, var(--felt-light) 0%, var(--felt) 60%, var(--bg-glow) 100%);
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  padding: 32px 28px;
  max-width: 380px;
  width: 100%;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  animation: milestone-pop 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.gamechange-emoji {
  font-size: 2.6rem;
  margin-bottom: 8px;
}

.gamechange-card h2 {
  margin: 0 0 10px;
  color: var(--accent);
  font-size: 1.5rem;
}

.gamechange-card p {
  margin: 0 0 22px;
  font-size: 1.15rem;
  font-weight: 700;
}

.gamechange-card .btn-primary {
  width: 100%;
}

/* ---------- Save session before reset popup ---------- */

.save-session-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
}

.save-session-card {
  background: linear-gradient(180deg, var(--felt-light) 0%, var(--felt) 60%, var(--bg-glow) 100%);
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  padding: 32px 28px;
  max-width: 380px;
  width: 100%;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  animation: milestone-pop 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.save-session-emoji {
  font-size: 2.6rem;
  margin-bottom: 8px;
}

.save-session-card h2 {
  margin: 0 0 10px;
  color: var(--accent);
  font-size: 1.5rem;
}

.save-session-card p {
  margin: 0 0 22px;
  font-size: 1rem;
  font-weight: 600;
  color: var(--text-dim);
}

.save-session-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.save-session-actions .btn {
  width: 100%;
}

/* ---------- Manual rating adjustment popup ---------- */

.rating-edit-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
}

.rating-edit-card {
  background: linear-gradient(180deg, var(--felt-light) 0%, var(--felt) 60%, var(--bg-glow) 100%);
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  padding: 32px 28px;
  max-width: 380px;
  width: 100%;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  animation: milestone-pop 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.rating-edit-emoji {
  font-size: 2.6rem;
  margin-bottom: 8px;
}

.rating-edit-card h2 {
  margin: 0 0 10px;
  color: var(--accent);
  font-size: 1.5rem;
}

.rating-edit-player-name {
  margin: 0 0 12px;
  font-size: 1.1rem;
  font-weight: 700;
}

.rating-edit-warning {
  margin: 0 0 20px;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--text-dim);
  text-align: left;
  background: rgba(0, 0, 0, 0.3);
  border-radius: 10px;
  padding: 10px 12px;
}

.rating-edit-row {
  justify-content: center;
  margin-bottom: 20px;
}

.rating-edit-row input {
  max-width: 120px;
  text-align: center;
  font-weight: 700;
  font-size: 1.1rem;
}

.rating-edit-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.rating-edit-actions .btn {
  width: 100%;
}

.removed-players-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
}

.removed-players-card {
  background: linear-gradient(180deg, var(--felt-light) 0%, var(--felt) 60%, var(--bg-glow) 100%);
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  padding: 32px 28px;
  max-width: 480px;
  width: 100%;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  animation: milestone-pop 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.removed-players-card h2 {
  margin: 0 0 10px;
  color: var(--accent);
  font-size: 1.5rem;
}

.removed-players-explain {
  margin: 0 0 20px;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--text-dim);
  text-align: left;
  background: rgba(0, 0, 0, 0.3);
  border-radius: 10px;
  padding: 10px 12px;
}

/* ---------- Same-name import conflicts ---------- */

.player-conflict-list {
  list-style: none;
  margin: 0 0 16px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-height: 48vh;
  overflow-y: auto;
  text-align: left;
}

.player-conflict-row {
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
}

.player-conflict-row-name {
  font-weight: 800;
  margin-bottom: 4px;
}

.player-conflict-row-facts {
  color: var(--text-dim);
  font-size: 0.82rem;
  margin-bottom: 8px;
}

/* ---------- Recover Data (reset snapshots + comparison/recovery) ---------- */

.recover-data-list {
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.recover-data-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
}

.recover-data-row-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.recover-data-row-label {
  font-weight: 700;
}

.recover-data-row-meta {
  font-size: 0.82rem;
  color: var(--text-dim);
}

.report-archive-row-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

.recover-detail-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
}

.recover-detail-card {
  background: linear-gradient(180deg, var(--felt-light) 0%, var(--felt) 60%, var(--bg-glow) 100%);
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  padding: 28px 24px;
  max-width: 560px;
  width: 100%;
  max-height: 86vh;
  overflow-y: auto;
  text-align: left;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  animation: milestone-pop 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.recover-detail-card h2 {
  margin: 0 0 10px;
  color: var(--accent);
  font-size: 1.4rem;
  text-align: center;
}

.recover-detail-explain {
  margin: 0 0 18px;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--text-dim);
  background: rgba(0, 0, 0, 0.3);
  border-radius: 10px;
  padding: 10px 12px;
}

.recover-detail-card h3 {
  margin: 0 0 8px;
  font-size: 1rem;
  color: var(--accent);
}

.recover-games-checklist {
  list-style: none;
  margin: 0 0 16px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 200px;
  overflow-y: auto;
}

.recover-games-checklist label {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
  cursor: pointer;
  font-size: 0.88rem;
}

.recover-games-checklist input {
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
  flex-shrink: 0;
}

.recover-detail-card .rating-edit-actions {
  margin-top: 8px;
}

/* ---------- Generic modal alert/confirm/prompt (replaces native
   alert()/confirm()/prompt(), which pause the whole page and read as a
   browser chrome popup rather than part of the app) ---------- */

.confirm-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1200;
}

.confirm-modal-card {
  background: var(--modal-surface);
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  padding: 28px 24px;
  max-width: 400px;
  width: 100%;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  animation: milestone-pop 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.confirm-modal-title {
  margin: 0 0 10px;
  font-size: 1.15rem;
  color: var(--accent);
}

.confirm-modal-message {
  margin: 0 0 20px;
  font-size: 1rem;
  font-weight: 600;
  color: var(--text-dim);
  white-space: pre-line;
  overflow-y: auto;
}

/* Used for long multi-line dumps (e.g. the day report clipboard-copy
   fallback) instead of a short one-off confirmation sentence. */
.confirm-modal-message.is-long-text {
  text-align: left;
  font-size: 0.85rem;
  font-weight: 500;
}

.confirm-modal-input-row {
  justify-content: center;
  margin-bottom: 20px;
}

.confirm-modal-input-row input {
  width: 100%;
  text-align: center;
  font-weight: 700;
  font-size: 1.1rem;
}

.confirm-modal-actions {
  display: flex;
  flex-direction: column-reverse;
  gap: 10px;
}

.confirm-modal-actions .btn {
  width: 100%;
}

/* ---------- Delete by Dates ---------- */

.delete-by-date-card {
  max-width: 480px;
}

.delete-by-date-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin: 4px 0 10px;
}

.delete-by-date-nav-btn {
  width: 40px;
  height: 40px;
  padding: 0;
  font-size: 1.3rem;
  line-height: 1;
  flex-shrink: 0;
}

.delete-by-date-month-label {
  font-weight: 700;
  color: var(--on-felt-light);
  text-transform: capitalize;
}

.delete-by-date-calendar {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
  margin-bottom: 12px;
}

.delete-by-date-weekday {
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--on-felt-light);
  opacity: 0.7;
  text-align: center;
  padding: 4px 0;
}

.delete-by-date-day {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid transparent;
  background: none;
  color: var(--on-felt-light);
  opacity: 0.3;
  font: inherit;
  font-size: 0.85rem;
  padding: 0;
}

.delete-by-date-day.is-blank {
  visibility: hidden;
}

.delete-by-date-day.has-data {
  opacity: 1;
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  font-weight: 700;
}

.delete-by-date-day.is-today {
  box-shadow: 0 0 0 2px var(--accent-dark) inset;
}

.delete-by-date-day.is-selected {
  background: var(--danger);
  border-color: var(--danger);
  color: #fff;
}

.delete-by-date-selected-summary {
  margin: 0 0 16px;
  font-size: 0.85rem;
  color: var(--on-felt-light);
  opacity: 0.9;
}

/* ---------- Setup wizard ---------- */

.wizard-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
}

.wizard-card {
  position: relative;
  background: linear-gradient(180deg, var(--felt-light) 0%, var(--felt) 60%, var(--bg-glow) 100%);
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  padding: 28px 24px 20px;
  max-width: 520px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  animation: milestone-pop 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.onboarding-card {
  max-width: 460px;
  text-align: center;
}

.onboarding-card .wizard-header {
  justify-content: center;
}

.onboarding-card .wizard-body {
  text-align: left;
  min-height: 0;
}

.onboarding-final-footer {
  margin-top: 20px;
}

.wizard-close-btn {
  position: absolute;
  top: 10px;
  right: 12px;
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
}

.wizard-close-btn:hover {
  color: var(--text);
}

.wizard-help-btn {
  right: 46px;
  font-size: 1.1rem;
}

/* ---------- Help & Guide ---------- */

.help-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
}

.help-card {
  position: relative;
  background: var(--felt);
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  padding: 0;
  max-width: 640px;
  width: 100%;
  max-height: 88vh;
  overflow-y: auto;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  animation: milestone-pop 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
  text-align: left;
}

.help-header {
  position: sticky;
  top: 0;
  z-index: 5;
  background: linear-gradient(180deg, var(--felt-light) 0%, var(--felt) 100%);
  padding: 24px 24px 16px;
  border-bottom: 1px solid var(--border);
}

.help-body {
  padding: 20px 24px 24px;
}

.help-title {
  margin: 0 0 8px;
  color: var(--accent);
  font-size: 1.3rem;
  padding-right: 30px;
}

.help-intro {
  margin: 0;
  color: var(--text-dim);
  font-size: 0.9rem;
}

.help-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}

.help-nav-link {
  display: inline-block;
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text);
  text-decoration: none;
  font-size: 0.82rem;
  background: rgba(0, 0, 0, 0.2);
}

.help-nav-link:hover,
.help-nav-link.is-active {
  border-color: var(--accent);
  color: var(--accent);
}

.help-section {
  margin-bottom: 26px;
  padding-bottom: 22px;
  border-bottom: 1px solid var(--border);
  scroll-margin-top: 10px;
}

.help-section:last-child {
  border-bottom: none;
  margin-bottom: 0;
  padding-bottom: 0;
}

.help-section h3 {
  margin: 0 0 10px;
  color: var(--accent);
  font-size: 1.05rem;
}

.help-section p {
  margin: 0 0 12px;
  line-height: 1.5;
  font-size: 0.92rem;
}

.help-section ul {
  margin: 0 0 12px;
  padding-left: 20px;
  line-height: 1.5;
  font-size: 0.92rem;
}

.help-section li {
  margin-bottom: 8px;
}

.help-section li:last-child {
  margin-bottom: 0;
}

.help-section strong {
  color: var(--text);
}

.wizard-header {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 6px;
}

.wizard-mascot {
  width: 64px;
  height: 80px;
  flex-shrink: 0;
}

.wizard-header-text h2 {
  margin: 0 0 2px;
  color: var(--accent);
  font-size: 1.3rem;
}

.wizard-progress {
  margin: 0;
  color: var(--text-dim);
  font-size: 0.9rem;
}

.wizard-progress-dots {
  display: flex;
  gap: 6px;
  margin: 14px 0 18px;
}

.wizard-progress-dots .wizard-dot {
  flex: 1 1 auto;
  height: 5px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.12);
}

.wizard-progress-dots .wizard-dot.is-done,
.wizard-progress-dots .wizard-dot.is-active {
  background: var(--accent);
}

.wizard-body {
  text-align: left;
  min-height: 180px;
}

.wizard-step-explain {
  color: var(--text-dim);
  font-size: 0.9rem;
  margin: 0 0 16px;
}

.wizard-format-options {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 12px;
}

.wizard-format-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  background: rgba(0, 0, 0, 0.2);
}

.wizard-format-option:has(input:checked) {
  border-color: var(--accent);
  background: rgba(232, 178, 60, 0.12);
}

.wizard-format-option input {
  flex-shrink: 0;
}

.wizard-temp-counter-row {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
}

.wizard-temp-counter-divider {
  color: var(--text-dim);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.wizard-temp-counter-checkbox-label {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 12px;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  background: rgba(0, 0, 0, 0.2);
}

.wizard-temp-counter-checkbox-label:has(input:checked) {
  border-color: var(--accent);
  border-style: solid;
  background: rgba(232, 178, 60, 0.12);
}

.wizard-temp-counter-checkbox-label input {
  flex-shrink: 0;
}

.wizard-quick-counter-start-btn {
  width: 100%;
}

.wizard-just-counter-btn {
  width: 100%;
}

.wizard-player-chips {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.wizard-player-chips li {
  display: flex;
  align-items: center;
  gap: 6px;
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 5px 8px 5px 12px;
  font-size: 0.9rem;
}

.wizard-player-chip-remove {
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 1.1rem;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}

.wizard-player-chip-remove:hover {
  color: var(--danger);
}

.wizard-summary {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.wizard-summary-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  background: rgba(0, 0, 0, 0.25);
  border-radius: var(--radius);
  padding: 10px 14px;
}

.wizard-summary-row .label {
  color: var(--text-dim);
}

.wizard-summary-row .value {
  font-weight: 700;
  text-align: right;
}

.wizard-footer {
  display: flex;
  gap: 8px;
  margin-top: 20px;
  flex-wrap: wrap;
}

.wizard-footer .btn {
  flex: 1 1 auto;
}

/* ---------- Player stats page ---------- */

.player-page-header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  text-align: left;
}

.player-page-header h1 {
  margin: 0;
  font-size: 1.4rem;
  flex: 1;
  text-align: center;
}

.player-page-header #btn-return-to-global-stats {
  font-size: 0.85rem;
  padding: 8px 12px;
}

.player-page-added {
  margin: -8px 0 16px;
  text-align: center;
  font-size: 0.82rem;
  color: var(--text-dim);
}

.player-page-switcher {
  flex: 0 1 auto;
  max-width: 40vw;
  font-size: 0.85rem;
  padding: 8px 10px;
}

/* Pinned to the upper-left corner of the screen, and stays there as the
   page scrolls — the browser's own back button (see popstate handling in
   app.js) does exactly what tapping this does. */
.screen-back-btn {
  position: sticky;
  top: max(10px, env(safe-area-inset-top));
  left: max(10px, env(safe-area-inset-left));
  z-index: 20;
  align-self: flex-start;
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.screen-back-btn svg {
  display: block;
}

#player-page-actions-panel .row {
  margin-bottom: 0;
}

#player-page-actions-panel .row button {
  flex: 1;
}

.player-stats-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 14px;
  margin-bottom: 8px;
}

.player-stats-row:last-child {
  margin-bottom: 0;
}

.player-stats-row .label {
  color: var(--text-dim);
  font-size: 0.9rem;
}

.player-stats-row .value {
  font-weight: 800;
  font-size: 1.1rem;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.rotation-position-row {
  margin: 0 16px 12px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 14px;
}

.rotation-position-label {
  color: var(--text-dim);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-align: center;
  margin-bottom: 8px;
}

.rotation-position-track-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.rotation-position-btn {
  flex: 0 0 auto;
  min-width: 32px;
  min-height: 32px;
  padding: 4px 8px;
  font-size: 0.85rem;
  line-height: 1;
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--accent);
  cursor: pointer;
}

.rotation-position-btn:hover {
  background: rgba(0, 0, 0, 0.45);
}

.rotation-position-track {
  flex: 1;
  display: flex;
  align-items: center;
  min-width: 0;
}

.rotation-position-node {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--border);
}

.rotation-position-node.is-current {
  width: 16px;
  height: 16px;
  background: var(--accent);
  box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.25);
}

.rotation-position-connector {
  flex: 1 1 auto;
  height: 2px;
  min-width: 4px;
  background: var(--border);
}

.rotation-position-fill-track {
  flex: 1;
  height: 4px;
  background: var(--border);
  border-radius: 2px;
  overflow: hidden;
}

.rotation-position-fill-bar {
  height: 100%;
  background: var(--accent);
  border-radius: 2px;
}

.rotation-position-text {
  margin-top: 8px;
  text-align: center;
  font-weight: 700;
  font-size: 0.92rem;
  color: var(--accent);
}

.player-stats-note {
  color: var(--text-dim);
  font-size: 0.85rem;
  text-align: center;
}

.run-records-summary {
  font-weight: 700;
  color: var(--accent);
}

.new-player-name-requirement {
  color: var(--danger);
  font-size: 14px;
  margin: 0 0 8px;
}

.tournament-winner-banner {
  margin: 10px 0 4px;
  padding: 12px 14px;
  background: linear-gradient(180deg, var(--felt-light) 0%, var(--felt) 100%);
  border: 2px solid var(--accent);
  border-radius: 10px;
  color: var(--accent);
  font-size: 1.25rem;
  font-weight: 800;
  text-align: center;
  letter-spacing: 0.01em;
}

#backup-panel .row {
  margin-top: 4px;
  align-items: stretch;
}

#backup-panel .row .btn {
  flex: 1 1 140px;
}

#resets-panel .row {
  margin-top: 4px;
  align-items: stretch;
}

#resets-panel .row .btn {
  flex: 1 1 140px;
}

.player-stats-row-wrap {
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

.value-list {
  font-size: 0.95rem;
  font-weight: 600;
  white-space: normal;
  text-align: left;
}

.player-history-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.player-history-row {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
}

.player-history-row.won-tournament {
  border-color: var(--accent);
}

.player-history-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 6px;
}

.player-history-date {
  font-weight: 700;
}

.player-history-wins {
  color: var(--accent);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.player-history-detail {
  color: var(--text-dim);
  font-size: 0.85rem;
  line-height: 1.5;
}

.player-game-log {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 2px 0 8px;
}

.player-game-log-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: baseline;
  font-size: 0.85rem;
  color: var(--text-dim);
}

.player-game-log-time {
  font-variant-numeric: tabular-nums;
  opacity: 0.75;
  min-width: 60px;
}

.player-game-log-duration {
  flex-basis: 100%;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.player-game-log-label {
  color: var(--text);
}

.player-game-log-winner {
  color: var(--accent);
}

.player-game-log-row.is-loss {
  opacity: 0.7;
}

/* ---------- Player stats synopsis ---------- */

.period-filter-row {
  margin-bottom: 14px;
}

.period-filter-row .period-btn {
  flex: 1 1 auto;
}

.btn-ghost.is-active,
.period-btn.is-active {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
  font-weight: 700;
}

.player-stats-row .value.value-win {
  color: var(--accent);
}

.player-stats-row .value.value-loss {
  color: var(--danger);
}

.clubteam-block {
  margin-bottom: 14px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}

.clubteam-block-heading {
  font-weight: 800;
  color: var(--accent);
  margin: 10px 0 6px;
  font-size: 0.95rem;
}

.clubteam-block-heading:first-child {
  margin-top: 0;
}

.player-h2h-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.player-h2h-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 14px;
}

.player-h2h-name {
  flex: 1;
  font-weight: 700;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.player-h2h-record {
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  font-size: 0.9rem;
}

.player-h2h-pct {
  min-width: 48px;
  text-align: right;
  font-weight: 800;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.achievement-badge-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 10px;
}

.achievement-badge {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 2px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 8px;
  filter: grayscale(1);
  opacity: 0.55;
}

.achievement-badge.is-unlocked {
  filter: none;
  opacity: 1;
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent) inset;
}

.achievement-badge-icon {
  font-size: 1.8rem;
  line-height: 1;
}

.achievement-badge-label {
  font-weight: 700;
  font-size: 0.85rem;
}

.achievement-badge-progress {
  color: var(--accent);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-size: 0.8rem;
}

.achievement-badge-desc {
  color: var(--text-dim);
  font-size: 0.72rem;
}

/* ---------- All Players page ---------- */

.all-players-list {
  list-style: none;
  margin: 0;
  padding: 8px 16px 24px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.contact-sheet-list {
  list-style: none;
  margin: 0;
  padding: 8px 16px 24px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.contact-sheet-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px;
  min-width: 0;
}

.contact-sheet-row input[type="checkbox"] {
  width: 20px;
  height: 20px;
  accent-color: var(--accent);
  margin-top: 8px;
  flex-shrink: 0;
}

.contact-sheet-fields {
  flex: 1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px;
  min-width: 0;
}

.contact-sheet-fields label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 0.72rem;
  opacity: 0.7;
  min-width: 0;
}

.contact-sheet-fields input {
  min-width: 0;
  width: 100%;
}

.contact-sheet-row .player-link-icon {
  margin-top: 4px;
  flex-shrink: 0;
}

/* ---------- Leaderboard ---------- */

.leaderboard-banner {
  text-align: center;
  padding: 20px 16px 24px;
  margin-bottom: 12px;
  border-radius: var(--radius);
  background: linear-gradient(
    135deg,
    color-mix(in srgb, var(--accent) 35%, var(--felt)) 0%,
    var(--felt) 45%,
    color-mix(in srgb, var(--accent-dark) 40%, var(--felt)) 100%
  );
  border: 1px solid var(--border);
  animation: leaderboard-banner-in 0.4s ease;
}

@keyframes leaderboard-banner-in {
  from {
    transform: scale(0.92);
    opacity: 0;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}

.leaderboard-banner-title {
  font-size: clamp(1.5rem, 6vw, 2.1rem);
  font-weight: 800;
  color: var(--on-felt-light);
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
}

.leaderboard-banner-sub {
  margin: 6px 0 0;
  color: var(--on-felt-light);
  opacity: 0.85;
  font-size: 0.9rem;
}

.leaderboard-view-toggle {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin: 14px 0;
}

.leaderboard-view-option {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 18px;
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
  background: var(--bg-panel);
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--text-dim);
}

.leaderboard-view-option:has(input:checked) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 20%, var(--bg-panel));
  color: var(--accent);
}

.leaderboard-view-option input {
  accent-color: var(--accent);
}

.leaderboard-team-members {
  margin-top: 2px;
  color: var(--text-dim);
  font-size: 0.8rem;
  font-style: italic;
}

/* ---------- Group Session (LAN multiplayer relay) ---------- */

/* The page view itself is a bare top-level sibling of #app (same as
   every other view-*-page), with no ambient width constraint - #app
   gets its own max-width/centering rule, this needs the same. */
#view-group-session-page {
  max-width: 640px;
  margin: 0 auto;
  padding: 0 16px 24px;
  box-sizing: border-box;
}

#btn-group-session-host,
#group-session-host-panel #btn-group-session-stop {
  display: block;
  width: max-content;
  max-width: 100%;
  margin: 20px auto;
  padding: 14px 28px;
  font-size: 1.05rem;
  font-weight: 800;
}

/* An <a>, not a <button> - this project's button styling (padding,
   radius, cursor) lives on the bare `button` element selector, so a link
   needs its own rule to look the same. */
.group-session-install-link {
  display: block;
  width: max-content;
  max-width: 100%;
  margin: 16px auto;
  padding: 12px 24px;
  border-radius: 10px;
  background: var(--btn-primary-surface);
  color: var(--on-accent);
  font-weight: 700;
  font-size: 1rem;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: filter 0.15s ease;
}

.group-session-install-link:hover {
  filter: brightness(1.08);
}

.network-status-bar {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 16px;
  background: var(--bg-panel);
  border-bottom: 1px solid var(--border);
}

.network-status-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: 999px;
  font-weight: 700;
  font-size: 0.82rem;
  border: 1px solid var(--border);
  color: var(--text-dim);
}

.network-status-pill.is-connecting {
  border-color: var(--accent);
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-panel));
}

.network-status-pill.is-connected {
  border-color: var(--accent);
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 24%, var(--bg-panel));
}

.network-status-pill.is-host-away,
.network-status-pill.is-disconnected {
  border-color: var(--danger);
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 16%, var(--bg-panel));
}

.group-session-join-url-label {
  margin: 16px 0 2px;
  color: var(--text-dim);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  text-align: center;
}

.group-session-join-url {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--accent);
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 14px;
  word-break: break-all;
  user-select: all;
  text-align: center;
  box-sizing: border-box;
}

.group-session-qr {
  display: block;
  width: 220px;
  height: 220px;
  margin: 16px auto 4px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: #fff;
  padding: 10px;
}

.group-session-qr-hint {
  text-align: center;
  color: var(--text-dim);
  font-size: 0.85rem;
}

.group-session-error {
  text-align: center;
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 40%, transparent);
  border-radius: var(--radius);
  padding: 12px 16px;
  margin: 16px 0;
  font-size: 0.9rem;
  font-weight: 600;
}

.group-session-guest-count {
  text-align: center;
  font-weight: 700;
  color: var(--accent);
  margin: 12px 0;
}

/* Guest-mode scoping: hide everything except the live scoreboard/
   standings and (if a tournament is running) the active match view - a
   guest can tap +/- and undo, nothing else. Reuses the real, already-
   fully-featured panels rather than a separate stripped-down template. */
body.network-guest-mode #rotation-panel,
body.network-guest-mode #game-setup-panel,
body.network-guest-mode #players-panel,
body.network-guest-mode #history-panel,
body.network-guest-mode #day-notes-panel,
body.network-guest-mode #report-archive-panel,
body.network-guest-mode #backup-panel,
body.network-guest-mode #resets-panel,
body.network-guest-mode #tournament-setup-panel,
body.network-guest-mode .header-nav-row #btn-test-onboarding,
body.network-guest-mode .header-nav-row #btn-open-wizard,
body.network-guest-mode .header-nav-row #btn-open-tournament,
body.network-guest-mode .header-nav-row #btn-open-global-stats,
body.network-guest-mode .header-nav-row #btn-open-contact-sheet,
body.network-guest-mode .header-nav-row #btn-open-group-session {
  display: none !important;
}

/* Unlocked-achievement icons under a leaderboard player's name (see
   buildLeaderboardAchievementIcons) - every icon opens the same full
   achievements popup for that player (openAchievementsModal), so they're
   just a compact trophy case, not individually distinct controls. */
.leaderboard-achievements-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 4px;
}

.leaderboard-achievement-icon {
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 2px 6px;
  font-size: 1.1rem;
  line-height: 1.3;
  cursor: pointer;
}

.leaderboard-achievement-icon:hover {
  border-color: var(--accent);
}

.achievements-modal-card {
  max-width: 480px;
}

#achievements-modal-list {
  overflow-y: auto;
  min-height: 0;
  margin-bottom: 16px;
}

/* The circular back arrow up in the header is easy to miss on a screen
   that auto-pops-up uninvited (see maybeAutoShowLeaderboard) - this is
   the unmissable, unambiguous way out, sitting right below the ranked
   list (or the empty-state hint standing in for it) so it's the next
   thing there is to do once you're done reading. */
.leaderboard-action-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 12px;
  margin: 18px 0 16px;
}

.leaderboard-play-btn {
  font-size: 1.05rem;
  font-weight: 800;
  padding: 14px 28px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
}

.leaderboard-share-btn {
  font-size: 1.05rem;
  font-weight: 700;
  padding: 14px 24px;
}

.leaderboard-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.leaderboard-row {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
  min-width: 0;
}

.leaderboard-rank-1 {
  background: linear-gradient(90deg, color-mix(in srgb, gold 30%, var(--bg-panel)) 0%, var(--bg-panel) 70%);
  border-color: color-mix(in srgb, gold 55%, var(--border));
}

.leaderboard-rank-2 {
  background: linear-gradient(90deg, color-mix(in srgb, silver 26%, var(--bg-panel)) 0%, var(--bg-panel) 70%);
  border-color: color-mix(in srgb, silver 50%, var(--border));
}

.leaderboard-rank-3 {
  background: linear-gradient(90deg, color-mix(in srgb, #cd7f32 26%, var(--bg-panel)) 0%, var(--bg-panel) 70%);
  border-color: color-mix(in srgb, #cd7f32 50%, var(--border));
}

.leaderboard-rank {
  font-size: 1.6rem;
  min-width: 2.2rem;
  text-align: center;
  flex-shrink: 0;
}

.leaderboard-row-top .leaderboard-rank {
  font-size: 2.1rem;
}

.leaderboard-row-body {
  min-width: 0;
  flex: 1;
}

.leaderboard-name-row {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.leaderboard-name-btn {
  background: none;
  border: none;
  padding: 0;
  color: var(--accent);
  font: inherit;
  font-size: 1.05rem;
  font-weight: 800;
  text-align: left;
  text-decoration: underline;
  text-decoration-color: transparent;
  transition: text-decoration-color 0.15s ease;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* Opens a plain-language explanation (see explainLeaderboardRanking) of
   why this player ranks where they do relative to whoever's directly
   above them - kept small and secondary so it doesn't compete with the
   name for attention. */
.leaderboard-explain-btn {
  background: var(--bg-glow);
  border: 1px solid var(--border);
  color: var(--text-dim);
  width: 22px;
  height: 22px;
  min-width: 22px;
  border-radius: 50%;
  padding: 0;
  font-size: 0.72rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.leaderboard-name-btn:hover,
.leaderboard-name-btn:focus-visible {
  text-decoration-color: currentColor;
}

.leaderboard-row-top .leaderboard-name-btn {
  font-size: 1.35rem;
}

.leaderboard-mvp-tag {
  background: var(--accent);
  color: var(--on-accent);
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  padding: 2px 7px;
  border-radius: 999px;
  text-decoration: none;
}

.leaderboard-row-stats {
  margin-top: 2px;
  color: var(--text-dim);
  font-size: 0.85rem;
}

.leaderboard-formula-note {
  margin: 18px 2px 4px;
  color: var(--text-dim);
  font-size: 0.78rem;
  text-align: center;
  line-height: 1.4;
}

.all-player-card {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  max-width: 100%;
  overflow-x: hidden;
}

#player-page-graph-panel {
  max-width: 100%;
  overflow-x: hidden;
}

.all-player-card-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 16px;
}

.all-player-name {
  background: none;
  border: none;
  padding: 0;
  border-radius: 0;
  color: var(--accent);
  font: inherit;
  font-size: 1.15rem;
  font-weight: 800;
  text-align: left;
  text-decoration: underline;
  text-decoration-color: transparent;
  transition: transform 0.06s ease, filter 0.15s ease, text-decoration-color 0.15s ease;
}

.all-player-name:hover,
.all-player-name:focus-visible {
  text-decoration-color: var(--accent);
}

.all-player-name-group {
  display: flex;
  align-items: center;
  gap: 6px;
}

.player-link-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border-radius: 50%;
  flex-shrink: 0;
}

.player-link-icon svg {
  display: block;
}

.all-player-summary {
  color: var(--text-dim);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.all-player-rating-status {
  color: var(--text-dim);
  font-size: 0.82rem;
  margin: -8px 0 14px;
}

.all-player-rating-status.is-up {
  color: #6fcf6f;
}

.all-player-rating-status.is-down {
  color: var(--danger);
}

.all-player-show-graph-btn {
  width: 100%;
}

.scale-row {
  margin-bottom: 22px;
}

.scale-row-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 6px;
}

.scale-row-label {
  font-size: 0.8rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.scale-row-value {
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.scale-track {
  position: relative;
  height: 10px;
  background: rgba(0, 0, 0, 0.35);
  border-radius: 6px;
}

.scale-fill {
  height: 100%;
  border-radius: 6px;
  min-width: 2px;
}

.scale-fill-played { background: var(--info); }
.scale-fill-won { background: var(--accent); }
.scale-fill-lost { background: var(--danger); }
.scale-fill-tourn-played { background: #00b4d8; }
.scale-fill-tourn-won { background: #3a86ff; }
.scale-fill-tourn-lost { background: #8338ec; }

.scale-ticks {
  position: absolute;
  inset: 0;
}

.scale-tick {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: rgba(255, 255, 255, 0.18);
}

.scale-tick::after {
  content: attr(data-value);
  position: absolute;
  top: 100%;
  left: 0;
  transform: translateX(-50%);
  margin-top: 3px;
  font-size: 0.65rem;
  color: var(--text-dim);
  white-space: nowrap;
}

.scale-tick:first-child::after { transform: translateX(0); }
.scale-tick:last-child::after { transform: translateX(-100%); }

.timeline-title {
  font-size: 0.8rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 0 0 22px;
}

.timeline-track {
  position: relative;
  height: 2px;
  background: var(--border);
  margin: 0 6px 22px;
}

.timeline-tick {
  position: absolute;
  top: -4px;
  width: 1px;
  height: 10px;
  background: rgba(255, 255, 255, 0.25);
}

.timeline-tick::after {
  content: attr(data-label);
  position: absolute;
  top: 14px;
  left: 0;
  transform: translateX(-50%);
  font-size: 0.65rem;
  color: var(--text-dim);
  white-space: nowrap;
}

.timeline-tick:first-child::after { transform: translateX(0); }
.timeline-tick:last-child::after { transform: translateX(-100%); }

.timeline-dot {
  position: absolute;
  top: 50%;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  border: 2px solid var(--bg-panel);
}

.timeline-dot-won { background: var(--accent); }
.timeline-dot-lost { background: var(--danger); }

/* ---------- All Players page: graph view ---------- */

.player-graph-wrap {
  margin-top: 4px;
}

.player-graph-empty {
  color: var(--text-dim);
  font-size: 0.9rem;
  text-align: center;
  padding: 24px 0;
}

.player-graph-chart {
  display: flex;
  align-items: stretch;
  gap: 8px;
  /* Without this, the SVG's intrinsic aspect-ratio can act as a flex
     min-width floor and push the whole chart past the card's edge
     instead of shrinking to fit — that's what was spilling out. */
  min-width: 0;
}

.player-graph-yaxis {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-width: 26px;
  padding: 2px 0;
  font-size: 0.65rem;
  color: var(--text-dim);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.player-graph-svg {
  flex: 1;
  min-width: 0;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 1;
  display: block;
  background: var(--graph-bg);
  border-radius: 8px;
  padding: 10px;
  box-sizing: border-box;
  overflow: hidden;
}

.player-graph-gridline {
  stroke: rgba(255, 255, 255, 0.1);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.player-graph-line {
  stroke-width: 2.5;
  vector-effect: non-scaling-stroke;
}

.player-graph-line-dashed {
  stroke-dasharray: 8 6;
}

.player-graph-line-dotted {
  stroke-dasharray: 1.5 5;
  stroke-linecap: round;
}

.player-graph-line-ind-played { stroke: var(--info); }
.player-graph-line-ind-won { stroke: var(--accent); }
.player-graph-line-ind-lost { stroke: var(--danger); }

.player-graph-dot {
  stroke: var(--bg-panel);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
}

.player-graph-dot-ind-played { fill: var(--info); }
.player-graph-dot-ind-won { fill: var(--accent); }
.player-graph-dot-ind-lost { fill: var(--danger); }

/* Invisible, larger tap target layered over each visible dot — the dots
   themselves (r=3.2) are too small to reliably tap on a phone. */
.player-graph-dot-hit {
  fill: transparent;
  cursor: pointer;
}

.player-graph-tooltip {
  position: fixed;
  z-index: 200;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  min-width: 160px;
  max-width: 260px;
  pointer-events: none;
}

.player-graph-tooltip-title {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  margin-bottom: 6px;
}

.player-graph-tooltip-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  font-size: 0.85rem;
  padding: 3px 0;
}

.player-graph-tooltip-name {
  font-weight: 700;
  color: var(--text);
}

.player-graph-tooltip-record {
  color: var(--text-dim);
  white-space: nowrap;
}

.player-rating-graph-line {
  stroke: var(--accent);
  stroke-width: 2.5;
  vector-effect: non-scaling-stroke;
}

.player-rating-graph-dot {
  fill: var(--accent);
  stroke: var(--bg-panel);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
}

.player-rating-graph-wrap {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

.player-rating-graph-heading {
  margin: 0 0 10px;
  font-size: 0.9rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.player-graph-time-axis {
  position: relative;
  height: 26px;
  margin: 6px 0 4px 34px;
  font-size: 0.65rem;
  color: var(--text-dim);
}

.player-graph-time-tick {
  position: absolute;
  top: 0;
  width: 1px;
  height: 5px;
  background: var(--border);
  transform: translateX(-50%);
}

.player-graph-time-label {
  position: absolute;
  top: 10px;
  transform: translateX(-50%);
  white-space: nowrap;
}

.player-graph-time-label.is-first { transform: translateX(0); }
.player-graph-time-label.is-last { transform: translateX(-100%); }

.player-graph-time-label.is-now {
  color: var(--accent);
  font-weight: 700;
}

.player-graph-legend {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 12px;
}

.player-graph-legend-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.85rem;
  color: var(--text-dim);
}

.player-graph-legend-label {
  flex: 1;
  min-width: 0;
}

.player-graph-legend-row.is-off {
  opacity: 0.45;
}

.player-graph-legend-toggle {
  flex-shrink: 0;
  padding: 3px 10px;
  font-size: 0.72rem;
  border-radius: 20px;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-dim);
}

.player-graph-legend-toggle:hover {
  color: var(--text);
  border-color: var(--text-dim);
}

.player-graph-series.is-hidden {
  display: none;
}

.player-graph-legend-swatch {
  width: 22px;
  height: 3px;
  border-radius: 2px;
  flex-shrink: 0;
  background: var(--swatch-color);
}

.player-graph-legend-swatch.is-dashed {
  background: repeating-linear-gradient(
    to right,
    var(--swatch-color) 0,
    var(--swatch-color) 6px,
    transparent 6px,
    transparent 10px
  );
}

.player-graph-legend-swatch.is-dotted {
  background: repeating-linear-gradient(
    to right,
    var(--swatch-color) 0,
    var(--swatch-color) 2px,
    transparent 2px,
    transparent 6px
  );
}

/* ---------- Now playing banner ---------- */

.now-playing-banner {
  text-align: center;
  font-size: 1.6rem;
  font-weight: 800;
  color: var(--accent);
  letter-spacing: 0.02em;
  padding: 4px 16px 14px;
}

.now-playing-banner .target-note {
  display: block;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-dim);
  letter-spacing: normal;
  margin-top: 2px;
}

.now-playing-banner .rotation-note {
  display: block;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-dim);
  letter-spacing: normal;
  margin-top: 2px;
}

.now-playing-banner .game-duration-live {
  display: block;
  font-size: 1.1rem;
  font-weight: 800;
  color: var(--accent);
  letter-spacing: normal;
  font-variant-numeric: tabular-nums;
  margin-top: 6px;
}

/* ---------- Header nav row ---------- */

.header-nav-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
}

/* ---------- Elimination Tournament ---------- */

/* Tournament Type and Game Type are narrow, self-contained option
   groups with no real reason to stretch across the whole panel.
   align-items: flex-start on a column flex container is what actually
   does the "shrink to content, don't stretch to fill the row" job here
   (a CSS Grid minmax(0, max-content) track was tried first, but
   max-content sizing around a <select> is a known cross-browser soft
   spot - this flex pattern has a much longer, more reliable track
   record). Each section is then separately capped at 40% of the
   viewport - they no longer force-match each other's width the way the
   grid version aimed for, but they do reliably stop stretching, which
   is the part that was actually broken. Players is deliberately its own
   section below this one - it needs room for up to 4 player cards
   across, which is routinely wider than 40vw. */
.tournament-setup-narrow-sections {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  margin-bottom: 14px;
}

.tournament-setup-section {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
  width: fit-content;
  max-width: 40vw;
  margin: 0 auto;
}

.tournament-setup-players-section {
  max-width: 100%;
  margin: 0 auto 14px;
}

/* Radio rows default to stretching across their container (see
   .wizard-format-options elsewhere) - here they should only ever be as
   wide as their own widest option, not the whole section. */
#tournament-setup-panel .wizard-format-options {
  width: fit-content;
  max-width: 100%;
}

/* Groups the three seed-mode choices with Select All (a checkbox, not
   a seed mode - it lives here for quick access right next to them, not
   because it's actually one of them) into one visually distinct block,
   set apart from the rest of the Players section below it. */
.tournament-selection-group {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
  background: rgba(255, 255, 255, 0.05);
  margin-bottom: 10px;
}

.tournament-start-row {
  margin: 0 0 14px;
}

.tournament-teams-row {
  margin-bottom: 10px;
}

.tournament-tables-row {
  margin-bottom: 14px;
}

.tournament-tables-row span {
  font-size: 1.05rem;
  font-weight: 700;
}

.tournament-tables-row input[type="number"] {
  width: 70px;
}

#tournament-setup-panel .wizard-format-option {
  flex-wrap: wrap;
}

.tournament-players-heading,
.tournament-section-heading {
  margin: 4px 0 10px;
  font-size: 0.9rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.format-info-btn {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: rgba(0, 0, 0, 0.25);
  color: var(--text-dim);
  font-size: 0.8rem;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.format-info-btn:hover {
  color: var(--text);
  border-color: var(--text-dim);
}

/* The shared `select { flex: 1 }` rule makes a dropdown greedily fill
   whatever's left in its row - fine for a row that's just one select,
   but here it stretches the Game picker across most of the panel's
   width even though "Game" and "Target"/rack-count still need room
   next to it, leaving an oddly sparse-looking row. Capped instead of
   overriding the shared rule, so every other select in the app keeps
   its current (wider, single-control-per-row) behavior. */
#tournament-setup-panel select {
  flex: 0 1 220px;
  max-width: 220px;
}

.tournament-player-checklist {
  list-style: none;
  margin: 0 0 16px;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  /* Never more than 4 across, however wide the screen - a 5th+ player
     wraps to another row instead of the grid just keeping adding columns. */
  max-width: calc(4 * 220px + 3 * 8px);
  gap: 8px;
}

.tournament-player-check-row {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  /* A shade lighter than the section card it sits in (which is already
     lighter than the page background) so each player still reads as its
     own distinct row instead of blending into the section around it. */
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 10px;
}

.tournament-player-check-row label {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  cursor: pointer;
  font-size: 0.95rem;
}

.tournament-player-check-row input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: var(--accent);
}

.tournament-seed-input {
  /* Wide enough for a 3-digit seed plus the native number spinner
     arrows - 44px was clipping both. */
  width: 64px !important;
  height: auto !important;
  padding: 6px 4px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-card);
  color: var(--text);
  text-align: center;
  font-size: 0.9rem;
}

.tournament-team-input {
  flex: 1 1 84px;
  min-width: 70px;
  height: auto !important;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-card);
  color: var(--text);
  font-size: 0.85rem;
}

.tournament-match-team-members {
  font-size: 0.78rem;
  color: var(--text-dim);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  margin-top: 2px;
}

.tournament-match-team-tag {
  font-size: 0.78rem;
  color: var(--text-dim);
  font-style: italic;
  margin-left: 4px;
}

.tournament-match-lb-tag {
  display: block;
  font-size: 0.72rem;
  font-weight: 700;
  font-style: normal;
  color: var(--danger);
  margin-top: 1px;
}

.tournament-match-table-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin-top: 4px;
  font-size: 0.78rem;
  font-weight: 700;
  opacity: 0.85;
}

.tournament-match-table-select {
  font-size: 0.78rem;
  padding: 2px 4px;
}

.tournament-match-table-played {
  margin-top: 2px;
  text-align: center;
  font-size: 0.76rem;
  font-weight: 700;
  opacity: 0.65;
}

.tournament-bracket-heading {
  margin: 20px 16px 8px;
}

.tournament-bracket-scroll {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  padding: 0 16px 8px;
  -webkit-overflow-scrolling: touch;
}

.tournament-round-col {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 190px;
  flex-shrink: 0;
}

.tournament-round-heading {
  font-size: 0.75rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  text-align: center;
}

.tournament-match-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 190px;
  flex-shrink: 0;
}

/* ---------- Winners bracket tree ----------
   Built as nested "children + this round's match" wrappers instead of flat
   columns. The connector lines are pure CSS percentages against each pair's
   own wrapper: a 2-item space-around column always places its items at
   exactly 25%/75% of the wrapper's height, so no pixel measurement is
   needed regardless of how tall any given match card ends up. */

.wb-tree-root {
  display: flex;
  align-items: center;
}

.wb-tree-node {
  display: flex;
  align-items: center;
}

/* Each match card gets its own round label directly above it (Final /
   Semifinal / Quarterfinal / Round of N) rather than one shared header
   row - the nested tree layout doesn't have stable per-round columns to
   align a single header against, but every card already knows its own
   round, so labeling each one is both simpler and never misaligned. */
.wb-tree-card-wrap {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 190px;
  flex-shrink: 0;
}

.wb-tree-card-wrap .tournament-round-heading {
  margin: 0;
}

/* A losers-bracket tree's "leaf" is often just a reference to the
   winners-bracket match someone dropped out of, not a full duplicate
   card (that match is already shown in full in the winners tree above). */
.lb-tree-wb-stub {
  justify-content: center;
  padding: 10px 8px;
  border: 1px dashed var(--border);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.15);
  text-align: center;
}

.lb-tree-wb-stub-name {
  font-size: 0.85rem;
  color: var(--text-dim);
}

/* The collapsed "+" placeholder standing in for however many earlier
   rounds have scrolled past the visible window - a real <button> (not a
   div) so it's focusable/clickable like everything else, with its
   native button chrome reset back to match the rest of the tree. */
.lb-tree-collapsed-stub {
  justify-content: center;
  align-items: center;
  padding: 10px 8px;
  border: 2px dashed var(--accent);
  border-radius: 10px;
  background: rgba(232, 185, 35, 0.08);
  text-align: center;
  cursor: pointer;
  font: inherit;
  color: inherit;
}

.lb-tree-collapsed-stub:hover {
  background: rgba(232, 185, 35, 0.18);
}

.lb-tree-collapsed-plus {
  font-size: 1.3rem;
  line-height: 1;
  margin-bottom: 4px;
}

.lb-tree-collapsed-label {
  font-size: 0.78rem;
  color: var(--accent);
  font-weight: 700;
}

.lb-tree-root + .lb-tree-root {
  margin-left: 24px;
  padding-left: 24px;
  border-left: 1px dashed var(--border);
}

.tournament-wb-inline,
.tournament-gf-inline {
  display: flex;
  align-items: center;
}

.tournament-gf-inline {
  margin-left: 28px;
  padding-left: 28px;
  border-left: 1px dashed var(--border);
}

.tournament-gf-inline .tournament-round-col {
  min-width: 190px;
}

.wb-tree-children {
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  gap: 14px;
  position: relative;
  padding-right: 28px;
}

.wb-tree-children::before {
  content: "";
  position: absolute;
  top: 25%;
  bottom: 25%;
  right: 28px;
  width: 1px;
  background: var(--border);
}

/* A pendingBye node (see buildWinnersBracketRounds/renderWbTreeNode)
   only ever has one real feeder, not two - with a single centered
   child instead of a 25%/75% pair, the vertical connector collapses to
   the same midpoint the horizontal one already meets it at, instead of
   floating disconnected between two positions nothing occupies. */
.wb-tree-children:has(> :only-child)::before {
  top: 50%;
  bottom: 50%;
}

.wb-tree-children::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 0;
  width: 28px;
  height: 1px;
  background: var(--border);
}


.tournament-match-card.is-ready {
  border-color: var(--accent);
}

.tournament-match-card.is-active {
  border-color: var(--info);
  box-shadow: 0 0 0 1px var(--info);
}

.tournament-playing-note {
  margin-top: 2px;
  padding: 6px 8px;
  text-align: center;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--info);
}

.tournament-bye-note {
  margin-top: 2px;
  padding: 6px 8px;
  text-align: center;
  font-size: 0.78rem;
  font-weight: 700;
  font-style: italic;
  opacity: 0.75;
}

.tournament-match-side {
  padding: 6px 8px;
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.2);
  font-size: 0.88rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tournament-match-side.is-winner {
  background: rgba(232, 185, 35, 0.18);
  color: var(--accent);
  font-weight: 800;
}

.tournament-match-side.is-loser {
  opacity: 0.4;
  color: var(--text-dim);
  text-decoration: line-through;
  text-decoration-thickness: 2.5px;
  text-decoration-color: var(--text-dim);
}

/* Losers-bracket match cards get their own tint so it's obvious at a
   glance which side of the bracket you're looking at, without having to
   read the column heading. */
#tournament-lb .tournament-match-card {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, var(--bg-card));
}

.tournament-play-btn {
  margin-top: 2px;
  padding: 8px;
  font-size: 0.85rem;
}

.tournament-ready-list {
  list-style: none;
  margin: 0 16px 16px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.tournament-ready-heading {
  color: var(--text-dim);
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.tournament-ready-row {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 10px;
  max-width: 520px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 14px;
}

/* Bumps a semifinal/final match up visually - a little gold glow so it
   reads as a bigger moment than an early-round match. */
.tournament-ready-row.is-marquee {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), 0 0 16px rgba(232, 185, 35, 0.25);
}

.tournament-ready-tag {
  font-weight: 800;
  color: var(--accent);
  white-space: nowrap;
}

.tournament-ready-names {
  flex: 1 1 auto;
  min-width: 0;
}

.tournament-ready-row .btn {
  flex-shrink: 0;
}

.tournament-timeline {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 16px 16px;
  padding: 10px 14px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 0.85rem;
}

.tournament-timeline-stage {
  color: var(--text-dim);
}

.tournament-timeline-stage.is-done {
  color: var(--text-dim);
  text-decoration: line-through;
  opacity: 0.7;
}

.tournament-timeline-stage.is-current {
  color: var(--accent);
  font-weight: 800;
  font-size: 0.95rem;
}

.tournament-timeline-arrow {
  color: var(--text-dim);
  opacity: 0.6;
}

.tournament-now-playing {
  margin: 0 16px 12px;
}

#tournament-current-match-panel .scoreboard {
  margin: 0 16px 16px;
}

.tournament-table-focus-row {
  margin: 0 16px 10px;
  align-items: center;
}

/* A multi-table event's live boards. Picking "See All Tables" from the
   focus dropdown shows every one of them at once, each a full,
   independently-scorable board - stacked as a cascading deck (each
   card nudged over from the last) purely as a visual cue that these
   are separate tables, not a single long scoreboard. Picking one
   specific table instead shows just that one board, full width, no
   cascade. Either way each card gets a felt-toned background so it
   reads as its own distinct surface against the page behind it,
   noticeably more than the standard panel background most cards use. */
.tournament-live-boards-stack {
  margin: 0 16px 16px;
}

.tournament-floating-board {
  background: var(--felt);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.35);
  padding: 10px 0;
}

.tournament-live-boards-stack.is-stacked .tournament-floating-board {
  margin: 0 0 14px calc(var(--stack-index, 0) * 14px);
}

.tournament-live-boards-stack.is-stacked .tournament-floating-board:last-child {
  margin-bottom: 0;
}

/* ---------- Round Robin: standings list + a wrapping grid of every
   match (no bracket tree — every match is visible from the start). ---------- */

.tournament-rr-standings {
  list-style: none;
  margin: 0 16px 16px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  counter-reset: rr-rank;
}

.tournament-rr-standings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 14px;
  counter-increment: rr-rank;
}

.tournament-rr-standings-row::before {
  content: counter(rr-rank) ".";
  color: var(--text-dim);
  font-size: 0.85rem;
  margin-right: 4px;
}

.tournament-rr-standings-row.is-champion {
  border-color: var(--accent);
}

.tournament-rr-standings-name {
  font-weight: 700;
  display: flex;
  align-items: center;
}

.tournament-rr-standings-record {
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
  white-space: nowrap;
  font-size: 0.85rem;
}

.tournament-rr-matches {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  padding: 0 16px 16px;
}

/* ---------------------------------------------------------------------
   Print views - the bracket prints its live on-screen DOM directly (chrome
   hidden, scroll containers unclipped); the daily report has no on-screen
   DOM at all, so it renders into #day-report-print-view on demand and that
   container becomes the only thing visible for the duration of the print.
   --------------------------------------------------------------------- */

@media print {
  .app-header.player-page-header,
  #tournament-setup-panel,
  #btn-tournament-abandon,
  #tournament-current-match-panel,
  #tournament-ready-list,
  #tournament-timeline,
  .tournament-play-btn,
  #btn-tournament-print { display: none !important; }

  .tournament-bracket-scroll { overflow-x: visible !important; }
  .tournament-round-col,
  .tournament-match-card { width: auto !important; min-width: 150px; }

  body:has(#day-report-print-view:not(.hidden)) > *:not(#day-report-print-view) {
    display: none !important;
  }
  #day-report-print-view:not(.hidden) { display: block; }

  #day-report-print-view h1 { font-size: 1.4rem; margin: 0 0 4px; }
  #day-report-print-view .print-report-date { color: #444; margin: 0 0 20px; }
  #day-report-print-view h2 { font-size: 1.05rem; margin: 20px 0 8px; }
  #day-report-print-view .print-report-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9rem;
  }
  #day-report-print-view .print-report-table th,
  #day-report-print-view .print-report-table td {
    border: 1px solid #999;
    padding: 4px 8px;
    text-align: left;
  }
  #day-report-print-view ul { margin: 0; padding-left: 20px; font-size: 0.9rem; }
  #day-report-print-view .print-report-notes { white-space: pre-wrap; font-size: 0.9rem; }

  /* @page size intentionally left at the browser default (portrait) - it's
     a single global at-rule with no way to scope by which view is printing,
     and portrait suits the report table; the bracket's own print button can
     be flipped to landscape with one click in the browser's print dialog if
     a wide bracket needs it. */
  @page { margin: 0.5in; }
}
