  /* ---------------- THEME ----------------
     Two palettes, one set of names. Dark is the default; cream is opt-in from
     Settings and sets data-theme="light" on <html>. Nothing below this block
     should ever hardcode a colour again - if a new colour is needed, it gets a
     name here first, in BOTH palettes, or it will look wrong in one of them.

     The names are roles, not descriptions:
       --page        the ground behind everything
       --paper       a card or panel sitting on the ground
       --gray-bg     a recessed / secondary surface
       --raised      a surface that must read as lifted (menus, hover)
       --ink         primary text, and the strong border colour
       --muted       secondary text
       --line        ordinary borders and rules
       --invert-bg   a deliberately contrasting strip (table headers, badges)
       --invert-fg   text on --invert-bg
       --accent-ink  text sitting ON --lime (lime is light in both themes, so
                     this stays dark in both - do not flip it)

     --ink and --paper keep their old names because 281 declarations already
     use them. Read them as "foreground" and "surface": in dark mode --ink is
     near-white. That is why --invert-bg exists - the handful of places that
     wanted a dark BLOCK rather than dark TEXT can't just use --ink any more. */
  :root{
    /* DARK (default) */
    --page:#0d0f12;
    --paper:#161a20;
    --gray-bg:#1d2128;
    --raised:#222831;
    --line:#2b313b;
    --ink:#eceef0;
    --muted:#8d94a1;
    --invert-bg:#252b34;
    --border-strong:#39414d;  /* --ink is near-white here; 2px of it as a border is glare, not structure */
    --invert-fg:#f5f6f7;
    --yellow:#f4e04d;
    --orange:#f6a13a;
    --green:#b9d62c;        /* winner / positive text - lime is too dim as body text on #161a20 */
    --lime:#afc821;         /* brand accent, identical in both themes */
    --accent-ink:#111214;   /* on lime: 9.9:1. White would be 1.9:1 - never use it */
    --olive:#b5ab63;
    --red-text:#ff6f61;
    --blue:#7aa2ea;
    --danger-soft:#3a1f1d;  /* destructive hover wash */
    --success-soft:#1e2a16;
    --shadow:rgba(0,0,0,0.5);
    --scrim:rgba(0,0,0,0.66);
    --heat-cold:#3c4250;
    --heat-hot:#d98a3a;
    --divider-dot:#3a4048;
    /* Text sitting ON a filled swatch. Each of these backgrounds flips from
       dark-in-cream to light-in-dark, so the text on them has to flip the
       other way - white on a light lime avatar would be unreadable. */
    --on-green:#0f1206;
    --on-danger:#2a0f0c;
    --on-olive:#17150a;
    --pos-text:#8fd94f;     /* a won cell / positive delta */
    --neg-text:#ff8a7d;     /* the same, for a loss - only ever on --danger-soft */
    --warn-text:#e0924a;    /* pending, "played many times" */
    --top-half:#1b2413;     /* ladder promotion zone */
    --bottom-half:#2a1715;  /* ladder relegation zone */
    --notice-bg:#2b2410;
    --notice-line:#6b5a1f;
    --parity-0:rgba(175,200,33,0.16);
    --parity-1:rgba(122,162,234,0.16);
    color-scheme:dark;      /* native scrollbars, form controls and the URL bar follow */
  }
  :root[data-theme="light"]{
    /* CREAM - the original ALLIN4 palette, unchanged */
    --page:#f0efe3;
    --paper:#faf9f1;
    --gray-bg:#eeebdd;
    --raised:#ffffff;
    --line:#e5e2d3;
    --ink:#111214;
    --muted:#6b6b6f;
    --invert-bg:#111214;
    --border-strong:#111214;  /* the cream theme's signature hard ink outline */
    --invert-fg:#ffffff;
    --yellow:#f4e04d;
    --orange:#f6a13a;
    --green:#6e8c1a;
    --lime:#afc821;
    --accent-ink:#111214;
    --olive:#9b9247;
    --red-text:#c0392b;
    --blue:#2e5fae;
    --danger-soft:#f6ded9;
    --success-soft:#eef7e4;
    --shadow:rgba(0,0,0,0.16);
    --scrim:rgba(17,18,20,0.6);
    --heat-cold:#b8b8c4;
    --heat-hot:#b5651d;
    --divider-dot:#c9c9c9;
    --on-green:#ffffff;
    --on-danger:#ffffff;
    --on-olive:#ffffff;
    --pos-text:#2f7a1f;
    --neg-text:#9e2b1c;
    --warn-text:#b5651d;
    --top-half:#f1f6d9;
    --bottom-half:#fbeeee;
    --notice-bg:#fff8e1;
    --notice-line:#e6c860;
    --parity-0:rgba(175,200,33,0.12);
    --parity-1:rgba(46,95,174,0.10);
    color-scheme:light;
  }
  /* ---------------- TYPE ----------------
     Three roles, no webfont. Everything here resolves to a face already on the
     device, which matters more than usual for this app: the club wifi is poor,
     and a Google Fonts request that hangs would leave the score sheet in a
     fallback face mid-match. It also keeps the no-CDN rule the charts follow.

       --font-ui       names, buttons, prose. system-ui first, so this picks up
                       SF Pro on iPhone, Roboto on Android, Segoe on Windows -
                       each the face its own OS is hinted for. 'Helvetica Neue'
                       stays as the fallback the app used before.
       --font-mono     dates, court captions, every column of figures. A fixed
                       advance is what makes a results table scan cleanly.
       --font-display  headings. Prefers a genuinely condensed cut where the OS
                       has one, and simply falls back to --font-ui where it
                       doesn't - no layout shift either way, because the
                       fallback is the face the rest of the page already uses. */
  :root{
    --font-ui: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Helvetica, Arial, sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
    --font-display: 'Helvetica Neue Condensed', 'HelveticaNeue-CondensedBold', 'Arial Narrow', var(--font-ui);
  }

  *{box-sizing:border-box;}
  html{
    -webkit-text-size-adjust:100%;
    text-size-adjust:100%;
    overscroll-behavior-y:none;
    /* manipulation = panning and PINCH-ZOOM stay on, double-tap-to-zoom is
       off, and there's no 300ms delay before a tap registers. Keeping
       double-tap off matters on the scoring screens, where two quick taps on
       a score button would otherwise zoom the page instead of counting. */
    touch-action:manipulation;
  }
  body{
    /* Stops the iOS callout/selection UI appearing on a long-press of chrome.
       Real content (tables, names, scores) opts back in below. */
    -webkit-touch-callout:none;
    -webkit-tap-highlight-color:transparent;
  }
  input, textarea, select, table, .coach-p, .standings-footnote, .empty-note{
    -webkit-user-select:text; user-select:text; -webkit-touch-callout:default;
  }
  body{
    margin:0;
    background:var(--page);
    font-family:var(--font-ui);
    /* Figures line up in every table and score column without each one
       needing to opt in. */
    font-variant-numeric:tabular-nums;
    color:var(--ink);
    padding:24px;
    padding-top:calc(24px + env(safe-area-inset-top));
    padding-bottom:calc(24px + env(safe-area-inset-bottom));
    padding-left:calc(24px + env(safe-area-inset-left));
    padding-right:calc(24px + env(safe-area-inset-right));
    /* Kills the browser's rubber-band bounce / pull-to-refresh gesture when
       installed as a standalone PWA — the single biggest "this is a website
       in a browser" tell on mobile. */
    overscroll-behavior-y:none;
  }
  .sheet{ max-width:1320px; margin:0 auto; }

  /* Form fields render below 16px on phones for compact tables/score
     grids, which makes iOS Safari auto-zoom the whole page on focus.
     Bumping only the focused size to 16px avoids that jump without
     changing the resting layout the tables were tuned for. */
  @media (max-width:600px){
    input:focus, select:focus, textarea:focus{ font-size:1rem; }
  }

  .panel{
    background:var(--paper);
    border:1px solid var(--line);
    border-radius:8px;
    padding:20px 24px;
    margin-bottom:20px;
  }
  @media (max-width: 480px){
    body{ padding:10px; }
    .panel{ padding:14px 12px; border-radius:6px; }
  }
  .panel h2{
    font-size:0.8125rem;
    text-transform:uppercase;
    letter-spacing:0.6px;
    margin:0 0 14px;
    display:flex;
    align-items:center;
    flex-wrap:wrap;
    gap:8px;
  }
  .panel h2 .arrow{ color:var(--muted); font-size:0.6875rem; }
  .format-toggle-btn{
    border:1.5px solid var(--line); background:var(--raised); color:var(--muted);
    font-size:0.625rem; font-weight:600; letter-spacing:0.4px; text-transform:uppercase;
    padding:4px 9px; border-radius:4px; cursor:pointer;
  }
  .format-toggle-btn.active{ background:var(--lime); color:var(--accent-ink); border-color:var(--border-strong); }

  /* CONFIG PANEL */
  .config-row{
    display:flex;
    align-items:center;
    gap:10px;
    flex-wrap:wrap;
  }
  .preset-btn{
    border:1.5px solid var(--border-strong);
    background:var(--raised);
    color:var(--ink);
    font-weight:600;
    font-size:0.8125rem;
    padding:9px 16px;
    border-radius:6px;
    cursor:pointer;
  }
  .preset-btn.active{ background:var(--lime); color:var(--accent-ink); border-color:var(--border-strong); }
  .config-field{
    display:flex; align-items:center; gap:8px;
    font-size:0.75rem; font-weight:500; color:var(--muted);
  }
  .config-field select{
    padding:8px 10px; border:1.5px solid var(--border-strong); border-radius:6px;
    font-weight:600; font-size:0.8125rem; color:var(--ink); background:var(--raised); cursor:pointer;
  }
  .rounds-field{
    display:flex; align-items:center; gap:8px; margin-left:8px;
    font-size:0.75rem; font-weight:500; color:var(--muted);
  }
  .rounds-field input{
    width:56px; padding:7px; border:1px solid var(--line); border-radius:5px; font-weight:600; text-align:center;
  }
  /* The verify-email banner used to carry these two colours in a style
     attribute on the element itself, where no palette could reach them. */
  #verify-email-banner{ background:var(--notice-bg); border:1px solid var(--notice-line); }
  .config-note{ font-size:0.71875rem; color:var(--muted); margin:10px 0 16px; line-height:1.5; }
  /* Setup's "How this game works" - collapsed by default, same chevron as the coach card. */
  details.rules-toggle{ margin:26px 0 4px; }
  details.rules-toggle > summary{
    display:inline-flex; align-items:center; gap:8px; cursor:pointer;
    font-size:0.875rem; font-weight:600; color:var(--green); list-style:none;
    -webkit-tap-highlight-color:transparent;
  }
  details.rules-toggle > summary::-webkit-details-marker{ display:none; }
  details.rules-toggle > summary::after{
    content:''; width:6px; height:6px;
    border-right:2px solid currentColor; border-bottom:2px solid currentColor;
    transform:translateY(-2px) rotate(45deg); transition:transform 0.15s ease;
  }
  details.rules-toggle[open] > summary::after{ transform:translateY(1px) rotate(-135deg); }
  details.rules-toggle > summary:hover{ color:var(--lime); }
  details.rules-toggle > summary:focus-visible{ outline:2px solid var(--green); outline-offset:2px; }
  ul#config-note{ margin:10px 0 4px; padding-left:20px; font-size:0.9375rem; line-height:1.55; color:var(--ink); }
  ul#config-note li + li{ margin-top:6px; }
  @media (prefers-reduced-motion: reduce){ details.rules-toggle > summary::after{ transition:none; } }

  /* PLAYERS PANEL */
  .players-grid{
    display:grid;
    grid-template-columns:repeat(4, 1fr);
    gap:10px;
  }
  .player-field label{
    display:block; font-size:0.625rem; color:var(--muted); font-weight:500;
    text-transform:uppercase; margin-bottom:3px;
  }
  .player-field input{
    width:100%; padding:7px 9px; border:1px solid var(--line); border-radius:4px;
    font-size:0.875rem; font-weight:500; color:var(--ink);
  }
  .player-field.teammate{ padding:6px; border-radius:6px; }
  .player-field.teammate.team-parity-0{ background:var(--parity-0); }
  .player-field.teammate.team-parity-1{ background:var(--parity-1); }
  .player-linked-badge{ display:block; font-size:0.625rem; font-weight:600; color:var(--green); margin-top:3px; }
  /* Linked slots (member OR guest) are green; only an unlinked slot is grey. */
  .slot-code-btn{
    display:inline-block; margin-top:3px; margin-left:8px; cursor:pointer;
    border:1px solid var(--line); background:var(--raised); color:var(--green);
    font-size:0.5625rem; font-weight:600; letter-spacing:0.4px; text-transform:uppercase;
    padding:1px 7px; border-radius:999px;
  }
  .slot-code-btn:hover{ background:var(--lime); border-color:var(--border-strong); color:var(--accent-ink); }
  .slot-remove-btn{
    display:inline-block; margin-top:3px; margin-left:8px; cursor:pointer;
    border:1px solid var(--line); background:var(--raised); color:var(--red-text);
    font-size:0.5625rem; font-weight:600; letter-spacing:0.4px; text-transform:uppercase;
    padding:1px 7px; border-radius:999px;
  }
  .slot-remove-btn:hover{ background:var(--danger-soft); border-color:var(--red-text); }
  .player-linked-badge.pending{ color:var(--warn-text); }
  /* Not yet confirmed. Red until the player accepts, then it reverts to ink
     like any other name - so an unanswered invite is visible at a glance. */
  .pending-name{ color:var(--red-text) !important; }
  .player-linked-badge.unlinked{ color:var(--muted); font-weight:500; text-transform:uppercase; letter-spacing:0.4px; }

  /* Per-player-slot member search dropdown — replaces the old standalone
     "invite a member" search box; searching now happens right in whichever
     slot you're typing into, and links that exact slot instead of the
     first open one. */
  .player-field{ position:relative; }
  .invite-result-list{
    position:absolute; left:0; right:0; top:100%; z-index:5;
    background:var(--raised); border:1.5px solid var(--border-strong); border-top:none; border-radius:0 0 6px 6px;
    max-height:220px; overflow-y:auto;
  }
  .invite-result-row{
    display:flex; justify-content:space-between; align-items:center; gap:10px;
    padding:8px 12px; cursor:pointer; border-bottom:1px solid var(--line);
  }
  .invite-result-row:last-child{ border-bottom:none; }
  .invite-result-row:hover{ background:var(--lime); color:var(--accent-ink);}
  .invite-result-tier{ font-size:0.625rem; font-weight:600; letter-spacing:0.4px;
    text-transform:uppercase; color:var(--green); }
  .invite-result-name{ font-weight:600; font-size:0.8125rem; }
  .invite-result-email{ font-size:0.6875rem; color:var(--muted); }
  .invite-result-empty{ padding:10px 12px; font-size:0.78125rem; color:var(--muted); font-style:italic; }

  .waiting-list-title{ font-size:0.8125rem; text-transform:uppercase; letter-spacing:0.6px; margin:0 0 8px; }
  .waiting-list-card h3{ font-size:0.6875rem; font-weight:600; text-transform:uppercase; letter-spacing:0.09em; color:var(--green); margin:0 0 10px; }
  .waiting-list-row{
    display:flex; justify-content:space-between; align-items:center;
    padding:6px 0; border-bottom:1px solid var(--line); font-size:0.8125rem; font-weight:500;
  }
  .waiting-list-row:last-child{ border-bottom:none; }
  .waiting-list-row .wl-position{ color:var(--muted); font-weight:600; margin-right:8px; }

  /* META / SESSION INFO */
  .meta-fields-grid{
    display:grid;
    grid-template-columns:repeat(4, 1fr);
    gap:10px;
  }
  .meta-fields-grid label{
    display:block; font-size:0.625rem; color:var(--muted); font-weight:500;
    text-transform:uppercase; margin-bottom:3px;
  }
  .meta-fields-grid input,
  .meta-fields-grid select{
    width:100%; padding:7px 9px; border:1px solid var(--line); border-radius:4px;
    font-size:0.875rem; font-weight:500; color:var(--ink); background:var(--raised);
  }
  .meta-fields-grid input[readonly]{ background:var(--gray-bg); color:var(--muted); cursor:default; }
  /* A single-field grid: used inside modals, where the 4-column default
     squeezes one field into a quarter of the width. */
  .meta-fields-grid.one-col{ grid-template-columns:1fr; }
  /* Modal fields match the settings cards - the roomier of the two styles,
     which is what the rest of the app's forms look like. */
  .modal-box .meta-fields-grid input, .modal-box .meta-fields-grid select,
  #request-join-overlay .meta-fields-grid input,
  #request-join-overlay .meta-fields-grid select,
  #guest-upgrade-overlay .meta-fields-grid input{
    border:1.5px solid var(--border-strong); border-radius:10px; padding:9px 11px; font-size:0.9375rem;
  }
  #request-join-overlay .phone-input-row select{ width:132px; }
  .meta-fields-grid textarea{
    width:100%; padding:7px 9px; border:1px solid var(--line); border-radius:4px;
    font-size:0.875rem; font-weight:500; color:var(--ink); background:var(--raised);
    font-family:inherit; resize:vertical;
  }
  .avatar-placeholder{
    width:72px; height:72px; border-radius:50%; background:var(--green); color:var(--on-green);
    display:flex; align-items:center; justify-content:center; font-weight:600; font-size:1.5rem;
  }
  .day-checkbox-row{ display:flex; flex-wrap:wrap; gap:10px; font-size:0.8125rem; font-weight:500; }
  .day-checkbox-row label{ display:flex; align-items:center; gap:4px; cursor:pointer; }
  .time-range-row{ display:flex; align-items:center; gap:6px; }
  .phone-input-row{ display:flex; gap:6px; }
  .phone-input-row select{ flex:0 0 auto; width:110px; }
  .phone-input-row input{ flex:1; min-width:0; }
  .time-range-row select{ flex:1; min-width:0; }
  .time-range-sep{ font-size:0.6875rem; color:var(--muted); font-weight:500; flex-shrink:0; }

  @media (max-width: 700px){
    .meta-fields-grid{ grid-template-columns:repeat(2, 1fr); }
    .players-grid{ grid-template-columns:1fr; }
  }
  @media (max-width: 420px){
    .meta-fields-grid{ grid-template-columns:1fr; }
  }

  /* COURT NAMES */
  .court-names-grid{
    display:grid;
    grid-template-columns:repeat(4, 1fr);
    gap:10px;
    margin-top:14px;
  }
  @media (max-width: 700px){
    .court-names-grid{ grid-template-columns:repeat(2, 1fr); }
  }
  .court-name-field label{
    display:block; font-size:0.625rem; font-weight:600; margin-bottom:3px; text-transform:uppercase;
  }
  .court-name-field input{
    width:100%; padding:7px 9px; border:1px solid var(--line); border-radius:4px;
    font-size:0.8125rem; font-weight:500; color:var(--ink);
  }

  /* SEASON PANEL */
  .season-actions{ display:flex; gap:8px; flex-wrap:wrap; margin-bottom:14px; }
  .saved-weeks-list{ display:flex; flex-direction:column; gap:8px; margin-bottom:20px; }
  .season-name-row{ display:flex; align-items:center; gap:10px; margin-bottom:14px; flex-wrap:wrap; }
  .season-name-fields{ display:flex; align-items:center; gap:10px; }
  /* Menu sits at the right-hand end of the row, away from the name field. */
  .season-name-row .tool-menu-wrap{ margin-left:auto; }
  .season-name-row label{ font-size:0.75rem; font-weight:600; text-transform:uppercase; color:var(--muted); }
  .season-name-row input{ padding:7px 10px; border:1px solid var(--line); border-radius:5px; font-weight:600; font-size:0.875rem; min-width:180px; }

  .progress-table-wrap{ overflow-x:auto; margin-top:0; }
  /* Metrics are deliberately identical to table.standings (font size, header
     size, cell padding, full width) — the two sit side by side in a
     .season-pair-row over the same player list, so equal row counts have to
     produce equal heights or the pair looks broken. Change both together. */
  table.progress-table{ width:100%; border-collapse:collapse; font-size:0.8125rem; white-space:nowrap; }
  table.progress-table th{ background:var(--invert-bg); color:var(--invert-fg); font-size:0.6875rem; text-transform:uppercase; padding:8px; text-align:center; position:sticky; top:0; }
  /* --paper, not #fff: the sticky column sits on the card's paper background,
     and white left a visible stripe down the first column. */
  table.progress-table th.name-col, table.progress-table td.name-col{ text-align:left; position:sticky; left:0; background:var(--paper); z-index:1; }
  table.progress-table th.name-col{ background:var(--invert-bg); z-index:2; }
  table.progress-table td{ padding:7px 8px; text-align:center; border-bottom:1px solid var(--line); font-weight:500; }
  table.progress-table td.pos{ color:var(--pos-text); }
  table.progress-table td.neg{ color:var(--red-text); }
  table.progress-table td.zero{ color:var(--muted); }
  table.progress-table td.blank{ color:var(--line); }

  /* Five cards now (Champion sits with them). auto-fit wraps them rather than
     squeezing five into one row on a laptop. */
  .achievements-row{ display:grid; grid-template-columns:repeat(auto-fit, minmax(190px, 1fr)); gap:12px; margin:16px 0; }
  /* The champion is the headline of the row — marked, not just another card. */
  .achievement-card.champion-card-inline{ border-color:var(--border-strong); border-width:1.5px; }
  .achievement-card.champion-card-inline .a-name{ font-size:1.125rem; }
  .achievement-card .champion-score{ font-size:1rem; }
  /* Segmented control over the season views, in the same pill idiom as
     .auth-pill-toggle. inline-flex so it hugs its labels rather than spanning
     the page; wraps to a second line on a phone instead of overflowing. */
  .season-tab-bar{
    display:inline-flex; flex-wrap:wrap; gap:4px; padding:4px; max-width:100%;
    border:1.5px solid var(--border-strong); border-radius:999px; margin-bottom:16px;
  }
  .season-tab{
    border:none; background:transparent; color:var(--ink); cursor:pointer;
    font-weight:600; font-size:0.75rem; letter-spacing:0.3px; white-space:nowrap;
    padding:8px 16px; border-radius:999px;
  }
  .season-tab:hover{ background:var(--gray-bg); }
  .season-tab.active{ background:var(--lime); color:var(--accent-ink);}
  /* Belt and braces over the hidden attribute: a display rule elsewhere would
     otherwise win and show every panel at once. */
  .season-tab-panel[hidden]{ display:none !important; }
  @media (max-width: 480px){
    .season-tab{ padding:8px 12px; font-size:0.6875rem; }
  }

  /* iOS zooms the whole page in when you focus a field whose text is under
     16px, and leaves it zoomed. Now that pinch-zoom is unlocked (see the
     viewport meta in index.html) that would fire on every form in the app, so
     the 16px floor has to actually hold.
     !important is load-bearing here, not laziness: this is a bare element
     selector, so every rule below with a class in front of it - .player-field
     input, #auth-panel .meta-fields-grid input, .ladder-players-grid input -
     outranks it and wins on specificity alone. Without !important this rule
     silently did nothing for most fields in the app.
     select is left out on purpose: iOS opens a picker for it rather than
     zooming, and the narrow ones (country code, claim-name) are sized to fit
     their row. */
  @media (max-width: 480px){
    input, textarea{ font-size:16px !important; }
  }

  /* Pairs two peer cards across one row. auto-fit drops them back to a single
     column on a phone with no media query; min-width:0 lets each card's own
     overflow-x scroll instead of a wide table stretching the whole grid.
     align-items:stretch so two paired cards are the same height, not ragged. */
  .season-pair-row{
    display:grid; grid-template-columns:repeat(auto-fit, minmax(420px, 1fr));
    gap:16px; align-items:stretch; margin-bottom:16px;
  }
  .season-pair-row > *{ min-width:0; margin-bottom:0; }
  .fairness-cards-row{ display:grid; grid-template-columns:repeat(2, 1fr); gap:12px; margin:16px 0; }
  .achievement-card{ border:1px solid var(--line); border-radius:6px; padding:12px 14px; }
  .achievement-card h4{ font-size:0.65625rem; text-transform:uppercase; letter-spacing:0.4px; color:var(--green); margin:0 0 8px; }
  .achievement-card .a-name{ font-size:0.9375rem; font-weight:700; }
  .achievement-card .a-detail{ font-size:0.71875rem; color:var(--muted); margin-top:3px; }
  .fairness-pair-list{ list-style:none; margin:0; padding:0; font-size:0.71875rem; color:var(--muted); line-height:1.7; }

  /* Deliberately left at table.standings' own size/padding so this reads as
     the same table as Season Fairness sitting beside it — only the cell
     alignment and nowrap differ, which the grid of numbers needs. */
  .partner-matrix th, .partner-matrix td{ padding:7px 8px; text-align:center; white-space:nowrap; }
  /* Row labels are <th>, which table.standings paints white-on-ink for the
     header row — override both here so they stay readable on the paper
     background of the body rows. */
  .partner-matrix .pm-rowname{
    text-align:left; font-weight:500; position:sticky; left:0;
    background:var(--paper); color:var(--ink); border-bottom:1px solid var(--line);
    /* These are full player names, not a column heading — table.standings th
       uppercases every th, which turned 'Dan M' into 'DAN M'. Names render as
       stored, matching the NAME column in Season Fairness next to it. The
       initials in the column headers stay uppercase. */
    text-transform:none;
  }
  .partner-matrix .pm-self{ color:var(--line); }
  .partner-matrix .pm-none{ color:var(--muted); opacity:0.5; }
  /* Partnered is the figure the fairness view exists to police, so it leads
     in green; against is the context beside it, in ink. 2+ stands out in
     each — red for a repeated partner, amber for a repeated opponent.
     Amber is #b5651d (4.1:1 on paper), not --orange, which reads 2.0:1. */
  .partner-matrix .pm-p{ color:var(--green); font-weight:600; }
  .partner-matrix .pm-o{ color:var(--ink); font-weight:500; }
  .partner-matrix .pm-p.pm-many{ color:var(--red-text); font-weight:700; }
  .partner-matrix .pm-o.pm-many-o{ color:var(--warn-text); font-weight:700; }
  .partner-matrix .pm-z{ color:var(--muted); opacity:0.5; font-weight:500; }
  .partner-matrix .pm-sep{ color:var(--muted); opacity:0.55; margin:0 2px; }
  /* Two figures per cell now — trim the side padding so a 16-player grid
     doesn't get much wider than the single-figure version was. */
  .partner-matrix td{ padding-left:6px; padding-right:6px; }
  /* Same ink as every other table cell in this row of cards. Zeros used to be
     muted at 0.45 opacity, which made the whole card look washed out next to
     Season Fairness. 2+ keeps the red — that's the actual signal (an uneven
     rotation), not decoration. */
  .partner-matrix .pm-zero{ color:var(--ink); }
  .partner-matrix .pm-one{ color:var(--ink); }
  .partner-matrix .pm-many{ color:var(--red-text); font-weight:700; }
  /* Same green the +/- column uses for a positive figure — a win rate is the
     other "how are they doing" number in this table. */
  table.standings td.winpct{ color:var(--green); font-weight:600; }
  /* Real is the raw figure — ink, so the adjusted column stays the one the
     eye lands on, as in the reference layout. */
  table.standings td.winpct-real{ color:var(--ink); font-weight:600; }
  /* ---- MY STATS (profile dashboard) ---- */
  #profile-stats .card{ margin-bottom:16px; }
  .card-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:12px; flex-wrap:wrap; }

  .profile-hero-card .ph-top{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
  .profile-hero-card .ph-name{ font-size:1.75rem; font-weight:700; line-height:1.1; }
  .profile-hero-card .ph-badge{
    padding:4px 12px; border:1.5px solid var(--border-strong); border-radius:999px;
    font-size:0.6875rem; font-weight:600; text-transform:uppercase; letter-spacing:0.4px;
  }
  .profile-hero-card .ph-sub{ font-size:0.8125rem; color:var(--muted); margin:6px 0 2px; font-weight:500; }
  .profile-hero-card .ph-range{ font-size:0.6875rem; color:var(--muted); font-weight:500;
    text-transform:uppercase; letter-spacing:0.4px; margin-bottom:16px; }

  /* auto-fit so four tiles reflow to two rows on a phone with no media query */
  .stat-tile-row{ display:grid; grid-template-columns:repeat(auto-fit, minmax(148px, 1fr)); gap:12px; }
  .stat-tile{ border:1px solid var(--line); border-radius:8px; padding:14px 16px; min-width:0; }
  .stat-tile .st-label{ font-size:0.625rem; font-weight:600; letter-spacing:0.5px; text-transform:uppercase; color:var(--green); }
  .stat-tile .st-value{ font-size:1.875rem; font-weight:700; line-height:1.1; margin:6px 0 2px; }
  .stat-tile .st-sub{ font-size:0.6875rem; color:var(--muted); font-weight:500; }

  .coach-card{ border-color:var(--border-strong); }
  .coach-spark{ color:var(--green); margin-right:4px; }
  .coach-p{ font-size:0.875rem; line-height:1.65; margin:0 0 12px; }
  .coach-p:last-of-type{ margin-bottom:8px; }

  /* Collapsible Coach insight. Selectors are element-qualified (details.x, not
     .x) on purpose: .card sets the padding 200 lines further down, and an
     equal-specificity .coach-card rule up here would lose to it on source
     order. details.coach-card outranks .card outright, so these stay next to
     the rest of the coach styles where they belong.
     The card's padding moves off the card and onto the summary and body, so
     the tap target for open/close is the full width of the header strip
     rather than just the words. */
  details.coach-card{ padding:0; }
  details.coach-card > summary{
    display:flex; align-items:center; gap:10px;
    padding:14px 16px; cursor:pointer;
    -webkit-tap-highlight-color:transparent;
    list-style:none;            /* kills the default triangle in Firefox */
  }
  details.coach-card > summary::-webkit-details-marker{ display:none; }  /* and in Safari */
  details.coach-card > summary h3{ margin:0; flex:1 1 auto; min-width:0; }
  /* Chevron drawn from two borders - no icon font, no SVG, no extra request. */
  details.coach-card > summary::after{
    content:''; flex:0 0 auto; width:7px; height:7px; margin-right:2px;
    border-right:2px solid var(--muted); border-bottom:2px solid var(--muted);
    transform:translateY(-2px) rotate(45deg);
    transition:transform 0.15s ease;
  }
  details.coach-card[open] > summary::after{ transform:translateY(2px) rotate(-135deg); }
  details.coach-card > summary:focus-visible{ outline:2px solid var(--green); outline-offset:-2px; }
  .coach-body{ padding:0 16px 14px; }
  @media (prefers-reduced-motion: reduce){
    details.coach-card > summary::after{ transition:none; }
  }

  .stat-card-row{ display:grid; grid-template-columns:repeat(auto-fit, minmax(210px, 1fr)); gap:16px; align-items:stretch; }
  .stat-card-row > .card{ margin-bottom:0; }
  .stat-big.pos{ color:var(--green); }
  .stat-big.neg{ color:var(--red-text); }
  .ctx-row{ display:flex; gap:10px; justify-content:space-between; align-items:baseline; padding:5px 0; border-top:1px solid var(--line); }
  .ctx-k{ font-size:0.6875rem; color:var(--muted); font-weight:500; flex-shrink:0; }
  .ctx-v{ font-size:0.75rem; font-weight:600; text-align:right; overflow-wrap:anywhere; }

  /* Chart is inline SVG: no build step, and no CDN dependency at the club. */
  .chart{ width:100%; height:auto; display:block; margin-top:8px; }
  .chart-line{ fill:none; stroke:var(--green); stroke-width:2.5; stroke-linejoin:round; stroke-linecap:round; }
  .chart-dot{ fill:var(--green); }
  .chart-grid{ stroke:var(--line); stroke-width:1; stroke-dasharray:4 4; }
  .chart-axis{ font-size:11px; fill:var(--muted); font-weight:500; }

  .scale-toggle{ display:inline-flex; gap:3px; padding:3px; border:1.5px solid var(--border-strong); border-radius:999px; flex-shrink:0; }
  .scale-btn{
    border:none; background:transparent; cursor:pointer; color:var(--ink);
    font-size:0.6875rem; font-weight:600; padding:5px 12px; border-radius:999px;
    text-transform:none; letter-spacing:normal;
  }
  .scale-btn.active{ background:var(--lime); color:var(--accent-ink);}

  .heat-head{ display:flex; justify-content:space-between; gap:12px; flex-wrap:wrap; font-size:0.8125rem; margin:10px 0 0; }
  .heat-bar{
    position:relative; height:14px; border-radius:999px; margin:26px 0 10px;
    background:linear-gradient(90deg, var(--heat-cold) 0%, var(--heat-hot) 25%, #e8d27a 50%, var(--lime) 75%, var(--green) 100%);
  }
  .heat-marker{ position:absolute; top:-6px; transform:translateX(-50%); }
  .heat-marker::before{
    content:''; display:block; width:26px; height:26px; border-radius:50%;
    background:var(--paper); border:3px solid var(--border-strong); box-sizing:border-box;
  }
  .heat-marker span{
    position:absolute; top:28px; left:50%; transform:translateX(-50%);
    background:var(--invert-bg); color:var(--invert-fg); padding:2px 8px; border-radius:999px;
    font-size:0.625rem; font-weight:600; white-space:nowrap;
  }
  .tier-row{ display:grid; grid-template-columns:repeat(auto-fit, minmax(104px, 1fr)); gap:8px; margin-top:28px; }
  .tier{ border:1.5px solid var(--line); border-radius:8px; padding:8px 6px; text-align:center; display:flex; flex-direction:column; gap:2px; min-width:0; }
  .tier b{ font-size:0.75rem; font-weight:600; }
  .tier span{ font-size:0.625rem; color:var(--muted); font-weight:500; }
  .tier.active{ border-color:var(--border-strong); background:var(--lime); color:var(--accent-ink);}
  .tier.active span{ color:var(--ink); }

  .ineligible-badge{ color:var(--muted); cursor:help; font-weight:400; }

  .past-seasons-block{ margin-top:24px; padding-top:20px; border-top:1px solid var(--line); }
  .past-season-row{ display:flex; justify-content:space-between; align-items:center; padding:10px 0; border-bottom:1px solid var(--line); }
  .past-season-row .ps-name{ font-weight:600; }
  .past-season-row .ps-sub{ font-size:0.71875rem; color:var(--muted); }

  @media (max-width: 900px){
    .achievements-row{ grid-template-columns:1fr 1fr; }
  }
  @media (max-width: 480px){
    .achievements-row{ grid-template-columns:1fr; }
  }
  .sw-game{ display:block; font-size:0.625rem; font-weight:600; letter-spacing:0.5px;
    text-transform:uppercase; color:var(--green); margin-bottom:2px; }
  .saved-week-row{
    display:flex; align-items:center; justify-content:space-between;
    border:1px solid var(--line); border-radius:6px; padding:10px 14px;
    font-size:0.8125rem;
  }
  .saved-week-row .sw-info{ font-weight:500; }
  .saved-week-row .sw-info .sw-sub{ font-weight:400; color:var(--muted); font-size:0.71875rem; margin-top:2px; }
  .btn.danger{ background:var(--raised); color:var(--red-text); border-color:var(--red-text); font-size:0.6875rem; padding:6px 12px; }
  .empty-note{ font-size:0.78125rem; color:var(--muted); font-style:italic; }
  #importFileInput{ display:none; }

  /* ROUNDS PANEL */
  .rounds-grid{
    display:grid;
    grid-template-columns: 1fr;
    gap:16px;
  }
  .round-day-header{
    grid-column: 1 / -1;
    font-weight:700; font-size:0.8125rem; letter-spacing:0.5px; text-transform:uppercase;
    color:var(--ink); background:var(--gray-bg); padding:8px 12px; border-radius:6px;
    margin-top:4px;
  }
  .round-day-header:first-child{ margin-top:0; }
  .round-card{
    border:1px solid var(--line);
    border-radius:6px;
    padding:12px 14px;
  }
  .round-card .rhead{
    font-family:var(--font-display);
    font-weight:700; font-size:0.875rem; text-transform:uppercase; letter-spacing:0.04em;
    margin-bottom:10px; padding-bottom:8px;
    border-bottom:1px solid var(--line);
  }
  .round-card .rhead .rn{ color:var(--green); }
  /* One match = a score box, the bordered block, a score box. The boxes sit
     OUTSIDE the border so the block holds only the fixture (court + players)
     and the inputs read as separate things to fill in. */
  /* ---------------- MATCH CARDS ----------------
     One card per match: a caption, then a line per pair with that pair's own
     score beside it.
     The previous layout put the two scores at the far LEFT and far RIGHT of a
     single row, with the names between them, so reading one result meant
     zig-zagging - name, across, number, back. Stacking the pairs puts every
     number next to the names it belongs to and lets a long pair name use the
     full width of the card instead of half of it.
     The score boxes are still <input>. This is the screen organizers type
     into during a match, and one box per line is an easier target than two
     boxes at opposite edges of the screen. */
  .match-line{ margin-bottom:8px; }
  .match-line:last-child{ margin-bottom:0; }

  .mcard{
    border:1px solid var(--line); border-radius:10px;
    background:var(--gray-bg);
    padding:10px 12px 8px;
  }

  /* Wide-tracked small caps, as on the logo. Muted, because which court you
     are on matters far less than who won - it's a locator, not a result. */
  .mcard-head{
    display:flex; align-items:baseline; gap:8px;
    font-family:var(--font-mono); font-size:0.625rem; font-weight:500;
    letter-spacing:0.12em; text-transform:uppercase; color:var(--muted);
    margin-bottom:6px;
  }
  .mcard-head .ct-word,
  .mcard-head .ct-num,
  .mcard-head .ct-num.only{ font-size:inherit; line-height:inherit; }
  /* The court number keeps the brand colour so it still reads at a glance
     from courtside, which was the whole point of the old coloured tile. */
  .mcard-head .ct-num{ color:var(--lime); }
  .mc-round{ color:var(--muted); opacity:0.75; }
  .mc-round::before{ content:'\00b7'; margin-right:8px; }

  .mrow{
    display:flex; align-items:center; gap:10px;
    padding:3px 0; min-width:0;
  }
  .pair{
    flex:1 1 auto; min-width:0;
    font-weight:500; font-size:0.9375rem; line-height:1.3;
    overflow-wrap:break-word; word-break:normal;
  }
  /* The winner is marked by COLOUR, not by weight. Both names stay at the
     same weight so the card reads as one thing; the accent is what your eye
     lands on. Only applied once a match actually has a result - an unplayed
     or drawn match highlights neither line. */
  .mrow.won .pair{ color:var(--green); }

  .round-byes{
    font-size:0.65625rem; color:var(--muted); margin-top:10px; padding-top:8px;
    border-top:1px dashed var(--line);
  }

  .score-box{
    flex:0 0 auto; width:50px; height:38px;
    text-align:right; padding:0 6px;
    border:none; border-radius:8px;
    background:transparent; color:inherit;
    font-family:inherit; font-weight:600; font-size:1.375rem; line-height:1;
    -moz-appearance:textfield;
  }
  /* Quiet until touched, so the number reads as a result rather than as a
     form field - but never invisible: the tinted resting state is what tells
     an organizer this is the thing to tap. */
  .score-box:hover{ background:var(--raised); }
  .score-box:focus{
    outline:none; background:var(--raised);
    box-shadow:0 0 0 2px var(--lime);
  }
  .mrow.won .score-box{ color:var(--green); }

  /* Stand-in used only while exporting the score sheet as an image —
     html2canvas renders real <input> values as empty boxes. */
  .score-box-static{ display:inline-flex; align-items:center; justify-content:flex-end; box-sizing:border-box; }

  .score-box::-webkit-outer-spin-button,
  .score-box::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }

  /* REPORT — logo big on top, session info stacked underneath (not side by
     side), matching the rest of the app's centered hero treatment. */
  .header{ margin-bottom:18px; }
  .logo-title{ display:flex; flex-direction:column; align-items:center; gap:14px; text-align:center; }
  .logo{ width:100%; max-width:280px; height:auto; }
  .logo img{ width:100%; height:auto; display:block; object-fit:contain; }
  /* Two artworks, one shown. The dark-background version (white + lime on a
     transparent ground) is the default because dark is the default theme, so
     the common case paints correctly with no swap and no flash.
     A CSS swap rather than setting .src from JS: the markup is parsed with the
     right logo already chosen, where a script running at the end of <body>
     would show the wrong one first on every load.
     The cream logo has NO alpha - it carries a baked-in light background - so
     it must never be shown on the dark page, and vice versa. */
  /* These MUST outrank '.welcome-hero .logo img' and '.dashboard-header .logo
     img', which both set display:block at (0,2,1) and sit further down the
     file. Written as '.logo .logo-light' this rule was (0,2,0) - it lost, and
     both logos rendered, one under the other.
     The :root prefix and the img qualifier buy the extra weight. Any new
     '<ancestor> .logo img' rule added below must stay at or under (0,2,1), or
     this breaks again the same way. */
  :root .logo img.logo-light{ display:none; }
  :root[data-theme="light"] .logo img.logo-dark{ display:none; }
  :root[data-theme="light"] .logo img.logo-light{ display:block; }
  .title-block{ margin-left:0; }
  /* The game's own name is the biggest thing on the header - it's what the
     organizer typed, and what everyone in the room calls this game. */
  .season-heading{ margin-bottom:14px; }
  .season-heading-sub{ font-size:0.75rem; font-weight:500; color:var(--muted);
    text-transform:uppercase; letter-spacing:0.4px; margin-top:2px; }
  /* Set like the wordmark in the logo: uppercase, heavy, and TIGHT. The old
     0.5px of tracking pushed the letters apart, which is the opposite of what
     the logo does and why the title never looked related to it. */
  .game-title{
    font-family:var(--font-display);
    font-size:1.5rem; font-weight:700; letter-spacing:-0.01em;
    text-transform:uppercase; line-height:1.1; margin-bottom:4px;
  }
  @media (max-width: 480px){ .game-title{ font-size:1.25rem; } }
  .title-block h1{ font-size:0.8125rem; font-weight:600; margin:0; text-transform:uppercase; letter-spacing:0.6px;}
  /* Date - format - status. A fixed advance makes this read as data rather
     than as a sentence, which is exactly what it is. */
  .meta-row{
    font-family:var(--font-mono);
    font-size:0.6875rem; font-weight:400; color:var(--muted);
    text-transform:uppercase; letter-spacing:0.02em; margin-top:6px;
  }
  /* The format leads the row and carries the brand colour - it's the one
     thing there that says what you're actually playing. */
  #game-format-name{ color:var(--green); font-weight:600; }
  .meta-row span:not(:last-child)::after{ content:"•"; margin:0 10px; color:var(--divider-dot); }

  hr.rule{ border:none; border-top:2px solid var(--border-strong); margin:0 0 20px; }

  .summary-row{
    display:grid;
    grid-template-columns: 1.3fr 1.3fr 1fr 1fr 1fr;
    gap:14px;
    margin-bottom:24px;
  }
  .card{ border:1px solid var(--line); border-radius:6px; padding:14px 16px; }
  /* Wide-tracked small caps, the way PADEL COMMUNITY is set on the logo.
     At 11px a label needs the extra tracking to stay legible; at body size
     the same treatment would be unreadable, so it lives only here. */
  .card h3{
    font-size:0.6875rem; font-weight:600; text-transform:uppercase;
    letter-spacing:0.09em; color:var(--green); margin:0 0 10px;
  }
  .champion-name{ font-size:1.5rem; font-weight:700; margin-right:8px; }
  .champion-score{ font-size:1.25rem; font-weight:700; color:var(--green); }
  .champion-sub{ font-size:0.75rem; color:var(--muted); margin-top:8px; line-height:1.5; }
  .champion-sub strong{ color:var(--ink); }

  .podium-card-row{ display:flex; gap:8px; }
  .pcard{ flex:1; text-align:center; padding:10px 4px; border-radius:4px; }
  .pcard .r{ font-size:0.625rem; font-weight:600; }
  .pcard .n{ font-size:0.8125rem; font-weight:600; margin:4px 0 2px; }
  .pcard .s{ font-size:0.9375rem; font-weight:700; }
  .pcard.g2{ background:var(--gray-bg); }
  .pcard.g1{ background:var(--yellow); border:2px solid var(--border-strong); color:var(--accent-ink);}
  .pcard.g3{ background:var(--orange); color:var(--accent-ink);}

  .stat-big{ font-size:1.125rem; font-weight:700; margin-bottom:6px; }
  .stat-sub{ font-size:0.75rem; color:var(--muted); }
  .stat-name-line{ font-size:0.9375rem; font-weight:600; }
  .stat-name-line .num{ font-weight:700; margin-left:4px; }

  .table-scroll{ overflow-x:auto; }
  table.standings{ width:100%; border-collapse:collapse; font-size:0.8125rem; }
  table.standings th{
    text-align:left; font-size:0.625rem; font-weight:600; text-transform:uppercase;
    letter-spacing:0.09em; background:var(--invert-bg); color:var(--invert-fg); padding:9px 8px;
  }
  table.standings td{ padding:7px 8px; border-bottom:1px solid var(--line); font-weight:500; }
  table.standings tr.rank-1 td{ background:var(--yellow); font-weight:600; color:var(--accent-ink);}
  table.standings tr.rank-2 td{ background:var(--gray-bg); font-weight:600; }
  table.standings tr.rank-3 td{ background:var(--orange); font-weight:600; color:var(--accent-ink);}
  table.standings td.win{ color:var(--pos-text); font-weight:600; }
  table.standings td.loss{ color:var(--red-text); font-weight:600; }
  table.standings td.tied{ color:var(--muted); font-weight:600; }
  table.standings td.plusminus{ font-weight:700; }
  table.standings th.num, table.standings td.num{ text-align:center; }
  /* Rank movement pill, as on the reference: a small tinted capsule carrying
     an arrow and the number of places. Colour does the work, so the arrow is
     redundant for anyone who can see it - which is exactly why it is there
     for anyone who can't distinguish the two tints. */
  .move-col{ width:1%; white-space:nowrap; text-align:center; }
  .move-pill{
    display:inline-flex; align-items:center; gap:2px;
    min-width:34px; justify-content:center;
    padding:2px 6px; border-radius:999px;
    font-family:var(--font-mono); font-size:0.625rem; font-weight:600;
    letter-spacing:0.02em; line-height:1.4;
  }
  .move-pill.up{ color:var(--pos-text); background:var(--success-soft); }
  .move-pill.down{ color:var(--neg-text); background:var(--danger-soft); }
  .move-pill.same{ color:var(--muted); background:transparent; }
  .move-pill.new{ color:var(--ink); background:var(--gray-bg); font-size:0.5625rem; letter-spacing:0.08em; }

  .standings-footnote{ font-size:0.6875rem; color:var(--muted); margin-top:12px; line-height:1.7; }

  /* Nav buttons left, Game tools right, pushed apart by .toolbar-spacer. */
  .toolbar{ display:flex; align-items:center; justify-content:flex-start; gap:8px; margin-bottom:14px; flex-wrap:wrap; }
  .toolbar-spacer{ margin-left:auto; }
  /* Inline in the toolbar rather than a block above the scores. Wraps to its
     own line on a phone, since .toolbar is flex-wrap:wrap. */
  #claim-name-row{
    align-items:center; gap:8px; flex-wrap:wrap;
    padding:5px 10px; border:1px solid var(--green); border-radius:999px;
    background:var(--success-soft); font-size:0.75rem; font-weight:500; color:var(--ink);
  }
  /* Just the button: no pill around it. */
  #claim-name-row.bare{ border:none; background:transparent; padding:0; }
  #claim-name-row select{ font-size:0.75rem; padding:3px 6px; }
  #claim-name-row .btn{ padding:5px 11px; font-size:0.6875rem; }
  .tool-menu-wrap{ position:relative; }
  .tool-menu{
    position:absolute; right:0; top:calc(100% + 6px); z-index:55; min-width:220px;
    background:var(--paper); border:1.5px solid var(--border-strong); border-radius:10px;
    padding:6px; box-shadow:0 10px 26px var(--shadow);
  }
  .tool-menu[hidden]{ display:none !important; }
  /* Set by placeDropdown() when the default right-alignment would push the
     menu off the left edge. */
  .tool-menu.align-left, .account-menu.align-left{ left:0; right:auto; }
  .tool-menu, .account-menu{ max-width:calc(100vw - 16px); }
  /* The buttons keep .btn for their ids' sake but read as menu rows here. */
  .tool-menu .btn{
    display:block; width:100%; text-align:left; border:none; background:transparent;
    border-radius:7px; padding:9px 12px; font-size:0.8125rem; font-weight:500;
  }
  .tool-menu .btn:hover{ background:var(--lime); color:var(--accent-ink);}
  .tool-menu .btn.danger-item{ color:var(--red-text); }
  .tool-menu .btn.danger-item:hover{ background:var(--danger-soft); }
  /* Reset is destructive - separate it from the routine actions. */
  .tool-menu .btn.danger-item{ margin-top:5px; border-top:1px solid var(--line); border-radius:0 0 7px 7px; padding-top:11px; }
  .btn{
    border:1px solid var(--border-strong); background:var(--lime); color:var(--accent-ink); font-weight:600;
    font-size:0.75rem; padding:8px 14px; border-radius:5px; cursor:pointer;
  }
  .btn.secondary{ background:var(--raised); color:var(--ink); }
  .btn:disabled{ opacity:0.5; cursor:not-allowed; }
  .btn.danger{ background:var(--red-text); color:var(--on-danger); border-color:var(--red-text); }
  .btn.danger:hover{ filter:brightness(0.92); }

  @media (max-width: 1000px){
    .players-grid{ grid-template-columns:repeat(2,1fr); }
    .summary-row{ grid-template-columns:1fr 1fr; }
  }
  @media (max-width: 480px){
    .summary-row{ grid-template-columns:1fr; }
  }

  /* Enter Scores + the Report below it (stats cards, standings) stay a
     single stacked column on anything narrower than this — phones and
     tablets get the exact same layout as before. Only on a genuinely wide
     desktop window do they sit side by side, 50/50. */
  @media (min-width: 1300px){
    .play-columns{ display:flex; align-items:flex-start; gap:24px; }
    .play-columns > #rounds-panel,
    .play-columns > #report-panel{ flex:1 1 50%; min-width:0; }
    /* The 5-card stat row doesn't know it's been squeezed into half the
       viewport (media queries read viewport width, not column width) —
       without this it would try to cram 5 cards into ~600px. */
    .play-columns #report-panel .summary-row{ grid-template-columns:1fr 1fr; }
  }

  /* LADDER MODE */
  .ladder-wrap{ display:grid; grid-template-columns:1fr 1fr; gap:20px; }
  .ladder-group{ border:1px solid var(--line); border-radius:8px; padding:16px; }
  .ladder-group h3{ font-size:0.875rem; margin:0 0 4px; }
  .ladder-group h3 input{
    font-size:0.9375rem; font-weight:700; border:none; border-bottom:2px dashed var(--line);
    padding:2px 0; width:100%; background:transparent;
  }
  .ladder-players-grid{ display:grid; grid-template-columns:1fr 1fr; gap:8px; margin:12px 0; }
  .ladder-players-grid input{
    width:100%; padding:6px 8px; border:1px solid var(--line); border-radius:4px;
    font-size:0.8125rem; font-weight:500;
  }
  .ladder-rounds{ display:flex; flex-direction:column; gap:8px; margin:14px 0; }
  .ladder-round-card{ border:1px solid var(--line); border-radius:6px; padding:8px 10px; }
  .ladder-round-card .lrhead{ font-weight:600; font-size:0.75rem; margin-bottom:6px; color:var(--green); }
  .ladder-match-row{
    display:grid; grid-template-columns:1fr 30px 8px 30px 1fr; align-items:center; gap:4px;
    font-size:0.6875rem; font-weight:500; margin-bottom:4px;
  }
  .ladder-match-row input{
    width:100%; text-align:center; padding:4px 2px; border:1px solid var(--line); border-radius:4px; font-weight:600;
  }
  .ladder-standings{ width:100%; border-collapse:collapse; font-size:0.75rem; margin-top:8px; }
  .ladder-standings th{ background:var(--invert-bg); color:var(--invert-fg); font-size:0.625rem; text-transform:uppercase; padding:5px 6px; text-align:left; }
  .ladder-standings td{ padding:5px 6px; border-bottom:1px solid var(--line); font-weight:500; }
  .ladder-standings th.num, .ladder-standings td.num{ text-align:center; }
  .ladder-standings tr.top-half td{ background:var(--top-half); }
  .ladder-standings tr.bottom-half td{ background:var(--bottom-half); }

  @media (max-width: 900px){
    .ladder-wrap{ grid-template-columns:1fr; }
  }

  /* CHAMPION ROUND */
  .champion-groups-grid{ display:grid; grid-template-columns:repeat(2,1fr); gap:16px; margin-top:10px; }
  .champion-group-card{ border:2px solid var(--border-strong); border-radius:8px; padding:14px 16px; }
  .champion-group-card h4{ margin:0 0 10px; font-size:0.8125rem; text-transform:uppercase; letter-spacing:0.4px; }
  .champion-rank-list{ font-size:0.6875rem; color:var(--muted); margin-bottom:10px; line-height:1.6; }
  .champion-match-row{
    display:grid; grid-template-columns:1fr 34px 10px 34px 1fr; align-items:center; gap:6px;
    font-size:0.78125rem; font-weight:500; margin-bottom:8px;
  }
  .champion-match-row input{
    width:100%; text-align:center; padding:5px 2px; border:1px solid var(--line); border-radius:4px; font-weight:600;
  }
  .champion-winner{
    font-size:0.75rem; font-weight:600; padding:6px 8px; border-radius:5px; background:var(--yellow);
    text-align:center; color:var(--accent-ink);}
  .champion-winner.pending{ background:var(--gray-bg); color:var(--muted); font-weight:500; }

  @media (max-width: 900px){
    .champion-groups-grid{ grid-template-columns:1fr; }
  }

  /* PRINT SCORE SHEET */
  #print-sheet-panel{ display:none; }
  #print-sheet{ display:none; }
  .print-game-name{ font-size:1.25rem; font-weight:900; text-transform:uppercase; letter-spacing:0.5px; }
  .print-season-line{ font-size:0.75rem; font-weight:700; color:#444; margin-top:2px; }
  .print-header{ text-align:center; margin-bottom:6px; }
  .print-header h1{ font-size:1.5rem; margin:0 0 4px; text-transform:uppercase; letter-spacing:0.5px; }
  .print-header .print-meta{ font-size:0.75rem; color:#333; text-transform:uppercase; letter-spacing:0.5px; }
  .print-header .print-meta span:not(:last-child)::after{ content:"-"; margin:0 8px; color:#999; }
  .print-players{ font-size:0.6875rem; margin:8px 0 14px; color:#333; }
  .print-players b{ font-size:0.6875rem; text-transform:uppercase; }

  /* Print round cards — mirrors the on-screen "Enter Scores" card layout,
     sized up for courtside legibility instead of a dense packed grid. */
  .print-rounds-grid{
    display:grid; grid-template-columns:repeat(2, 1fr); gap:14px; margin-bottom:22px;
  }
  .print-round-card{
    border:1.5px solid #000; border-radius:6px; padding:12px 14px;
    break-inside:avoid; page-break-inside:avoid;
  }
  .print-round-head{
    font-weight:900; font-size:1.125rem; color:var(--green); margin-bottom:10px;
    padding-bottom:8px; border-bottom:1.5px solid #000;
  }
  .print-match-line{
    display:flex; align-items:stretch; gap:8px; margin-bottom:8px;
  }
  .print-match-block{
    flex:1 1 auto; min-width:0;
    border:2px solid #000; border-radius:6px; overflow:hidden;
    display:flex; align-items:stretch;
  }
  .print-match-block:last-child{ margin-bottom:0; }
  .print-court-banner{
    flex:0 0 62px; align-self:stretch;
    border:2px solid #000; border-radius:6px;
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    color:#111214; font-weight:900; font-size:0.75rem; letter-spacing:0.4px; text-transform:uppercase;
    text-align:center; line-height:1.2; padding:4px; overflow-wrap:anywhere;
    /* .ct-word / .ct-num sizes below — same word-over-number treatment as
       the on-screen block, scaled up for paper. */
    /* Printers often skip background colors by default — force it on. */
    -webkit-print-color-adjust:exact; print-color-adjust:exact;
  }
  .print-court-banner .ct-word{ font-size:0.625rem; letter-spacing:0.6px; }
  .print-court-banner .ct-num{ line-height:1.1; }
  .print-match-row{
    display:grid; grid-template-columns: 1fr auto 1fr;
    align-items:center; gap:8px; padding:4px 10px;
    flex:1 1 auto; min-width:0;
  }
  .print-pair-stack{
    font-size:1.0625rem; font-weight:900; line-height:1.25; text-align:center;
    min-width:0; overflow-wrap:anywhere;
  }
  .print-vs{ text-align:center; font-weight:900; font-size:1.375rem; text-transform:uppercase; }
  .print-score-box{
    flex:0 0 38px; align-self:center;
    display:block; width:38px; height:38px; border:3px solid #000; border-radius:6px;
    background:#f2f2f0;
  }
  .print-round-byes{
    font-size:0.75rem; font-style:italic; color:#555; margin-top:8px;
    padding-top:8px; border-top:1px dashed #999;
  }

  .print-season-header{ display:flex; align-items:center; justify-content:center; gap:16px; margin-bottom:18px; text-align:left; }
  .print-season-header img{ width:64px; height:64px; object-fit:contain; flex-shrink:0; }
  .print-season-header h1{ margin:0; font-size:1.375rem; text-transform:uppercase; }
  .print-season-header .print-meta{ text-align:left; margin-top:4px; font-size:0.75rem; color:#333; text-transform:uppercase; letter-spacing:0.5px; }
  .print-season-header .print-meta span:not(:last-child)::after{ content:"-"; margin:0 8px; color:#999; }
  .print-season-section{ margin-bottom:24px; }

  /* GROUP PICKER / WELCOME / STEP NAV */
  .welcome-hero{ text-align:center; padding:20px 0 28px; }
  /* Grows with the viewport instead of stopping at a fixed cap — scales
     between 220px (narrow screens) and 480px (wide desktop), proportional
     to available width, rather than a single fixed size for everyone. */
  .welcome-hero .logo{ margin:0 auto 10px; width:100%; max-width:clamp(220px, 28vw, 480px); height:auto; }
  .welcome-hero .logo img{ width:100%; height:auto; display:block; object-fit:contain; }
  .welcome-hero h1{ font-size:2rem; font-weight:700; margin:0 0 4px; text-transform:uppercase; letter-spacing:-0.5px; }
  .welcome-tagline{ font-size:0.8125rem; color:var(--muted); margin:0; }
  @media (max-width: 480px){
    .welcome-hero h1{ font-size:1.5rem; }
  }

  .game-choice-grid{ display:grid; grid-template-columns:repeat(3, 1fr); gap:16px; }
  .game-choice-btn{
    border:1.5px solid var(--border-strong); background:var(--raised); color:var(--ink);
    border-radius:8px; padding:20px 18px; cursor:pointer; text-align:left;
    display:flex; flex-direction:column; gap:6px;
  }
  .game-choice-btn:hover{ background:var(--lime); color:var(--accent-ink);}
  .game-choice-btn .gc-title{ font-size:1rem; font-weight:700; text-transform:uppercase; }
  .game-choice-btn .gc-desc{ font-size:0.71875rem; color:var(--muted); font-weight:400; }
  .game-choice-btn:hover .gc-desc{ color:var(--ink); }

  /* Group cards are a div (not a button) since they contain a nested Delete
     button — the open-area still behaves like the button cards above. */
  .group-card{ cursor:default; }
  .group-card:hover{ background:var(--raised); }
  .group-card-open{ display:flex; flex-direction:column; gap:6px; cursor:pointer; margin:-20px -18px 0; padding:20px 18px 12px; border-radius:8px 8px 0 0; }
  .group-card-open:hover{ background:var(--lime); color:var(--accent-ink);}
  .group-card-open:hover .gc-desc{ color:var(--ink); }
  .group-card .btn.danger{ align-self:flex-start; margin-top:10px; }

  @media (max-width: 900px){ .game-choice-grid{ grid-template-columns:1fr 1fr; } }
  @media (max-width: 600px){ .game-choice-grid{ grid-template-columns:1fr; } }
  /* The join-game screen is capped to the logo's width (340px) regardless
     of viewport — a 3-column grid squeezed into that box would be cramped
     no matter the screen size, so it's forced to one column here rather
     than relying on the viewport-width media queries above. */
  .join-panel-content .game-choice-grid{ grid-template-columns:1fr; }

  /* LOG IN / SIGN UP (the unconditional first screen) */
  #auth-panel .welcome-hero{ padding:6px 0 14px; }

  /* ORGANIZER DASHBOARD (group-picker-panel) */
  .dashboard-header{ text-align:center; padding:6px 0 10px; }
  .dashboard-header .logo{ margin:0 auto 8px; width:100%; max-width:clamp(160px, 18vw, 320px); height:auto; }
  .dashboard-header .logo img{ width:100%; height:auto; display:block; object-fit:contain; }
  .dashboard-welcome{ font-size:0.875rem; font-weight:500; color:var(--muted); margin:0 0 10px; }
  /* Matches .dashboard-header .logo's width exactly, so the stats, create-game
     row, and game list below the logo never spill wider than the logo above them. */
  .dashboard-content{ max-width:clamp(160px, 18vw, 320px); margin:0 auto; }
  .dashboard-content .game-choice-grid{ grid-template-columns:1fr; }
  .dashboard-content .new-group-row input{ min-width:0; } /* .new-group-row's default 240px min-width would overflow this 220px column */
  .dashboard-stats{ grid-template-columns:repeat(2, 1fr); max-width:360px; margin:0 auto 20px; }
  .dashboard-stats .card{ text-align:center; }
  .game-card-meta{ font-size:0.6875rem; color:var(--muted); margin-top:4px; }
  .public-badge{
    display:inline-block; font-size:0.5625rem; font-weight:700; letter-spacing:0.5px;
    color:var(--accent-ink); background:var(--lime); padding:2px 6px; border-radius:10px;
    vertical-align:middle; margin-left:6px;
  }
  .modal-overlay{
    position:fixed; inset:0; background:var(--scrim);
    display:flex; align-items:center; justify-content:center;
    z-index:100; padding:20px;
  }
  .modal-box{
    background:var(--paper); border:1.5px solid var(--border-strong); border-radius:10px;
    max-width:640px; width:100%; max-height:85vh; overflow-y:auto;
    padding:32px 24px 24px; position:relative;
  }
  .modal-close{
    position:absolute; top:10px; right:10px; width:30px; height:30px;
    border:1.5px solid var(--border-strong); border-radius:50%; background:var(--raised);
    font-size:1.125rem; font-weight:700; line-height:1; cursor:pointer;
    display:flex; align-items:center; justify-content:center; padding:0;
  }
  .modal-close:hover{ background:var(--lime); color:var(--accent-ink);}

  /* BRAND INTRO (landing page manifesto) — compact: tight vertical rhythm,
     short lines folded into wrapped/bulleted runs instead of stacked <br>s. */
  .brand-intro{ max-width:580px; margin:0 auto; text-align:center; padding:0 8px; }
  .brand-kicker{
    font-size:1.125rem; font-weight:700; text-transform:uppercase; letter-spacing:0.4px;
    color:var(--green); margin:0 auto 8px; line-height:1.3;
    width:340px; max-width:100%; /* exactly matches .welcome-hero .logo's width */
  }
  .brand-lead{ font-size:0.75rem; font-weight:500; line-height:1.4; margin:0 0 14px; color:var(--muted); }
  .brand-copy{ font-size:0.84375rem; line-height:1.6; color:var(--muted); margin:0 0 16px; }

  .brand-name-grid{ display:grid; grid-template-columns:1fr 1fr; gap:10px; text-align:center; margin-bottom:16px; }
  .brand-name-card{ background:var(--olive); padding:14px; }
  .brand-name-card h3{
    font-size:1.0625rem; font-weight:700; text-transform:uppercase; letter-spacing:0.4px;
    color:var(--on-olive); margin:0 0 4px;
  }
  .brand-name-card p{ font-size:0.75rem; line-height:1.5; color:var(--on-olive); margin:0; }
  @media (max-width: 480px){ .brand-name-grid{ grid-template-columns:1fr; } }

  .brand-callout{ font-size:0.8125rem; font-weight:500; line-height:1.7; margin:0 0 14px; color:var(--ink); }
  .brand-closing{
    font-size:1rem; font-weight:700; line-height:1.4; margin:0; color:var(--green);
    text-transform:uppercase; letter-spacing:0.3px;
  }

  .new-group-row{ display:flex; gap:10px; justify-content:center; margin:20px 0; flex-wrap:wrap; }
  .new-group-row input{
    padding:9px 12px; border:1.5px solid var(--border-strong); border-radius:6px;
    font-weight:500; font-size:0.8125rem; color:var(--ink); min-width:240px; flex:1;
  }
  @media (max-width: 480px){
    .new-group-row{ flex-direction:column; align-items:stretch; }
    .new-group-row input{ min-width:0; }
  }

  .role-choice-grid{ grid-template-columns:repeat(2, 1fr); max-width:640px; margin:0 auto; }
  @media (max-width: 600px){ .role-choice-grid{ grid-template-columns:1fr; } }

  .form-error{ color:var(--red-text); font-size:0.78125rem; font-weight:500; text-align:center; margin-top:8px; min-height:1em; }


  /* SKILL LEVEL QUIZ */
  .quiz-progress-bar{ height:6px; background:var(--gray-bg); border-radius:3px; overflow:hidden; margin:14px 0 6px; }
  .quiz-progress-fill{ height:100%; background:var(--lime); transition:width 0.2s ease; }
  .quiz-progress-label{ font-size:0.75rem; color:var(--muted); margin-bottom:22px; }
  .quiz-category{ margin:26px 0 10px; }
  .quiz-category-label{ font-size:0.6875rem; font-weight:600; text-transform:uppercase; letter-spacing:0.4px; color:var(--muted); }
  .quiz-question{ margin-bottom:18px; }
  .quiz-qtext{ font-weight:600; font-size:0.9375rem; margin-bottom:8px; }
  .quiz-answers{ display:flex; flex-direction:column; gap:6px; }
  .quiz-answer-btn{ text-align:left; width:100%; white-space:normal; line-height:1.35; }
  .quiz-answer-btn.active, .quiz-dominant-hand-btn.active{ background:var(--lime); border-color:var(--border-strong); color:var(--accent-ink);}
  .auth-fields-grid{ max-width:340px; margin:0 auto; grid-template-columns:1fr; gap:12px; } /* matches .welcome-hero .logo's width */

  .group-header-bar{
    display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap;
    padding:8px 4px; margin-bottom:10px; gap:8px 16px;
    font-size:0.75rem; font-weight:600; text-transform:uppercase; letter-spacing:0.4px; color:var(--muted);
  }
  .group-header-bar #group-header-name{ flex:1 1 auto; }
  .group-header-bar a, .account-bar a{ color:var(--blue); text-transform:none; letter-spacing:normal; font-weight:500; }

  .account-bar{
    display:flex; align-items:center; justify-content:flex-start; gap:10px;
    padding:0 4px 8px; font-size:0.6875rem; font-weight:500;
    text-transform:uppercase; letter-spacing:0.3px; color:var(--muted);
  }
  /* The real actions still live in these anchors (applyStepVisibility drives
     their visibility); the menu clicks through to them, so they're hidden
     rather than deleted. */
  .account-legacy-links{ display:none; }

  .jr-wa{ margin-left:8px; font-size:0.6875rem; font-weight:500; color:var(--blue); text-decoration:none; white-space:nowrap; }
  .jr-wa:hover{ text-decoration:underline; }
  .day-hint{ display:block; margin-top:4px; font-size:0.6875rem; font-weight:600;
    letter-spacing:0.4px; text-transform:uppercase; color:var(--green); }
  .viewonly-badge{
    padding:2px 9px; border-radius:999px; background:var(--gray-bg); border:1px solid var(--line);
    color:var(--muted); font-size:0.5625rem; font-weight:600; letter-spacing:0.5px;
    text-transform:uppercase; white-space:nowrap;
  }
  .account-menu-wrap{ position:relative; margin-left:auto; }
  .account-pill{
    display:flex; align-items:center; gap:8px; cursor:pointer;
    border:1.5px solid var(--border-strong); border-radius:999px; background:var(--paper);
    padding:4px 12px 4px 4px; color:var(--ink);
    font-size:0.75rem; font-weight:600; text-transform:none; letter-spacing:normal;
    max-width:min(240px, 55vw);
  }
  .account-pill:hover{ background:var(--lime); color:var(--accent-ink);}
  .account-pill > span:nth-child(2){ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .account-caret{ font-size:0.625rem; color:var(--muted); flex-shrink:0; }
  .account-avatar{
    position:relative; flex-shrink:0; width:26px; height:26px; border-radius:50%;
    background:var(--green); color:var(--on-green); overflow:hidden;
    display:inline-flex; align-items:center; justify-content:center;
    font-size:0.625rem; font-weight:700; letter-spacing:0.3px;
  }
  /* The <img> sits over the initials and removes itself on 404, so a member
     with no photo falls back without a flash of empty circle. */
  .account-avatar img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }

  .account-menu{
    position:absolute; right:0; top:calc(100% + 6px); z-index:60; min-width:210px;
    background:var(--paper); border:1.5px solid var(--border-strong); border-radius:10px;
    padding:6px; box-shadow:0 10px 26px var(--shadow);
  }
  .account-menu[hidden]{ display:none !important; }
  .account-menu-item{
    display:flex; align-items:center; gap:10px; width:100%; cursor:pointer;
    border:none; background:transparent; border-radius:7px; padding:9px 12px;
    color:var(--ink); font-size:0.8125rem; font-weight:500;
    text-transform:none; letter-spacing:normal; text-align:left;
  }
  .account-menu-item:hover{ background:var(--lime); color:var(--accent-ink);}
  .account-menu-item.danger{ color:var(--red-text); }
  .account-menu-item.danger:hover{ background:var(--danger-soft); }
  .ami-icon{ width:18px; text-align:center; flex-shrink:0; }
  .account-menu-sep{ height:1px; background:var(--line); margin:5px 4px; }

  @media (max-width: 480px){
    .account-bar > span:first-child{ display:none; } /* the pill already names them */
    .account-menu{ min-width:190px; }
  }

  .auth-top-links{
    display:flex; align-items:center; justify-content:space-between;
    padding:8px 4px 0; font-size:0.75rem; font-weight:600;
    text-transform:uppercase; letter-spacing:0.4px;
  }
  .auth-top-links a{ color:var(--blue); text-decoration:none; }

  .auth-card{
    border:1.5px solid var(--border-strong); border-radius:16px; padding:24px 22px;
    background:var(--paper);
  }

  .settings-card{
    border:1.5px solid var(--border-strong); border-radius:14px; padding:18px;
    margin-bottom:18px; background:var(--paper); max-width:100%; overflow:hidden;
  }
  .settings-card-title{
    display:flex; align-items:center; gap:8px; font-size:0.75rem; font-weight:600;
    text-transform:uppercase; letter-spacing:0.5px; color:var(--muted); margin:0 0 14px;
  }
  .settings-card .meta-fields-grid input,
  .settings-card .meta-fields-grid select{
    border:1.5px solid var(--border-strong); border-radius:10px; padding:9px 11px;
  }
  @media (max-width: 480px){
    .settings-card{ padding:14px; border-radius:12px; }
  }
  .auth-pill-toggle{
    display:flex; border:1.5px solid var(--border-strong); border-radius:999px;
    padding:4px; gap:4px; margin-bottom:20px;
  }
  .auth-pill-toggle a{
    flex:1; text-align:center; padding:9px 10px; border-radius:999px;
    font-weight:600; font-size:0.75rem; text-transform:uppercase; letter-spacing:0.4px;
    text-decoration:none; color:var(--ink); cursor:pointer;
  }
  .auth-pill-toggle a.active{ background:var(--lime); color:var(--accent-ink);}
  .auth-card-heading{
    font-size:1.375rem; font-weight:700; margin:0 0 6px;
  }
  .auth-card-subtitle{ font-size:0.8125rem; color:var(--muted); margin:0 0 18px; line-height:1.4; }
  #auth-panel .meta-fields-grid input{
    border:1.5px solid var(--border-strong); border-radius:10px; padding:10px 12px; font-size:0.9375rem;
  }
  #auth-panel .meta-fields-grid input:focus{ outline:2px solid var(--lime); outline-offset:1px; }

  .step-nav-row{
    display:flex; align-items:center; justify-content:space-between;
    position:sticky; top:0; z-index:10; background:var(--page);
    padding:10px 4px; margin-bottom:12px; gap:8px; flex-wrap:wrap;
  }
  .step-progress{ font-size:0.6875rem; font-weight:600; text-transform:uppercase; letter-spacing:0.4px; color:var(--muted); }
  @media (max-width: 480px){
    .step-nav-row{ justify-content:center; text-align:center; }
    .step-nav-row .btn{ width:100%; }
  }

  @media print{
    /* Paper is always white and ink is always black, whichever theme is on
       screen. background is forced above; `color` has to be forced too, or a
       dark-theme page inherits its near-white --ink onto the sheet and prints
       blank. Every .print-* rule below sets its own literal colours already,
       so this one declaration is the whole of the fix. */
    body{ background:#fff; padding:0; color:#111214; }
    #print-sheet-panel{ display:block !important; }
    #print-sheet{ display:block !important; }
    body > .sheet > *:not(#print-sheet-panel){ display:none !important; }
    #print-sheet-panel{ border:none; padding:0; margin:0; }
    /* @page size/orientation is no longer fixed here — applyPrintPageSize()
       in app.js injects it per-print based on the orientation picker next
       to each print button (A4 either way; only orientation is a choice). */
  }

/* Facebook sign-in button. Google renders its own button into an iframe we
   can't style, so this is hand-matched to it instead: same 40px height, same
   full-row width, Facebook's own brand blue (#1877F2) and the white "f" mark,
   per their brand guidelines. */
.fb-signin-btn{
  display:flex; align-items:center; justify-content:center; gap:10px;
  width:100%; height:40px; padding:0 14px; box-sizing:border-box;
  background:#1877F2; color:#fff; border:none; border-radius:4px;
  font-family:var(--font-ui);
  font-size:14px; font-weight:500; letter-spacing:.2px;
  cursor:pointer; transition:background .15s ease;
}
.fb-signin-btn:hover{ background:#166FE5; }
.fb-signin-btn:active{ background:#1464D4; }
.fb-signin-btn svg{ width:20px; height:20px; fill:#fff; flex:0 0 auto; }

/* ---------- BACKOFFICE ---------- */
/* Row actions sit in a cramped final column, so these are a size down from a
   normal .btn and allowed to wrap rather than widening the table. */
.btn.btn-mini{ font-size:0.625rem; padding:5px 9px; letter-spacing:0.2px; }
.admin-actions{ white-space:nowrap; }
.admin-actions .btn{ margin:2px 2px 2px 0; }
.admin-sub{ font-size:0.6875rem; opacity:0.7; }
.tier-badge{
  display:inline-block; padding:2px 8px; border-radius:999px;
  font-size:0.625rem; font-weight:600; letter-spacing:0.4px;
  background:var(--lime); color:var(--accent-ink);
}
.tier-badge.guest{ background:var(--gray-bg); color:var(--ink); border:1px solid var(--line); }
.season-chip{
  display:inline-flex; align-items:center; gap:4px;
  padding:2px 4px 2px 8px; margin:2px 2px 2px 0;
  border:1px solid var(--line); border-radius:999px; font-size:0.6875rem; white-space:nowrap;
}
.season-chip .chip-x{
  border:none; background:transparent; cursor:pointer; color:var(--red-text);
  font-size:0.875rem; line-height:1; padding:0 3px;
}
.season-chip .chip-x:hover{ background:var(--danger-soft); border-radius:50%; }
@media (max-width: 640px){
  .admin-actions{ white-space:normal; }
}

/* Admin sign-in state. The point is that it should NOT look like the ordinary
   login box - someone landing here by accident should see immediately that
   this is a different door. */
.admin-mode-badge{
  display:block; text-align:center; margin:0 0 12px;
  padding:7px 10px; border-radius:6px;
  background:var(--invert-bg); color:var(--invert-fg);
  font-size:0.625rem; font-weight:600; letter-spacing:1px;
}
.auth-card.admin-mode{ border-color:var(--border-strong); border-width:2px; }

/* Administrator reads as a rank above the guest/member tier, so it gets the
   inverted treatment rather than another pale pill. */
.tier-badge.admin{ background:var(--invert-bg); color:var(--invert-fg); }

/* Add-to-home-screen prompt. Fixed to the bottom so it never displaces the
   game content, and clear of the iPhone home indicator. */
.install-bar{
  position:fixed; left:0; right:0; bottom:0; z-index:120;
  display:flex; align-items:center; gap:10px;
  padding:12px 14px calc(12px + env(safe-area-inset-bottom));
  background:var(--invert-bg); color:var(--invert-fg);
  box-shadow:0 -4px 16px var(--shadow);
}
.install-bar-text{ flex:1; font-size:0.8125rem; line-height:1.35; }
.install-bar .btn{ flex:0 0 auto; }
.install-bar-x{
  flex:0 0 auto; border:none; background:transparent; color:var(--invert-fg);
  font-size:1.25rem; line-height:1; cursor:pointer; padding:4px 6px; opacity:.75;
}
.install-bar-x:hover{ opacity:1; }
@media (max-width: 380px){
  .install-bar{ flex-wrap:wrap; }
  .install-bar-text{ flex:1 1 100%; }
}

/* ============================================================
   PHONE LAYOUT PASS
   Everything below only applies at phone widths. The desktop
   layout is deliberately untouched — it was already right.
   ============================================================ */
@media (max-width: 600px){

  /* --- 1. Gutters and vertical balance ---------------------
     24px of padding PLUS the notch inset pushed content down by
     ~80px, while short screens (role choice, sign in) sat
     top-aligned with half the display empty beneath them.
     `margin-block:auto` inside a full-height flex column centres
     a short panel but collapses to normal top alignment as soon
     as content is taller than the screen — so a game in progress
     still scrolls exactly as before. */
  body{
    display:flex;
    flex-direction:column;
    min-height:100vh;
    min-height:100dvh;
    padding-top:calc(10px + env(safe-area-inset-top));
    padding-bottom:calc(10px + env(safe-area-inset-bottom));
    padding-left:calc(12px + env(safe-area-inset-left));
    padding-right:calc(12px + env(safe-area-inset-right));
  }
  /* Deliberately NOT centred: `margin-block:auto` pushed the account bar -
     the first thing in .sheet - down into the middle of the screen with the
     rest of the content, leaving the player-name pill floating. Top aligned,
     with tight gutters, puts it just under the status bar where it belongs. */
  .sheet{ width:100%; }
  .welcome-hero{ padding:8px 0 14px; }
  .welcome-hero .logo{ max-width:min(200px, 60vw); margin-bottom:6px; }

  /* --- 2. Organizer dashboard ------------------------------
     .dashboard-content was clamped to clamp(160px,18vw,320px);
     on a 390px phone 18vw is 70px, so it collapsed to the 160px
     floor — which is why the game cards were far narrower than
     the "Create New Game" field above them. Full width on phone,
     so every element shares one edge. */
  .dashboard-content,
  .dashboard-stats{ max-width:none; width:100%; }
  .dashboard-header .logo{ max-width:min(190px, 56vw); }
  /* Two stat cards side by side had their labels clipped
     ("MY GAMES" losing its second line). Let them breathe. */
  .dashboard-stats .card{ padding:10px 6px; }
  .dashboard-stats .card h3{ font-size:0.5625rem; line-height:1.25; margin-bottom:6px; }

  /* --- 3. Form fields --------------------------------------
     A date input carries an intrinsic width on iOS that ignored
     its grid track and overflowed the card's right edge. One
     column, and nothing wider than its container. */
  .meta-fields-grid{ grid-template-columns:1fr; gap:8px; }
  .meta-fields-grid input,
  .meta-fields-grid select,
  .new-group-row input{ width:100%; min-width:0; }
  /* A grid item's min-width defaults to auto, so it can never be narrower than
     its content. An iOS date input reports a wide min-content (the formatted
     date plus the picker chrome), which pushed the whole cell past the card's
     right edge no matter what width the input itself was given. */
  .meta-fields-grid > div{ min-width:0; }
  /* iOS sizes a date input from its shadow-DOM content and ignores `width`
     entirely until the native appearance is switched off - which is why this
     one field kept spilling past the card while every other input behaved.
     Scoped to phones, so desktop Safari keeps its native stepper. */
  .meta-fields-grid input[type="date"]{
    -webkit-appearance:none; appearance:none;
    width:100%; max-width:100%; min-width:0;
  }
  /* With the native appearance gone the inner value would otherwise centre
     itself in the box (which is why "21 Sep 2026" sat mid-field earlier). */
  .meta-fields-grid input[type="date"]::-webkit-date-and-time-value{
    text-align:left; margin:0;
  }
  .meta-fields-grid input[type="date"]::-webkit-calendar-picker-indicator{
    margin-inline-start:auto; padding:0;
  }
  /* MUST come after the generic rule above: at equal specificity the later one
     wins, and width:100% on this select stretched the country picker across the
     whole row and pushed the number field off screen. Narrower here than on
     desktop too - the code needs far less room than it was taking. */
  .phone-input-row{ width:100%; }
  .phone-input-row select{ flex:0 0 auto; width:100px; font-size:0.8125rem; padding-left:6px; padding-right:4px; }
  .phone-input-row input{ flex:1 1 auto; min-width:0; width:auto; }
  .time-range-row{ width:100%; }
  .time-range-row select{ flex:1 1 0; min-width:0; }
  /* Setup rows had labels of different widths leaving every
     control starting and ending at a different x. Letting the
     control take the remaining space aligns their right edges. */
  .config-row{ gap:8px; }
  .config-row > .config-field{ flex:1 1 auto; min-width:0; }
  .config-field select,
  .config-field input{ flex:1 1 auto; min-width:0; }

  /* --- 4. Season / backoffice tab strip --------------------
     Three tabs could not fit 390px, so the pill wrapped onto two
     lines and read as broken. One line that scrolls instead —
     the standard mobile pattern — with the scrollbar hidden. */
  .season-tab-bar{
    display:flex; flex-wrap:nowrap; width:100%;
    justify-content:space-between; gap:2px; padding:3px;
    overflow-x:auto; -webkit-overflow-scrolling:touch;
    scrollbar-width:none; border-radius:999px;
  }
  .season-tab-bar::-webkit-scrollbar{ display:none; }
  .season-tab{ flex:0 0 auto; padding:8px 10px; font-size:0.625rem; letter-spacing:0.1px; }
  /* --- 5. Match cards --------------------------------------
     The stacked card layout already suits a narrow screen - one
     pair per line, its score at the right edge - so there is very
     little to undo here. Only the paddings and the score come down
     a step; the structure is identical to desktop.
     Names wrap at spaces, never mid-word: overflow-wrap:break-word
     with word-break:normal. `anywhere` used to be set here and it
     shattered names into "Jo & Da n M". */
  .mcard{ padding:9px 10px 7px; border-radius:9px; }
  .mcard-head{ font-size:0.5625rem; margin-bottom:5px; }
  .pair{ font-size:0.875rem; line-height:1.3; }
  .score-box{ width:44px; height:34px; font-size:1.1875rem; }
  .round-card{ padding:10px 8px; }

  /* --- 6. Wide tables must scroll, not drag the page -------
     The standings and the partners/opponents grid are genuinely
     wider than a phone. They were overflowing their container
     instead of scrolling inside it, so the whole PAGE slid
     sideways and every other panel drifted off-screen with it.
     Containing them takes two things: ancestors that are allowed
     to be narrower than their contents (min-width:0 — a flex or
     grid item defaults to min-content, which is what let the
     table push everything wide), and a hard stop at the document
     edge. `clip` rather than `hidden` because it doesn't create a
     scroll container, so sticky headers still work. */
  html, body{ max-width:100%; overflow-x:clip; }
  .sheet, .panel, .round-card, .card{ min-width:0; max-width:100%; }
  /* Chrome around the data, trimmed to match. The tables above lost ~12px a
     row; leaving 14/16px of card padding and 16px between panels around them
     would have spent that saving back on empty margin. */
  .card{ padding:10px 12px; }
  .panel{ padding:12px 10px; }
  #profile-stats .card{ margin-bottom:12px; }
  .card h3{ margin-bottom:7px; }
  .standings-footnote{ font-size:0.625rem; line-height:1.5; margin-top:8px; }
  .table-scroll{
    max-width:100%; overflow-x:auto; -webkit-overflow-scrolling:touch;
    overscroll-behavior-x:contain; /* swiping the table stops at its ends */
  }
  /* Fill the container when the table fits, overflow (and scroll) when it
     doesn't. `width:max-content` alone made short tables stop short of the
     right edge; min-width:100% puts that back. */
  .table-scroll table{ min-width:100%; width:max-content; }

  /* Freeze the identifying columns while the numbers scroll.
     A wide table on a phone is not really unusable because it scrolls - it is
     unusable because two swipes in you are reading a row of numbers with no
     idea whose they are. Pinning the name turns the same table into something
     you can actually read: swipe the figures, the person stays put.
     Sticky cells must be opaque, or the scrolling columns show through them -
     hence the explicit background, with the podium colours still winning
     because they are set on the row. */
  .table-scroll table.standings th,
  .table-scroll table.standings td{ background:var(--paper); }
  /* thead ONLY. The partner matrix puts a <th class="pm-rowname"> at the start
     of every body row, and painting every th with the dark header colour turned
     those into unreadable black blocks - dark text on a dark background. */
  .table-scroll table.standings thead th{ background:var(--invert-bg); }
  .table-scroll table.standings tbody th{ background:var(--paper); color:var(--ink); }
  .table-scroll table.standings tr.rank-1 td{ background:var(--yellow); color:var(--accent-ink);}
  .table-scroll table.standings tr.rank-2 td{ background:var(--gray-bg); }
  .table-scroll table.standings tr.rank-3 td{ background:var(--orange); color:var(--accent-ink);}

  /* Season standings: rank + move + name stay frozen while the figures scroll.
     The MOVE column sits between rank and name, so the frozen pane is three
     columns wide - pinning only the first two would leave the player's NAME
     scrolling away with the data, which is the exact problem this was added
     to solve.
     The left offsets are cumulative and must match the widths below: rank is
     26px, move is 34px, so the name starts at 60px. Change one, change both.
     The partner matrix has no rank column and pins only its first cell -
     handled by the :not() and the .pm-rowname rule below. */
  /* Only the SEASON table has a MOVE column, so only it freezes three. Scoped
     by class rather than by nth-child alone, because .standings is also worn
     by Season Fairness (NAME / COVERAGE / SIT-OUT %, no rank column at all) -
     a blanket three-column pin would freeze that table end to end. */
  .table-scroll table.season-standings th:nth-child(1),
  .table-scroll table.season-standings td:nth-child(1){
    position:sticky; left:0; z-index:3; padding-left:6px; padding-right:4px;
    width:26px; min-width:26px;
  }
  .table-scroll table.season-standings th:nth-child(2),
  .table-scroll table.season-standings td:nth-child(2){
    position:sticky; left:26px; z-index:3;
    width:34px; min-width:34px; padding-left:0; padding-right:0;
  }
  .table-scroll table.season-standings th:nth-child(3),
  .table-scroll table.season-standings td:nth-child(3){
    position:sticky; left:60px; z-index:3;
    box-shadow:1px 0 0 var(--line);   /* the seam where the frozen pane ends */
  }
  /* Every other standings table keeps the original two-column pin. */
  .table-scroll table.standings:not(.partner-matrix):not(.season-standings) th:nth-child(1),
  .table-scroll table.standings:not(.partner-matrix):not(.season-standings) td:nth-child(1){
    position:sticky; left:0; z-index:3; padding-left:6px; padding-right:4px;
  }
  .table-scroll table.standings:not(.partner-matrix):not(.season-standings) th:nth-child(2),
  .table-scroll table.standings:not(.partner-matrix):not(.season-standings) td:nth-child(2){
    position:sticky; left:26px; z-index:3;
    box-shadow:1px 0 0 var(--line);
  }
  /* The pinned cells need their own opaque background or the scrolling
     figures show through them. Row tints must still win, so these come first
     and the rank-1/2/3 rules above override them. */
  .table-scroll table.season-standings tbody td:nth-child(-n+3){ background:var(--paper); }
  .table-scroll table.season-standings thead th:nth-child(-n+3){ background:var(--invert-bg); }
  /* ...but a medal row still shows its tint across the frozen columns. These
     have to be restated at this specificity: the generic pinned-background
     rule above carries an extra :nth-child and would otherwise outrank the
     plain tr.rank-N rules, blanking the top three rows' first three cells. */
  .table-scroll table.season-standings tbody tr.rank-1 td:nth-child(-n+3){ background:var(--yellow); }
  .table-scroll table.season-standings tbody tr.rank-2 td:nth-child(-n+3){ background:var(--gray-bg); }
  .table-scroll table.season-standings tbody tr.rank-3 td:nth-child(-n+3){ background:var(--orange); }

  /* Narrower pill on a phone so the frozen pane stays within 60px. */
  .table-scroll .move-pill{ min-width:30px; padding:2px 4px; }
  /* The matrix's first cell in each row is a th, not a td - so pin that. */
  .table-scroll table.partner-matrix th.pm-rowname{
    position:sticky; left:0; z-index:3; box-shadow:1px 0 0 var(--line);
    background:var(--paper); color:var(--ink);
  }
  /* The header's own corner has to outrank both, or it is overlapped when the
     table is scrolled in two directions at once. */
  .table-scroll table.standings thead th{ z-index:4; }

  /* More columns before a swipe is needed, without shrinking the type past
     what is readable at arm's length on a counter. */
  /* Compact rows. A wide table on a phone is won or lost on ROW HEIGHT: at
     7px of vertical padding and a 1.5 line-height each row ran ~33px, which
     is why only four opponent columns cleared the screen. Trimming the
     padding to 3px and pulling the line-height in takes a row to ~21px -
     about seven columns in the same space, with no loss of type size.
     line-height is set explicitly because tables inherit the body's 1.5,
     which is a reading measure and far too generous for a grid of figures. */
  .table-scroll table.standings th{ padding:4px 6px; font-size:0.5625rem; line-height:1.25; }
  .table-scroll table.standings td{ padding:3px 6px; font-size:0.75rem; line-height:1.25; }
  .table-scroll table.progress-table th,
  .table-scroll table.progress-table td{ padding:3px 6px; font-size:0.6875rem; line-height:1.25; }
  /* The matrix is the densest grid in the app - every extra pixel of padding
     here costs a whole opponent column. */
  .table-scroll table.partner-matrix th,
  .table-scroll table.partner-matrix td{ padding:3px 5px; font-size:0.6875rem; line-height:1.25; }
  .table-scroll table.partner-matrix .pm-rowname{ font-size:0.6875rem; padding-right:8px; }
  .table-scroll table.partner-matrix .pm-sep{ margin:0 1px; }

  /* Season Fairness / Times Partnered sit in a grid whose track is
     minmax(420px, 1fr). On a ~342px-wide phone that 420px floor forced the
     CARD wider than the screen — so the card overflowed, not the table inside
     it, which left .table-scroll with nothing to scroll and the page simply
     clipped the missing column. One column that actually fits; the wide table
     inside it then scrolls the way every other table here does. */
  .season-pair-row{ grid-template-columns:1fr; }

  /* --- 7. Sign in / Create account toggle ------------------
     "CREATE ACCOUNT" wrapped onto two lines while "SIGN IN"
     stayed on one, so the two halves of the pill were different
     heights and the lime highlight looked lopsided. Equal halves,
     no wrapping, and a slightly smaller face so the longer label
     fits a 390px screen on one line. */
  .auth-pill-toggle a{
    flex:1 1 0; min-width:0; white-space:nowrap;
    font-size:0.6875rem; letter-spacing:0.2px; padding:10px 6px;
    display:flex; align-items:center; justify-content:center;
  }

  /* --- 8. Verify-email banner ------------------------------
     The message and the Dismiss button were one flex row, so on a
     phone the text wrapped underneath and collided with the button
     ("email" sitting beside Dismiss). Stack them instead: message
     on its own line, button beneath it and right-aligned. */
  #verify-email-banner{
    flex-direction:column; align-items:stretch; gap:8px;
  }
  #verify-email-banner > span{ line-height:1.45; }
  #verify-email-banner > button{ align-self:flex-end; }
}

/* SHARE SHEET */
.share-preview{
  white-space:pre-wrap; word-break:break-word; margin:10px 0 0;
  background:var(--gray-bg); border:1px solid var(--line); border-radius:8px;
  padding:12px 14px; font-family:inherit; font-size:0.8125rem; line-height:1.5;
  max-height:220px; overflow-y:auto;
}
.btn.share-wa{ background:#25D366; border-color:#1da851; color:#0b3d22; }
.btn.share-wa:hover{ filter:brightness(0.95); }

/* Organizer confirming an invited player without waiting for their reply.
   Green to read as the affirmative next to the neutral "Send code" and the
   red "Remove". */
.slot-confirm-btn{
  border:1px solid var(--green); background:var(--raised); color:var(--green);
  border-radius:999px; cursor:pointer;
  font-weight:600; font-size:0.5625rem; letter-spacing:0.4px; text-transform:uppercase;
  padding:3px 9px; margin-left:6px;
}
.slot-confirm-btn:hover{ background:var(--success-soft); }
.slot-confirm-btn:disabled{ opacity:0.5; cursor:default; }
