:root{
  /* Design team direction (2026-10-02): the app-wide primary color moved from royal blue to the
     same indigo used on the Groups splash/roundthepost.com. Follow-up round: white against an
     indigo this dark computes to ~16:1 contrast (vs. the original royal blue's ~6.7:1) -- high
     enough to read as flatly black rather than as a color, confirmed by direct WCAG luminance
     calculation, not just by eye. So the indigo now stays reserved for large fixed *surfaces*
     only (the header, the Rounds screen's round-strip) where that weight reads as intentional,
     while --royal-light (~10.4:1, roughly the midpoint) is a visibly lighter blue for every
     discrete button/border/link that sits directly on the white page -- still clearly deeper
     than the original bright royal, just not indistinguishable from black. */
  --royal:#142043; --royal-light:#1E3A8A;
  --sky:#38BDF8; --sky-tint:#E5F5FF;
  --green:#16C47F; --green-dark:#0E7A4F; --green-tint:#E3FBF0;
  /* --orange-pale is the progress-strip's pending-round segment only (its one consumer) -- against
     the new indigo round-strip it lost contrast, so moved 50% toward white from its original
     #FBC7A8 to read clearly again. --orange-tint (the pending *banner*'s bg, also shared with the
     M/W pill's selected box-shadow) is untouched -- not reported as a problem, and changing it
     would move that pill's shadow too. */
  --orange:#FB6D2C; --orange-pale:#FDE3D4; --orange-tint:#FFEBDF;
  /* The Rounds screen's "current round" indicators (banner + progress segment) sit directly
     against the new indigo round-strip above/around them -- full-strength --orange there read as
     too intense next to that much darker backdrop. Moved 30% back toward full-strength --orange
     from the first lightened attempt (#ffb066) after seeing it live. Used only for those two
     current-round indicators; every other --orange usage (M/W pills, "in progress" labels, etc.)
     is unaffected. */
  --orange-current:#FE9C55;
  --red:#EF4444; --red-tint:#FDEAEA;
  --ink:#0F172A; --slate:#475569; --line:#E2E8F0; --surface:#F8FAFC;
}
*{box-sizing:border-box;}
button{-webkit-appearance:none;appearance:none;margin:0;font:inherit;color:inherit;}
/* overscroll-behavior:none (phone-testing feedback): a plain pull-down gesture, with nothing
   focused at all, was rubber-banding the whole page (including the sticky header) down and
   revealing blank space above it, then springing back — the exact same flash reported when a
   score box gets focus. iOS's own bounce animation temporarily un-sticks position:sticky
   elements, which is what let the header move in the first place; this disables the bounce
   itself (not scrolling generally) so there's nothing left for a keyboard-triggered scroll
   attempt, ours or Safari's own, to interact badly with. */
html,body{margin:0;overscroll-behavior-y:none;}
body{background:#dbe2ea;font-family:'Lexend',system-ui,sans-serif;color:var(--ink);display:flex;justify-content:center;}
/* On a phone the grey page background only frames the app on wider screens anyway; any time
   iOS briefly exposed it below the app (after the keyboard closed) it showed up as a grey bar
   the same shade as the keypad (punchlist #134). White on phone widths hides that. */
@media (max-width:480px){body{background:#fff;}}
a{color:var(--royal-light);}
.num{font-family:'Space Grotesk',system-ui,sans-serif;}
/* Punchlist #124: the page itself scrolls (as in the original app) — no fixed-height shell, no
   nested scroll containers — so the browser handles the on-screen keyboard natively. Only the
   top bar is pinned (sticky); nothing is ever pinned to the bottom. */
/* min-height cascade: 100vh for anything with no dvh support, 100dvh as the real-time-viewport
   default, then --app-vh (app.html's own early inline script) last — it wins when set, since
   iOS Safari's dvh can get stuck at a stale toolbar-collapsed/expanded value instead of tracking
   the real visible height live (punchlist #166's follow-up; see that script's own comment). */
.app{width:100%;max-width:480px;min-height:100vh;min-height:100dvh;min-height:var(--app-vh, 100dvh);background:#fff;display:flex;flex-direction:column;box-shadow:0 0 40px rgba(0,0,0,.15);}

/* The outer bar extends its blue background up under the status bar / Dynamic Island (needs
   `viewport-fit=cover` in index.html's viewport meta, or `env()` below always reads 0) — phone
   testing found the buttons themselves needed to stay clear of that area instead of being
   centered across the whole tall bar, which visually crowded them up against the island. The
   real button row is `.topbar-inner`, a fixed 48px sitting below the safe-area padding; every
   sibling bar pinned directly under the header (`.players-pinned-row`, `.rounds-strip`) anchors
   to `calc(48px + env(...))`, the same total height, instead of assuming a plain 48px header. */
.topbar{position:sticky;top:0;z-index:10;padding-top:env(safe-area-inset-top, 0px);background:var(--royal);color:#fff;}
.topbar-inner{height:48px;display:flex;align-items:center;justify-content:space-between;padding:0 8px;}
.icon-btn{width:40px;height:40px;border-radius:12px;display:flex;align-items:center;justify-content:center;background:none;border:none;color:#fff;cursor:pointer;padding:0;}
.icon-btn:disabled{opacity:.35;cursor:default;}
.icon-btn.circle{border:2px solid rgba(255,255,255,.85);border-radius:999px;font-weight:800;font-size:16px;}
/* Phone-testing feedback, second pass: Back/Forward/Help all match the header-btn pills' own
   32px height instead of the default 40px, so the whole header reads as one consistent row. */
.icon-btn.small{width:32px;height:32px;}
.topbar-title{flex:1;text-align:center;font-weight:800;font-size:19px;letter-spacing:.2px;}
.spacer40{width:40px;height:40px;flex:0 0 40px;}
.spacer32{width:32px;height:32px;flex:0 0 32px;}
.topbar-right{display:flex;align-items:center;gap:4px;}
/* Players header (design.md's UI Specification): mode and Courts buttons in the middle, in
   place of a title — outlined pills so they read as tappable controls on the royal bar. */
.topbar-controls{flex:1;display:flex;align-items:center;justify-content:center;gap:8px;}
.header-btn{display:flex;align-items:center;gap:4px;height:32px;padding:0 12px;background:rgba(255,255,255,.14);border:1.5px solid rgba(255,255,255,.7);border-radius:999px;color:#fff;font-weight:800;font-size:14px;cursor:pointer;white-space:nowrap;}
/* Courts screen's Done (punchlist #170) — solid green, not the semi-transparent default, so it
   reads as a clear confirming action rather than one more neutral header pill. */
.header-btn-green{background:var(--green);border-color:var(--green);}

.content{flex:1;display:flex;flex-direction:column;}

.pill{border-radius:999px;font-weight:800;font-size:12px;height:26px;padding:0 10px;display:flex;align-items:center;justify-content:center;}
.pill.m{background:var(--surface);color:var(--slate);border:1px solid var(--line);}
.pill.m.active{background:var(--sky);color:var(--ink);border-color:transparent;box-shadow:0 0 0 2px var(--sky-tint);}
.pill.w{background:var(--surface);color:var(--slate);border:1px solid var(--line);}
.pill.w.active{background:var(--orange);color:var(--ink);border-color:transparent;box-shadow:0 0 0 2px var(--orange-tint);}

/* Players / roster row */
.prow{display:flex;align-items:center;gap:12px;padding:12px 4px;border-bottom:1px solid var(--line);cursor:pointer;}
.prow input[type=checkbox]{display:none;}
.check-circle{width:26px;height:26px;border-radius:999px;background:#fff;border:2px solid var(--line);flex:0 0 26px;display:flex;align-items:center;justify-content:center;}
.prow input:checked + .check-circle,.check-circle.checked{background:var(--green);border-color:var(--green);}
.check-circle svg{display:none;}
.prow input:checked + .check-circle svg,.check-circle.checked svg{display:block;}
.pname{flex:1;font-weight:700;font-size:18px;}
.pgenders{display:flex;gap:6px;}
/* Matches .check-circle's width so the New Player row's name/M-W lines up with real rows above
   it, which have a checkbox there instead (found testing on a phone). */
.row-spacer{flex:0 0 26px;width:26px;}

/* "..." row menu (punchlist #121, #124): a small popup inside the tapped row — plain page
   content, so it scrolls with the list and can't be clipped — that opens above the row instead
   when it would run off the bottom of the screen. The backdrop sits under the sticky header. */
.menu-backdrop{position:fixed;inset:0;z-index:11;}
/* position:fixed, placed by JS (positionRowMenu) from the tapped row's real screen position —
   z-index above the sticky header (10) so it's never hidden behind it if placement ever has to
   clamp there; starts invisible so it never flashes at (0,0) before JS measures and places it. */
/* Widened, and every text size below bumped up (punchlist #140: these were noticeably smaller
   than the rest of the app's now sun-readable text — score digits 24px, player names 22px —
   while carrying messages just as important, sometimes more so, like a delete confirmation). */
.row-menu{position:fixed;z-index:12;visibility:hidden;background:#fff;border-radius:12px;box-shadow:0 6px 24px rgba(15,23,42,.2);overflow:hidden;min-width:200px;max-width:min(320px,92vw);}
.menu-msg{padding:16px 18px 12px;font-size:18px;line-height:1.35;font-weight:700;color:var(--ink);}
.menu-item{display:block;width:100%;text-align:left;background:none;border:none;font-family:inherit;font-weight:700;font-size:17px;padding:18px 20px;cursor:pointer;color:var(--ink);}
.menu-item:not(:last-child){border-bottom:1px solid var(--line);}
.menu-item.danger{color:var(--red);}
.menu-item.active{color:var(--royal-light);}
.menu-item:disabled{color:var(--slate);opacity:.5;cursor:default;}
.menu-actions{display:flex;border-top:1px solid var(--line);}
.menu-actions .menu-item{flex:1;text-align:center;border-bottom:none;}
.menu-actions .menu-item:first-child{border-right:1px solid var(--line);}
.error-text{color:var(--red);font-size:15px;font-weight:700;line-height:1.35;margin-top:6px;}

/* Fixed inline "New Group"/"New Player" row (punchlist #121) — typing here adds an entry
   directly in place, no separate "Add" button or activated/inactive state. */
.new-player-row{border-top:1px dashed var(--line);}
/* min-width:0 overrides the flex-item default of min-width:auto — a plain div (.pname on real
   rows) has no meaningful min-content floor, but a text <input> does, and without this it wins
   over flex:1's shrinking, overflowing the row and pushing the M/W pills right of where they
   sit on every row above (confirmed by measuring both rows' actual rendered rects, not guessed:
   the input was exactly ~27px wider than the space really available, and the row overflowed the
   screen edge by the same amount). */
.pname-input{flex:1;min-width:0;font-weight:700;font-size:18px;font-family:inherit;border:none;background:none;padding:0;color:var(--ink);}
.pname-input::placeholder{color:var(--slate);font-weight:600;}
.pname-input:focus{outline:none;}

.btn{font-family:inherit;font-weight:700;font-size:13px;border-radius:999px;cursor:pointer;}
.btn-outline{background:#fff;color:var(--royal-light);border:2px solid var(--royal-light);padding:8px 14px;}
.btn-danger-outline{background:#fff;color:var(--red);border:2px solid var(--red);border-radius:14px;font-weight:800;font-size:13px;font-family:inherit;cursor:pointer;padding:0 14px;}
.btn-primary{width:100%;height:50px;background:var(--royal-light);color:#fff;border:none;border-radius:14px;font-weight:800;font-size:16px;letter-spacing:.3px;box-shadow:0 4px 10px rgba(30,58,138,.25);}
.btn-primary:disabled{background:var(--line);color:var(--slate);box-shadow:none;}

.setup-toolbar{padding:14px 16px 10px;border-bottom:1px solid var(--line);}
.caption{font-size:11px;font-weight:700;color:var(--slate);text-transform:uppercase;letter-spacing:.5px;}
/* Team mode's Players-screen list (punchlist #90/#141): the 2nd player's row of a completed
   team gets a bold border-bottom in place of its own normal pale one — sits exactly where that
   line already is, rather than a separate thick bar stacked below it with its own spacing. */
.prow.team-row-end{border-bottom:3px solid var(--ink);}
.setup-toolbar .error-text{margin-top:8px;}
/* Players screen's pinned row, stuck directly under the header. Side padding is .list-wrap's
   16px plus .prow's 4px, so the select-all circle lines up with the row circles. The group-
   context caption line (an earlier phone-testing addition) didn't earn its place and was
   dropped — the room goes to a bigger, easier-to-read live count instead (phone-testing
   feedback: easy to read at a glance, including in bright light). */
.players-pinned-row{position:sticky;top:calc(48px + env(safe-area-inset-top, 0px));z-index:9;background:#fff;display:flex;align-items:center;gap:12px;padding:10px 20px;border-bottom:1px solid var(--line);}
.players-count{font-size:16px;font-weight:800;color:var(--slate);}
.players-pinned-row .btn-primary{flex:0 0 auto;width:auto;height:44px;padding:0 18px;}
/* Courts screen: − count + stepper, and one name box per court. "Number of Courts" used to be
   the tiny all-caps .caption style, which read as disconnected from the big bold number right
   next to it — this matches the number's own size/weight instead, just kept in the slate color
   so label and value still read as two different things, not two identical numbers. */
.stepper-label{font-size:22px;font-weight:700;color:var(--slate);}
/* The Courts screen's one genuine choice (punchlist #169) — never more than exactly two named
   options (see courtRange), so two plain buttons read more honestly than a +/- spinner that
   implies a wider range. Same filled-vs-outlined convention as .btn-primary/.btn-ghost. */
.court-choice-row{display:flex;gap:10px;}
.court-choice{font-family:inherit;font-weight:800;font-size:16px;padding:10px 18px;border-radius:999px;border:2px solid var(--royal-light);background:#fff;color:var(--royal-light);cursor:pointer;}
.court-choice.active{background:var(--royal-light);color:#fff;}
.stepper{display:flex;align-items:center;gap:14px;}
.stepper button{width:40px;height:40px;border-radius:999px;border:2px solid var(--royal-light);background:#fff;color:var(--royal-light);font-size:22px;font-weight:800;line-height:1;cursor:pointer;padding:0;}
.stepper button:disabled{border-color:var(--line);color:var(--line);cursor:default;}
.stepper .num{min-width:24px;text-align:center;font-size:22px;font-weight:700;}

.list-wrap{padding:2px 16px 0;}
.inline-input{flex:1;height:44px;background:#fff;border:2px solid var(--royal-light);border-radius:12px;padding:0 14px;font-weight:700;font-size:16px;font-family:inherit;}
.inline-input:focus{outline:none;}

/* Roster screen */
/* MLP team standings (punchlist #142), above the individual Stats table. */
.mlp-match-card{border:1.5px solid var(--line);border-radius:14px;padding:12px 14px;margin-bottom:12px;}
.mlp-match-row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:4px 0;}
.mlp-team-name{font-weight:800;font-size:16px;}
.mlp-team-roster{font-size:12px;color:var(--slate);}
.mlp-match-score{font-weight:800;font-size:22px;font-family:'Space Grotesk',system-ui,sans-serif;flex:0 0 auto;}
.col-caps{display:flex;align-items:center;padding:8px 16px 4px;border-bottom:1px solid var(--line);}
.stat-col{width:44px;text-align:center;}
.roster-row{display:flex;align-items:center;padding:13px 4px;border-bottom:1px solid var(--line);}
.roster-name{flex:1;display:flex;align-items:center;font-weight:700;font-size:17px;}

/* Rounds screen (punchlist #124 step 7, restructured per phone-testing feedback): the
   round-number strip+progress bar and the Round N banner are both pinned (stacked stickies
   below the header, same pattern as the Players screen's pinned row) — replaces the old
   horizontal-swipe pager entirely (dropped per design.md: needed sync code for the pinned
   strip, rounds differ in height, and people use the strip anyway). Byes scrolls with the game
   cards, directly below the pinned banner. The strip has a fixed height so the banner's `top` can
   be pure CSS (punchlist #135): it used to be measured once in JS at render, and in a Safari tab
   the top safe-area inset changes after load (the toolbar collapses), leaving the banner stuck
   61px too high, hidden behind the strip. */
/* Contents pushed to the bottom edge (punchlist #137): the strip's fixed height is a point or two
   taller than its contents, which left a thin blue gap between the progress bar and the banner. */
.rounds-strip{position:sticky;top:calc(48px + env(safe-area-inset-top, 0px));z-index:9;height:34px;display:flex;flex-direction:column;justify-content:flex-end;background:var(--royal);padding:2px 16px 0;}
.rounds-banner{position:sticky;top:calc(48px + 34px + env(safe-area-inset-top, 0px));z-index:8;}
.tabstrip{display:flex;}
.tab{flex:1;text-align:center;text-decoration:none;font-weight:800;display:block;cursor:pointer;line-height:1.1;}
.segbar{display:flex;margin-top:4px;border-radius:3px;overflow:hidden;}
.seg{flex:1;height:7px;}
/* min-height (phone-testing feedback): Complete's checkmark+text, Pending's plain text, and
   Now Playing's Round Done button (the tallest, since it's a real padded button) used to give
   the banner three different heights depending on which round you were viewing — switching
   between them shifted the game cards below up and down, which read as a distracting jump.
   Fixed height (align-items:center already there centers whichever content is shorter) instead
   of letting the tallest state set it implicitly. */
.banner{min-height:76px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;}
.banner-title{font-weight:800;font-size:22px;}
/* MLP's round-robin rotates matchups every 3 rounds (punchlist #142), so a bare "Round 4" alone
   doesn't say which match or phase is on court — this subtitle does. No min-height bump needed:
   the banner's own flex row grows to fit it naturally, consistently, for every MLP round. */
.banner-subtitle{font-weight:700;font-size:14px;opacity:.65;margin-top:2px;}
.banner-status{display:flex;align-items:center;gap:6px;font-weight:800;font-size:14px;}
.court{padding:14px 18px;border-top:1px solid var(--line);}
/* MLP's swap control (punchlist #142), sitting between the two game cards of one matchup within
   a mixed round — lets either captain cross the opponent pairing before either game is scored. */
.mlp-swap-row{display:flex;justify-content:center;padding:2px 18px;border-top:1px solid var(--line);}
.mlp-swap-btn{display:flex;align-items:center;gap:6px;background:#fff;color:var(--royal-light);border:2px solid var(--royal-light);border-radius:999px;font-weight:800;font-size:13px;font-family:inherit;padding:6px 16px;cursor:pointer;}
.mlp-swap-btn:disabled{color:var(--line);border-color:var(--line);cursor:default;}
/* Court label centered (phone-testing feedback) — its own full-width wrapper, since the pill
   itself is still inline-block/auto-width and needs a block parent to center within. */
.courttag-wrap{text-align:center;}
.courttag{display:inline-block;background:var(--sky-tint);color:var(--royal-light);font-weight:800;font-size:22px;letter-spacing:.4px;padding:3px 9px;border-radius:999px;margin-bottom:9px;}
.courttag.dim{background:var(--surface);border:1px solid var(--line);color:var(--slate);}
/* Game card layout v2 (phone-testing feedback): partner names drop the "+" and stack two lines
   each, flanking the score boxes directly instead of a name line above a separate score row.
   One shared grid template (not two) so a pending round's names land in exactly the same
   position as a scored round's — `.no-score` fills the two score-box columns with invisible
   `.score-box-spacer`s the same width as a real box, rather than collapsing to a narrower
   3-column grid that let the divider (and the names beside it) drift inward. Both name columns
   stay `1fr` each — equal width regardless of content — so the center divider always lands
   dead-center, and a long name wraps its own two lines rather than pushing the divider off to
   one side. */
/* column-gap widened (phone-testing feedback: names sat too snug against the score boxes) —
   was 8px. */
.matchup-v2{display:grid;align-items:center;column-gap:14px;grid-template-columns:1fr auto auto auto 1fr;}
.score-box-spacer{width:48px;}
/* No `align-items:flex-end/flex-start` here on purpose — that would let each name div grow to
   its own content width (shrink-to-fit) instead of staying constrained to the grid column,
   which is exactly what let a long name overflow the card edge instead of wrapping (confirmed
   by rendering "Christopher"/"Bartholomew" at 390px and 375px). Default `align-items:stretch`
   keeps every name div at the column's actual width; `text-align` on the divs themselves does
   the left/right visual alignment instead. */
.team-names{display:flex;flex-direction:column;gap:2px;font-weight:700;font-size:22px;line-height:1.15;min-width:0;}
/* hyphens:auto gives a genuinely long single name (no natural word break) a proper hyphenated
   break instead of an abrupt mid-word split — matters here specifically since this is an
   iOS-first app and Safari does support it, given the `lang="en"` on <html>. */
.team-names div{overflow-wrap:break-word;min-width:0;-webkit-hyphens:auto;hyphens:auto;}
/* Both sides left-aligned now (phone-testing feedback: right-justifying team1's names read odd
   for the eye) — `.right`/`.left` stay as class names describing which side of the card each
   sits on, but no longer differ in their own text-align. */
.team-names.right div,.team-names.left div{text-align:left;}
/* Negative margin tightens just the two box<->divider gaps (phone-testing feedback: the score
   boxes' own outer column-gap, shared with the name columns, left a right-side name at risk of
   clipping) without touching the wider name<->box gap on either outside edge. */
.score-divider{font-weight:800;font-size:16px;color:var(--slate);white-space:nowrap;justify-self:center;margin:0 -4px;}
/* Height matches a 2-line name stack at this font/line-height (22px * 1.15 * 2 lines + the 2px
   gap between them ≈ 53px) so the box reads as balanced next to the names beside it instead of
   looking small and squat under them; font size increased to match (phone-testing feedback). */
.score-box{width:48px;height:53px;border-radius:10px;border:2px solid var(--line);background:#fff;color:var(--ink);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:24px;text-align:center;font-family:'Space Grotesk',system-ui,sans-serif;padding:0;cursor:pointer;}
.score-box.locked{background:var(--surface);color:var(--slate);cursor:default;}
/* Editable boxes are buttons now (punchlist #136); an empty one shows a faint 0 like the old
   input placeholder, and the one taking digits gets a heavy royal outline. */
.score-box[data-box]:empty::before{content:'0';color:#B6C0CC;}
.score-box.selected{border:3px solid var(--royal-light);background:var(--sky-tint);}
.byesrow{flex:0 0 auto;padding:12px 18px;border-top:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;gap:12px;}
/* Byes now sits right below the pinned banner (not pinned itself, no Done button anymore —
   that moved into the banner as "Round Done") — a bottom border instead of the old top one,
   separating it from the game cards that follow instead of from the strip above. */
/* The app's own number pad for scores (punchlist #136), replacing the iPhone keyboard. Pinned
   to the bottom only while a score box is selected — the one exception to "nothing pinned at
   the bottom", which existed to keep the iPhone keyboard from covering pinned bars; this pad is
   ours, so nothing else can land on it. Big keys (52px tall, 26px digits) for sunlight and
   sunglasses; about 250px tall plus the home-indicator inset, well under the iPhone number
   pad's ~375. `.keypad-room` below the last card is the same height, so the last game can
   always scroll up above the pad; it only exists while the pad is open (hidden otherwise). */
.keypad{position:fixed;left:0;right:0;bottom:0;margin:0 auto;max-width:480px;z-index:20;display:grid;grid-template-columns:repeat(3,1fr);gap:8px;padding:8px 8px calc(8px + env(safe-area-inset-bottom, 0px));background:#D1D5DB;box-shadow:0 -4px 16px rgba(15,23,42,.15);}
.keypad[hidden]{display:none;}
.key{height:52px;border:none;border-radius:10px;background:#fff;color:var(--ink);font-family:'Space Grotesk',system-ui,sans-serif;font-weight:700;font-size:26px;cursor:pointer;box-shadow:0 1px 0 rgba(15,23,42,.25);display:flex;align-items:center;justify-content:center;}
.key:active{background:var(--line);}
.key-fn{background:var(--slate);color:#fff;font-family:inherit;font-size:18px;font-weight:800;}
.key-fn:active{background:var(--ink);}
.keypad-room{height:calc(4 * 52px + 3 * 8px + 16px + env(safe-area-inset-bottom, 0px) + 16px);}
.rounds-byesrow-scrolled{border-top:none;border-bottom:1px solid var(--line);}
/* Matches the game cards' own name text now (phone-testing feedback) — was 14px/600. */
.byeslabel{font-size:22px;font-weight:700;color:var(--slate);}
/* Black border and text (phone-testing feedback): went royal blue, then solid green, then a
   white outline matching Rounds/Stats' own pills — all rejected in turn (blue and green read
   poorly against the orange Now Playing background; white read weak next to "Round N"'s own
   black title text right beside it). Black matches that title directly. No :disabled variant —
   roundStatusHTML only ever renders this for the current round, never disabled. */
/* Bolder border and bigger text (design team feedback) than the generic pill-button convention
   elsewhere — this one sits right next to the 22px bold "Round N" title and read weak next to
   it at the old 1.5px/14px sizing. */
.donebtn{background:rgba(255,255,255,.14);color:var(--ink);border:2.5px solid var(--ink);font-weight:800;font-size:17px;padding:10px 22px;border-radius:999px;font-family:inherit;flex:0 0 auto;cursor:pointer;}

/* Round timer (design team request): after the last game card, a plain grey rule (same as every
   other court-to-court divider) then the countdown — reuses the Courts screen's own `.stepper`
   for the -/+ circles rather than a second copy of the same pill-stepper CSS. */
.timer-divider{border-top:1px solid var(--line);}
.timer-section{padding:20px 18px;display:flex;flex-direction:column;align-items:center;gap:16px;}
.timer-display{min-width:120px;text-align:center;font-size:40px;font-weight:800;font-family:'Space Grotesk',system-ui,sans-serif;color:var(--ink);}
.timer-display.warning{color:var(--red);}
.timer-display.expired{color:var(--red);cursor:pointer;}
.timer-actions{display:flex;align-items:center;gap:14px;}
.timer-startstop{background:var(--royal-light);color:#fff;border:none;border-radius:999px;font-weight:800;font-size:17px;padding:13px 30px;font-family:inherit;cursor:pointer;}
.timer-reset-btn{width:46px;height:46px;border-radius:999px;border:2px solid var(--line);background:#fff;color:var(--slate);font-size:22px;line-height:1;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;}

/* Timer's "don't lock your phone" warning (design team: "big and clear") — a full-attention
   centered card over a dimmed backdrop, not the small row-anchored popup used for menus
   elsewhere (erase confirm, mode picker): those are easy to tap past without reading, and this
   one needs to actually be read every time Start is pressed. */
.center-modal-backdrop{position:fixed;inset:0;z-index:30;background:rgba(15,23,42,.6);display:flex;align-items:center;justify-content:center;padding:24px;}
.center-modal-card{background:#fff;border-radius:20px;padding:30px 24px;max-width:340px;width:100%;text-align:center;box-shadow:0 16px 50px rgba(15,23,42,.35);}
.timer-warn-title{font-weight:800;font-size:25px;line-height:1.25;color:var(--red);margin:0 0 10px;}
.timer-warn-body{font-weight:600;font-size:16px;line-height:1.4;color:var(--slate);margin:0 0 18px;}

/* Score entry's "log this round?" prompt (design team request) — same center-modal-card as the
   timer warning above. Green confirm on top (the expected path, since every box already has a
   value by the time this shows), black-outline Cancel below — not side by side, so both stay
   big, thumb-friendly targets instead of splitting the card's width in half. */
.round-complete-title{font-weight:800;font-size:22px;line-height:1.3;color:var(--ink);margin:0 0 20px;}
.round-complete-confirm{width:100%;background:var(--green);color:#fff;border:none;border-radius:14px;font-weight:800;font-size:18px;padding:14px;font-family:inherit;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;margin-bottom:12px;}
.round-complete-cancel{width:100%;background:#fff;color:var(--ink);border:2px solid var(--ink);border-radius:14px;font-weight:800;font-size:17px;padding:12px;font-family:inherit;cursor:pointer;}

/* Start RR's "How many rounds?" card (punchlist #150) — same centered card; one big stacked
   button per length, each with a one-line note on what it means for byes. */
.rounds-choice{width:100%;background:var(--royal-light);color:#fff;border:none;border-radius:14px;padding:12px 14px;margin-bottom:10px;font-family:inherit;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:2px;}
.rounds-choice-num{font-weight:800;font-size:20px;}
.rounds-choice-note{font-weight:600;font-size:14px;opacity:.9;}
.rounds-choice + .round-complete-cancel{margin-top:4px;}
/* Bye swap popups (punchlist #151) — same centered card; names in a 2-column grid of big
   outlined buttons so a full roster (up to ~16 on court) fits, scrolling if it ever doesn't. */
.swap-card{max-height:85vh;overflow-y:auto;}
.swap-choices{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px;}
.swap-choice{background:#fff;color:var(--ink);border:2px solid var(--royal-light);border-radius:12px;padding:12px 6px;font-weight:700;font-size:17px;font-family:inherit;cursor:pointer;}

.empty-state{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;padding:40px;text-align:center;color:var(--slate);font-size:15px;}

/* ---------------- Groups screen: splash + dark/navy redesign ----------------
   Distinct --rtp-* tokens, deliberately not aliased to the app's existing --royal/--orange
   (the website's orange #ff7a29 differs from the app's own #FB6D2C) -- scoped to this one
   screen only. Players/Courts/Rounds keep the plain --royal look untouched. */
:root{
  --rtp-navy-deep:#05070f; --rtp-navy:#0a1226; --rtp-navy-mid:#142043; --rtp-navy-light:#1c2c58;
  --rtp-silver:#e7ecf5; --rtp-silver-dim:#aeb8d6;
  --rtp-orange:#ff7a29; --rtp-orange-bright:#ffb066;
}

/* min-height:100% (relative to .content's resolved flex height) was occasionally coming up short
   -- e.g. a short group list, or a mobile browser's dynamic viewport/address-bar resize -- showing
   a stray strip of .app's own white background below the dark screen. 100dvh (falling back to
   100vh) is a hard viewport-relative floor instead, so this can never happen regardless of how
   tall .content itself actually resolves to. */
/* No min-height here (punchlist #166 — real-phone report of a pale strip at the very bottom,
   above the device's own home-indicator/toolbar area): `.app` already sets min-height:100dvh
   for the WHOLE page (topbar + content together, style.css's own .app rule), and flex:1 here
   already fills whatever .content has left after the sticky topbar. Also forcing this one
   screen's own content to be at least 100dvh made the total page (topbar height + this) taller
   than one viewport by exactly the topbar's own height, every time — the overflow past the
   bottom edge was .app's white background's own pale body{} backdrop (#dbe2ea) peeking through.
   flex:1 alone sizes this correctly with no overflow. */
.groups-screen{flex:1;display:flex;flex-direction:column;background:linear-gradient(180deg, var(--rtp-navy-deep) 0%, var(--rtp-navy-mid) 70%, var(--rtp-navy-light) 100%);color:var(--rtp-silver);}
.groups-screen .list-wrap{padding:16px 16px 0;}
/* Sequenced reveal (first launch only): full-screen gradient + comet are already visible from
   frame one (no `playing` gate needed there), so the only thing actually held back is the group
   list itself, until the title above has finished appearing -- `listReady` is added by the same
   timeout chain that settles the hero/title. A return visit never gets the `playing` class at
   all, so the list is simply visible immediately, same as everything else. */
.groups-screen.playing .list-wrap{opacity:0;transform:translateY(10px);transition:opacity .6s ease, transform .6s ease;}
.groups-screen.playing.listReady .list-wrap{opacity:1;transform:none;}
@media (prefers-reduced-motion:reduce){
  .groups-screen.playing .list-wrap{transition:none;opacity:1;transform:none;}
}

/* Hero: the comet/net/post animation, adapted from the roundthepost.com landing page's proven
   build. Lives in the normal scrolling content area (not the sticky 48px topbar), so the
   existing "only the top is pinned" layout principle is undisturbed -- group cards simply
   follow it in normal document flow. Source is a 400x400 composition; "xMidYMin slice" crops
   it to this shorter banner shape while keeping the post/comet (its top-anchored focal point)
   intact, letting the net's long lower-right tail run off the edge instead. */
.groups-hero{position:relative;height:210px;flex:0 0 auto;overflow:hidden;}
.groups-hero svg{display:block;width:100%;height:100%;}
.groups-hero .post{stroke:url(#rtpPostGrad);stroke-linecap:round;filter:drop-shadow(0 0 5px rgba(231,236,245,.4));}
.groups-hero .net{stroke:url(#rtpNetGrad);fill:none;}
.groups-hero .comet-head{fill:url(#rtpCometGrad);filter:drop-shadow(0 0 20px rgba(255,122,41,.95));offset-path:path('M 320,300 C 365,230 345,110 255,65 C 190,35 110,55 65,130');offset-rotate:0deg;}
.groups-hero .comet-trail-glow,.groups-hero .comet-trail-core{fill:none;stroke-linecap:round;stroke-dasharray:494;stroke-dashoffset:494;}
.groups-hero .comet-trail-glow{stroke:url(#rtpTrailGradOuter);stroke-width:16;}
.groups-hero .comet-trail-core{stroke:url(#rtpTrailGradInner);stroke-width:7;}
.groups-hero.play .comet-head{animation:rtpFlyHead 2.6s cubic-bezier(.3,0,.2,1) .15s both;}
.groups-hero.play .comet-trail-glow{animation:rtpDrawGlow 2.6s cubic-bezier(.3,0,.2,1) .15s both;}
.groups-hero.play .comet-trail-core{animation:rtpDrawCore 2.6s cubic-bezier(.3,0,.2,1) .15s both;}
.groups-hero.settled .comet-head{offset-distance:100%;opacity:1;}
.groups-hero.settled .comet-trail-glow{stroke-dashoffset:0;opacity:.4;}
.groups-hero.settled .comet-trail-core{stroke-dashoffset:0;opacity:.95;}
@keyframes rtpFlyHead{0%{offset-distance:0%;opacity:0;}8%{opacity:1;}100%{offset-distance:100%;opacity:1;}}
@keyframes rtpDrawGlow{0%{stroke-dashoffset:494;opacity:0;}8%{opacity:.4;}100%{stroke-dashoffset:0;opacity:.4;}}
@keyframes rtpDrawCore{0%{stroke-dashoffset:494;opacity:0;}8%{opacity:.95;}100%{stroke-dashoffset:0;opacity:.95;}}
@media (prefers-reduced-motion:reduce){
  .groups-hero.play .comet-head{animation:none;offset-distance:100%;opacity:1;}
  .groups-hero.play .comet-trail-glow,.groups-hero.play .comet-trail-core{animation:none;stroke-dashoffset:0;opacity:.6;}
}

/* Topbar: matches the exact color the full-screen gradient below starts at (--rtp-navy-deep),
   not the shared indigo var(--royal) every other screen's header uses -- a deliberate exception
   so there's no seam between header and content on this one screen, same color on both sides of
   the boundary rather than two different colors meeting at a hard line. Flat and constant (no
   fade needed): there's nothing to visibly swap to since it's the same color before, during, and
   after the splash. Only the title's reveal is timed, once the comet settles. */
.topbar.groups-splash{background:var(--rtp-navy-deep);}
/* The "Round the Post" wordmark (design team: at the header's 19px it got lost under the
   animation). Lives in the content now, one line sized to the screen width, sitting right on
   top of the comet — the hero is pulled up under it into its own empty top band of sky, the
   same arrangement as the roundthepost.com landing page on a phone. Still revealed when the
   comet settles (renderTeamScreen's first timeout adds `titleReady`), before the group list. */
.groups-wordmark{position:relative;z-index:1;text-align:center;white-space:nowrap;font-family:'Space Grotesk',system-ui,sans-serif;font-weight:700;font-size:min(12.5vw, 56px);line-height:1.05;letter-spacing:-.01em;color:var(--rtp-silver);margin-top:4px;}
.groups-wordmark em{font-style:normal;color:var(--rtp-orange);}
.groups-wordmark + .groups-hero{margin-top:-26px;}
.groups-screen.playing .groups-wordmark{opacity:0;transition:opacity .8s ease;}
.groups-screen.playing.titleReady .groups-wordmark{opacity:1;}
@media (prefers-reduced-motion:reduce){
  .groups-screen.playing .groups-wordmark{transition:none;opacity:1;}
}

/* Each group is now its own rounded box on a dark card, with a lightweight shared net-pattern
   texture (one repeating-linear-gradient, not a unique inline SVG per card -- stays cheap with
   a long group list) instead of the old plain white full-width row. */
.group-card{
  display:flex;align-items:center;gap:8px;padding:18px 16px;margin-bottom:10px;border-radius:16px;
  background-color:var(--rtp-navy-mid);
  background-image:repeating-linear-gradient(135deg, rgba(231,236,245,.08) 0 1px, transparent 1px 28px);
  border:1px solid rgba(231,236,245,.1);
}
.group-name{font-weight:700;font-size:18px;color:var(--rtp-silver);}
.group-meta{font-size:13px;color:var(--rtp-silver-dim);margin-top:2px;}
.groups-screen .new-player-row{border-top:none;background-color:var(--rtp-navy-mid);border:1px dashed rgba(231,236,245,.25);border-radius:16px;padding:14px 16px;margin-bottom:10px;}
.groups-screen .pname-input{color:var(--rtp-silver);}
.groups-screen .pname-input::placeholder{color:var(--rtp-silver-dim);}
.groups-screen .empty-state{color:var(--rtp-silver-dim);}
.group-menu-btn{color:var(--rtp-silver-dim);}
/* Help now lives at the end of the group list instead of the header (design team follow-up) --
   a plain low-key link, not a boxed card like the groups themselves, since it's navigation, not
   data. Inside .list-wrap, so it fades in with the rest of the list's own reveal, last of all. */
.groups-help-link{width:100%;background:none;border:none;font-family:inherit;font-weight:700;font-size:15px;color:var(--rtp-silver-dim);text-align:center;padding:18px 0 4px;cursor:pointer;}
.groups-build-version{width:100%;text-align:center;font-size:11px;color:var(--rtp-silver-dim);opacity:.55;padding:0 0 28px;}

/* Help sheet */
.help-overlay{position:fixed;inset:0;z-index:20;}
.help-scrim{position:absolute;inset:0;background:rgba(15,23,42,.5);}
.help-sheet{position:absolute;left:0;right:0;bottom:0;max-width:480px;margin:0 auto;max-height:82%;background:#fff;border-radius:24px 24px 0 0;display:flex;flex-direction:column;box-shadow:0 -8px 30px rgba(15,23,42,.25);}
.help-body{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:16px 20px 8px;}
.help-section{margin-bottom:20px;}
.help-h{display:flex;align-items:center;gap:8px;margin-bottom:6px;font-weight:800;font-size:15px;}
.help-p{font-size:14px;line-height:1.55;color:var(--ink);}
/* A section can now hold more than one paragraph (Players screen, punchlist #171) — spacing
   only between them, not before the first one under the heading. */
.help-p + .help-p{margin-top:10px;}
