/* =============================================
   CourtScore — iOS Minimalist Design System
   ============================================= */

/* CSS VARIABLES */
:root, html[data-theme="light"] {
  --font-ui: 'Inter', -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif;
  --font-display: 'Inter Tight', -apple-system, BlinkMacSystemFont, 'SF Pro Display', system-ui, sans-serif;
  --font-mono: ui-monospace, 'SF Mono', Menlo, monospace;

  --bg:        oklch(0.975 0 0);
  --bg-2:      oklch(0.92 0 0);
  --paper:     oklch(1 0 0);
  --surface:   oklch(0.94 0 0);
  --ink:       oklch(0.15 0 0);
  --ink-2:     oklch(0.40 0 0);
  --ink-3:     oklch(0.55 0 0);
  --line:      oklch(0.85 0 0);
  --line-2:    oklch(0.90 0 0);
  --accent:    oklch(0.15 0 0);
  --on-accent: oklch(1 0 0);
  --good:      oklch(0.66 0.15 145);
  --good-soft: oklch(0.93 0.04 145);
  --warn:      oklch(0.70 0.16 50);
  --warn-soft: oklch(0.95 0.04 50);
  --warn-bg:   oklch(0.97 0.03 90);
  --warn-text: oklch(0.40 0.12 60);
  --info:      oklch(0.50 0.17 255);
  --info-soft: oklch(0.92 0.04 255);
  --btn-danger: oklch(0.55 0.20 25);
  --glass:     rgba(255,255,255,.72);
  --glass-bd:  rgba(0,0,0,.06);
  --shadow:    0 1px 2px rgba(0,0,0,.04), 0 8px 24px rgba(0,0,0,.06);
  --shadow-lg: 0 8px 24px rgba(0,0,0,.10), 0 24px 60px rgba(0,0,0,.12);
  --border:    var(--line);
}

html[data-theme="dark"] {
  --bg:        oklch(0.13 0 0);
  --bg-2:      oklch(0.17 0 0);
  --paper:     oklch(0.18 0 0);
  --surface:   oklch(0.22 0 0);
  --ink:       oklch(0.98 0 0);
  --ink-2:     oklch(0.72 0 0);
  --ink-3:     oklch(0.52 0 0);
  --line:      oklch(0.28 0 0);
  --line-2:    oklch(0.23 0 0);
  --accent:    oklch(0.98 0 0);
  --on-accent: oklch(0.13 0 0);
  --good:      oklch(0.78 0.16 145);
  --good-soft: oklch(0.28 0.06 145);
  --warn:      oklch(0.80 0.15 50);
  --warn-soft: oklch(0.30 0.06 50);
  --warn-bg:   oklch(0.25 0.05 70);
  --warn-text: oklch(0.82 0.13 60);
  --info:      oklch(0.75 0.15 255);
  --info-soft: oklch(0.25 0.06 255);
  --btn-danger: oklch(0.62 0.19 25);
  --glass:     rgba(28,28,30,.72);
  --glass-bd:  rgba(255,255,255,.08);
  --shadow:    0 1px 2px rgba(0,0,0,.3), 0 8px 24px rgba(0,0,0,.4);
  --shadow-lg: 0 8px 24px rgba(0,0,0,.5), 0 24px 60px rgba(0,0,0,.6);
  --border:    var(--line);
}

/* RESET + BASE */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
  font-family: var(--font-ui);
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  letter-spacing: -0.005em;
  transition: background .2s, color .2s;
}
a { color: inherit; text-decoration: none; touch-action: manipulation; }
button { font-family: var(--font-ui); touch-action: manipulation; }
input, textarea, select { font-family: var(--font-ui); font-size: 16px; }
*:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* LAYOUT */
.app-shell {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  max-width: 680px;
  margin: 0 auto;
  position: relative;
}

.header {
  flex: 0 0 auto;
  padding: calc(10px + env(safe-area-inset-top, 0px)) 18px 12px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: var(--glass);
  backdrop-filter: blur(40px) saturate(180%);
  -webkit-backdrop-filter: blur(40px) saturate(180%);
  border-bottom: 1px solid var(--glass-bd);
  position: sticky;
  top: 0;
  z-index: 20;
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  text-decoration: none;
  transition: opacity .15s;
}
.brand:hover { opacity: .75; }
.brand .mark {
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  display: block;
}
[data-theme="dark"] .brand .mark { filter: invert(1); }
.brand .name {
  font-weight: 700;
  letter-spacing: -.02em;
  font-size: 18px;
  color: var(--ink);
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.theme-fab {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--glass);
  backdrop-filter: blur(40px) saturate(180%);
  -webkit-backdrop-filter: blur(40px) saturate(180%);
  border: 1px solid var(--glass-bd);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--ink);
}
.theme-fab svg { width: 16px; height: 16px; }


.scroll {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  /* Reserves room below the fixed tabbar for trailing page content - tall
     enough for two stacked form-actions buttons (e.g. Add Game + Save PDF
     on the Games tab), not just one. */
  padding-bottom: calc(140px + env(safe-area-inset-bottom, 0px));
}

/* TABBAR */
.tabbar {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(14px + env(safe-area-inset-bottom, 0px));
  width: calc(min(680px, 100vw) - 28px);
  background: var(--glass);
  backdrop-filter: blur(40px) saturate(180%);
  -webkit-backdrop-filter: blur(40px) saturate(180%);
  border: 1px solid var(--glass-bd);
  border-radius: 26px;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  padding: 6px;
  z-index: 30;
  box-shadow: var(--shadow-lg);
  transition: padding .38s cubic-bezier(.4,0,.2,1), width .38s cubic-bezier(.4,0,.2,1);
}
.tabbar a {
  appearance: none;
  border: none;
  background: transparent;
  color: var(--ink-3);
  cursor: pointer;
  padding: 6px 4px 5px;
  border-radius: 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  font: 500 9.5px/1.1 var(--font-ui);
  letter-spacing: -.005em;
  transition: color .15s, background .15s, padding .38s cubic-bezier(.4,0,.2,1), gap .38s cubic-bezier(.4,0,.2,1);
  text-decoration: none;
}
.tabbar a.active { color: var(--ink); }
.tabbar a svg { width: 20px; height: 20px; stroke-width: 1.7; display: block; transition: width .38s cubic-bezier(.4,0,.2,1), height .38s cubic-bezier(.4,0,.2,1); }
.tabbar a.active svg { stroke-width: 2.1; }
.tabbar-label {
  display: block;
  max-height: 12px;
  opacity: 1;
  overflow: hidden;
  transition: max-height .34s cubic-bezier(.4,0,.2,1), opacity .22s ease;
}

/* Compact state: applied while scrolling down, past the top of the page.
   Width is sized for 5 icon-only items (18px icon + horizontal padding) -
   bump this if another tab is ever added. */
.tabbar.is-compact { padding: 4px; width: 240px; }
.tabbar.is-compact a { padding: 9px 8px; gap: 0; }
.tabbar.is-compact a svg { width: 18px; height: 18px; }
.tabbar.is-compact .tabbar-label { max-height: 0; opacity: 0; transition: max-height .34s cubic-bezier(.4,0,.2,1), opacity .12s ease; }

/* ATOMS */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 24px;
  padding: 0 10px;
  border-radius: 999px;
  font: 600 11px/1 var(--font-ui);
  letter-spacing: -.005em;
  background: var(--bg-2);
  color: var(--ink-2);
  white-space: nowrap;
}
.pill.live  { background: var(--good-soft); color: var(--good); }
.pill.done  { background: var(--bg-2); color: var(--ink-3); }
.pill.setup { background: var(--warn-soft); color: var(--warn); }
.pill.admin { background: var(--info-soft); color: var(--info); }
.pill.scheduled { background: var(--info-soft); color: var(--info); }
.pill.dot::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  animation: pulse 1.6s ease-in-out infinite;
}
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.35} }

.avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink);
  display: grid;
  place-items: center;
  font-weight: 600;
  font-size: 12px;
  letter-spacing: -.005em;
  flex: 0 0 auto;
}
.avatar.tone-1 { background: oklch(0.92 0.04 145); color: oklch(0.30 0.10 145); }
.avatar.tone-2 { background: oklch(0.92 0.05 240); color: oklch(0.30 0.12 240); }
.avatar.tone-3 { background: oklch(0.93 0.06 50);  color: oklch(0.40 0.13 50); }
.avatar.tone-4 { background: oklch(0.92 0.05 320); color: oklch(0.32 0.12 320); }
html[data-theme="dark"] .avatar.tone-1 { background: oklch(0.30 0.08 145); color: oklch(0.92 0.10 145); }
html[data-theme="dark"] .avatar.tone-2 { background: oklch(0.30 0.10 240); color: oklch(0.92 0.10 240); }
html[data-theme="dark"] .avatar.tone-3 { background: oklch(0.32 0.10 50);  color: oklch(0.92 0.12 50); }
html[data-theme="dark"] .avatar.tone-4 { background: oklch(0.30 0.10 320); color: oklch(0.92 0.10 320); }

.progress {
  height: 6px;
  background: var(--bg-2);
  border-radius: 999px;
  overflow: hidden;
}
.progress > i {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: 999px;
  transition: width .3s;
}

.progress-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
  font-size: 11.5px;
  color: var(--ink-3);
  font-weight: 500;
}
.progress-row .progress { flex: 1; }
.progress-row b { color: var(--ink); font-weight: 600; }

.cta {
  appearance: none;
  border: none;
  cursor: pointer;
  width: 100%;
  background: var(--accent);
  color: var(--on-accent);
  font: 600 15px/1 var(--font-ui);
  letter-spacing: -.01em;
  padding: 15px;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  transition: opacity .15s;
}
.cta:active { opacity: .85; }
.cta.ghost { background: var(--bg-2); color: var(--ink); }

.display {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -.03em;
  line-height: 1.02;
}
.num { font-variant-numeric: tabular-nums; }

/* FLASH MESSAGES */
.flashes {
  padding: 10px 20px 0;
  display: grid;
  gap: 8px;
}
.flash {
  padding: 12px 16px;
  border-radius: 16px;
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: -.005em;
}
.flash.success { background: var(--good-soft); color: var(--good); }
.flash.error   { background: var(--warn-soft); color: var(--warn); }
.flash.info    { background: var(--bg-2); color: var(--ink-2); }
.flash.warning { background: var(--warn-bg); color: var(--warn-text); }

/* HOME */
.hero {
  padding: 18px 20px 6px;
}
.hero .greet {
  font-size: 14px;
  color: var(--ink-3);
  font-weight: 500;
  margin-bottom: 4px;
}
.hero h1 {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 34px;
  line-height: 1.05;
  letter-spacing: -.035em;
  margin-bottom: 18px;
  color: var(--ink);
}

/* RESUME CARD */
.resume-card {
  margin: 0 20px;
  background: var(--paper);
  border-radius: 28px;
  padding: 18px;
  box-shadow: var(--shadow);
  position: relative;
  overflow: hidden;
  /* On desktop, .scroll is a column flexbox and this card is a flex item.
     overflow:hidden makes its flexbox automatic minimum height compute to 0
     instead of its natural content height (spec behavior), so on a short
     viewport flexbox can shrink this card below its content instead of
     letting .scroll's own overflow-y:auto scroll the page - silently
     clipping whichever child (typically the CTA button) no longer fits,
     rather than visibly overflowing. flex-shrink:0 keeps the card at its
     natural height so the page scrolls instead of clipping. */
  flex-shrink: 0;
  /* Previously a -webkit-mask-image hack sat here to work around Safari not
     clipping a compositing-layer-promoted child (.cta, promoted because it
     transitioned `transform`) to this card's rounded corners. That mask
     itself introduced new bugs (a gradient fade hid the CTA text on tall
     cards; a solid mask still failed in the desktop layout's nested
     overflow:hidden ancestors). Root cause fixed instead: .cta no longer
     transitions `transform` (see .cta below), so it's never promoted to its
     own layer, and plain overflow:hidden clips it correctly everywhere. */
}
/* DASHBOARD: card rows. Mobile default stacks everything; desktop lays
   the current-game/new-tournament pair and the stats/live/scheduled/history
   group into their own rows (see the min-width:900px override below). */
#dash-top-row {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin: 0 20px 20px;
}
#dash-top-row > .resume-card { margin: 0; }
#dash-stats-row {
  display: grid;
  /* Live plus exactly one of Scheduled/History (never both, never
     neither) - always two tiles, so two equal columns, not three. */
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin: 0 20px 20px;
}
/* DOM order is now Live, My Stats, History (for the desktop 1fr-2fr-1fr
   row below) - `order:-1` keeps mobile's original visual order (My Stats
   full-width on top, Live/History below it) unaffected. */
#dash-stats-row > .overview-card { grid-column: 1 / -1; margin: 0; order: -1; }
#dash-stats-row > .dash-tile { margin: 0; }

/* New Tournament card's feature list is desktop-only (see the
   min-width:900px override) - hidden by default so mobile's card is
   unchanged. */
.nt-features { display: none; }

.rc-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.new-tournament-card .rc-head { margin-bottom: 16px; }
.rc-head .l { display: flex; align-items: center; gap: 10px; }
.rc-head .ico-tile {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: var(--bg-2);
  display: grid;
  place-items: center;
  color: var(--ink);
  flex: 0 0 auto;
}
.rc-head .meta { font-size: 11.5px; color: var(--ink-3); margin-top: 2px; font-weight: 500; }
.rc-head .title { font-size: 15px; font-weight: 600; color: var(--ink); letter-spacing: -.01em; }

.rc-stats {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  margin-bottom: 14px;
}
.rc-stat {
  background: var(--bg-2);
  border-radius: 18px;
  padding: 12px;
}
.rc-stat .k { font-size: 11px; color: var(--ink-3); font-weight: 500; margin-bottom: 6px; letter-spacing: -.005em; }
.rc-stat .v {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 24px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.025em;
  line-height: 1;
}
.rc-stat .v small { font-size: 13px; color: var(--ink-3); font-weight: 500; margin-left: 1px; }

/* TILES */
.tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  padding: 18px 20px 6px;
}
.tile {
  background: var(--paper);
  border-radius: 24px;
  padding: 16px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-height: 120px;
  box-shadow: var(--shadow);
  transition: transform .12s;
  text-decoration: none;
  color: inherit;
}
.tile:active { transform: scale(.98); }
.tile .top { display: flex; justify-content: space-between; align-items: center; }
.tile .label { font-size: 13px; font-weight: 500; color: var(--ink-2); letter-spacing: -.005em; }
.tile .ico-tile {
  width: 30px;
  height: 30px;
  border-radius: 9px;
  background: var(--bg-2);
  display: grid;
  place-items: center;
  color: var(--ink-2);
}
.tile .count {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 36px;
  letter-spacing: -.035em;
  color: var(--ink);
  line-height: 1;
  margin-top: auto;
}
.tile .hint { font-size: 11.5px; color: var(--ink-3); font-weight: 500; margin-top: 4px; }

/* OVERVIEW CARD */
.overview-card {
  margin: 8px 20px 14px;
  background: var(--paper);
  border-radius: 24px;
  padding: 16px 18px 18px;
  box-shadow: var(--shadow);
}
.overview-head {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--ink-3);
  margin-bottom: 16px;
}
/* Home highlights: one lead line with two supporting ones under it. */
.hl-lead {
  font-size: 16.5px;
  line-height: 1.35;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -.01em;
  margin-bottom: 10px;
}
.hl-rest {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
  line-height: 1.45;
  font-weight: 500;
  color: var(--ink-3);
  margin-bottom: 14px;
}
/* Form guide. Filled is a win, hollow a loss - the difference has to read
   without colour, so it is the fill that carries it, not a hue. */
.hl-form {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}
.hl-form-label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-3);
}
.hl-form-pips { display: inline-flex; gap: 7px; }
.hl-pip {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1.5px solid var(--line);
  background: transparent;
}
.hl-pip.is-win { background: var(--ink); border-color: var(--ink); }
/* The most recent result, which leads the row. A ring rather than a size
   or colour change, so it reads the same on a filled pip and a hollow one.
   The paper-coloured inner ring is the gap. */
.hl-pip.is-latest {
  box-shadow: 0 0 0 2px var(--paper), 0 0 0 3.5px var(--line);
}
.hl-form-pips { padding: 3px 4px; margin: -3px -4px; }

.overview-grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
}
.ov-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 0 6px;
  position: relative;
}
.ov-item + .ov-item {
  border-left: 1px solid var(--line-2);
}
.ov-ico {
  width: 32px;
  height: 32px;
  border-radius: 9px;
  background: var(--bg-2);
  display: grid;
  place-items: center;
  color: var(--ink-2);
  margin-bottom: 10px;
}
.ov-v {
  font-family: var(--font-display);
  font-weight: 700;
  /* Eased down from 26px. These are the reference under the card's
     highlights, and at the larger size they pulled the eye past the
     sentences that are the reason to look at it. */
  font-size: 22px;
  color: var(--ink);
  letter-spacing: -.025em;
  line-height: 1;
  margin-bottom: 4px;
}
.ov-k {
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-3);
  letter-spacing: -.005em;
}

/* SECTION HEADER */
.section-h {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 24px 20px 10px;
}
.section-h .t {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 20px;
  letter-spacing: -.025em;
  color: var(--ink);
}
.section-h .r {
  font-size: 13px;
  color: var(--ink);
  font-weight: 500;
  text-decoration: none;
  letter-spacing: -.005em;
}

/* LIST CARDS */
.list-card {
  background: var(--paper);
  border-radius: 24px;
  padding: 6px;
  margin: 0 20px 12px;
  box-shadow: var(--shadow);
}
.li {
  padding: 14px;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 12px;
  border-radius: 18px;
  cursor: pointer;
  transition: background .12s;
  text-decoration: none;
  color: inherit;
  position: relative;
}
.li + .li::before {
  content: "";
  position: absolute;
  left: 60px;
  right: 14px;
  top: 0;
  height: 1px;
  background: var(--line-2);
}
.li:active { background: var(--bg-2); }
.li .ico-tile {
  width: 40px;
  height: 40px;
  border-radius: 12px;
  background: var(--bg-2);
  display: grid;
  place-items: center;
  color: var(--ink);
  flex: 0 0 auto;
}
/* .li is a CSS grid row (auto 1fr auto) - a grid item's automatic minimum
   width defaults to its content's own intrinsic size, not 0, so without
   this the long tournament-name span's un-truncated width (its .nm row
   sets white-space:nowrap) was winning: .body refused to shrink below
   that, forcing the whole row wider than the screen and pushing the
   Admin/Live pills in .end off the right edge entirely instead of the
   name eliding with "…" as its own overflow:hidden/text-overflow:ellipsis
   already intended. */
.li .body { min-width: 0; }
.li .body .nm { font-weight: 600; font-size: 15px; letter-spacing: -.01em; color: var(--ink); }
.li .body .sub { font-size: 12px; color: var(--ink-3); margin-top: 2px; font-weight: 500; }
.li .body .sub b { color: var(--ink-2); font-weight: 600; }
.li .end { display: flex; align-items: center; gap: 6px; }
.li .chev { width: 14px; height: 14px; color: var(--ink-3); flex: 0 0 auto; }

.li.history .winner {
  position: absolute;
  bottom: 7px;
  right: 34px;
  font-size: 10px;
  line-height: 1;
  color: var(--ink-3);
  font-weight: 500;
  white-space: nowrap;
  pointer-events: none;
}

/* TOURNAMENT HEADER */
.tour-header {
  padding: 6px 20px 10px;
  background: var(--bg);
}
/* New Tournament has no back button above its title (unlike other
   tour-header pages), so its own 6px top padding reads as too tight -
   give this page a bit more breathing room above "New Tournament". */
#new-tournament-header {
  padding-top: 22px;
}
/* Same reasoning as #new-tournament-header above - the tournament page's
   own header has no back button either, so it reads too tight at the
   shared 6px top padding. */
#fixtures-header {
  padding-top: 22px;
}
.tour-header .back {
  appearance: none;
  border: 0;
  background: transparent;
  cursor: pointer;
  font: 500 15px/1 var(--font-ui);
  color: var(--ink);
  padding: 8px 0;
  margin-bottom: 4px;
  letter-spacing: -.01em;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  text-decoration: none;
}
.tour-header .back svg { width: 18px; height: 18px; stroke-width: 2.4; }
.tour-header h2 {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 28px;
  line-height: 1.05;
  letter-spacing: -.03em;
  margin-top: 6px;
  margin-bottom: 8px;
  color: var(--ink);
}
.tour-header .meta-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tour-header .meta-row > span:not(.pill) { font-size: 13px; color: var(--ink-3); font-weight: 500; }
.tour-header .meta-row b { color: var(--ink-2); font-weight: 600; }

/* SUBNAV — segmented control */
.subnav-wrap {
  padding: 0 20px 8px;
  background: var(--bg);
  position: sticky;
  top: 53px;
  z-index: 10;
}
.subnav {
  display: grid;
  /* Column count follows however many tab buttons actually render (2 when
     Playoffs is hidden, 3 otherwise) instead of a hardcoded 3, so 2 tabs
     split the bar evenly instead of each taking a cramped 1/3 with a
     blank third column. */
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  background: var(--bg-2);
  border-radius: 14px;
  padding: 3px;
}
.subnav button {
  appearance: none;
  border: 0;
  background: transparent;
  cursor: pointer;
  padding: 9px 4px;
  border-radius: 11px;
  font: 600 13px/1 var(--font-ui);
  color: var(--ink-3);
  letter-spacing: -.01em;
  transition: background .15s, color .15s, box-shadow .15s;
}
.subnav button.active {
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 1px 3px rgba(0,0,0,.08);
}

/* GROUP SUB-NAV — horizontal scrollable pill row (group count varies, unlike
   the fixed 3-way Games/Standings/Finals subnav above) */
.group-subnav {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding: 4px 20px 12px;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  /* Stack directly beneath the sticky Games/Standings/Finals subnav above
     (position:sticky, top:53px) instead of scrolling out of view with the
     rest of the page - needs its own opaque background since it's now
     sticky too, so content scrolling underneath doesn't show through. */
  background: var(--bg);
  position: sticky;
  top: 101px;
  z-index: 9;
}
.group-subnav::-webkit-scrollbar { display: none; }
.group-subnav button {
  appearance: none;
  border: 1.5px solid var(--line);
  background: var(--paper);
  cursor: pointer;
  padding: 7px 14px;
  border-radius: 999px;
  font: 600 13px/1 var(--font-ui);
  color: var(--ink-2);
  white-space: nowrap;
  flex-shrink: 0;
  transition: background .15s, color .15s, border-color .15s;
}
.group-subnav button.active {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}

/* Same pill treatment as .group-subnav, but not sticky: when both rows
   render together (a grouped, courts-enabled tournament with a specific
   group selected), a second sticky row would need its own hardcoded top
   offset stacked under the first - fragile against the first row's height
   changing. Scrolling away with the page is an acceptable tradeoff here. */
.round-subnav {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding: 4px 20px 12px;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.round-subnav::-webkit-scrollbar { display: none; }
.round-subnav button {
  appearance: none;
  border: 1.5px solid var(--line);
  background: var(--paper);
  cursor: pointer;
  padding: 7px 14px;
  border-radius: 999px;
  font: 600 13px/1 var(--font-ui);
  color: var(--ink-2);
  white-space: nowrap;
  flex-shrink: 0;
  transition: background .15s, color .15s, border-color .15s;
}
.round-subnav button.active {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}

.qualified-badge {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .03em;
  padding: 0 8px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  flex-shrink: 0;
}
.qualified-badge.auto { background: var(--accent); color: var(--on-accent); }
.qualified-badge.wildcard { background: var(--bg-3); color: var(--ink-2); border: 1px solid var(--border); }

/* GAMES */
.round-h {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px 8px;
}
.round-h .t { font: 600 13px/1 var(--font-ui); color: var(--ink); letter-spacing: -.005em; }
.round-h .e { font: 500 12px/1 var(--font-ui); color: var(--ink-3); letter-spacing: -.005em; }

.game-wrap {
  margin: 0 20px 8px;
  background: var(--paper);
  border-radius: 22px;
  box-shadow: var(--shadow);
  overflow: hidden;
}
/* Games tab "Overall" view: a label dividing each group's run of games. */
.games-group-header {
  padding: 14px 16px 6px;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-3);
}
.games-group-header:not(:first-child) {
  border-top: 1px solid var(--line-2);
  margin-top: 4px;
}
/* Session heading in the fixture list: "Tue 19 Aug". Makes a multi-week
   tournament read as separate evenings rather than one long list. Sits above
   the group header's weight, since a date is the coarser division - you look
   for tonight first, then your group within it. */
.games-session-header {
  padding: 16px 16px 6px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: -.01em;
  color: var(--ink);
}
.games-session-header:not(:first-child) {
  border-top: 1px solid var(--line-2);
  margin-top: 6px;
}
/* Scheduled start time, shown beside the court badge - together they answer
   "when and where". Quieter than the court badge: the court is what you act
   on (tap to swap), the time is only ever informational. */
.slot-badge {
  /* Pushed right so time and court read as one "when and where" pair. The
     court badge's own margin-left:auto is cancelled below, or the two autos
     would split the free space and separate them. */
  margin-left: auto;
  font: 600 11px/1 var(--font-ui);
  letter-spacing: .02em;
  color: var(--ink-3);
  background: var(--bg-2);
  border-radius: 999px;
  padding: 5px 9px;
  white-space: nowrap;
  flex-shrink: 0;
}
.slot-badge + .court-badge { margin-left: 0; }

/* Playoffs tab: a "Round N" label dividing a bracket stage's matches when
   a stage spans multiple rounds. */
.games-round-header {
  padding: 12px 16px 4px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--ink-3);
}
.games-round-header:not(:first-child) {
  border-top: 1px solid var(--line-2);
  margin-top: 4px;
}
/* Playoffs tab: each round's name (Quarterfinal, Semifinal, ...) needs to
   read clearly at a glance, not as a quiet caption above the cards - was
   an 11px muted eyebrow label, easy to miss when scanning for which
   round you're looking at. */
.bracket-stage-heading {
  text-transform: uppercase;
  font-size: 15px;
  letter-spacing: .02em;
  color: var(--ink);
  font-weight: 800;
  margin: 0 20px 10px;
}
/* Best-of-N: all of a match's legs live in one visually distinct band
   within the games list, instead of reading as several unrelated games -
   a tint (not a second nested card) keeps this from stacking card chrome
   inside .game-wrap's own card. */
.match-card {
  background: var(--bg-2);
}
.match-card .gc-wrap + .gc-wrap .game-card {
  border-top: 1px solid var(--line-2);
}
/* Games tab's match-card no longer nests each leg in its own .gc-wrap
   (the whole match-card is the single swipeable/hoverable delete unit
   now - see _games_list.html) - its legs are .game-card siblings
   directly, so the divider needs its own selector for that flatter
   structure. The Finals tab's match-card (_bracket_card.html) still
   nests per-leg .gc-wrap, which the rule above continues to cover. */
.match-card .game-card + .game-card {
  border-top: 1px solid var(--line-2);
}
/* Games tab specifically: each game gets its own card (paper/shadow/
   radius) instead of the whole list sharing one big card - matches the
   desktop grid's per-card look on mobile too, just stacked in one column
   instead of a 4-across grid (see the desktop override further down).
   #tab-games wins over the plain .game-wrap/.match-card rules above
   regardless of source order (ID beats class), so this is safe to place
   anywhere in the file. */
#tab-games .game-wrap {
  background: none;
  box-shadow: none;
  border-radius: 0;
  overflow: visible;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
#tab-games .gc-wrap {
  background: var(--paper);
  border-radius: 18px;
  box-shadow: var(--shadow);
}
#tab-games .gc-delete-bg { border-radius: 18px 0 0 18px; }
/* Finals tab: same idea as #tab-games above - each MATCH (not each leg)
   gets its own full card (paper/shadow/radius), stacked in one column,
   instead of every match in a stage sharing one big .game-wrap card with
   just a tint between them. A best-of-N match's legs (game 1/2/3) stay
   grouped inside that one card - see the .gc-wrap divider rule further
   up - so a viewer can tell at a glance where one match ends and the
   next begins. This is the mobile/base layout; the desktop override
   further down switches to a 2-up grid with legs side by side instead
   of stacked. */
#tab-playoffs .bracket-stage-block .game-wrap {
  background: none;
  box-shadow: none;
  border-radius: 0;
  overflow: visible;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
#tab-playoffs .bracket-stage-block .game-wrap > .gc-wrap,
#tab-playoffs .bracket-stage-block .game-wrap > .match-card {
  background: var(--paper);
  border-radius: 18px;
  box-shadow: var(--shadow);
  overflow: hidden;
}
/* Best-of-N match card: also carries .gc-wrap directly (see
   _games_list.html), which already gives it paper/shadow/radius via the
   #tab-games .gc-wrap rule above, plus overflow:hidden (from the base
   .gc-wrap rule in fixtures.html) so the delete-swipe panel stays
   clipped to the card's rounded corners - nothing further needed here. */
.game-card {
  padding: 14px 16px;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 12px;
  align-items: center;
  cursor: pointer;
  position: relative;
  transition: background .12s;
}
.game-card + .game-card { border-top: 1px solid var(--line-2); }
.game-card:active { background: var(--bg-2); }
.game-card .teams { display: grid; gap: 8px; }
.leg-label { font: 700 10px/1 var(--font-ui); color: var(--ink-3); text-transform: uppercase; letter-spacing: .05em; margin-bottom: 8px; }
/* Courts feature: leg-label and the Court badge share one row (see
   _games_list.html/_playoffs_card.html) - the badge always sits at the
   row's right edge via margin-left:auto, whether or not a leg-label is
   also present (a single-game match with courts on has only the badge). */
.leg-label-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.leg-label-row .leg-label { margin-bottom: 0; }
.court-badge {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--bg-3);
  color: var(--ink-2);
  font: 700 10px/1 var(--font-ui);
  text-transform: uppercase;
  letter-spacing: .03em;
  padding: 0 8px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  flex-shrink: 0;
  margin-left: auto;
  cursor: default;
}
.court-badge-editable { cursor: pointer; }
@media (hover: hover) and (pointer: fine) {
  .court-badge-editable:hover { background: var(--bg-2); }
}
.court-swap-popup {
  z-index: 70;
  background: var(--paper);
  border-radius: 14px;
  box-shadow: var(--shadow-lg);
  padding: 6px;
  width: 240px;
  max-width: calc(100vw - 16px);
}
.court-swap-popup-title {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--ink-3);
  padding: 6px 10px 4px;
}
.court-swap-popup-opt {
  display: block;
  width: 100%;
  text-align: left;
  appearance: none;
  border: 0;
  background: none;
  padding: 8px 10px;
  border-radius: 9px;
  font: 500 13px var(--font-ui);
  color: var(--ink);
  cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {
  .court-swap-popup-opt:hover { background: var(--bg-2); }
}
.team-row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 10px; }
.team-row .names-wrap { min-width: 0; }
.team-row .team-name-label { font-size: 13px; color: var(--ink); font-weight: 700; letter-spacing: -.005em; }
.team-row .names { font-size: 14px; color: var(--ink-2); font-weight: 500; letter-spacing: -.005em; }
.team-row .names-wrap .team-name-label + .names { font-size: 12px; color: var(--ink-3); font-weight: 500; }
.team-row .names .vs-name { color: var(--ink-3); }
.team-row.win .names { font-weight: 600; color: var(--ink); }
.team-row.win .team-name-label { color: var(--ink); }
.team-row.win .s { color: var(--ink); font-weight: 700; }
.team-row .s {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 20px;
  color: var(--ink-3);
  min-width: 24px;
  text-align: right;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}
.game-card .end { align-self: center; display: flex; align-items: center; gap: 6px; }
.game-card .enter {
  appearance: none;
  border: 0;
  background: var(--accent);
  color: var(--on-accent);
  cursor: pointer;
  font: 600 12px/1 var(--font-ui);
  letter-spacing: -.005em;
  padding: 9px 13px;
  border-radius: 999px;
}
.game-card.live { position: relative; }
.game-card.live::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--good);
  border-radius: 0 3px 3px 0;
}

/* SCORE SHEET */
.sheet-bg {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.4);
  z-index: 40;
  display: none;
  animation: fade .15s ease;
}
.sheet-bg.show { display: block; }
.sheet {
  position: fixed;
  left: 50%;
  transform: translateX(-50%) translateY(120%);
  bottom: 0;
  width: min(680px, 100vw);
  background: var(--glass);
  backdrop-filter: blur(50px) saturate(180%);
  -webkit-backdrop-filter: blur(50px) saturate(180%);
  border: 1px solid var(--glass-bd);
  border-radius: 36px 36px 0 0;
  padding: 18px 18px 34px;
  z-index: 41;
  transition: transform .26s cubic-bezier(.32,.72,0,1);
  box-shadow: var(--shadow-lg);
}
.sheet.show {
  transform: translateX(-50%) translateY(0);
}
.sheet .grab {
  width: 36px;
  height: 5px;
  background: var(--ink-3);
  opacity: .4;
  border-radius: 999px;
  margin: 0 auto 14px;
}
.sheet h4 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 20px;
  margin-bottom: 4px;
  letter-spacing: -.02em;
  color: var(--ink);
  text-align: center;
}
.sheet .desc {
  font-size: 13px;
  color: var(--ink-3);
  margin-bottom: 18px;
  font-weight: 500;
  text-align: center;
}

.score-entry {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
}
.score-entry .team {
  text-align: center;
  padding: 16px 8px;
  border-radius: 24px;
  background: var(--paper);
  transition: outline .12s;
  outline: 2px solid transparent;
}
.score-entry .team.active { outline-color: var(--ink); }
.score-entry .team .nm {
  font-size: 11.5px;
  color: var(--ink-2);
  margin-bottom: 8px;
  font-weight: 600;
  line-height: 1.3;
}
.score-entry .team .num {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 48px;
  line-height: 1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.04em;
  min-height: 48px;
}
.score-entry .vs {
  font: 600 11px/1 var(--font-ui);
  color: var(--ink-3);
  letter-spacing: .04em;
}

.keypad-team-toggle {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
  background: var(--bg-2);
  padding: 3px;
  border-radius: 14px;
  margin-bottom: 12px;
}
.keypad-team-toggle button {
  appearance: none;
  border: 0;
  background: transparent;
  cursor: pointer;
  padding: 10px;
  border-radius: 11px;
  font: 600 12.5px/1 var(--font-ui);
  color: var(--ink-3);
  letter-spacing: -.005em;
  transition: background .15s, color .15s, box-shadow .15s;
}
.keypad-team-toggle button.active {
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 1px 3px rgba(0,0,0,.08);
}

.keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-bottom: 8px;
}
.keypad button {
  appearance: none;
  border: 0;
  background: var(--paper);
  padding: 16px 0;
  border-radius: 18px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 22px;
  cursor: pointer;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.025em;
  transition: transform .08s, background .1s;
}
.keypad button:active { transform: scale(.95); background: var(--bg-2); }
.keypad button.act {
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -.005em;
}

/* STANDINGS */
.stand-card {
  /* Standings only ever renders inside the Standings modal now (it used
     to be a full-width page tab, where this side margin made sense) -
     the modal already has its own padding, so this margin was pure
     extra squeeze on an already-narrow name column. */
  margin: 8px 0 0;
  background: var(--paper);
  border-radius: 22px;
  box-shadow: var(--shadow);
  overflow: hidden;
}
.stand-row {
  display: grid;
  /* Won and Played used to be separate 30px columns - merged into one
     "W-P" stat (e.g. "7-7") so the name column gets that width back;
     a name/team's real content is far more useful screen space than a
     redundant Won/Played split most viewers read as one number anyway. */
  grid-template-columns: 24px 1fr 50px 44px;
  align-items: center;
  gap: 8px;
  padding: 13px 16px;
  font-size: 13.5px;
  position: relative;
}
.stand-row + .stand-row::before {
  content: "";
  position: absolute;
  left: 60px;
  right: 16px;
  top: 0;
  height: 1px;
  background: var(--line-2);
}
.stand-row.head { padding: 10px 16px; background: transparent; }
.stand-row.head + .stand-row::before { display: none; }
.stand-row.head > span { font: 600 11px/1 var(--font-ui); color: var(--ink-3); letter-spacing: -.005em; }
.stand-row .rk { font: 600 13px/1 var(--font-ui); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.stand-row .who { display: flex; align-items: center; gap: 9px; font-weight: 500; letter-spacing: -.005em; color: var(--ink); }
.stand-row .who .avatar { width: 26px; height: 26px; font-size: 10.5px; }
.stand-row .num { font-variant-numeric: tabular-nums; text-align: right; font-weight: 500; color: var(--ink-2); }
.stand-row .pd { font-weight: 600; text-align: right; font-variant-numeric: tabular-nums; }
.stand-row .pd.pos { color: var(--good); }
.stand-row .pd.neg { color: var(--ink-3); }
.stand-row.finalist .rk { color: var(--ink); font-weight: 700; }
.stand-row.finalist .who { font-weight: 600; }

.finalist-callout {
  margin: 18px 20px 0;
  padding: 36px 24px;
  background: var(--paper);
  border-radius: 22px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 8px;
  box-shadow: var(--shadow);
}
.finalist-callout-title {
  font: 600 15px/1.2 var(--font-ui);
  color: var(--ink);
  letter-spacing: -.01em;
  margin-top: 4px;
}
.finalist-callout-sub {
  font: 400 13px/1.4 var(--font-ui);
  color: var(--ink-3);
  letter-spacing: -.005em;
}
.finalist-callout-note {
  font: 400 12px/1.4 var(--font-ui);
  color: var(--ink-3);
  opacity: .75;
  letter-spacing: -.005em;
  margin-top: 2px;
}

/* FINALS */
.finals-bg {
  margin: 18px 20px;
  background: var(--paper);
  border-radius: 28px;
  padding: 22px;
  position: relative;
  box-shadow: var(--shadow-lg);
}
.finals-bg .e {
  font: 600 12px/1 var(--font-ui);
  color: var(--ink-3);
  margin-bottom: 8px;
  letter-spacing: -.005em;
}
.finals-bg h2 {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 32px;
  line-height: 1.02;
  letter-spacing: -.035em;
  margin-bottom: 8px;
  color: var(--ink);
}
.finals-bg .sub {
  font-size: 13.5px;
  color: var(--ink-2);
  margin-bottom: 18px;
  line-height: 1.45;
  font-weight: 500;
  letter-spacing: -.005em;
}

.finals-card {
  background: var(--bg-2);
  border-radius: 20px;
  padding: 0;
  overflow: hidden;
}
.finals-team {
  padding: 14px 16px;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 12px;
  position: relative;
}
.finals-team + .finals-team::before {
  content: "";
  position: absolute;
  left: 16px;
  right: 16px;
  top: 0;
  height: 1px;
  background: var(--line-2);
}
.finals-team .seed {
  font: 600 11px/1 var(--font-ui);
  color: var(--ink-2);
  background: var(--paper);
  padding: 6px 9px;
  border-radius: 999px;
  letter-spacing: -.005em;
}
.finals-team .ppl { font-weight: 600; font-size: 14px; letter-spacing: -.005em; line-height: 1.25; color: var(--ink); }
.finals-team .ppl small { display: block; font-weight: 500; font-size: 11.5px; color: var(--ink-3); margin-top: 3px; letter-spacing: 0; }
.finals-team .num {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 30px;
  line-height: 1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.035em;
}

/* A best-of-N Final's legs (each its own .leg-label + .finals-card) used
   to just stack as loose siblings with an inline margin-top hack for
   spacing. Grouping them under one .finals-legs flex column replaces
   that hack with a plain gap, and gives the desktop row layout below
   something to switch flex-direction on. */
.finals-legs { display: flex; flex-direction: column; gap: 16px; }
@media (min-width: 900px) {
  /* Multiple legs sit side by side on desktop instead of stacking - a
     best-of-3/5 Final reads as one row of games instead of a long
     scroll. Single-leg finals (finals-legs without -multi) are
     untouched, still one full-width card. The hero card's oversized
     score/seed styling is scaled down here only, so 2-3 cards still
     fit comfortably in a row - single-leg finals keep the full-size
     hero look. */
  .finals-legs-multi { flex-direction: row; align-items: flex-start; gap: 14px; }
  .finals-legs-multi .finals-leg { flex: 1; min-width: 0; }
  .finals-legs-multi .finals-team { padding: 10px 12px; gap: 8px; }
  .finals-legs-multi .finals-team .seed { font-size: 10px; padding: 4px 7px; }
  .finals-legs-multi .finals-team .ppl { font-size: 12px; }
  .finals-legs-multi .finals-team .num { font-size: 20px; }

  /* Quarterfinal/Semifinal on desktop: the mobile base rules (further up
     this file) already give each match its own card, stacked in one
     column - widen that into a 2-up grid here, with a match's legs
     (game 1/2/3 of a best-of-N) sharing one row inside their card
     instead of stacking. Scoped to the Finals tab's own stage blocks so
     the Games tab's best-of-N round robin matches (same classes,
     different context) are untouched. */
  #tab-playoffs .bracket-stage-block .game-wrap {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
  }
  #tab-playoffs .bracket-stage-block .match-card {
    display: flex;
    flex-direction: row;
    align-items: stretch;
  }
  #tab-playoffs .bracket-stage-block .match-card .gc-wrap {
    flex: 1;
    min-width: 0;
  }
  #tab-playoffs .bracket-stage-block .match-card .gc-wrap + .gc-wrap .game-card {
    border-top: none;
    border-left: 1px solid var(--line-2);
  }
  /* A best-of-5 match (3 guaranteed legs) doesn't fit readably in half
     the 2-up grid - names and the "Game X of 5" label wrap onto several
     lines. Spanning both grid columns gives it the full row width
     instead, while a best-of-3 match (2 legs) still shares a row with
     another match. */
  #tab-playoffs .bracket-stage-block .match-card-wide {
    grid-column: 1 / -1;
  }
}

/* LEADERBOARD */
.lb-hero { padding: 18px 20px 6px; }
.lb-hero .greet { font-size: 14px; color: var(--ink-3); font-weight: 500; margin-bottom: 4px; }
.lb-empty-hero { font-size: 14px; color: var(--ink-3); margin: 0 0 20px; line-height: 1.5; }
.lb-hero h2 {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 34px;
  line-height: 1.05;
  letter-spacing: -.035em;
  margin-bottom: 18px;
  color: var(--ink);
}
.lb-hero .filters {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  background: var(--bg-2);
  border-radius: 14px;
  padding: 3px;
}
.lb-hero .filters button {
  appearance: none;
  border: 0;
  background: transparent;
  cursor: pointer;
  font: 600 13px/1 var(--font-ui);
  padding: 9px;
  border-radius: 11px;
  color: var(--ink-3);
  letter-spacing: -.005em;
  transition: background .15s, color .15s, box-shadow .15s;
}
.lb-hero .filters button.active {
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 1px 3px rgba(0,0,0,.08);
}

.lb-podium {
  display: grid;
  grid-template-columns: 1fr 1.15fr 1fr;
  gap: 8px;
  align-items: end;
  padding: 18px 20px 8px;
}
.podium-cell {
  background: var(--paper);
  border-radius: 22px;
  padding: 14px 8px;
  text-align: center;
  box-shadow: var(--shadow);
}
.podium-cell.gold   { padding: 30px 8px; }
.podium-cell.silver { padding: 18px 8px; }
.podium-cell.bronze { padding: 10px 8px; }
.podium-cell .pl { font: 600 11px/1 var(--font-ui); color: var(--ink-3); margin-bottom: 10px; letter-spacing: -.005em; }
.podium-cell.gold .pl { color: var(--ink); }
.podium-cell .av {
  margin: 0 auto 8px;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-weight: 600;
  background: var(--bg-2);
  color: var(--ink);
  font-size: 14px;
  letter-spacing: -.005em;
}
.podium-cell.gold .av { background: var(--ink); color: var(--bg); }
.podium-cell .nm { font-weight: 600; font-size: 12px; letter-spacing: -.01em; margin-bottom: 2px; color: var(--ink); word-break: break-word; }
.podium-cell .wins { font-size: 11.5px; color: var(--ink-3); font-weight: 500; font-variant-numeric: tabular-nums; }

.lb-table {
  margin: 14px 20px 0;
  background: var(--paper);
  border-radius: 22px;
  box-shadow: var(--shadow);
  overflow: hidden;
}
.lb-row {
  display: grid;
  grid-template-columns: 24px 1fr 50px 36px 50px;
  align-items: center;
  gap: 10px;
  padding: 13px 16px;
  position: relative;
}
.lb-row + .lb-row::before {
  content: "";
  position: absolute;
  left: 60px;
  right: 16px;
  top: 0;
  height: 1px;
  background: var(--line-2);
}
.lb-row.head { padding: 10px 16px; }
.lb-row.head + .lb-row::before { display: none; }
.lb-row.head > span { font: 600 11px/1 var(--font-ui); color: var(--ink-3); letter-spacing: -.005em; }
.lb-row .rk { font: 600 13px/1 var(--font-ui); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.lb-row .who { display: flex; align-items: center; gap: 10px; font-weight: 600; font-size: 14px; color: var(--ink); letter-spacing: -.005em; }
.lb-row .who .avatar { width: 28px; height: 28px; font-size: 11px; }
.lb-row .who small { display: block; color: var(--ink-3); font-weight: 500; font-size: 11px; margin-top: 2px; letter-spacing: -.005em; }
.lb-row .num { text-align: right; font-variant-numeric: tabular-nums; font-size: 13.5px; color: var(--ink-2); font-weight: 500; }
.lb-row .rate {
  text-align: right;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-size: 13.5px;
  color: var(--ink);
  font-family: var(--font-display);
  letter-spacing: -.02em;
}

/* NEW TOURNAMENT FORM */
.form-section { padding: 18px 20px; }

/* New Tournament "Setup" block: Tournament Name sits above this grid now
   (see start_tournament.html - it is the one field every organizer fills
   in regardless of path, so it moved above the escape card and Guided
   panel entirely). What's left here is one card holding every toggle -
   Split into groups, Assign courts to games, Number of players/teams,
   Lock score entry, Exclude from ranking - in that order, stacked on
   mobile. Still a grid (not a plain block) so a future second column has
   somewhere to land without restructuring this rule again. */
.tour-settings-grid {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-areas: "toggles";
  gap: 18px;
}
.tour-settings-top { display: flex; flex-direction: column; }
.tour-settings-toggles {
  grid-area: toggles;
  background: var(--paper);
  border-radius: 20px;
  box-shadow: var(--shadow);
  padding: 4px 16px;
}
/* Guided's hosts for controls it borrows from other steps are styled cards.
   While the control is not there - dates switched off, no best-of format to
   count - the card still painted its own white background, shadow and
   padding, leaving an empty pill floating in the panel. :empty rather than a
   JS toggle so it stays correct however the contents come and go. */
#guided-dates-body:empty,
#guided-court-names-body:empty,
#guided-counting-body:empty {
  display: none;
}

.tour-settings-toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px 0;
  font-size: 14px;
  color: var(--ink-2);
  font-weight: 500;
}
/* Desktop-only split of the one mobile card into two (see the
   min-width:900px override below and start_tournament.html) - on mobile
   these are display:contents, so their children render as plain direct
   items of .tour-settings-toggles, identical to before this existed. */
.tss-format-card, .tss-access-card {
  display: contents;
}
/* Every top-level item in the card (a toggle, or a plain stepper like
   Number of players) is a .tss-item - dividers go between these, never
   between a toggle and its own nested stepper (Number of groups/courts),
   which lives inside the same .tss-item as part of one unit. */
.tss-item + .tss-item {
  border-top: 1px solid var(--line-2);
}
/* The adjacent-sibling rule above matches by DOM position, not visibility
   - a hidden item (e.g. Groups/Courts in Quick mode) still counts as the
   "previous sibling" for CSS purposes, so whichever item actually renders
   first still gets a line above it. updateSettingsCardDividers() in
   start_tournament.html tags the true first-visible item with this class
   to strip it back off. */
.tss-item.tss-item-first {
  /* Same specificity as .tss-item + .tss-item (two class selectors each) -
     needs the compound form to actually win, not just come later. */
  border-top: none;
}
/* Mobile only: display:contents on .tss-format-card/.tss-access-card
   flattens their boxes for layout, but never makes their children real
   siblings of each other - .tss-item + .tss-item can't bridge across the
   two cards' content, so the divider between Format's last visible item
   and Access's first one has to be added explicitly (see
   updateSettingsCardDividers() in start_tournament.html), not just have
   a wrongly-present one stripped off. */
.tss-item-boundary {
  border-top: 1px solid var(--line-2);
}
.tss-nested-stepper {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 0 0 14px;
}
.tss-plain-stepper {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 0;
}
.tour-count-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}
.tour-count-row .stepper-label { margin-bottom: 0; padding-left: 14px; font-size: 17px; font-weight: 600; }
.tour-count-row .stepper-wrap { margin-bottom: 0; width: 160px; flex: 0 1 160px; min-width: 130px; }
/* ── Compact stepper row ──
   The small "label + [- n +]" row used for every count in the app: number of
   groups, players, teams, courts, matches per player, minutes per match,
   sessions, and the per-group player count.

   Add `compact-stepper` to the ROW, not the stepper itself. This used to be
   six parallel lists of IDs, and every new stepper silently rendered with no
   visible number until someone remembered to add it to all six - the base
   .stepper-wrap .v is `width: 0`, so a stepper outside the lists collapses
   its own value to nothing. That bug shipped three times before this became
   a class. */
.compact-stepper {
  /* Mobile: the stepper sits at the row's right edge, under the toggle above
     it, rather than bunched against its own label - and narrower, since it
     only ever holds a couple of digits. Desktop keeps the compact "label,
     then stepper beside it" layout (see the min-width:900px override). */
  justify-content: space-between;
}
.compact-stepper .stepper-label {
  font-size: 14px;
  font-weight: 500;
}
/* A top-level row in a settings card, rather than one nested under a toggle,
   shouldn't inherit .stepper-label's padding-left (which exists to align
   under a rounded input field) - it reads as indented beside the plain
   toggle-row labels above and below it. */
.tss-plain-stepper .stepper-label { padding-left: 0; }

.compact-stepper .stepper-wrap {
  height: 36px;
  padding: 4px;
  /* Wide enough for the largest value any of these allows (players/teams go
     to 100) plus two 28px buttons, with a little breathing room. Same width
     everywhere rather than a wider desktop-only size. */
  width: 100px;
  flex: 0 0 100px;
  min-width: 0;
  /* .stepper-wrap's base margin-bottom:14px suits a stepper stacked above
     other content; in these flex rows it only inflates the row height. */
  margin-bottom: 0;
}
.compact-stepper .stepper-wrap button {
  width: 28px;
  height: 28px;
  font-size: 15px;
}
.compact-stepper .stepper-wrap .v { font-size: 17px; }
/* .modal input carries margin-bottom:12px for stacked form fields, which also
   matches a stepper's value input inside a modal. The asymmetric margin (0
   top, 12px bottom) shifts what align-items:center treats as the flex item's
   centre, so the number sits visibly high in its box. */
.modal .compact-stepper .stepper-wrap .v { margin-bottom: 0; }
/* Number of courts sits directly under "Assign courts to games" in the
   settings modal, so it matches that toggle row's label (13px regular)
   rather than the 14px/500 shared by every other compact stepper. */
#courts-count-settings-row .stepper-label {
  font-size: 13px;
  font-weight: 400;
}
.review-summary-title {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--ink-3);
  margin-bottom: 10px;
}
.review-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line-2);
  font-size: 13.5px;
}
.review-row:last-child { border-bottom: none; }
.review-k { color: var(--ink-3); font-weight: 500; flex-shrink: 0; }
.review-v { color: var(--ink); font-weight: 600; text-align: right; }

label.field { display: block; margin-bottom: 14px; }
label.field .lab {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-2);
  margin-bottom: 8px;
  display: block;
  letter-spacing: -.005em;
  padding-left: 14px;
}
label.field input {
  width: 100%;
  border: 0;
  background: var(--paper);
  font: 500 16px/1.2 var(--font-ui);
  color: var(--ink);
  padding: 14px 16px;
  border-radius: 16px;
  outline: none;
  box-shadow: var(--shadow);
}
label.field input:focus { outline: 2px solid var(--ink); outline-offset: 0; }
label.field input::placeholder { color: var(--ink-3); }

.stepper-label {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-2);
  margin-bottom: 8px;
  display: block;
  letter-spacing: -.005em;
  padding-left: 14px;
}
/* Guided-setup hint: a quiet line under a control explaining what the current
   choice actually produces, in the same register as #mode-picker-note. Read
   at a glance rather than discovered by opening something, which is why most
   settings still don't pair one with an info tip (see .info-tip-panel below)
   even where one now exists. .warn is for "this works, but here's the
   tradeoff", never for an error - nothing here blocks submission. */
.setup-hint {
  font-size: 12px;
  font-weight: 500;
  line-height: 1.45;
  color: var(--ink-3);
  letter-spacing: -.005em;
  margin: 2px 0 12px;
}
.setup-hint.warn { color: var(--warn); }
.setup-hint b { color: var(--ink-2); font-weight: 600; }
/* Tap-to-apply: turns advice into one action instead of arithmetic the
   organizer has to do themselves. Sized to sit inline within the hint. */
.setup-hint-apply {
  appearance: none;
  border: 1.5px solid var(--line);
  background: var(--paper);
  color: var(--ink);
  font: 600 12px/1 var(--font-ui);
  letter-spacing: -.005em;
  padding: 5px 10px;
  border-radius: 999px;
  cursor: pointer;
  margin-left: 6px;
  vertical-align: baseline;
  transition: background .15s, border-color .15s;
}
.setup-hint-apply:hover { background: var(--bg-2); border-color: var(--ink-3); }
.setup-hint-apply:active { transform: scale(.97); }
/* Wraps every pill that follows a hint sentence (one pill, or "Use N" plus
   the round-robin revert together). Inline by default so desktop keeps the
   pill trailing the last word, same as before this span existed. */
.setup-hint-pills {
  display: inline;
}
@media (max-width: 899px) {
  /* Below the app's breakpoint the sentence wraps unpredictably, so a
     trailing inline pill lands wherever the text happens to run out - worse
     still with two. Forcing the pill group onto its own line, flush with
     the hint text's left edge, keeps it findable regardless of how the
     sentence above it wraps. */
  .setup-hint-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 8px;
  }
  /* The inline margin-left only made sense trailing text on the same line;
     the flex gap above replaces it here. */
  .setup-hint-pills .setup-hint-apply {
    margin-left: 0;
  }
}

/* ── Info tip: floating help bubble for a settings-row label ──
   Reusable (app/templates/start_tournament.html's attachInfoTip) - a row
   label wraps its "i" trigger in .info-tip-label, and the trigger's sibling
   panel (appended to <body> by attachInfoTip, so it isn't clipped by an
   ancestor with overflow:hidden) gets .info-tip-panel. Positioned with
   inline left/top from JS, not CSS anchoring, since it has to fit inside a
   390px viewport regardless of where its trigger sits. */
.info-tip-label {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.info-tip-trigger {
  appearance: none;
  border: 1.5px solid var(--line);
  background: var(--paper);
  color: var(--ink-3);
  width: 16px;
  height: 16px;
  border-radius: 50%;
  /* Not italic: an italic glyph leans right, so the box centres while the
     ink reads as offset inside a 16px circle. */
  font: 700 10px/1 var(--font-ui);
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
  transition: background .15s, border-color .15s, color .15s;
}
.info-tip-trigger:hover,
.info-tip-trigger:focus-visible,
.info-tip-trigger[aria-expanded="true"] {
  color: var(--ink);
  border-color: var(--ink-3);
  background: var(--bg-2);
}
.info-tip-trigger:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.info-tip-panel {
  position: fixed;
  z-index: 10010;
  width: 272px;
  max-width: calc(100vw - 20px);
  background: var(--paper);
  color: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: var(--shadow-lg);
  padding: 14px 16px;
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: -.005em;
}
.info-tip-panel[hidden] { display: none; }
.info-tip-panel p + p { margin-top: 8px; }
.info-tip-panel b { color: var(--ink); font-weight: 600; }
/* Small arrow pointing back at whatever trigger opened it - --tip-arrow-left
   is set from JS to the trigger's own horizontal centre, since the panel
   itself may have been shifted well clear of the trigger to stay on screen. */
.info-tip-panel::before {
  content: "";
  position: absolute;
  top: -5px;
  left: var(--tip-arrow-left, 20px);
  width: 9px;
  height: 9px;
  background: var(--paper);
  border-left: 1px solid var(--line);
  border-top: 1px solid var(--line);
  transform: translateX(-50%) rotate(45deg);
}
/* Flipped above the trigger (not enough room below it) - the arrow moves to
   the panel's bottom edge and points down instead. */
.info-tip-panel-flip::before {
  top: auto;
  bottom: -5px;
  border-left: none;
  border-top: none;
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

/* ── Schedule step (New Tournament wizard) ── */
/* Date/time inputs sit in one row on desktop and wrap on mobile. Native
   date/time controls are used deliberately: they bring the platform's own
   picker and locale formatting, which beats anything hand-rolled here. */
.schedule-fields {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 14px 0 4px;
}
.schedule-field { flex: 1 1 140px; min-width: 0; }
/* The date/time row specifically. As a wrapping flex row the three fields
   broke as "date + from" then "to" alone on the next line, which reads as
   though To belongs to something else. A date is also the widest of the
   three and the one you set first, so it gets its own line and the two
   times, which are a pair, share the one below. Grid rather than flex so
   the two times stay equal width instead of sizing to their content. */
.schedule-fields-datetime {
  display: grid;
  /* minmax(0, 1fr), not bare 1fr - a bare 1fr track has its own implicit
     min-width:auto, separate from and not overridden by min-width:0 on
     the grid items inside it, so the native date/time control's own
     content could still force a track wider than its fair share (eating
     the gap, then overflowing the card) no matter what the item's own
     min-width said. This is what every earlier attempt here was missing. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
  /* Hard safety net: the minmax(0, 1fr) fix above is correct CSS grid
     behavior but did not stop it in practice - iOS's native date/time
     controls can render their own internal content wider than the box
     asked for, and no ordinary sizing property reaches inside a native
     control's own rendering. This clips anything that tries to at the
     container's own edge, guaranteeing nothing can visually extend past
     it regardless of why the control wanted to be wider. */
  overflow: hidden;
}
.schedule-fields-datetime .schedule-field-full { grid-column: 1 / -1; }
/* overflow:hidden here, not just on the input inside (below) or the grid
   container above - a native date/time control is a replaced element that
   can lay itself out wider than the box it's told to fill, and self-clip
   via its own overflow:hidden is not reliable for that. .schedule-field is
   a plain <label>, not a replaced element, so it always sizes to its grid
   track exactly; clipping there guarantees the control can never bleed
   into its neighbor's track (killing the gap between From and To) even
   when the outer container's own edge-only clip lets it get that far. */
.schedule-fields-datetime .schedule-field { min-width: 0; overflow: hidden; }
/* From/To specifically (not Date, which keeps the full-size input) -
   smaller text and tighter side padding so the pair actually fits two
   to a 50/50 row on a phone, rather than relying on the native time
   control shrinking to whatever room a plain 14px/10px-padding input
   left it, which it wouldn't - see the overflow this was replacing. */
.schedule-fields-datetime .schedule-field:not(.schedule-field-full) input {
  font-size: 12.5px;
  padding: 0 4px;
}
.schedule-field .stepper-label { padding-left: 2px; margin-bottom: 4px; }
.schedule-field input {
  /* Without this, iOS Safari ignores border/background/border-radius/
     padding on a native date/time input entirely and renders it as bare
     text with the system's own (unstyled, intrinsically-sized) control
     next to it - which is also what was actually causing the touching-
     boxes/overflow symptoms on a real iPhone, not the grid sizing this
     file spent so much effort on above. Desktop browsers apply the box
     styling either way, which is why this only showed up on-device. */
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  height: 38px;
  border: 1.5px solid var(--line);
  background: var(--paper);
  color: var(--ink);
  font: 500 14px/1 var(--font-ui);
  padding: 0 10px;
  border-radius: 10px;
  outline: none;
  box-sizing: border-box;
  /* Same hard clip as .schedule-fields-datetime, one level down - that
     one only stops content bleeding past the row's own outer edge, not
     two adjacent boxes losing their gap to each other internally if the
     native control still wants to render wider than its own box. This
     caps each input to its own assigned width specifically. */
  overflow: hidden;
}
.schedule-field input:focus { border-color: var(--ink); }

.sched-session-list { display: flex; flex-direction: column; gap: 8px; padding: 8px 0 14px; }
.sched-session {
  background: var(--bg-2);
  border-radius: 10px;
  padding: 8px 10px;
  font-size: 13px;
  color: var(--ink-2);
  transition: background .15s;
}
/* The row itself - idx, date summary, chevron, remove button - is a nested
   flex container so an editable row can stack it above the expanded fields
   (.sched-session-edit) without fighting .sched-session's own layout. The
   duplicate dialog's read-only rows use this same nested markup even though
   nothing below ever expands, so one set of rules covers both. */
.sched-session-row { display: flex; align-items: center; gap: 10px; }
.sched-session .idx {
  flex: 0 0 auto;
  font: 600 11px/1 var(--font-ui);
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: .05em;
}
.sched-session .when { flex: 1; min-width: 0; color: var(--ink); font-weight: 500; }
.sched-session .when .dim { color: var(--ink-3); font-weight: 500; }
.sched-session .drop {
  appearance: none; border: none; background: none; cursor: pointer;
  color: var(--ink-3); padding: 4px; display: grid; place-items: center;
  position: relative;
}
/* Invisible hit-area, not the button itself, grows to a comfortable ~44px
   tap target - the visible icon stays 14px and the row's height/layout is
   untouched at either breakpoint. */
.sched-session .drop::after {
  content: '';
  position: absolute;
  inset: -11px;
}
.sched-session .drop:hover { color: var(--btn-danger); }

/* Tap-to-edit rows (New Tournament wizard + Tournament Settings). A row
   looking identical to static text is the known failure mode of "tap to
   reveal fields", so it gets its own cursor, a hover/active tint, and a
   chevron that rotates open - three small signals that together read as
   "this responds to a tap" without adding a visible button to every row. */
.sched-session.is-editable { cursor: pointer; }
.sched-session.is-editable:hover { background: var(--line-2); }
.sched-session.is-editable:active { background: var(--line); }
.sched-session.is-editable .sched-session-row { cursor: pointer; }
.sched-session .edit-chevron {
  flex: 0 0 auto;
  color: var(--ink-3);
  transition: transform .18s ease;
}
.sched-session.is-open { background: var(--paper); box-shadow: inset 0 0 0 1.5px var(--line); cursor: default; }
.sched-session.is-open .sched-session-row { cursor: default; }
.sched-session.is-open .edit-chevron { transform: rotate(90deg); color: var(--ink); }
/* Expanded fields reuse .schedule-fields/.schedule-field wholesale (the
   wizard's own date/time inputs) rather than a second style for the same
   three controls. */
.sched-session-edit { padding: 10px 0 2px; border-top: 1px dashed var(--line); margin-top: 8px; }
.sched-session-edit.schedule-fields { padding-top: 10px; }

.leg-counting-options { display: flex; flex-direction: column; gap: 10px; }
.leg-counting-options .cta { width: 100%; }
/* Tournament Settings' Match Format row only ever holds 3 short labels
   (Single game/Best of 3/Best of 5) - fits comfortably on one line at a
   smaller size, unlike .leg-counting-options' other uses (Counting,
   best-of-N confirm prompts) whose longer button text needs the full-
   width stacked layout above. Scoped to this one section rather than
   changing the shared class, so those don't get squeezed too. */
#match-format-section .leg-counting-options,
[id^="match-format-options-"] .leg-counting-options {
  flex-direction: row;
  gap: 6px;
}
#match-format-section .leg-counting-options .cta {
  width: auto;
  flex: 1;
  min-width: 0;
  font-size: 12px;
  padding: 10px 4px;
  white-space: nowrap;
}
[id^="match-format-options-"] .leg-counting-options .cta {
  width: auto;
  flex: 1;
  min-width: 0;
  font-size: 11.5px;
  padding: 10px 4px;
  line-height: 1.25;
}
.substep-heading {
  font-size: 19px;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -.01em;
  display: block;
  margin-bottom: 8px;
}
.select-mode-heading {
  font-size: 18px;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -.01em;
  display: block;
  margin-bottom: 12px;
}
.stepper-wrap {
  background: var(--paper);
  border-radius: 16px;
  padding: 6px;
  display: flex;
  align-items: center;
  gap: 0;
  box-shadow: var(--shadow);
  margin-bottom: 14px;
}
.stepper-wrap button {
  appearance: none;
  border: 0;
  background: var(--bg-2);
  width: 38px;
  height: 38px;
  border-radius: 11px;
  cursor: pointer;
  font: 500 18px/1 var(--font-ui);
  color: var(--ink);
  display: grid;
  place-items: center;
  transition: transform .1s;
}
.stepper-wrap button:active { transform: scale(.92); }
.stepper-wrap .v {
  flex: 1;
  text-align: center;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 22px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  background: none;
  border: none;
  outline: none;
  padding: 0;
  width: 0;
  min-width: 0;
}

.player-list {
  background: var(--paper);
  border-radius: 24px;
  padding: 6px;
  box-shadow: var(--shadow);
  margin-bottom: 6px;
}
.player-row {
  padding: 12px 14px;
  display: flex;
  align-items: center;
  gap: 12px;
  border-radius: 18px;
  position: relative;
}
.player-row + .player-row::before {
  content: "";
  position: absolute;
  left: 48px;
  right: 14px;
  top: 0;
  height: 1px;
  background: var(--line-2);
}
.player-row .idx {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--bg-2);
  display: grid;
  place-items: center;
  font: 600 12px/1 var(--font-ui);
  color: var(--ink-2);
  flex: 0 0 auto;
  letter-spacing: -.005em;
}
.player-row .inputs { flex: 1; display: grid; gap: 0; }
.player-row .inputs input {
  border: 0;
  background: transparent;
  padding: 4px 6px;
  margin: 0 -6px;
  box-sizing: border-box;
  outline: none;
  width: calc(100% + 12px);
  font: 500 16px/1.2 var(--font-ui);
  color: var(--ink);
  letter-spacing: -.005em;
}
.player-row .inputs input.email { font-size: 16px; color: var(--ink-3); font-weight: 500; }
.player-row .inputs input::placeholder { color: var(--ink-3); opacity: .55; }
.player-row .inputs input.email::placeholder { color: var(--ink-3); opacity: .55; }
.player-row .inputs input.field-invalid { background: #fef2f2; }
.player-row .inputs input.field-invalid::placeholder { color: #b91c1c; opacity: .75; }

.suggest {
  position: absolute;
  left: 48px;
  right: 0;
  top: calc(100% - 4px);
  z-index: 20;
  background: var(--paper);
  border-radius: 16px;
  padding: 6px;
  box-shadow: var(--shadow-lg);
  display: none;
  border: 1px solid var(--glass-bd);
}
.suggest.show { display: block; }
.suggest button {
  display: flex;
  width: 100%;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border: 0;
  background: transparent;
  cursor: pointer;
  border-radius: 11px;
  text-align: left;
  font: 500 13.5px/1 var(--font-ui);
  color: var(--ink);
  letter-spacing: -.005em;
}
.suggest button:active { background: var(--bg-2); }
.suggest button .a {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--bg-2);
  display: grid;
  place-items: center;
  font: 600 10px/1 var(--font-ui);
  color: var(--ink-2);
}

.form-actions { padding: 6px 20px 24px; display: grid; gap: 10px; }

/* TOAST */
.toast {
  position: fixed;
  left: 50%;
  bottom: 110px;
  transform: translate(-50%, 12px);
  background: var(--glass);
  backdrop-filter: blur(40px) saturate(180%);
  -webkit-backdrop-filter: blur(40px) saturate(180%);
  color: var(--ink);
  padding: 12px 18px;
  border-radius: 999px;
  font-size: 13.5px;
  font-weight: 600;
  opacity: 0;
  pointer-events: none;
  transition: all .25s;
  z-index: 50;
  letter-spacing: -.005em;
  box-shadow: var(--shadow-lg);
  border: 1px solid var(--glass-bd);
  white-space: nowrap;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }

/* LIST CONTROLS (search/sort on Active/History/Leaderboard pages) */
.list-controls {
  padding: 0 20px 12px;
  display: grid;
  gap: 8px;
}
.search-input {
  width: 100%;
  border: 0;
  background: var(--paper);
  font: 500 16px/1.2 var(--font-ui);
  color: var(--ink);
  padding: 12px 16px;
  border-radius: 14px;
  outline: none;
  box-shadow: var(--shadow);
}
.search-input:focus { outline: 2px solid var(--ink); outline-offset: 0; }
.search-input::placeholder { color: var(--ink-3); }

.sort-row {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
}
.sort-row::-webkit-scrollbar { display: none; }
.sort-btn {
  appearance: none;
  border: 0;
  background: var(--paper);
  color: var(--ink-3);
  font: 500 12.5px/1 var(--font-ui);
  padding: 8px 14px;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
  transition: background .15s, color .15s;
  box-shadow: var(--shadow);
}
.sort-btn.active { background: var(--accent); color: var(--on-accent); }

/* PAGINATION */
.pagination-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px 20px;
}
.page-btn {
  appearance: none;
  border: 0;
  background: var(--paper);
  color: var(--ink);
  font: 600 13px/1 var(--font-ui);
  padding: 8px 14px;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: background .15s, color .15s;
}
.page-btn.active { background: var(--accent); color: var(--on-accent); }
.page-btn:disabled { opacity: .4; cursor: default; }
.page-info { font-size: 12.5px; color: var(--ink-3); font-weight: 500; }

/* FINALIZED BANNER */
.finalized-banner {
  margin: 8px 20px 8px;
  padding: 14px 16px;
  background: var(--good-soft);
  border-radius: 18px;
  display: flex;
  align-items: center;
  gap: 14px;
}
.finalized-banner-icon {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--good);
  color: #fff;
  display: grid;
  place-items: center;
  flex: 0 0 auto;
}
.finalized-banner-title {
  font: 600 14px/1.2 var(--font-ui);
  color: var(--good);
  letter-spacing: -.01em;
}
.finalized-banner-sub {
  font: 400 12px/1.2 var(--font-ui);
  color: var(--good);
  opacity: .75;
  margin-top: 2px;
  letter-spacing: -.005em;
}

/* DUPLICATE MODAL */
.modal-bg {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.4);
  z-index: 60;
  display: none;
  align-items: center;
  justify-content: center;
  /* Safe-area insets on top of the base inset, so the sheet clears a notch
     and a home indicator rather than tucking its rounded corners behind
     them. */
  padding: calc(20px + env(safe-area-inset-top, 0px))
           calc(20px + env(safe-area-inset-right, 0px))
           calc(20px + env(safe-area-inset-bottom, 0px))
           calc(20px + env(safe-area-inset-left, 0px));
}
.modal-bg.show { display: flex; }
.modal {
  background: var(--paper);
  border-radius: 28px;
  padding: 24px;
  width: 100%;
  max-width: 380px;
  /* 100vh on a phone is the viewport with the browser chrome hidden, which
     is taller than what you can actually see - sizing to it pushed the
     bottom of a long modal (and its buttons) underneath the browser UI and
     the tabbar. dvh is the visible height, and shrinks as the chrome comes
     back. The vh line stays as the fallback for browsers without dvh. */
  max-height: calc(100vh - 40px);
  max-height: calc(100dvh - 40px);
  overflow-y: auto;
  /* Without this, scrolling past the end of the modal hands the gesture to
     the page underneath and the background starts moving instead. */
  overscroll-behavior: contain;
  box-shadow: var(--shadow-lg);
}

/* Set by modal_scroll_lock.js whenever any .modal-bg is showing.

   Which element scrolls depends on the breakpoint: on desktop it is
   <main class="scroll">, on mobile .scroll grows to its full height and the
   document scrolls instead. Locking only one leaves the background moving
   at the other breakpoint, so both are covered. .scroll keeps its scrollTop
   while locked, so closing the modal returns you where you were. */
body.modal-open .scroll {
  overflow: hidden;
}
/* The document case. position:fixed rather than overflow:hidden because iOS
   Safari ignores the latter on <body>; the JS records the offset, sets the
   negative top, and scrolls back to it on close. */
body.modal-open-pinned {
  position: fixed;
  left: 0;
  right: 0;
  width: 100%;
  overflow: hidden;
}
/* Standings has a wider table (rank/name/won/played/+-) than the other
   modals' forms/confirmations - the default 380px cap made every column
   cramped, especially team names with a sub-line of player names. Side
   padding is also trimmed well below the default 24px since the table
   itself has no use for that margin - every pixel back goes straight to
   the name column, the one that's actually tight on space. */
#standings-modal .modal { max-width: 480px; padding-left: 10px; padding-right: 10px; }
/* The duplicate dialog only widens when it actually carries the schedule
   fields (source was scheduled) - an unscheduled source's dialog stays the
   plain 380px name-only form, unchanged from before this existed. */
#dup-modal.has-schedule .modal { max-width: 440px; }
.dup-schedule { margin-top: -4px; }
/* .modal input's generic pill styling (border:0, 13px/16px padding, 12px
   margin) is the same specificity as .schedule-field input (one class, one
   type each) and sits later in the stylesheet, so it would otherwise win by
   cascade order and flatten the date/time fields back to the modal's default
   look. Same fix as .modal .compact-stepper .stepper-wrap .v above. */
.modal .schedule-field input {
  height: 38px;
  border: 1.5px solid var(--line);
  background: var(--paper);
  font: 500 14px/1 var(--font-ui);
  padding: 0 10px;
  border-radius: 10px;
  margin-bottom: 0;
  box-sizing: border-box;
}
.modal .schedule-field input:focus { border-color: var(--ink); outline: none; }
/* The wizard's wide Schedule step fits all three fields on one row; the
   ~392px modal content box does not, so the base flex:1 1 140px wraps "To"
   onto its own row, stranded away from the "From" it pairs with. Force
   First date onto its own full-width row and let From/To share the next
   one instead - 2 * 140px + 10px gap is 290px, well inside the 392px box.
   Scoped to #dup-modal specifically (id + class beats the plain class
   selector above) so the wizard's own Schedule step is untouched. */
#dup-modal .schedule-field:first-child { flex: 1 1 100%; }
/* Standings modal reuses .group-subnav from the full-page Games/Standings
   tabs, where position:sticky/top:101px keeps it pinned below that page's
   sticky Games/Standings/Finals bar as the page scrolls - the modal has no
   such bar (and doesn't scroll the same way), so that offset and its
   page-tuned top padding just added dead vertical space stacked on top of
   the modal header's own margin. Pin it in normal flow with no extra top
   padding; the header's margin-bottom is the single source of that gap. */
#standings-modal-body .group-subnav {
  position: static;
  padding-top: 0;
  /* .group-subnav's own opaque background exists so the sticky page
     version doesn't show scrolling content through it - inside the modal
     it just paints a mismatched box behind the pills since the modal's
     own background differs from var(--bg). */
  background: transparent;
}
.modal h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 20px;
  letter-spacing: -.025em;
  color: var(--ink);
  margin-bottom: 8px;
}
.modal p { font-size: 13.5px; color: var(--ink-3); margin-bottom: 16px; font-weight: 500; }
.modal input {
  width: 100%;
  border: 0;
  background: var(--bg-2);
  font: 500 16px/1.2 var(--font-ui);
  color: var(--ink);
  padding: 13px 16px;
  border-radius: 14px;
  outline: none;
  margin-bottom: 12px;
}
.modal input:focus { outline: 2px solid var(--ink); outline-offset: 0; }
.modal .modal-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.modal-btn {
  appearance: none;
  border: 0;
  cursor: pointer;
  padding: 13px;
  border-radius: 999px;
  font: 600 14px/1 var(--font-ui);
  letter-spacing: -.01em;
  transition: transform .1s, opacity .15s;
}
.modal-btn:active { transform: scale(.97); opacity: .9; }
.modal-btn.primary { background: var(--accent); color: var(--on-accent); }
.modal-btn.secondary { background: var(--bg-2); color: var(--ink); }
.modal-btn.danger { background: var(--btn-danger); color: #fff; }

/* Danger Zone: bottom of Tournament Settings, deletion only - kept visually
   apart (tinted border/background) so it doesn't read as just another
   setting to skim past. */
.danger-zone {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--line-2);
}
.danger-zone-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--btn-danger);
  margin-bottom: 6px;
}
.danger-zone-body {
  font-size: 12px;
  color: var(--ink-3);
  margin: 0 0 12px;
  line-height: 1.5;
}
.danger-zone .modal-btn.danger { width: 100%; padding: 11px; }

/* UTILITIES */
.text-ink-3 { color: var(--ink-3); }
.small { font-size: 12.5px; }
.row-between { display: flex; justify-content: space-between; align-items: center; }
.mt-8 { margin-top: 8px; }
.mt-16 { margin-top: 16px; }
.mb-16 { margin-bottom: 16px; }
.empty-state {
  padding: 40px 20px;
  text-align: center;
  color: var(--ink-3);
  font-size: 14px;
  font-weight: 500;
}

/* ANIMATIONS */
@keyframes fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* ── SIDEBAR (desktop only — hidden by default) ── */
.sidebar { display: none; }

/* MOBILE RESPONSIVE — slight border on medium screens */
@media (min-width: 680px) and (max-width: 899px) {
  .app-shell {
    border-left: 1px solid var(--line);
    border-right: 1px solid var(--line);
  }
}

/* ── DESKTOP LAYOUT ── */
@media (min-width: 900px) {
  html, body { height: 100%; overflow: hidden; }

  .app-shell {
    flex-direction: row;
    max-width: none;
    min-height: unset;
    height: 100vh;
    overflow: hidden;
    margin: 0;
  }

  /* Hide mobile chrome */
  .header { display: none !important; }
  .tabbar { display: none !important; }

  /* ── Sidebar ── */
  .sidebar {
    display: flex;
    flex-direction: column;
    width: 220px;
    flex: 0 0 220px;
    height: 100vh;
    padding: calc(22px + env(safe-area-inset-top)) 14px 28px;
    border-right: 1px solid var(--line);
    background: var(--glass);
    backdrop-filter: blur(40px) saturate(180%);
    -webkit-backdrop-filter: blur(40px) saturate(180%);
    overflow: visible;
    position: sticky;
    top: 0;
    z-index: 10;
  }

  .sidebar-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 28px;
  }

  .sidebar .brand {
    padding: 4px 6px;
    margin-bottom: 0;
  }
  .sidebar .brand .name { font-size: 17px; }

  .sidebar .theme-fab {
    width: 34px;
    height: 34px;
    flex: 0 0 34px;
  }

  .sidebar-nav {
    display: flex;
    flex-direction: column;
    gap: 3px;
    flex: 1;
  }

  .sn-item {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 10px 12px;
    border-radius: 12px;
    font: 500 14px/1 var(--font-ui);
    color: var(--ink-2);
    text-decoration: none;
    letter-spacing: -.005em;
    transition: background .12s, color .12s;
  }
  .sn-item svg { width: 18px; height: 18px; flex: 0 0 18px; stroke-width: 1.8; }
  .sn-item:hover { background: var(--bg-2); color: var(--ink); }
  .sn-item.active { background: var(--bg-2); color: var(--ink); font-weight: 600; }
  .sn-item.active svg { stroke-width: 2.2; }

  .sn-dd { position: relative; }
  .sn-dd-trigger { width: 100%; background: none; border: none; cursor: pointer; }
  .sn-dd-trigger .sn-label { flex: 1; text-align: left; }
  .sn-dd-trigger .sn-chevron { width: 14px; height: 14px; flex: 0 0 14px; stroke-width: 2; opacity: .5; }
  .sn-dd-trigger[aria-expanded="true"] { background: var(--bg-2); color: var(--ink); }

  .sn-dd-panel {
    display: none;
    position: absolute;
    bottom: 0;
    left: calc(100% + 8px);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: 10px;
    box-shadow: var(--shadow-lg);
    min-width: 180px;
    z-index: 200;
    overflow: hidden;
  }
  .sn-dd-panel.show { display: block; }
  .sn-dd-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 11px 14px;
    font: 500 13px/1 var(--font-ui);
    color: var(--ink);
    text-decoration: none;
    transition: background .12s;
  }
  .sn-dd-item svg { width: 15px; height: 15px; flex: 0 0 15px; stroke-width: 1.8; }
  .sn-dd-item:hover { background: var(--surface); }
  .sn-dd-item.active { font-weight: 600; }

  .sn-logout { background: none; border: none; width: 100%; cursor: pointer; justify-content: center; }

  .sidebar-foot {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding-top: 16px;
    border-top: 1px solid var(--line-2);
  }

  /* ── Content area ── */
  .scroll {
    flex: 1;
    height: 100vh;
    overflow-y: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 40px; /* no floating tabbar on desktop */
    min-width: 0;         /* prevent flex overflow */
    display: flex;
    flex-direction: column;
    align-items: center;  /* bound and center each top-level section below */
  }
  .scroll > * {
    width: 100%;
    max-width: 760px;
    /* .scroll becomes a column flex container on desktop (align-items:center
       above needs a flex parent to center each capped child) - that makes
       every direct child a flex item, and a flex item's default automatic
       minimum size is 0 in any axis where it sets its own overflow to
       something other than visible (which several page sections do, e.g.
       .faq-list's overflow:hidden for its rounded corners). Without
       flex-shrink:0, once a page's stacked sections add up to more than
       the viewport's height, flexbox shrinks each one below its actual
       content height instead of leaving that to .scroll's own
       overflow-y:auto - and overflow:hidden then silently clips the
       shrunk content rather than showing it, instead of the intended
       "the whole page scrolls, nothing shrinks." */
    flex-shrink: 0;
  }

  /* Modals defined inside a page's own content (Tournament Settings,
     Finalize/Duplicate/Delete confirms, etc.) are direct children of
     .scroll, so the 760px content-column cap above was matching and
     shrinking their fixed, full-viewport backdrop too - leaving the
     right portion of the screen undimmed and the popup off-center
     instead of centered in the content area. .modal-bg is always
     position:fixed with its own inset-based sizing regardless of which
     page it's on, so it should never inherit the content column's cap. */
  .scroll > .modal-bg {
    width: auto;
    max-width: none;
  }

  /* Subnav sticks to top of scroll container (no header offset) */
  .subnav-wrap { top: 0; }

  /* Tournament page (header, subnav, Round Robin/Playoffs/Final tabs): use
     the full available stage width instead of a capped column, so the game
     grid has room to breathe (see #tab-games .game-wrap below). */
  .scroll > .tour-header,
  .scroll > .subnav-wrap,
  .scroll > .finalized-banner,
  .scroll > #tab-games,
  .scroll > #tab-playoffs,
  .scroll > #tab-finals {
    max-width: none;
  }

  /* New Tournament wizard: use the full available stage width rather than
     the default narrow 760px column - the player-card grid and multi-CTA
     rows below need real room to lay out several per line. ID selectors
     win over the .tour-header/.form-section rules above without needing
     source-order tricks. */
  .scroll > #new-tournament-header,
  .scroll > #tForm {
    max-width: none;
  }

  /* Info pages (Privacy, FAQ, How To Use, How It Works): use the full
     available stage width like every other page instead of the default
     narrow 760px column - .tour-header already goes full width via the
     rule above, which left just the title lining up with the left edge
     while every card below it stayed centered in a much narrower column. */
  .scroll > .faq-list,
  .scroll > .help-steps,
  .scroll > .help-extras,
  .scroll > .info-nav-row {
    max-width: none;
  }
  /* At full stage width the mobile-sized 20px side inset these all share
     reads as barely any margin at all against a wide desktop viewport -
     give them real breathing room from the edge instead. Overrides each
     element's own side margin/padding property directly since they differ
     (.faq-list/.info-nav-row use margin, .help-steps/.info-page-header use
     padding), keeping their existing vertical spacing untouched.
     .faq-list/.info-nav-row also need their width trimmed to match - the
     .scroll > * rule above sets width:100%, and margin (unlike padding)
     is never absorbed by box-sizing:border-box, so adding side margin on
     top of an existing 100% width would just overflow .scroll's own
     overflow-x:hidden edge instead of visibly insetting the element. */
  .scroll > .faq-list,
  .scroll > .info-nav-row {
    width: calc(100% - 80px);
    margin-left: 40px;
    margin-right: 40px;
  }
  .scroll > .help-steps,
  .scroll > .help-extras,
  .scroll > .help-intro,
  .scroll > .info-page-header {
    padding-left: 40px;
    padding-right: 40px;
  }

  /* Cap the reading column. At full stage width these lines run past 170
     characters, which is well beyond what anyone reads comfortably, and a
     summary row stretched that wide leaves its chevron stranded from its
     label. The steps themselves still span the stage. */
  .help-step-body,
  .help-extra-body {
    max-width: 760px;
  }
  /* Keep the dividing rules to the column they divide, rather than running
     them the full width of the stage under much narrower text. */
  .help-extras-h,
  .help-extra {
    max-width: 812px;
  }
  /* .scroll is a centering flex column, which left the intro floating in the
     middle of the stage while the title and steps sat against the left edge.
     Same 760px reading column (800px less its own 40px insets), pinned left
     with align-self since flex alignment, not margin, is what centers it. */
  .scroll > .help-intro {
    max-width: 800px;
    align-self: flex-start;
  }

  /* Dashboard: greeting lines up with the cards below it (both go full
     width, rather than the greeting staying capped at the default 760px
     column while the cards below span wider), current-game/new-tournament
     side by side, then live, stats and history below - stats sized to
     match one top-row card's width, not equal to live/history. */
  .scroll > #dash-hero,
  .scroll > #dash-top-row,
  .scroll > #dash-stats-row {
    max-width: none;
  }
  /* Both rows are width:100% via .scroll > * above - their mobile-authored
     horizontal *margin* would add outside that 100% and overflow .scroll
     (clipped by its overflow-x:hidden, which is what made the whole row
     look shifted/misaligned). Swap to padding, which stays inside the
     border-box width, and match .hero's own 20px inset so the greeting
     and the cards below it line up on the same left edge. */
  #dash-top-row,
  #dash-stats-row {
    margin-left: 0;
    margin-right: 0;
    padding-left: 20px;
    padding-right: 20px;
  }
  #dash-top-row {
    flex-direction: row;
    align-items: stretch;
  }
  #dash-top-row > .resume-card {
    flex: 1;
  }
  /* My Stats is conditional (a brand new account with no player history
     has none), and Live's other companion is always exactly one of
     Scheduled/History (never both) - two small tiles at most, so only
     widen a middle column when Stats is actually present, or a bare
     two-tile row would inherit a 3-column split and render lopsided with
     an empty gap on the right. DOM order is Live, [Scheduled|History],
     My Stats, [History|nothing], so the 2fr stats column sits between
     the two small tiles either way. */
  #dash-stats-row {
    grid-template-columns: 1fr 1fr;
  }
  #dash-stats-row:has(.overview-card) {
    grid-template-columns: 1fr 2fr 1fr;
  }
  #dash-stats-row > .overview-card {
    grid-column: auto;
    order: 0;
  }

  /* New Tournament card only (Resume's own layout is untouched): a short
     feature list fills the space between the header and the CTA instead
     of leaving the card mostly empty next to the fuller Resume card.
     New Tournament becomes its own flex column with space-between - since
     both cards are stretched to Resume's height (align-items:stretch
     above, Resume's own natural content already reaches its card's
     bottom edge unaided), this pins New Tournament's CTA to the bottom of
     its own stretched card, landing it on the same line as Resume's
     without changing anything about Resume itself. When Resume is also
     present, .nt-features goes display:contents so its feature lines
     join the header and CTA as direct flex children of the card - that
     gives every gap between them (header-to-first-feature, between each
     feature, last-feature-to-CTA) equal size via the same space-between,
     instead of the features being one lumped-together block with its own
     fixed internal gap plus a separate, smaller gap before the CTA. */
  #dash-top-row > .new-tournament-card {
    /* !important: the element also carries an inline style="display:block"
       (needed for its mobile layout), which otherwise wins over any
       stylesheet rule regardless of selector specificity. */
    display: flex !important;
    flex-direction: column;
    justify-content: space-between;
  }
  .nt-features {
    display: flex;
    flex-direction: column;
    gap: 12px;
  }
  #dash-top-row > .new-tournament-card:not(:only-child) .rc-head {
    margin-bottom: 0;
  }
  #dash-top-row > .new-tournament-card:not(:only-child) .nt-features {
    display: contents;
  }
  .nt-feature {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 13px;
    color: var(--ink-2);
    font-weight: 500;
  }
  .nt-feature .ico-tile {
    width: 32px;
    height: 32px;
    border-radius: 9px;
    background: var(--bg-2);
    display: grid;
    place-items: center;
    color: var(--ink-3);
    flex-shrink: 0;
  }
  /* No tournament to resume: New Tournament is the sole card in the row
     and takes the entire width - the feature list turns into a real
     three-up row (icon above label) instead of staying a narrow
     left-aligned column with the right two-thirds of the card empty. */
  #dash-top-row > .new-tournament-card:only-child .nt-features {
    flex-direction: row;
    gap: 24px;
    margin: 6px 0 24px;
  }
  #dash-top-row > .new-tournament-card:only-child .nt-feature {
    flex: 1;
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }

  /* Rotating roster: one card per player, several per row, instead of a
     single stacked list - #players_container specifically (not the
     generic .player-list class), since Fixed Teams reuses .player-list
     for each team's own 2-person mini-list and must stay stacked there. */
  #players_container {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    justify-content: start;
    gap: 10px;
    background: none;
    box-shadow: none;
    padding: 0;
  }
  #players_container .player-row {
    background: var(--paper);
    box-shadow: var(--shadow);
  }
  #players_container .player-row::before {
    display: none; /* the divider line is only meaningful for a stacked list */
  }

  /* Fixed Teams roster: one card per team, same grid treatment. Each
     .ft-group currently has no card chrome of its own (it borrows its
     inner 2-player .player-list's card look) - give it one directly so
     the team name + its 2 players read as a single unit. */
  #teams_container {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    justify-content: start;
    gap: 10px;
  }
  #teams_container .ft-group {
    background: var(--paper);
    border-radius: 20px;
    box-shadow: var(--shadow);
    padding: 14px;
  }
  #teams_container .ft-group + .ft-group {
    margin-top: 0; /* grid gap replaces the stacked-list margin */
  }
  /* The outer .ft-group card now provides the paper/shadow chrome, so its
     inner 2-player .player-list must go flat - otherwise it's a card
     nested inside a card. */
  #teams_container .ft-group .player-list {
    background: none;
    box-shadow: none;
    padding: 0;
    margin-top: 8px;
  }

  /* Groups substep: two group cards per row, extra groups wrap below,
     instead of one long stacked column. */
  #rot-groups-container,
  #ft-groups-container {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
  }
  #rot-groups-container .roster-group,
  #ft-groups-container .roster-group {
    margin-bottom: 0; /* grid gap replaces the stacked-list margin */
  }
  /* Three per row, not four: at four the columns get narrow enough that
     longer player names truncate, and a fixture list whose names you cannot
     read is worse than one that fits fewer cards on screen. */
  #tab-games .game-wrap {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 14px;
  }
  /* Grid items stretch to match the tallest card in their row by default -
     a shorter match-card's full-height delete-swipe panel would then
     stretch past its own leg rows too, showing red in the leftover gap
     below them. Size each card to its own content instead. */
  #tab-games .game-wrap > * {
    align-self: start;
  }
  #tab-games .games-group-header {
    grid-column: 1 / -1;
    padding: 4px 4px 0;
    border-top: none;
    margin-top: 10px;
  }
  #tab-games .games-group-header:first-child { margin-top: 0; }

  /* Session headings must span the whole row, same as group headings. The
     games grid is 4 columns on desktop, so without this a heading occupies a
     single cell and the evening's matches wrap around it - exactly the bug
     the old "Round N" heading had before it was removed. */
  #tab-games .games-session-header {
    grid-column: 1 / -1;
    padding: 6px 4px 0;
    border-top: none;
    margin-top: 14px;
  }
  #tab-games .games-session-header:first-child { margin-top: 0; }

  /* Page-level action bars (Finalize Tournament, Add Game, bracket
     generate/regenerate, ...): mobile gives these full-width thumb-sized
     buttons; on desktop they sit centered under the card above them,
     primary action rightmost (row-reverse keeps that convention when
     there are two buttons, e.g. Regenerate/Generate). */
  .form-actions {
    display: flex;
    flex-direction: row-reverse;
    justify-content: center;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
  }
  .form-actions .cta {
    width: auto;
    padding: 12px 26px;
    font-size: 14px;
  }
  /* Playoff-depth picker (Quarterfinal/Semifinal/Direct Final): reads
     ascending (fewer rounds -> more rounds) left to right, so don't
     reverse this one's DOM order. */
  .form-actions.center { flex-direction: row; }
  .cta:hover { opacity: .88; }

  /* New Tournament "Setup" block: single column at every width, same as
     mobile - Tournament Name full width, then every setting (Split into
     groups, Number of groups, Number of players/teams, Matches per
     player, Assign courts, Lock score entry, Exclude from ranking) as its
     own full-width row underneath, one per line. A two-column split used
     to put Format on the right of Name/Access, but that left dead space
     beside short cards and made the settings harder to scan top to
     bottom - so desktop now just inherits the same single "top"/"toggles"
     stack mobile already uses (see the base .tour-settings-grid rule
     above); #form-actions-scorelock and .tss-format-card/.tss-access-card
     keep their base display:contents too, flattening into the one
     .tour-settings-toggles card. */
  .tour-settings-top label.field { margin-bottom: 0; }
  .tour-settings-top label.field input {
    height: 62px;
    padding: 0 16px;
  }

  /* "Players"/"Teams" reads as its own section heading, flush with "New
     Tournament" and "Setup" above it, rather than inheriting the 14px
     inset .stepper-label normally uses to align under a rounded field. */
  .roster-heading { padding-left: 0; }

  /* Sheet, toast, and modal popups center within the content area to the
     right of the 220px sidebar, not the full viewport - centering on the
     full viewport shifts them visibly left of the actual content. */
  .sheet-bg { position: fixed; left: 220px; }
  .sheet { position: fixed; left: calc(220px + (100% - 220px) / 2); }
  .toast { position: fixed; bottom: 32px; left: calc(220px + (100% - 220px) / 2); }
  .modal-bg { left: 220px; }
}

/* ── Skeleton shimmer ── */
@keyframes skeleton-shimmer {
  0%   { background-position: -600px 0; }
  100% { background-position: 600px 0; }
}
.skeleton-bg {
  background: linear-gradient(90deg, var(--surface) 25%, var(--bg-2) 50%, var(--surface) 75%);
  background-size: 1200px 100%;
  animation: skeleton-shimmer 1.4s ease-in-out infinite;
  border-radius: 6px;
}
.skeleton-card {
  background: var(--paper);
  border-radius: 18px;
  overflow: hidden;
  box-shadow: var(--shadow);
}

/* list-row skeleton (mirrors .li) */
.skeleton-li {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
}
.skeleton-li:last-child { border-bottom: none; }
.skeleton-li .sk-ico  { width: 36px; height: 36px; border-radius: 10px; flex: 0 0 36px; }
.skeleton-li .sk-body { flex: 1; display: flex; flex-direction: column; gap: 6px; }
.skeleton-li .sk-nm   { height: 13px; width: 55%; border-radius: 5px; }
.skeleton-li .sk-sub  { height: 11px; width: 35%; border-radius: 5px; }
.skeleton-li .sk-end  { display: flex; align-items: center; gap: 8px; }
.skeleton-li .sk-pill { width: 38px; height: 20px; border-radius: 20px; }
.skeleton-li .sk-chev { width: 8px; height: 14px; border-radius: 3px; }

/* leaderboard row skeleton (mirrors .lb-row) */
.skeleton-lb {
  display: grid;
  grid-template-columns: 28px 1fr repeat(3, 48px);
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--line);
}
.skeleton-lb:last-child { border-bottom: none; }
.skeleton-lb .sk-rk    { width: 20px; height: 14px; border-radius: 4px; }
.skeleton-lb .sk-who   { display: flex; align-items: center; gap: 10px; }
.skeleton-lb .sk-av    { width: 32px; height: 32px; border-radius: 50%; flex: 0 0 32px; }
.skeleton-lb .sk-nm-wrap { display: flex; flex-direction: column; gap: 5px; }
.skeleton-lb .sk-nm    { height: 13px; width: 90px; border-radius: 4px; }
.skeleton-lb .sk-sub   { height: 10px; width: 60px; border-radius: 4px; }
.skeleton-lb .sk-num   { height: 14px; width: 28px; border-radius: 4px; justify-self: end; }

/* standings row skeleton (mirrors .stand-row) */
.skeleton-stand {
  display: grid;
  grid-template-columns: 28px 1fr repeat(2, 48px);
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--line);
}
.skeleton-stand:last-child { border-bottom: none; }
.skeleton-stand .sk-rk  { width: 20px; height: 14px; border-radius: 4px; }
.skeleton-stand .sk-who { display: flex; align-items: center; gap: 10px; }
.skeleton-stand .sk-av  { width: 32px; height: 32px; border-radius: 50%; flex: 0 0 32px; }
.skeleton-stand .sk-nm  { height: 13px; width: 90px; border-radius: 4px; }
.skeleton-stand .sk-num { height: 14px; width: 28px; border-radius: 4px; justify-self: end; }

/* ── HELP STEPS ── */
.help-steps { padding: 4px 20px 20px; display: flex; flex-direction: column; }
.help-step { display: flex; gap: 16px; padding-bottom: 24px; }
.help-step:last-child { padding-bottom: 8px; }
.help-step-left { display: flex; flex-direction: column; align-items: center; flex: 0 0 28px; }
.step-num { width: 28px; height: 28px; border-radius: 50%; background: var(--accent); color: var(--on-accent); font: 700 12px/1 var(--font-display); display: grid; place-items: center; flex: 0 0 28px; }
.step-line { width: 2px; flex: 1; background: var(--line); margin-top: 8px; min-height: 16px; border-radius: 1px; }
.help-step:last-child .step-line { display: none; }
.help-step-body { padding-top: 3px; flex: 1; }
.help-step-title { font: 600 15px/1 var(--font-ui); color: var(--ink); letter-spacing: -.01em; margin-bottom: 6px; }
.help-step-desc { font-size: 13px; color: var(--ink-2); line-height: 1.55; font-weight: 500; }
.help-step-desc b { color: var(--ink); font-weight: 600; }

/* Progressive disclosure inside a help step. Each step shows the path almost
   everyone takes; the options and edge cases live behind these so the page can
   be skimmed for one answer instead of read start to finish. */
.help-more { margin-top: 8px; }
.help-more > summary {
  display: flex; align-items: center; gap: 8px; cursor: pointer;
  list-style: none; -webkit-tap-highlight-color: transparent;
  font: 600 12.5px/1 var(--font-ui); color: var(--ink-3);
  padding: 8px 10px; border-radius: 8px; background: var(--bg-2);
}
.help-more > summary::-webkit-details-marker { display: none; }
.help-more > summary::after {
  content: ""; margin-left: auto; width: 6px; height: 6px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg); transition: transform .18s ease;
}
.help-more[open] > summary { color: var(--ink-2); }
.help-more[open] > summary::after { transform: rotate(-135deg); }
.help-more > summary:hover { color: var(--ink-2); }
.help-more-body { font-size: 13px; color: var(--ink-2); line-height: 1.55; font-weight: 500; padding: 10px 10px 2px; }
.help-more-body p { margin: 0 0 10px; }
.help-more-body p:last-child { margin-bottom: 0; }
.help-more-body b { color: var(--ink); font-weight: 600; }

/* Features rather than steps, so they sit outside the numbered sequence. */
.help-extras { padding: 0 20px 16px; }
.help-extras-h { font: 600 11px/1 var(--font-ui); text-transform: uppercase; letter-spacing: .07em; color: var(--ink-3); margin-bottom: 12px; }
.help-extra { display: flex; gap: 16px; padding: 16px 0; border-top: 1px solid var(--line-2); }
.help-extra-body { flex: 1; min-width: 0; }

@media (prefers-reduced-motion: reduce) {
  .help-more > summary::after { transition: none; }
}

/* ── FAQ ACCORDION ── */
.faq-list { margin: 0 20px 14px; background: var(--paper); border-radius: 24px; box-shadow: var(--shadow); overflow: hidden; }
.faq-group-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-3); padding: 14px 16px 8px; }
.faq-item { border-top: 1px solid var(--line-2); }
.faq-item:first-of-type { border-top: none; }
.faq-q { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; background: none; border: none; cursor: pointer; text-align: left; font: 500 14px/1.4 var(--font-ui); color: var(--ink); letter-spacing: -.005em; }
.faq-q svg { flex: 0 0 16px; width: 16px; height: 16px; color: var(--ink-3); transition: transform .2s; }
.faq-item.open .faq-q svg { transform: rotate(180deg); }
.faq-a { display: none; padding: 0 16px 14px; font-size: 13px; color: var(--ink-2); line-height: 1.6; font-weight: 500; }
.faq-a b { color: var(--ink); font-weight: 600; }
.faq-item.open .faq-a { display: block; }

/* ── HELP SHEET LINKS (popup) ── */
.help-sheet-link { display: flex; align-items: center; gap: 14px; padding: 14px 4px; text-decoration: none; color: var(--ink); border-radius: 14px; transition: background .12s; }
.help-sheet-link:active { background: var(--bg-2); }
.help-sheet-link + .help-sheet-link { border-top: 1px solid var(--line-2); }
.help-sheet-link .lbl { font-weight: 600; font-size: 15px; letter-spacing: -.01em; }
.help-sheet-link .sub { font-size: 12px; color: var(--ink-3); margin-top: 2px; font-weight: 500; }

/* ── MODE PICKER CARDS ── */
.mode-cards { display: flex; flex-direction: column; gap: 12px; }
.mode-card {
  display: flex; align-items: flex-start; gap: 14px;
  background: var(--bg); border: 1.5px solid var(--line);
  border-radius: 14px; padding: 16px; cursor: pointer;
  text-align: left; width: 100%; box-sizing: border-box;
  transition: border-color .15s, background .15s;
  font: inherit; color: inherit; text-decoration: none;
}
.mode-card:hover { border-color: var(--accent); }
.mode-card-icon { color: var(--accent); flex-shrink: 0; margin-top: 2px; }
.mode-card-body { flex: 1; }
.mode-card-title { font-weight: 700; font-size: 15px; margin-bottom: 3px; color: var(--ink); }
.mode-card-sub { font-size: 13px; color: var(--ink-3); line-height: 1.4; }

/* ── CUSTOM SETUP WIZARD (progress + nav + bracket-depth picker) ── */
.wizard-progress {
  padding: 0 20px 10px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-3);
  letter-spacing: .02em;
}
.wizard-nav { display: flex; gap: 10px; padding: 4px 20px 24px; }
.wizard-nav .cta { flex: 1; }
.bracket-depth-options { display: flex; flex-direction: column; gap: 10px; }
.bracket-depth-options .cta { width: 100%; }

/* Finals modal's match-format row is narrower than the wizard's own
   format-card (the modal caps at 380px), so the shared .cta sizing let
   "Single game" wrap to two lines while its siblings stayed one -
   uneven pill heights in the same row. Smaller/tighter text keeps every
   option a single line and the row a uniform height. */
.finals-bestof-options .cta { font-size: 13px; padding: 11px 6px; white-space: nowrap; }

/* Desktop overrides below the base rules above, deliberately - both are
   defined later in the file than the shared @media (min-width: 900px)
   block near the top, so an override placed inside that earlier block
   would lose the cascade tie (same specificity, earlier source order)
   to the base rule and silently never apply. */
@media (min-width: 900px) {
  /* All choices in a single row, equal width, not wrapped or sized to
     each button's own text - e.g. Single / Best of 3 / Best of 5, read
     as one deliberate row of equally-weighted options rather than a
     wrapped, uneven list. The wizard's own playoff-depth picker opts
     out of this via .playoff-format-buttons below - its labels run far
     longer ("Round of 16 + Quarterfinal + Semifinal + Final") and a
     forced nowrap row of those would overflow rather than wrap. */
  .bracket-depth-options { flex-direction: row; flex-wrap: nowrap; gap: 10px; }
  .bracket-depth-options .cta { width: auto; flex: 1; }

  /* Mode-card pickers (Select Mode's Rotating/Fixed/Manual, and the
     Quick/Custom setup-type popup) - same single-row, equal-width
     treatment as the choices above, with each card's icon stacked above
     its title/subtitle instead of beside it, since a narrower column
     reads better with the icon leading top-down than squeezed to the
     left of two lines of text. */
  .mode-cards { flex-direction: row; gap: 10px; }
  .mode-card { flex: 1; flex-direction: column; align-items: flex-start; gap: 10px; }

  /* Custom setup wizard: Back/Next content-sized and centered, matching
     Cancel/Create Tournament's own sizing (.form-actions .cta), instead
     of stretching to fill the row like they do on mobile. */
  .wizard-nav { justify-content: center; }
  .wizard-nav .cta { flex: 0 0 auto; width: auto; padding: 12px 26px; font-size: 14px; }
}

/* ── FIXED TEAMS GROUP ── */
.ft-group + .ft-group { margin-top: 10px; }
.ft-group-label { font-size: 11px; color: var(--ink-3); font-weight: 600; text-transform: uppercase; letter-spacing: .05em; padding: 0 0 6px 2px; }
.team-name-input {
  display: block; width: 100%; box-sizing: border-box;
  font: 600 15px/1.2 var(--font-ui); color: var(--ink);
  background: var(--paper); border: 1px solid var(--line);
  border-radius: 10px; padding: 8px 12px; outline: none;
  margin-bottom: 8px;
}
.team-name-input:focus { outline: 2px solid var(--ink); outline-offset: 0; }

/* ── TOGGLE SWITCH (shared branded enable/disable control) ── */
.tog { position: relative; display: inline-block; width: 44px; height: 26px; flex-shrink: 0; }
.tog input { opacity: 0; width: 0; height: 0; position: absolute; }
.tog-track {
  position: absolute; inset: 0;
  background: var(--line);
  border-radius: 26px;
  cursor: pointer;
  transition: background .2s;
}
.tog-track::before {
  content: '';
  position: absolute;
  height: 20px; width: 20px;
  left: 3px; top: 3px;
  background: var(--paper);
  border-radius: 50%;
  transition: transform .2s;
  box-shadow: 0 1px 3px rgba(0,0,0,.25);
}
.tog input:checked ~ .tog-track { background: var(--accent); }
.tog input:checked ~ .tog-track::before { transform: translateX(18px); }

/* ── TOURNAMENT GROUPS (pools) SETUP ── */
/* Grid instead of flex-wrap: the label always keeps a narrow column
   reserved for the toggle, so long text wraps early to leave room for
   it on the right at any width, and align-items:center centers the
   toggle against the label's full (possibly multi-line) height instead
   of just its first line. */
.roster-group {
  background: var(--bg);
  border: 1.5px solid var(--line);
  border-radius: 16px;
  padding: 14px;
  margin-bottom: 14px;
}
/* Mobile: never wrap the stepper onto its own line below the group name -
   the name input already shrinks (min-width:0 below) to make room, so the
   count stays right of the name on the same row. Desktop restores the
   original wrapping behavior below. */
.roster-group-header { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: nowrap; }
.roster-group-header .roster-group-badge {
  font-size: 11px; font-weight: 700; color: var(--accent);
  text-transform: uppercase; letter-spacing: .05em; flex-shrink: 0;
}
.roster-group-header .group-name-input {
  flex: 1 1 120px; min-width: 0; font: 600 15px/1.2 var(--font-ui); color: var(--ink);
  background: var(--paper); border: 1px solid var(--line);
  border-radius: 10px; padding: 8px 12px; outline: none;
}
.roster-group-header .group-name-input:focus { outline: 2px solid var(--ink); outline-offset: 0; }
/* Same one-line label/control concept as the Setup block's Number of
   players row - the stepper sits beside the group's name instead of on
   its own row below, and no longer needs bottom margin now that it's not
   the header's last stacked element. */
/* Explicit width, not flex:0 0 auto (content-hugging) - .v inside has
   width:0 so it can flex:1 to fill a defined space, but with no defined
   space to fill (a content-hugging parent has none to give), it collapses
   to 0 instead of showing the number at all. */
/* Box size/style (including margin-bottom:0) comes from the shared rule
   above, same as Number of groups/players/teams/courts. */
@media (min-width: 900px) {
  .roster-group-header { flex-wrap: wrap; }
}

/* ── CUSTOM WIZARD: combined Playoff format + Match format page ──
   Same bordered-card treatment as .roster-group (this page's other
   content cards). These are settings rows like the rest of the wizard,
   not independent content blocks, so they stack full width on desktop
   too instead of sitting side by side - Match format is often much
   shorter than Playoff format, and a two-column layout left a large
   dead gap next to the shorter card. */
.format-cards { display: flex; flex-direction: column; gap: 14px; }
.format-card {
  background: var(--bg);
  border: 1.5px solid var(--line);
  border-radius: 16px;
  padding: 16px;
}
/* Match format's heading gets a small tag naming the thing that isn't
   otherwise visible on this card: its stage list comes from whatever was
   just picked in Playoff format, next to it. */
.format-card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.format-card-head .substep-heading { margin-bottom: 0; }
.format-card-tag {
  font-size: 11px; font-weight: 600; color: var(--ink-3);
  background: var(--bg-2); border-radius: 999px; padding: 3px 10px;
  white-space: nowrap;
}
/* Pill-chain preview of the actual stage progression (Round robin ->
   ... -> Final) - fills the space below Playoff format's buttons with
   something concrete instead of leaving it empty, and grows/shrinks
   live as the depth choice changes. */
.bracket-preview { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 16px; }
.bracket-preview-step {
  font-size: 12px; font-weight: 600; color: var(--ink-2);
  background: var(--bg-2); border-radius: 999px; padding: 5px 12px;
}
.bracket-preview-arrow { color: var(--ink-3); font-size: 13px; }
/* Match format's per-stage rows: label and its three best-of buttons
   share one line on desktop instead of stacking, so a deep bracket
   (5 stages) doesn't run noticeably taller than Playoff format's card
   right beside it. */
.stage-format-row { margin-bottom: 14px; animation: format-row-in .18s ease; }
.stage-format-row:last-of-type { margin-bottom: 0; }
@keyframes format-row-in {
  from { opacity: 0; transform: translateY(3px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .stage-format-row { animation: none; }
}
@media (min-width: 900px) {
  .stage-format-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
  .stage-format-row .stepper-label { margin-bottom: 0; flex-shrink: 0; }
  .stage-format-row .stage-format-buttons { flex-wrap: nowrap; }
  .stage-format-row .stage-format-buttons .cta { flex: 0 0 auto; width: auto; padding: 8px 14px; font-size: 12.5px; }

  /* Playoff format's depth row reuses the same row shell and the same
     compact-pill sizing as Match format above, so the two cards read as
     one system - but its options (Direct Final, Semifinal + Final, up
     to Round of 32 + ...) run longer and more numerous than Match
     format's fixed three, so unlike .stage-format-buttons this wraps
     onto further lines instead of forcing one unbroken row, and stays
     top-aligned with the label rather than centered against a height
     that can now vary. */
  .stage-format-row.bracket-format-row { align-items: flex-start; }
  .stage-format-row .playoff-format-buttons { flex-wrap: wrap; justify-content: flex-end; }
  .stage-format-row .playoff-format-buttons .cta { flex: 0 0 auto; width: auto; padding: 8px 14px; font-size: 12.5px; }
}

/* ── Guided setup panel (Custom + Rotating only) ──
   Sits at the top of the Setup step, above Tournament Name - same
   .format-card shell as the Playoff/Match format cards further down the
   wizard, so it reads as part of the same system rather than a bolted-on
   flow. Every field inside is its own .guided-q row; recommendations use
   the app's existing .setup-hint / .setup-hint-apply vocabulary, not a
   new one - see app/services/guided_setup.py for the derivation these
   mirror. */
.guided-panel { margin-bottom: 18px; }
.guided-intro {
  margin: 0 0 16px;
  color: var(--ink-2);
  font-size: 13px;
  line-height: 1.5;
}
.guided-q {
  padding: 12px 0;
  border-top: 1px solid var(--line-2);
}
.guided-q:first-of-type { border-top: none; padding-top: 0; }
.guided-q .setup-hint { margin-top: 6px; margin-bottom: 0; }
.guided-q .bracket-depth-options .cta {
  /* Same pill sizing as the wizard's own playoff/match-format buttons
     (.stage-format-row .playoff-format-buttons .cta above), so a button
     answered here looks identical to the one it will land on later. */
  width: auto;
}
/* Playoffs and Match format read as one deliberate row of options even
   on mobile, not a vertical stack - a grid (not the shared flex-column
   base rule) so the column count can be set precisely per row: three
   options in one line, or the fourth-depth case (Quarterfinal available)
   in a 2x2 grid via the inline grid-template-columns set by
   guidedRenderPlayoffButtons/guidedRenderFormatButtons. */
.guided-q .stage-format-buttons.bracket-depth-options {
  display: grid !important;
  grid-template-columns: repeat(3, 1fr) !important;
  gap: 8px;
}
/* Playoffs' own column count is set live by guidedRenderPlayoffButtons
   (2 or 3, depending on how many depth options exist) via inline style -
   !important on display alone (not grid-template-columns, which would
   permanently pin 3 columns and beat that inline style, leaving a fewer-
   option row left-aligned with an empty trailing column) hardens against
   the same unidentified override Match format's rule above needed. */
#guided-playoff-options.bracket-depth-options {
  display: grid !important;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
#guided-counting-body .leg-counting-options {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}
#guided-playoff-options.bracket-depth-options .cta,
.guided-q .stage-format-buttons.bracket-depth-options .cta,
#guided-counting-body .leg-counting-options .cta {
  width: 100%;
  padding: 10px 6px;
  font-size: 12.5px;
  white-space: normal;
  line-height: 1.25;
}
/* Guided's own Playoffs row (No playoffs/Direct Final/...) reads as the
   same compact pill row as Guided's Match format buttons right below
   it, rather than the full-width grid it uses at narrower widths -
   stays on one line and simply grows wider as more depth options
   become available, instead of wrapping to a second row. Placed after
   the grid rule above so it wins the tie on equal selector specificity. */
@media (min-width: 900px) {
  #guided-playoff-options.bracket-depth-options {
    display: flex;
    flex-wrap: nowrap;
    gap: 8px;
  }
  #guided-playoff-options.bracket-depth-options .cta {
    flex: 0 0 auto;
    width: auto;
    padding: 8px 14px;
    font-size: 12.5px;
    white-space: nowrap;
  }

  /* Same treatment for Counting best-of matches' two CTAs, now sat in
     its own .stage-format-row alongside its label like Playoffs above. */
  #guided-counting-body .leg-counting-options {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    gap: 8px;
  }
  #guided-counting-body .leg-counting-options .cta {
    flex: 0 0 auto;
    width: auto;
    padding: 8px 14px;
    font-size: 12.5px;
    white-space: nowrap;
  }
}
/* Escape hatch: its own card above Guided, so someone who already knows
   what they want does not have to read past every guided question to find
   the way out. Text-styled as a link rather than a button, since choosing
   it is opting out rather than performing an action. */
.guided-escape-card {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: 12px;
  font-size: 13px;
  color: var(--ink-3);
}
.guided-escape-text { color: var(--ink-3); }
.guided-escape-link {
  appearance: none;
  border: none;
  background: none;
  padding: 0;
  margin-left: 4px;
  font: inherit;
  font-weight: 600;
  color: var(--ink);
  text-decoration: underline;
  cursor: pointer;
}
.guided-escape-link:hover { color: var(--ink-2); }

@media (min-width: 900px) {
  .guided-q.tss-plain-stepper, .guided-q .tss-plain-stepper { padding-top: 0; }
}

/* ── PWA: NAVIGATION LOADER ── */
#nav-loader {
  position: fixed; inset: 0; z-index: 9999;
  display: flex; align-items: center; justify-content: center;
  background: var(--glass);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0; pointer-events: none;
  transition: opacity 0.15s ease;
}
#nav-loader.active { opacity: 1; pointer-events: auto; }
.nav-spinner {
  width: 28px; height: 28px;
  border: 2.5px solid var(--line);
  border-top-color: var(--ink);
  border-radius: 50%;
  animation: cs-spin 0.7s linear infinite;
}
@keyframes cs-spin { to { transform: rotate(360deg); } }

/* ── PWA: LAUNCH SPLASH (standalone only) ── */
@media (display-mode: standalone) {
  #app-splash {
    position: fixed; inset: 0; z-index: 10000;
    background: var(--bg);
    transition: opacity 0.25s ease;
  }
  #app-splash.done { opacity: 0; pointer-events: none; }
}


/* ── Email verification banner ── */
.verify-banner {
  background: var(--warn-soft);
  border: 1px solid rgba(180, 100, 20, 0.35); /* fallback for browsers without color-mix */
  border: 1px solid color-mix(in oklch, var(--warn) 40%, transparent);
  border-radius: 12px;
  padding: 10px 10px 10px 14px;
  font-size: 13px;
  color: var(--ink-2);
  display: flex;
  align-items: center;
  gap: 10px;
}
.verify-banner svg { color: var(--warn); flex-shrink: 0; }
.verify-banner span { flex: 1; }
.verify-banner-btn {
  flex-shrink: 0;
  appearance: none;
  border: none;
  background: var(--accent);
  color: var(--on-accent);
  border-radius: 8px;
  padding: 6px 14px;
  font-size: 12px;
  font-weight: 600;
  font-family: var(--font-ui);
  cursor: pointer;
  white-space: nowrap;
  transition: opacity .15s;
}
.verify-banner-btn:hover { opacity: .82; }

/* ── Shared button utilities ── */
.btn-primary {
  display: inline-block;
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: 10px;
  font: 600 15px/1 var(--font-ui);
  padding: 12px 20px;
  cursor: pointer;
  text-align: center;
  text-decoration: none;
  transition: opacity .15s;
}
.btn-primary:hover { opacity: .85; }

/* ── User avatar button + dropdown (mobile header) ── */
.avatar-wrap {
  position: relative;
  flex: 0 0 auto;
}
.user-avatar-btn {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  display: grid;
  place-items: center;
  font: 700 12px/1 var(--font-display);
  letter-spacing: -0.01em;
  border: none;
  cursor: pointer;
  transition: opacity .15s;
}
.user-avatar-btn:hover { opacity: .82; }
.avatar-dropdown {
  display: none;
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: var(--shadow-lg);
  min-width: 160px;
  z-index: 200;
  overflow: hidden;
}
.avatar-dropdown.show { display: block; }
.avatar-dd-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  text-decoration: none;
  transition: background .12s;
}
.avatar-dd-item:hover { background: var(--surface); }

/* ── Notification bell ── */
.notif-bell-wrap { position: relative; flex: 0 0 auto; }
.notif-bell-btn { position: relative; }
.notif-dot {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #111;
  border: 1.5px solid var(--paper);
}
[data-theme="dark"] .notif-dot { background: #fff; }

.notif-dropdown {
  display: none;
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: var(--shadow-lg);
  width: 300px;
  max-width: 84vw;
  z-index: 200;
  overflow: hidden;
}
.notif-dropdown.show { display: block; }
.notif-dd-head {
  padding: 12px 14px 8px;
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}

.notif-dd-list { display: flex; flex-direction: column; }
.notif-dd-empty {
  padding: 16px 14px;
  font-size: 13px;
  color: var(--ink-3);
  text-align: center;
}
.notif-dd-item {
  display: block;
  padding: 10px 14px;
  text-decoration: none;
  border-top: 1px solid var(--line);
  transition: background .12s;
  position: relative;
}
.notif-dd-item:first-child { border-top: none; }
.notif-dd-item:hover { background: var(--surface); }
.notif-dd-item.unread { background: var(--bg-2); }
.notif-dd-item-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 2px;
}
.notif-dd-item-body {
  font-size: 12px;
  color: var(--ink-3);
  line-height: 1.4;
  margin-bottom: 3px;
}
.notif-dd-item-time {
  font-size: 11px;
  color: var(--ink-3);
  opacity: .8;
}
.notif-dd-viewall {
  display: block;
  padding: 10px 14px;
  font-size: 12.5px;
  font-weight: 600;
  text-align: center;
  color: var(--accent);
  text-decoration: none;
  border-top: 1px solid var(--line);
  transition: background .12s;
}
.notif-dd-viewall:hover { background: var(--surface); }

.notif-dd-panel-desk { padding: 0; min-width: 260px; }
.notif-dd-panel-desk .notif-dd-list { max-height: 320px; overflow-y: auto; }

/* ── Long-name overflow fixes (standings, leaderboard, all tables) ── */
.stand-row {
  grid-template-columns: 24px minmax(0, 1fr) 50px 44px;
}
.stand-row .who {
  min-width: 0;
  overflow: hidden;
}
.stand-row .who .player-nm {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  /* flex:0 1 auto (not flex:1) - the name box must only take the space its
     text actually needs (shrinking/truncating if the row is too narrow),
     not stretch to fill the whole cell - flex:1 pushed the qualified-badge
     that follows it all the way to the cell's far edge, next to the Won
     column, instead of immediately next to the name. */
  flex: 0 1 auto;
}
.who-names {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}
.who-names .player-nm { flex: none; }
.team-sub-names {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-3);
  letter-spacing: -.005em;
  margin-top: 1px;
}
.lb-row .who {
  min-width: 0;
  overflow: hidden;
}
.lb-row .who .player-nm {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
