/* ===========================================================================
   Volley-Dash — styles
   "Modernist" design system: flat, hard-edged, one red accent, Archivo type,
   zero border-radius, no drop shadows. Mobile-first. Works standalone or
   embedded in a Squarespace iframe.
   =========================================================================== */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/static/fonts/archivo-variable.woff2') format('woff2');
}

:root {
  --font: "Archivo", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-heading: var(--font);
  --font-body: var(--font);
  --font-heading-weight: 800;

  /* Modernist primitives (also used directly, by name, in dashboard markup) */
  --color-bg: #f3f2f2;
  --color-surface: #eae9e9;
  --color-text: #201e1d;
  --color-accent: #ec3013;
  --color-accent-2: #e15b47;
  --color-divider: color-mix(in srgb, #201e1d 40%, transparent);

  --color-neutral-100: #f8f4f4;
  --color-neutral-200: #eae7e7;
  --color-neutral-300: #d7d3d3;
  --color-neutral-400: #bab6b6;
  --color-neutral-500: #9b9797;
  --color-neutral-600: #7d7979;
  --color-neutral-700: #605d5d;
  --color-neutral-800: #444141;
  --color-neutral-900: #2d2b2b;

  --color-accent-100: #fff2ef;
  --color-accent-200: #ffe0d9;
  --color-accent-300: #ffc4b8;
  --color-accent-400: #ff9783;
  --color-accent-500: #ff563c;
  --color-accent-600: #dd2b0f;
  --color-accent-700: #ae1800;
  --color-accent-800: #7c1405;
  --color-accent-900: #4d170e;

  --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-6: 24px; --space-8: 32px;
  --radius-sm: 0px; --radius-md: 0px; --radius-lg: 0px;
  --shadow-sm: 0 1px 2px color-mix(in srgb, #2d2b2b 14%, transparent);
  --shadow-md: 0 3px 10px color-mix(in srgb, #2d2b2b 16%, transparent);
  --shadow-lg: 0 12px 32px color-mix(in srgb, #2d2b2b 22%, transparent);

  /* Legacy aliases — most of the app was written against these names. */
  --bg: var(--color-bg);
  --card: var(--color-surface);
  --ink: var(--color-text);
  --muted: color-mix(in srgb, #201e1d 55%, transparent);
  --line: var(--color-divider);
  --line-strong: var(--color-text);
  --accent: var(--color-accent);
  --accent-2: var(--color-accent-700);
  --good: #1c7c3c; --good-bg: #e6f5ec;
  --warn: #ec3013; --warn-bg: #fff2ef;
  --danger: #ae1800; --danger-bg: #ffe0d9;
  --radius: 0px; --shadow: none; --maxw: 1040px;

  /* ---- Tokens the dark theme has to state outright ----------------------
     Everything above is either a raw colour (restated in the dark block) or
     an alias that resolves lazily, so it follows on its own. These are the
     places where light and dark disagree about more than a hex value — text
     that sits *on* a fill, a scrim that must always darken, a brand surface
     — and each one was a hard-coded literal until the theme arrived. */
  --on-accent: #fff;                        /* text on a solid --accent fill */
  --danger-solid: var(--danger);            /* .btn.danger's fill … */
  --on-danger: #fff;                        /* … and the text on it */
  --scrim: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);
  --scrim-strong: color-mix(in srgb, var(--color-neutral-900) 55%, transparent);
  /* "Sign in with Google": Google's mark needs their light or their dark
     surface, never an arbitrary one, so the two are named here. */
  --provider-bg: #fff;
  --provider-ink: #1f1f1f;
  /* The avatar wheel: six fills, all carrying --avatar-ink initials. Dark
     mode can't just invert these — they'd land as six near-identical pastels
     — so it names its own six. */
  --avatar-ink: var(--bg);
  --avatar-0: color-mix(in srgb, var(--color-accent-600) 92%, var(--color-text));
  --avatar-1: var(--color-accent-700);
  --avatar-2: var(--color-accent-800);
  --avatar-3: var(--color-accent-900);
  --avatar-4: var(--color-neutral-700);
  --avatar-5: var(--color-neutral-900);

  color-scheme: light;
}

/* ===========================================================================
   Dark theme
   ---------------------------------------------------------------------------
   Two selectors, one palette. `data-theme` is written onto <html> by
   js/theme.js before the page paints, and only ever when someone has *chosen*
   light or dark; leaving it off is "auto", which is why the device preference
   lives behind :not([data-theme="light"]) rather than :not([data-theme]) —
   an explicit "dark" must win over a light device, and an explicit "light"
   must win over a dark one. Plain CSS can't share one block between a media
   query and a selector, so the palette is written twice: KEEP THEM IN SYNC.

   Both ramps keep their meaning rather than their values. --color-neutral-100
   is still "the surface closest to the page" and -900 still "the strongest
   ink"; --color-accent-100 is still the faintest wash and -900 the boldest.
   That's what lets every var(--color-neutral-700) in this file — and in the
   dashboard's inline styles — land the right way round with no edit.
   =========================================================================== */
:root[data-theme="dark"] {
  color-scheme: dark;

  --color-bg: #161413;
  --color-surface: #211f1e;
  --color-text: #f1efee;
  /* Lifted off pure red: at 12px the accent has to carry text on near-black. */
  --color-accent: #ff4f31;
  --color-accent-2: #ff8064;
  --color-divider: color-mix(in srgb, #f1efee 26%, transparent);

  --color-neutral-100: #1d1b1a;
  --color-neutral-200: #262322;
  --color-neutral-300: #322f2d;
  --color-neutral-400: #474341;
  --color-neutral-500: #6b6663;
  --color-neutral-600: #928c89;
  --color-neutral-700: #b0aaa7;
  --color-neutral-800: #d2cdca;
  --color-neutral-900: #eceae8;

  --color-accent-100: #2a120c;
  --color-accent-200: #3d180f;
  --color-accent-300: #5c2113;
  --color-accent-400: #8a2c15;
  --color-accent-500: #c33a19;
  --color-accent-600: #ff5535;
  --color-accent-700: #ff8064;
  --color-accent-800: #ffa993;
  --color-accent-900: #ffd2c6;

  --shadow-sm: 0 1px 2px color-mix(in srgb, #000 55%, transparent);
  --shadow-md: 0 3px 10px color-mix(in srgb, #000 60%, transparent);
  --shadow-lg: 0 12px 32px color-mix(in srgb, #000 68%, transparent);

  --muted: color-mix(in srgb, #f1efee 58%, transparent);
  /* The design's heavy rules are the point; at full white on near-black they
     glare, so they come down to a bright grey rather than turning grey. */
  --line-strong: color-mix(in srgb, #f1efee 62%, transparent);
  --good: #63d48d; --good-bg: #102c1c;
  --warn: #ff8064; --warn-bg: #2b130c;
  --danger: #ff6b52; --danger-bg: #3a1811;

  /* Solid accent and danger fills flip their text rather than their colour:
     white on a bright red reads far worse than the page's own near-black. */
  --on-accent: var(--color-bg);
  --danger-solid: #ff6b52;
  --on-danger: #2a0d06;
  --scrim: color-mix(in srgb, #000 66%, transparent);
  --scrim-strong: color-mix(in srgb, #000 72%, transparent);
  --provider-bg: #131314;
  --provider-ink: #e3e3e3;

  --avatar-ink: #fff;
  --avatar-0: #cf2f14;
  --avatar-1: #ae2409;
  --avatar-2: #8a1f0c;
  --avatar-3: #6b2a1e;
  --avatar-4: #5e5a57;
  --avatar-5: #454240;
}

@media (prefers-color-scheme: dark) {
  /* Same palette as above, for everyone who hasn't chosen. KEEP IN SYNC. */
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --color-bg: #161413;
    --color-surface: #211f1e;
    --color-text: #f1efee;
    --color-accent: #ff4f31;
    --color-accent-2: #ff8064;
    --color-divider: color-mix(in srgb, #f1efee 26%, transparent);

    --color-neutral-100: #1d1b1a;
    --color-neutral-200: #262322;
    --color-neutral-300: #322f2d;
    --color-neutral-400: #474341;
    --color-neutral-500: #6b6663;
    --color-neutral-600: #928c89;
    --color-neutral-700: #b0aaa7;
    --color-neutral-800: #d2cdca;
    --color-neutral-900: #eceae8;

    --color-accent-100: #2a120c;
    --color-accent-200: #3d180f;
    --color-accent-300: #5c2113;
    --color-accent-400: #8a2c15;
    --color-accent-500: #c33a19;
    --color-accent-600: #ff5535;
    --color-accent-700: #ff8064;
    --color-accent-800: #ffa993;
    --color-accent-900: #ffd2c6;

    --shadow-sm: 0 1px 2px color-mix(in srgb, #000 55%, transparent);
    --shadow-md: 0 3px 10px color-mix(in srgb, #000 60%, transparent);
    --shadow-lg: 0 12px 32px color-mix(in srgb, #000 68%, transparent);

    --muted: color-mix(in srgb, #f1efee 58%, transparent);
    --line-strong: color-mix(in srgb, #f1efee 62%, transparent);
    --good: #63d48d; --good-bg: #102c1c;
    --warn: #ff8064; --warn-bg: #2b130c;
    --danger: #ff6b52; --danger-bg: #3a1811;

    --on-accent: var(--color-bg);
    --danger-solid: #ff6b52;
    --on-danger: #2a0d06;
    --scrim: color-mix(in srgb, #000 66%, transparent);
    --scrim-strong: color-mix(in srgb, #000 72%, transparent);
    --provider-bg: #131314;
    --provider-ink: #e3e3e3;

    --avatar-ink: #fff;
    --avatar-0: #cf2f14;
    --avatar-1: #ae2409;
    --avatar-2: #8a1f0c;
    --avatar-3: #6b2a1e;
    --avatar-4: #5e5a57;
    --avatar-5: #454240;
  }
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: var(--font);
  color: var(--ink);
  background: var(--bg);
  line-height: 1.5;
}
a { color: var(--accent-2); text-decoration: none; }
a:hover { color: var(--accent); text-decoration: underline; }
h1, h2, h3, h4, h5, h6 { font-family: var(--font); font-weight: var(--font-heading-weight); letter-spacing: -.015em; line-height: 1.15; }
h1 { font-size: 2rem; margin: 0 0 .2em; letter-spacing: -.02em; }
h2 { font-size: 1.2rem; margin: 0 0 .5em; }
h3 { font-size: 1rem; margin: 0 0 .4em; }
code { background: var(--card); padding: .1em .35em; font-size: .9em; }

.muted { color: var(--muted); }
.small { font-size: .85rem; }
.center { text-align: center; }

/* ---- Top bar ------------------------------------------------------------- */
.topbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding: .8rem clamp(1rem, 4vw, 2rem);
  background: var(--bg); color: var(--ink);
  border-bottom: 2px solid var(--line-strong);
  position: sticky; top: 0; z-index: 20;
}
.topbar-left { display: flex; align-items: center; gap: .35rem; }
.brand { display: inline-flex; align-items: center; color: var(--ink); }
.brand:hover { text-decoration: none; opacity: .7; }
/* The wordmark lockup. Its ink is a near-black that would vanish on the dark
   palette, so there are two cuts of the file and the swap follows the same two
   selectors the palette itself uses — see the palette block above.

   The width comes from the art's own ratio rather than a number typed here:
   with a fixed width the box outlives the next redraw of the logo, and the
   lockup ends up sitting against one edge of it with dead space beside it. */
.brand-logo {
  display: block; height: 26px; aspect-ratio: 320 / 72;
  background: url('/static/img/logo-wordmark.png') center / contain no-repeat;
}
:root[data-theme="dark"] .brand-logo { background-image: url('/static/img/logo-wordmark-dark.png'); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .brand-logo { background-image: url('/static/img/logo-wordmark-dark.png'); }
}
/* Phone width: the brand, the scoreboard link and the avatar share one line,
   so the name drops away and the mark carries the brand on its own — the same
   trade the scoreboard label makes just below. */
@media (max-width: 480px) {
  .brand-logo { aspect-ratio: 179 / 160; background-image: url('/static/img/logo-mark.png'); }
  :root[data-theme="dark"] .brand-logo { background-image: url('/static/img/logo-mark-dark.png'); }
}
@media (max-width: 480px) and (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .brand-logo { background-image: url('/static/img/logo-mark-dark.png'); }
}
.topbar-right { display: flex; align-items: center; gap: .5rem; }
.topnav { display: flex; align-items: center; gap: .2rem; flex-wrap: wrap; }
.topnav a, .topnav .linkbtn {
  color: var(--ink); padding: .4rem .65rem; font-weight: 700; font-size: .88rem;
}
.topnav a:hover, .topnav .linkbtn:hover { background: var(--card); text-decoration: none; }

.topbar-user { display: flex; align-items: center; gap: .25rem; }

/* The always-there scoreboard link. Deliberately outside .topnav so it never
   collapses behind the menu button — it's the one thing someone needs while
   standing at the net. */
.topbar-scoreboard {
  display: inline-flex; align-items: center; gap: .35rem; flex: 0 0 auto; white-space: nowrap;
  padding: .4rem .7rem; font-weight: 800; font-size: .88rem; color: var(--ink);
  border: 2px solid var(--line-strong);
}
.topbar-scoreboard:hover { background: var(--card); text-decoration: none; }
.topbar-scoreboard.active { background: var(--ink); color: var(--bg); }
.topbar-scoreboard .tsb-short { display: none; }
@media (max-width: 480px) {
  /* Phone width: the brand, this and the avatar all have to fit on one line,
     so the label shortens rather than the link disappearing. */
  .topbar-scoreboard .tsb-label { display: none; }
  .topbar-scoreboard .tsb-short { display: inline; }
  .topbar-scoreboard { padding: .4rem .55rem; font-size: .82rem; gap: .25rem; }
}

/* ---- Profile menu -------------------------------------------------------- */
/* The avatar is the button. <details> carries the open/closed state, so the
   menu works with JS off; main.js only closes it on an outside click. */
.usermenu { position: relative; }
.usermenu-trigger {
  display: inline-flex; align-items: center; cursor: pointer;
  list-style: none; /* Firefox: no disclosure triangle */
}
.usermenu-trigger::-webkit-details-marker { display: none; }
.usermenu-trigger:hover { opacity: .75; }
.usermenu-trigger:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.usermenu[open] .usermenu-trigger { opacity: .75; }
.usermenu-panel {
  position: absolute; top: calc(100% + .5rem); right: 0; z-index: 40;
  min-width: 220px; max-width: min(84vw, 320px);
  background: var(--bg); color: var(--ink);
  border: 2px solid var(--line-strong); box-shadow: var(--shadow-lg);
}
.usermenu-who { padding: .7rem .9rem; border-bottom: 2px solid var(--line-strong); }
.usermenu-name { font-weight: 800; font-size: .9rem; line-height: 1.2; }
.usermenu-email {
  font-size: .78rem; color: var(--color-neutral-700); margin-top: .15rem;
  overflow: hidden; text-overflow: ellipsis;
}
.usermenu-panel a,
.usermenu-panel button {
  display: block; width: 100%; box-sizing: border-box; text-align: left;
  padding: .7rem .9rem; font: inherit; font-size: .88rem; font-weight: 700;
  color: var(--ink); background: transparent; border: 0; cursor: pointer;
}
.usermenu-panel a:hover,
.usermenu-panel button:hover { background: var(--card); text-decoration: none; }

/* Menu button — three stacked bars at the far left of the bar, shown only
   once the nav it opens has collapsed. */
.topnav-toggle {
  display: none; align-items: center; justify-content: center;
  width: 44px; height: 44px; flex: 0 0 44px; cursor: pointer; margin-left: -.6rem;
}
.topnav-toggle:hover { background: var(--card); }
.nav-toggle-checkbox:focus-visible ~ .topbar .topnav-toggle { outline: 2px solid var(--color-accent); outline-offset: -2px; }
.topnav-bars, .topnav-bars::before, .topnav-bars::after {
  display: block; width: 20px; height: 2px; background: var(--ink);
}
.topnav-bars { position: relative; }
.topnav-bars::before { content: ''; position: absolute; left: 0; top: -6px; }
.topnav-bars::after { content: ''; position: absolute; left: 0; top: 6px; }

/* ---- Theme switch -------------------------------------------------------- */
/* One button cycling light → dark → auto ("whatever the device says"), showing
   the icon for wherever it currently stands. Hidden until js/theme.js has set
   data-theme-ready: with JS off there is nowhere to keep a choice, and the
   device preference already applies on its own, so a control that couldn't do
   anything never appears. */
.themeswitch { display: none; }
:root[data-theme-ready] .themeswitch {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; width: 32px; height: 32px; padding: 0; margin: 0;
  cursor: pointer; background: transparent; color: var(--ink);
  border: 1px solid var(--line); border-radius: 0;
}
.themeswitch:hover { background: color-mix(in srgb, var(--ink) 8%, transparent); }
.themeswitch:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Which of the three faces shows. Driven off <html data-theme-pref> rather
   than a class on the button, so the icon is right on the very first paint. */
.themeswitch-icon { display: none; width: 17px; height: 17px; }
:root[data-theme-pref="light"] .themeswitch-icon[data-theme-icon="light"],
:root[data-theme-pref="dark"] .themeswitch-icon[data-theme-icon="dark"],
:root[data-theme-pref="auto"] .themeswitch-icon[data-theme-icon="auto"] { display: block; }
@media (max-width: 480px) {
  /* The bar is full at this width — brand, this, the scoreboard link and the
     avatar all on one line — so the button gives up what it can. */
  :root[data-theme-ready] .themeswitch { width: 28px; height: 28px; }
  .themeswitch-icon { width: 15px; height: 15px; }
}

/* ---- Avatars ------------------------------------------------------------- */
/* The one round thing in the design — a face reads as a face, and it marks
   "this is you" apart from every square panel around it. */
.avatar {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; box-sizing: border-box; overflow: hidden;
  object-fit: cover; background: var(--color-neutral-300);
  border: 2px solid var(--line-strong); border-radius: 50%;
}
.avatar-initials { background: var(--ink); color: var(--bg); font-weight: 800; line-height: 1; }

/* Accounts with no photo get one of these, picked from their account id (see
   colorIndex in lib/avatar.js). Every shade is from the palette and dark
   enough to carry the light initials on top. Keep the count in sync with
   COLOR_COUNT — extra classes here are simply never used. */
/* The brightest red, nudged toward ink: raw accent-600 sits at 4.25:1 against
   the initials, which fails at the sm/xs sizes the header uses. */
.avatar-c0, .avatar-c1, .avatar-c2, .avatar-c3, .avatar-c4, .avatar-c5 { color: var(--avatar-ink); }
.avatar-c0 { background: var(--avatar-0); }
.avatar-c1 { background: var(--avatar-1); }
.avatar-c2 { background: var(--avatar-2); }
.avatar-c3 { background: var(--avatar-3); }
.avatar-c4 { background: var(--avatar-4); }
.avatar-c5 { background: var(--avatar-5); }
.avatar-xs { width: 28px; height: 28px; font-size: .68rem; border-width: 1px; }
.avatar-sm { width: 34px; height: 34px; font-size: .8rem; }
.avatar-md { width: 56px; height: 56px; font-size: 1.15rem; }
.avatar-lg { width: 112px; height: 112px; font-size: 2.4rem; }

.photo-edit { display: flex; gap: 1.2rem; align-items: flex-start; flex-wrap: wrap; }
.photo-edit-controls { flex: 1 1 260px; }
.photo-edit input[type="file"] { font: inherit; font-size: .9rem; }

/* Phones and portrait tablets: the header links collapse behind the menu
   button sitting to the right of the profile photo. Pure CSS, same hidden-
   checkbox trick as the dashboard drawer — the panel drops under the bar. */
@media (max-width: 900px) {
  .topnav-toggle { display: flex; }
  .topnav { display: none; }
  .nav-toggle-checkbox:checked ~ .topbar .topnav {
    display: flex; flex-direction: column; align-items: stretch; gap: 0;
    position: absolute; top: 100%; left: 0; right: 0; z-index: 25;
    background: var(--bg); border-bottom: 2px solid var(--line-strong);
    padding: .25rem 0;
  }
  .nav-toggle-checkbox:checked ~ .topbar .topnav a {
    display: block; width: 100%; text-align: left;
    padding: .85rem clamp(1rem, 4vw, 2rem); font-size: 1rem;
  }
}

/* ---- Layout -------------------------------------------------------------- */
.container { max-width: var(--maxw); margin: 0 auto; padding: clamp(1rem, 3vw, 2rem); }
.sitefoot { text-align: center; color: var(--muted); font-size: .85rem; padding: 2rem 1rem; }

.hero { text-align: center; padding: 1rem 0 1.5rem; }
.hero h1 { font-size: 2.2rem; }
/* The landing page runs with no top bar (routes/public.js passes hideTopbar),
   so this is where the brand is stated. Sized off the viewport rather than
   fixed, to keep it clear of the 393px floor, and held under the artwork's own
   320px so a retina screen isn't asked to upscale it. */
.hero-logo { display: block; margin: 0 auto .6rem; width: min(300px, 76vw); height: auto; }
:root[data-theme="dark"] .hero-logo { content: url('/static/img/logo-wordmark-dark.png'); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .hero-logo { content: url('/static/img/logo-wordmark-dark.png'); }
}

.page-head {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
  margin-bottom: 1.2rem; padding-bottom: 1rem; border-bottom: 2px solid var(--line-strong);
}
.head-actions { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }

.subnav { display: flex; gap: .4rem; flex-wrap: wrap; margin-bottom: 1.2rem; }
.subnav a {
  background: transparent; border: 1px solid var(--line); padding: .4rem .8rem;
  font-weight: 700; font-size: .78rem; letter-spacing: .04em; text-transform: uppercase; color: var(--ink);
}
.subnav a:hover { border-color: var(--accent); color: var(--accent-2); background: var(--card); text-decoration: none; }

/* ---- Cards & grids --------------------------------------------------------- */
.card {
  background: var(--card); border: 0; border-radius: var(--radius);
  box-shadow: none; padding: clamp(1rem, 3vw, 1.5rem); margin-bottom: 1.2rem;
}
.card.narrow { max-width: 460px; margin-left: auto; margin-right: auto; }
.card.subtle { background: transparent; box-shadow: none; border: 1px dashed var(--line); }
.card-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
  margin-bottom: .8rem; padding-bottom: .5rem; border-bottom: 2px solid var(--line-strong);
}
.card-head h2 { margin: 0; }

.grid { display: grid; gap: 1.2rem; }
.grid.two { grid-template-columns: 1fr; }
.grid.cards { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
@media (min-width: 760px) { .grid.two { grid-template-columns: 1fr 1fr; } }

.league-card { display: flex; flex-direction: column; gap: .4rem; }
.league-card h2 { margin: 0; }
.league-card .go { margin-top: auto; color: var(--accent-2); font-weight: 700; }
.league-card:hover { text-decoration: none; background: color-mix(in srgb, var(--ink) 4%, var(--card)); }

/* ---- Buttons (legacy single-class modifiers, still used by auth/public views) */
.btn, button.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  cursor: pointer; font: inherit; font-family: var(--font); font-weight: 800;
  padding: .6rem 1.1rem; border-radius: var(--radius); border: 1px solid var(--line);
  background: transparent; color: var(--ink); line-height: 1.2; text-align: center;
}
.btn:hover { text-decoration: none; border-color: var(--ink); background: color-mix(in srgb, var(--ink) 6%, transparent); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.btn.primary:hover { background: var(--accent-2); border-color: var(--accent-2); }
.btn.ghost { background: transparent; border-color: transparent; color: var(--accent-2); }
.btn.ghost:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: transparent; }
.btn.warn { background: var(--warn-bg); border-color: var(--accent); color: var(--accent-2); }
.btn.danger { background: var(--danger-solid); border-color: var(--danger-solid); color: var(--on-danger); }
.btn.danger:hover { background: color-mix(in srgb, var(--danger-solid) 82%, black); }
.btn.small { padding: .4rem .7rem; font-size: .9rem; }
.btn.big { font-size: 1.1rem; padding: .85rem 1.4rem; width: 100%; }

/* Federated sign-in ("Sign in with Google"). Full width to sit under the
   form's own full-width submit, and on one of Google's own two surfaces —
   their brand guidelines require the mark on a light or an explicitly dark
   background, never on an arbitrary one, so --provider-bg names both rather
   than following the page. */
.btn.provider {
  width: 100%; gap: 10px; background: var(--provider-bg); color: var(--provider-ink);
  border-color: var(--color-neutral-300); font-weight: 700;
}
.btn.provider:hover { background: var(--color-neutral-100); border-color: var(--color-neutral-400); }
.provider-mark { flex: 0 0 auto; display: block; }

/* One connected provider in Account settings: who on the left, action right. */
.connected-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap;
  padding: var(--space-3) 0; border-bottom: 1px solid var(--line);
}
.connected-row:last-of-type { border-bottom: 0; }
.connected-who { display: inline-flex; align-items: center; gap: var(--space-3); }

/* "or" rule between the credentials form and the provider buttons. */
.or-divider {
  display: flex; align-items: center; gap: var(--space-3);
  margin: var(--space-4) 0; color: var(--muted); font-size: .9rem;
}
.or-divider::before, .or-divider::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--line);
}

.linkbtn {
  background: none; border: none; cursor: pointer; font: inherit; font-family: var(--font); color: inherit;
  padding: .35rem .6rem; font-weight: 700;
}
.linkbtn:hover { text-decoration: underline; }
.linkbtn.danger { color: var(--danger); }

.mini {
  display: inline-block; cursor: pointer; font: inherit; font-family: var(--font); font-size: .8rem; font-weight: 800;
  padding: .32rem .6rem; border-radius: var(--radius); border: 1px solid var(--line); background: transparent; color: var(--ink);
}
.mini:hover { text-decoration: none; border-color: var(--ink); background: color-mix(in srgb, var(--ink) 6%, transparent); }
.mini.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.mini.danger { color: var(--danger); border-color: var(--danger-bg); }

/* ---- Forms (legacy bare-tag styling, still used by auth/public views) ---- */
label { display: block; font-weight: 700; font-size: .85rem; margin-bottom: .2rem; color: color-mix(in srgb, var(--ink) 72%, transparent); }
/* One line box and one vertical padding for every control, so the height below
   can be derived from them rather than guessed at. */
:root { --control-line: 1.25rem; --control-pad-y: .55rem; }
input, select, textarea {
  font: inherit; font-family: var(--font); width: 100%; padding: var(--control-pad-y) .65rem; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--card); color: var(--ink); margin-top: .25rem; caret-color: var(--accent);
  line-height: var(--control-line);
}
/* Padding alone doesn't make these the height of the text input beside them: a
   select sizes its box from the font's own metrics, and a date input packs a
   calendar button into its shadow DOM, which made "Date of birth" stand a
   couple of pixels proud of "Zip code" in the profile form. Give them the
   height the padding and line box add up to. Dashboard controls carry the
   .input class and set their own compact metrics, so they keep them. */
select:not(.input), input[type="date"]:not(.input), input[type="time"]:not(.input),
input[type="datetime-local"]:not(.input), input[type="month"]:not(.input) {
  height: calc(var(--control-line) + 2 * var(--control-pad-y) + 2px);
}
input[type="checkbox"], input[type="radio"] { width: auto; margin: 0 .4rem 0 0; accent-color: var(--accent); }
/* Multi-select (e.g. the days a new league plays) — a list box, not a bar. */
select[multiple] { height: auto; padding: .3rem; }
select[multiple] option { padding: .2rem .35rem; }
input:hover, select:hover, textarea:hover { border-color: color-mix(in srgb, var(--ink) 45%, transparent); }
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: var(--accent); }
.stack { display: flex; flex-direction: column; gap: .8rem; }
.stack > label { margin-bottom: 0; }
.row { display: flex; gap: .8rem; }
.row.gap { gap: .6rem; }
.row.wrap, .wrap { flex-wrap: wrap; }
.grow { flex: 1 1 auto; }
.check { display: flex; align-items: center; font-weight: 700; }
.form-actions { display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; }
.inline { display: inline; }
.inline-edit { display: flex; gap: .35rem; align-items: center; }
.inline-edit.col { flex-direction: column; align-items: stretch; }
.inline-edit input { margin-top: 0; }
.filter-bar { margin-bottom: 1rem; max-width: 320px; }

.alert { padding: .65rem .85rem; font-weight: 700; margin-bottom: .9rem; border: 1px solid transparent; }
.alert.error { background: var(--danger-bg); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 35%, transparent); }
.alert.info { background: var(--color-neutral-200); color: var(--color-text); border-color: var(--line); }

/* ---- Pills (legacy status labels, still used by auth/public views) ------- */
.pill {
  display: inline-block; font-size: .72rem; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  padding: .16rem .55rem; border-radius: var(--radius); background: var(--card); color: color-mix(in srgb, var(--ink) 78%, transparent);
  vertical-align: middle;
}
.pill.open { background: transparent; border: 1px solid var(--accent); color: var(--accent-2); }
.pill.pending { background: var(--warn-bg); color: var(--warn); }
.pill.confirmed { background: var(--good-bg); color: var(--good); }

/* ---- Responsive tables: stack into cards on small screens ---------------- */
@media (max-width: 640px) {
  .table thead { display: none; }
  .table, .table tbody, .table tr, .table td { display: block; width: 100%; }
  .table tr { border: 1px solid var(--line); margin-bottom: .6rem; padding: .3rem .5rem; }
  .table td { border: none; padding: .3rem .2rem; display: flex; justify-content: space-between; gap: 1rem; }
  .table td::before {
    content: attr(data-label); font-weight: 800; color: var(--muted);
    font-size: .74rem; text-transform: uppercase; letter-spacing: .04em;
  }
  /* A cell with nothing to label — the buttons at the end of a row — has no
     left half to sit opposite, so it drops the empty pseudo-element rather
     than being pushed to the far edge by space-between. */
  .table td[data-label=""]::before, .table td:not([data-label])::before { display: none; }
  .table td[data-label=""], .table td:not([data-label]) { justify-content: flex-start; gap: .5rem; }
  .actions-cell { justify-content: flex-start; }
}
.actions-cell { display: flex; gap: .35rem; flex-wrap: wrap; align-items: center; }

/* ---- Match lists --------------------------------------------------------- */
.match-list { list-style: none; margin: 0; padding: 0; }
.match-list li { padding: .7rem 0; border-bottom: 1px solid var(--line); display: flex; flex-wrap: wrap; gap: .3rem .8rem; align-items: center; }
.match-list li:last-child { border-bottom: none; }
.match-list .when { font-size: .85rem; color: var(--muted); flex-basis: 100%; }
/* On a phone the button rides up beside a short pair of team names and drops
   below a long one, so the list loses its rhythm. Giving the names the whole
   line puts every button on its own, every time. */
@media (max-width: 640px) { .match-list .teams { flex-basis: 100%; } }
.match-list .teams { font-size: 1rem; }
.match-list .score { font-weight: 800; color: var(--ink); margin: 0 .25rem; }
.sets { display: inline-flex; gap: .35rem; flex-wrap: wrap; }
.sets span { background: var(--card); padding: .05rem .4rem; font-size: .8rem; font-weight: 700; }

/* ---- Score entry grid ---------------------------------------------------- */
.setgrid { margin: .4rem 0 .2rem; }
.setgrid-head, .setrow { display: grid; grid-template-columns: 4rem 1fr 1fr; gap: .5rem; align-items: center; }
.setgrid-head { font-size: .76rem; color: var(--muted); font-weight: 800; text-transform: uppercase; letter-spacing: .03em; margin-bottom: .3rem; }
.setrow { margin-bottom: .45rem; }
.setrow input { margin-top: 0; text-align: center; font-weight: 800; font-size: 1.1rem; border-width: 2px; }
.setno { font-weight: 700; color: var(--muted); font-size: .9rem; }
.setgrid.small .setrow input { font-size: 1rem; padding: .35rem; }

.vs-head { display: flex; align-items: center; justify-content: center; gap: .8rem; font-size: 1.15rem; font-weight: 800; margin-bottom: .3rem; }
.vs-head .vs { color: var(--muted); font-weight: 600; }
.vs-head .home { color: var(--ink); }
.vs-head .away { color: var(--accent-2); }

.score-editor { margin: 0; }
.score-editor summary { cursor: pointer; display: inline-block; }
.score-editor[open] { display: block; background: var(--card); border: 1px solid var(--line); padding: .6rem; margin-top: .3rem; }
.score-form { margin-top: .4rem; }

.danger-zone { margin-top: 1.4rem; padding-top: 1rem; border-top: 2px dashed var(--danger-bg); }
.danger-zone h3 { color: var(--danger); }

/* ---- Report confirmation ------------------------------------------------- */
.big-check {
  width: 64px; height: 64px; line-height: 64px; margin: 0 auto .6rem;
  background: var(--good-bg); color: var(--good); font-size: 2rem; font-weight: 800;
}
/* Same badge, but for a page that isn't good news. */
.big-check.warn { background: var(--warn-bg); color: var(--warn); }
.result-summary { margin: 1rem 0; }
.result-summary .sets { justify-content: center; margin-top: .5rem; }
.set-chip { background: var(--card); padding: .25rem .6rem; font-weight: 700; font-size: .9rem; }

/* ===========================================================================
   Scoreboard (full-screen courtside view) — intentionally outside the app
   chrome/design system; left as its own high-contrast dark display.
   =========================================================================== */
.scoreboard-body { background: #06121f; overflow: hidden; }
.scoreboard {
  position: fixed; inset: 0; display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr auto;
  gap: 2px; background: #02060c;
}
.sb-side {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: env(safe-area-inset-top) 0 0; user-select: none; -webkit-user-select: none;
}
.sb-home { background: linear-gradient(160deg, #133450, #0e2740); }
.sb-away { background: linear-gradient(160deg, #b3550f, #8f3d08); }
.sb-name {
  position: absolute; top: max(1rem, env(safe-area-inset-top)); left: 0; right: 0; text-align: center;
  color: #fff; font-weight: 800; font-size: clamp(1rem, 4.5vw, 1.7rem); letter-spacing: .02em;
  padding: 0 .5rem; text-shadow: 0 1px 3px rgba(0,0,0,.4);
}
.sb-sets { position: absolute; top: max(3.2rem, calc(env(safe-area-inset-top) + 2.2rem)); color: #ffd9a8; font-size: 1.4rem; letter-spacing: .2rem; }
.sb-away .sb-sets { color: #fff3e3; }
.sb-point {
  flex: 1; width: 100%; border: none; background: transparent; cursor: pointer;
  display: flex; align-items: center; justify-content: center; color: #fff;
}
.sb-score { font-weight: 900; font-size: clamp(5rem, 30vw, 18rem); line-height: 1; font-variant-numeric: tabular-nums; text-shadow: 0 2px 12px rgba(0,0,0,.35); }
.sb-point:active .sb-score { transform: scale(1.05); transition: transform .05s; }
.sb-minus {
  position: absolute; bottom: 5rem; width: 56px; height: 56px; border-radius: 50%;
  border: 2px solid rgba(255,255,255,.5); background: rgba(0,0,0,.2); color: #fff;
  font-size: 1.8rem; line-height: 1; cursor: pointer;
}
.sb-minus:active { background: rgba(0,0,0,.4); }

.sb-bar {
  grid-column: 1 / -1; display: flex; flex-direction: column; gap: .4rem; align-items: center;
  background: #02060c; padding: .6rem .6rem calc(.6rem + env(safe-area-inset-bottom));
}
.sb-ctrls { display: flex; gap: .4rem; justify-content: center; flex-wrap: wrap; }
/* Sets banked so far, so the scorer can see the night at a glance. */
.sb-setline {
  color: #9fb7cc; font-size: .85rem; font-weight: 700; letter-spacing: .02em;
  font-variant-numeric: tabular-nums; text-align: center;
}
.sb-ctrl {
  cursor: pointer; font: inherit; font-weight: 700; color: #cfe0f0; background: #122537;
  border: 1px solid #1f3a52; border-radius: 10px; padding: .55rem .8rem; font-size: .9rem;
}
.sb-ctrl:hover { background: #173049; text-decoration: none; }
.sb-exit { color: #ffb98a; }
/* Lit up the moment the points on the board are a legal set win. */
.sb-ctrl.is-ready { background: #1d4a2c; border-color: #2f7a48; color: #d8ffe6; }
.sb-submit { background: #b3550f; border-color: #d76c1c; color: #fff; }
.sb-submit:hover { background: #c9631a; }

/* — the "is this right?" step before a score is filed — */
.sb-confirm {
  position: fixed; inset: 0; z-index: 30; display: flex; align-items: center; justify-content: center;
  background: rgba(2, 6, 12, .8); padding: 1rem;
}
.sb-confirm[hidden] { display: none; }
.sb-confirm-card {
  width: min(420px, 100%); max-height: 90vh; overflow-y: auto;
  background: #0d2136; border: 1px solid #23415c; border-radius: 14px;
  padding: 1.1rem; color: #e6f0f9;
}
.sb-confirm-title { margin: 0 0 .25rem; font-size: 1.25rem; color: #fff; }
.sb-confirm-sub { margin: 0 0 .8rem; font-size: .85rem; color: #9fb7cc; }
.sb-confirm-teams {
  display: flex; align-items: center; justify-content: space-between; gap: .6rem;
  padding-bottom: .5rem; margin-bottom: .5rem; border-bottom: 1px solid #23415c;
  font-weight: 700; text-align: center;
}
.sb-confirm-teams strong { font-size: 1.5rem; color: #fff; font-variant-numeric: tabular-nums; }
.sb-confirm-row {
  display: flex; justify-content: space-between; padding: .3rem 0;
  font-size: .95rem; color: #cfe0f0; font-variant-numeric: tabular-nums;
}
.sb-confirm-who { display: block; margin: .9rem 0 .2rem; font-size: .8rem; color: #9fb7cc; font-weight: 700; }
.sb-confirm-who input {
  display: block; width: 100%; box-sizing: border-box; margin-top: .25rem;
  font: inherit; padding: .5rem .6rem; border-radius: 8px;
  background: #071726; border: 1px solid #23415c; color: #e6f0f9;
}
.sb-confirm-actions { display: flex; gap: .5rem; justify-content: flex-end; margin-top: 1rem; }

.sb-toast {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 5.2rem;
  background: rgba(0,0,0,.82); color: #fff; padding: .6rem 1rem; border-radius: 999px;
  font-weight: 700; font-size: 1rem; z-index: 5; max-width: 90vw; text-align: center;
}

@media (orientation: landscape) {
  .sb-score { font-size: clamp(4rem, 24vh, 16rem); }
}

fieldset { border: 1px solid var(--line); padding: .8rem; margin: 0; }
legend { font-weight: 800; font-size: .9rem; padding: 0 .35rem; }

.dev-banner {
  background: var(--accent); color: var(--on-accent); text-align: center; font-weight: 700;
  font-size: .82rem; padding: .4rem .8rem; letter-spacing: .01em;
}
.dev-banner code { background: rgba(0,0,0,.25); color: #fff; }

/* ---- Cookie consent ------------------------------------------------------- */
/* A fixed bottom bar, built by public/js/consent.js and shown only until the
   visitor accepts or declines. Sits above page content but below the mobile
   drawer (60) and any modal scrim (50), so those still cover it. */
.cookie-consent {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: .5rem 1.1rem; padding: .8rem 1rem;
  background: var(--card); color: var(--ink); border-top: 2px solid var(--line-strong);
}
.cookie-consent-msg { margin: 0; font-size: .9rem; font-weight: 600; }
.cookie-consent-actions { display: flex; gap: .5rem; flex: 0 0 auto; }

/* ---- AdSense slot --------------------------------------------------------- */
/* Bottom-of-page display unit. Breathing room above it, and it can't push the
   layout wider than the content column — an unfilled slot (no consent) is 0-high
   and shows nothing. */
.ad-slot { margin: 1.5rem 0 .5rem; min-width: 0; overflow: hidden; text-align: center; }
.ad-slot .adsbygoogle { display: block; }
/* When AdSense has nothing to show, collapse the whole block — margins and all —
   so an unfilled slot never leaves a blank gap at the foot of the page. */
.ad-slot:has(ins.adsbygoogle[data-ad-status="unfilled"]) { display: none; }

.reset-link { display: block; padding: .6rem .7rem; word-break: break-all; font-size: .85rem; }

/* ---- Enhanced tables (sort + filter) -------------------------------------- */
th.sortable { cursor: pointer; user-select: none; -webkit-user-select: none; white-space: nowrap; }
th.sortable:hover { color: var(--accent-2); }
th.sortable::after { content: " ↕"; opacity: .4; font-size: .85em; }
th.sortable[data-dir="asc"]::after { content: " ↑"; opacity: 1; color: var(--accent-2); }
th.sortable[data-dir="desc"]::after { content: " ↓"; opacity: 1; color: var(--accent-2); }
.filter-row th { padding: .15rem .3rem .45rem; }
.col-filter {
  width: 100%; min-width: 4.5rem; margin: 0; padding: .3rem .45rem;
  font-size: .82rem; font-weight: 400;
}
/* The filter row stays on a phone. It used to be hidden here, from when a
   narrow table collapsed into stacked cards — the enhanced tables all live in
   a .table-scroll now and stay tables at every width, so the filters stay too
   and scroll sideways with the columns they belong to. */

/* ---- Home / account ------------------------------------------------------- */
/* The landing page has no top bar, so it centres a single narrow column. */
.landing { max-width: 560px; margin: 0 auto; }
.auth-card .auth-actions { display: flex; flex-direction: column; gap: .6rem; margin: 1rem 0 .8rem; }
.auth-card .btn.big { width: 100%; }

.league-picker {
  display: flex; align-items: center; justify-content: center;
  gap: .6rem; flex-wrap: wrap; margin-bottom: 1.2rem;
}
.league-picker label { font-weight: 800; white-space: nowrap; }
.league-picker select { flex: 1 1 12rem; min-width: 0; }
.league-picker .btn { flex: 0 0 auto; }
/* Too narrow for one row: drop the label above, keep the select and button paired. */
@media (max-width: 560px) {
  .league-picker label { flex: 1 0 100%; text-align: center; }
  .league-picker select { flex: 1 1 auto; }
}

/* Scoreboard: the dispute window over the match list. Taller than the stock
   dialog — it carries the reported score, a full set grid and the review note —
   so it scrolls rather than running off a phone. */
.dispute-dialog { width: min(460px, 100%); max-height: 88vh; overflow-y: auto; gap: var(--space-2); }
.dispute-dialog .dialog-title { margin-bottom: 0; }
.dispute-original {
  background: var(--card); border: 1px solid var(--line); padding: .7rem .8rem;
}
.dispute-original-head {
  font-size: .72rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); font-weight: 800; margin-bottom: .3rem;
}
.dispute-result {
  display: flex; align-items: center; justify-content: space-between; gap: .6rem;
  font-weight: 700; font-size: .9rem; text-align: center;
}
.dispute-result strong { font-size: 1.35rem; font-variant-numeric: tabular-nums; }
.dispute-original .sets { justify-content: flex-start; margin-top: .45rem; }
.dispute-note {
  margin: 0; padding: .6rem .7rem; font-size: .82rem; line-height: 1.45;
  background: var(--warn-bg); color: var(--warn); font-weight: 600;
}
.dispute-dialog .setgrid-head span:first-child { font-size: .72rem; line-height: 1.15; color: var(--muted); }

/* Scoreboard setup: your match, about to start — loud, and above everything. */
.starting-now { border: 2px solid var(--accent); }
.starting-now-kicker {
  font-size: .72rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent-2); margin-bottom: .6rem;
}
.starting-now .btn.big { margin: 0; }
.starting-now-meta { margin: .45rem 0 0; text-align: center; }
.starting-now-meta + .btn.big { margin-top: 1rem; }

/* Scoreboard setup: the no-league escape hatch under the league picker. */
.fun-start { text-align: center; }
.fun-start p { margin: 0 0 .6rem; }
.fun-names { display: flex; gap: .5rem; justify-content: center; flex-wrap: wrap; margin-bottom: .7rem; }
.fun-names input { flex: 1 1 10rem; min-width: 0; max-width: 14rem; margin: 0; }

.section-title { margin: 1.5rem 0 .8rem; }
.block { display: block; }

.role-list { display: flex; flex-direction: column; gap: .55rem; }
.role-check { align-items: flex-start; gap: .5rem; }
.role-check input { margin-top: .25rem; }

/* Minimal label-only fieldset (no box), used for small option groups */
fieldset.plain { border: 0; padding: 0; }
fieldset.plain > legend {
  padding: 0; margin-bottom: .3rem; font-size: .85rem; font-weight: 700;
  color: color-mix(in srgb, var(--ink) 72%, transparent);
}

/* Segmented radio group: equal-width, aligned options */
.choice-group { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; border: 1px solid var(--line); }
.choice { margin: 0; font-size: inherit; }
.choice + .choice { border-left: 1px solid var(--line); }
.choice input { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; }
.choice span {
  display: block; padding: .6rem .5rem; text-align: center;
  font-weight: 700; font-size: .9rem; color: var(--ink); cursor: pointer;
}
.choice input:not(:checked) + span:hover { background: color-mix(in srgb, var(--ink) 6%, transparent); }
.choice input:checked + span { background: var(--ink); color: var(--card); }
.choice input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ===========================================================================
   Modernist component library — ported verbatim (class names + tokens) from
   the design project, used by the /dash/* dashboard views. Coexists with the
   legacy classes above (different class names, no collisions) except `.table`,
   which this section owns going forward (the responsive stacking rules above
   still apply on top of it).
   =========================================================================== */
*, *::before, *::after { box-sizing: border-box; }

.hr { height: 2px; border: 0; margin: var(--space-4) 0; background: var(--color-divider); }

/* — buttons — */
.btn-primary, .btn-secondary, .btn-ghost, .btn-icon {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  cursor: pointer; text-decoration: none;
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 14px; line-height: 1.2; color: var(--color-text);
  background: transparent; border: 1px solid transparent;
  padding: var(--space-2) calc(var(--space-3) * 1.2);
  border-radius: var(--radius-md);
}
.btn-primary:disabled, .btn-secondary:disabled, .btn-ghost:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-primary { background: var(--color-accent); color: var(--color-bg); }
.btn-primary:hover { background: var(--color-accent-600); text-decoration: none; }
.btn-primary:active { background: var(--color-accent-700); }
.btn-secondary { border-color: var(--color-divider); }
.btn-secondary:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); text-decoration: none; }
.btn-secondary:active { background: color-mix(in srgb, var(--color-text) 14%, transparent); }
.btn-ghost { color: var(--color-accent); padding-inline: var(--space-1); }
.btn-ghost:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); text-decoration: none; }
.btn-ghost:active { background: color-mix(in srgb, var(--color-accent) 18%, transparent); }
.btn-icon { width: 36px; height: 36px; padding: 0; }
.btn-block { width: 100%; margin-top: var(--space-2); justify-content: flex-start; text-align: left; }

/* Phone: these sit at 35px, while every public `.btn` and form input is 40px —
   so a thumb got a smaller target on the dash than on the way in. One height
   across both button systems; desktop keeps the tighter 35px. */
@media (max-width: 560px) {
  .btn-primary, .btn-secondary { min-height: 40px; }
}

/* — forms — */
.field > label {
  display: block; font-size: 12px; margin-bottom: 5px;
  color: color-mix(in srgb, var(--color-text) 70%, transparent);
}
.input {
  width: 100%; min-height: 36px; padding: 6px 10px; font: inherit;
  font-size: 14px; color: var(--color-text); caret-color: var(--color-accent);
  background: var(--color-surface);
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
  margin-top: 0;
}
.input:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.input:focus-visible { border-color: var(--color-accent); outline-offset: 0; }
textarea.input { min-height: 90px; resize: vertical; }

/* — cards — */
.card-kicker { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-accent); }
.card-title { font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: 17px; line-height: 1.2; }
.card-body { margin: 0; font-size: 13px; opacity: 0.8; flex: 1; }
.card-meta { display: flex; align-items: center; gap: 6px; font-size: 11px; color: color-mix(in srgb, var(--color-text) 50%, transparent); }
.elev-sm { box-shadow: var(--shadow-sm); }
.elev-md { box-shadow: var(--shadow-md); }
.elev-lg { box-shadow: var(--shadow-lg); }

/* — tags — */
.tag { display: inline-flex; align-items: center; font-size: 11px; letter-spacing: 0.02em; padding: 3px 10px; border-radius: calc(var(--radius-md) * 0.75); }
.tag-accent { background: var(--color-accent-100); color: var(--color-accent-800); }
.tag-neutral { background: var(--color-neutral-100); color: var(--color-neutral-800); }
.tag-outline { border: 1px solid var(--color-accent); color: var(--color-accent); }

/* — table (canonical: used by /dash/* and public views) — */
.table {
  width: 100%; border-collapse: collapse; font-size: 13px;
  border: 1px solid var(--color-divider); background: var(--color-neutral-100);
}
.table th {
  text-align: left; font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase;
  font-weight: 800; color: var(--color-text);
  background: var(--color-neutral-300);
  padding: 7px var(--space-2);
  border: 1px solid var(--color-divider);
  white-space: nowrap;
}
.table td {
  padding: 5px var(--space-2);
  border: 1px solid var(--color-divider);
  vertical-align: middle;
}
.table tbody tr:nth-child(even) { background: var(--color-neutral-200); }
.table tbody tr:hover { background: color-mix(in srgb, var(--color-accent) 8%, transparent); }
.table td.num, .table th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* A table wrapped in .table-scroll stays a table at every width: the wrapper
   scrolls sideways instead of the rows stacking into cards on small screens.
   Used by the dashboard, where "Table" is an explicit layout choice. */
/* Present for a screen reader, absent for everything else. Used where the
   visual design already conveys the structure — the scoreboard's <h1>, whose
   two team names sit at opposite edges of the screen. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; min-width: 0; }
.table-scroll .table { display: table; min-width: 640px; }
/* Standings and other all-number tables: the columns are a character or two
   wide, so they need far less room before scrolling earns its keep. */
.table-scroll.tight .table { min-width: 460px; }
/* Three or four short columns that already fit a phone. Keeps the wrapper (so
   the table stays a table and scrolls if the content grows) without inventing
   a scrollbar the content doesn't need. */
.table-scroll.fit .table { min-width: 0; }
/* The registries (/account/players, /account/leagues): twenty-odd columns of
   record, read the way the spreadsheet they replace is read — by scrolling
   sideways. The floor is wide enough that the columns don't crush each other
   before the wrapper takes over. */
.table-scroll.wide .table { min-width: 1200px; }
/* Every cell on one line, so a row stays a row and the eye can track across
   it. Only for tables inside a .table-scroll, which is what makes it safe. */
.table.nowrap td { white-space: nowrap; }
.table-scroll .table thead { display: table-header-group; }
.table-scroll .table tbody { display: table-row-group; }
.table-scroll .table tr {
  display: table-row; width: auto; border: none; margin: 0; padding: 0;
}
.table-scroll .table td {
  display: table-cell; width: auto; padding: 5px var(--space-2);
  border: 1px solid var(--color-divider);
}
.table-scroll .table td::before { content: none; }

/* A form that adds to the list directly above it inside one card — the owner's
   organizations section, where the table is the record and this is how it
   grows. Without it the first label sits on the table's bottom border. */
.card-form { margin-top: var(--space-6); }

/* — dialog — */
.dialog-backdrop {
  position: fixed; inset: 0; display: grid; place-items: center; padding: var(--space-4);
  background: var(--scrim); z-index: 50;
}
.dialog {
  width: min(440px, 100%); display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-4); border-radius: var(--radius-lg);
  background: var(--color-surface); box-shadow: var(--shadow-lg);
}
.dialog-title { font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: 20px; }
.dialog-body { font-size: 14px; opacity: 0.85; }
.dialog-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-2); }

/* — the schedule summary dialog —
   A whole season's reading rather than a question, so it is far wider than a
   dialog usually is and its body scrolls inside the box: the head stays put
   and the Close button never scrolls away from someone nine nights down. */
/* min-width:0 is doing real work here: the dialog is a centred grid item, so
   its default min-width:auto is its min-content — and a seven-column table
   inside would push the whole box wider than the phone it is being read on. */
.dash-summary { width: min(940px, 100%); max-width: 100%; min-width: 0; max-height: 88vh; }
.dash-summary-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-3); flex-wrap: wrap;
}
.dash-summary-scroll {
  overflow-y: auto; min-height: 0; min-width: 0; -webkit-overflow-scrolling: touch;
  padding-right: 4px;
}
/* Four stat tiles across is a desktop reading; on a phone they stack two-up
   rather than squeezing a five-digit number into 90px. */
.dash-summary .dash-stats { grid-template-columns: repeat(2, 1fr); }
@media (min-width: 720px) {
  .dash-summary .dash-stats { grid-template-columns: repeat(4, 1fr); }
}
/* The night-by-night tables are read a row at a time, not compared across
   columns, so they can run narrower than a standalone table needs to. */
.dash-summary .table-scroll .table { min-width: 420px; }
.dash-summary .table-scroll.tight .table { min-width: 360px; }

/* One row per league in the "create a team" dialog: which league, how full it
   is, and the name of the team you're signing up. */
.league-row {
  display: grid; grid-template-columns: 1fr 10rem auto; gap: .6rem; align-items: center;
  padding: .7rem 0; border-top: 1px solid var(--line);
}
.league-row-name { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.league-row input { margin: 0; }
.league-row .btn { white-space: nowrap; }
@media (max-width: 560px) {
  .league-row { grid-template-columns: 1fr; }
}

/* ===========================================================================
   Dashboard shell (/dash/:slug) — top bar + 242px left nav + main, matching
   the design project's app frame.
   =========================================================================== */
.dash-shell {
  background: var(--color-bg); color: var(--color-text);
  display: flex; flex-direction: column; min-height: 100vh;
}

/* Sidebar retraction (mobile) — pure CSS, no JS required: a hidden checkbox
   toggled by the hamburger/close labels drives the drawer via :checked. */
.nav-toggle-checkbox { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.nav-close-btn { display: none; cursor: pointer; font-size: 16px; font-weight: 800; padding: 14px 20px; text-align: right; }
.nav-backdrop { display: none; position: fixed; inset: 0; z-index: 55; cursor: pointer; background: var(--scrim-strong); }

.dash-body { display: flex; align-items: stretch; flex: 1 1 auto; }
.dash-sidenav {
  width: 242px; flex: 0 0 242px; box-sizing: border-box; border-right: 2px solid var(--line-strong);
  padding: 20px 0; display: flex; flex-direction: column; gap: 2px;
}
.dash-sidenav-kicker { font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-600); padding: 0 20px 10px; }
/* Which league you are in — the header used to say it, but this is where the
   rest of the league context lives. */
.dash-sidenav-switch { width: calc(100% - 32px); margin: 0 16px 14px; font-size: 13px; font-weight: 700; padding: 6px 8px; }
.dash-sidenav-league { padding: 0 20px 14px; font-size: 13px; font-weight: 700; }
.dash-sidenav a {
  display: flex; align-items: center; gap: 10px; text-align: left; border: 0; padding: 9px 20px 9px 16px;
  font-size: 13px; font-weight: 700; cursor: pointer; color: var(--color-text); border-left: 4px solid transparent;
}
.dash-sidenav a:hover { background: var(--color-neutral-200); text-decoration: none; }
.dash-sidenav a.active { background: var(--color-neutral-200); border-left-color: var(--color-accent); }
/* "View as" — look at the league from a level below your own. Sits directly
   under the last nav link, with a rule above it because it switches what you
   are looking at rather than where you are. Viewer, the bottom of the ladder,
   leaves for the public page. */
.dash-sidenav-viewas {
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
  padding: 16px 16px 4px; border-top: 1px solid var(--color-divider);
}
.dash-sidenav-viewas .dash-sidenav-kicker { padding: 0; }
.dash-sidenav-viewas select { font-size: 13px; font-weight: 700; padding: 6px 8px; margin: 0; }

/* Standing reminder that the page is showing less than you actually have. */
.viewas-banner {
  display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: baseline; margin: 0 0 20px;
  padding: 10px 14px; font-size: 13px; border: 1px solid var(--line);
  background: var(--color-neutral-200); color: var(--color-text);
}
.viewas-banner a { font-weight: 700; }
.dash-main { flex: 1; min-width: 0; padding: 28px 32px 64px; }

.dash-pagehead { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; border-bottom: 2px solid var(--line-strong); padding-bottom: 16px; flex-wrap: wrap; }
.dash-eyebrow { font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-600); }
/* The same slot as .dash-eyebrow above the heading, and deliberately the same
   size and rhythm so the heading under it doesn't move — but this one is the
   way back, so it carries an arrow and darkens to full text colour under the
   cursor rather than sitting there as a label. See dash/partials/pagehead.ejs. */
.dash-back {
  /* Block-level rather than inline, and only as wide as its text: an inline
     box would be laid out against the parent's line-height strut, which is
     taller than 11px of text and would push the heading down a few pixels
     from where the label it replaced used to leave it. */
  display: flex; width: fit-content; align-items: center; gap: 6px; font-weight: 700;
  font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--color-accent);
  /* 11px of text is a 17px tap target, and on a phone this is the way back —
     the sidenav that would otherwise carry you is a closed drawer. The padding
     buys a thumb-sized target; the negative margin hands the space back, so
     the heading below sits exactly where it did. */
  padding: 14px 10px 14px 0; margin: -14px 0;
}
.dash-back:hover { color: var(--color-accent-600); text-decoration: none; }
/* Whatever the eyebrow used to say that was worth keeping — a team count, the
   league a team belongs to — sits under the heading now, where a status chip
   can sit beside it. */
.dash-headnote {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-size: 12px; color: var(--color-neutral-700); margin-top: 6px;
}
.dash-h1 { font-family: var(--font-heading); font-weight: 800; font-size: 38px; margin: 6px 0 0; letter-spacing: -0.02em; }

.dash-stats { display: grid; grid-template-columns: repeat(4, 1fr); border-bottom: 2px solid var(--line-strong); }
/* Each tile is a link to the page that explains it (teams, payments, schedule),
   so it has to keep looking like a panel rather than a line of link text. */
.dash-stat { padding: 20px 20px 22px; border-right: 1px solid var(--color-divider); display: block; color: var(--color-text); }
a.dash-stat:hover { background: var(--color-neutral-200); text-decoration: none; }
a.dash-stat:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.dash-stat-label { font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-700); }
.dash-stat-value { font-family: var(--font-heading); font-weight: 800; font-size: 40px; line-height: 1.05; margin-top: 8px; }
.dash-stat-note { font-size: 12px; color: var(--color-neutral-700); margin-top: 4px; }
@media (max-width: 900px) { .dash-stats { grid-template-columns: repeat(2, 1fr); } }

/* Standings on the overview: one ranked table for the league. It used to be a
   grid of one table per division, side by side while they fit — divisions are
   gone, so it's a single table on the page's own width and the grid with it.
   Your own team is still marked so you can find it without reading. */
.dash-section { padding: 24px 0 28px; border-bottom: 2px solid var(--line-strong); }
.dash-section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
.dash-section-h2 { font-family: var(--font-heading); font-weight: 800; font-size: 18px; margin: 0; }
.dash-section .table-scroll { margin-top: 16px; }
.dash-section .table tbody tr.is-mine { background: color-mix(in srgb, var(--color-accent) 14%, transparent); }
.dash-section .table tbody tr.is-mine td:first-child { border-left: 3px solid var(--color-accent); }
.dash-section .table a { color: inherit; }

@media (max-width: 900px) {
  .nav-close-btn { display: block; }
  .dash-body { flex-direction: column; }

  /* Off-canvas drawer: full-height panel sliding in from the left, opened by
     the hamburger button and closed by the ✕, the backdrop, or a nav link. */
  .dash-sidenav {
    position: fixed; top: 0; left: 0; height: 100vh; width: min(280px, 84vw);
    z-index: 60; box-sizing: border-box; background: var(--color-bg);
    border-right: 2px solid var(--line-strong); flex-direction: column; flex-wrap: nowrap;
    padding: 0 0 20px; overflow-y: auto;
    transform: translateX(-100%); transition: transform 0.22s ease;
  }
  .nav-toggle-checkbox:checked ~ .dash-body .dash-sidenav { transform: translateX(0); box-shadow: var(--shadow-lg); }
  .nav-toggle-checkbox:checked ~ .dash-body .nav-backdrop { display: block; }
  .dash-sidenav-kicker { display: block; }
  .dash-main { padding: 20px 16px 48px; }
}

/* ===========================================================================
   Dashboard page layouts
   ---------------------------------------------------------------------------
   Each /dash/* view lays its page out with a grid, and each grid used to be
   written inline on the element as one fixed track list — `200px 1fr 150px
   100px`, `70px 60px 1fr 90px 110px`, `repeat(4, 1fr)`. An inline style can't
   answer a media query, so at phone width every one of them either pushed the
   document past the viewport or squeezed a column down to one word per line.

   They live here instead, each named for what it lays out and each stating
   the width it gives up its columns at. The breakpoints are deliberately not
   all the same number: a four-up stat strip runs out of room long before a
   two-column page body does.
   =========================================================================== */

/* — two-column page body (team: roster beside captain) —
   The divider between the halves is a right border while they sit side by
   side and a top border once they stack, so the rule never dead-ends. */
.dash-split { display: grid; grid-template-columns: var(--dash-split-cols, 1.4fr 1fr); }
/* Rows inside the main half hold their own right inset on top of the column's,
   so their contents stop well clear of the divider rather than butting up to
   it. Naming it means the rows can keep asking for the gutter after it has
   gone to nothing — see the roster form in team.ejs. */
.dash-split-main { --dash-gutter: 24px; padding: 24px var(--dash-gutter) 28px 0; border-right: 1px solid var(--color-divider); min-width: 0; }
.dash-split-side { padding: 24px 0 28px 24px; min-width: 0; }
.dash-split-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding-right: var(--dash-gutter); }

@media (max-width: 760px) {
  .dash-split { grid-template-columns: 1fr; }
  .dash-split-main { --dash-gutter: 0px; padding: 20px 0 24px; border-right: 0; }
  .dash-split-side { padding: 20px 0 24px; border-top: 1px solid var(--color-divider); }
}

/* — payments, progress-bar view: team, bar, due date, action — */
.dash-payrow {
  display: grid; grid-template-columns: 200px 1fr 150px 100px; gap: 20px; align-items: center;
  padding: 14px 0; border-bottom: 1px solid var(--color-divider);
}
@media (max-width: 760px) {
  /* Team and due date pair off on the first line, the bar spans the second,
     the button gets the third — `order` because the bar is the middle child
     in source order but reads better under the two labels. */
  .dash-payrow { grid-template-columns: 1fr auto; gap: 8px 12px; }
  .dash-payrow-team { order: 1; }
  .dash-payrow-due { order: 2; text-align: right; }
  .dash-payrow-bar { order: 3; grid-column: 1 / -1; }
  .dash-payrow-action { order: 4; grid-column: 1 / -1; justify-self: start; }
}

/* — schedule, list view: one match per row —
   Narrow, it stops being a grid altogether: the names take the full line and
   the four scraps of metadata flow underneath on as many lines as they need. */
.dash-matchrow {
  width: 100%; border: 0; border-bottom: 1px solid var(--color-divider); background: transparent;
  text-align: left; padding: 14px 4px; color: inherit;
  /* The court column holds "Court 1" at one gym and "Riverside · North" at
     two, so it takes what it needs up to a ceiling rather than a fixed 60px
     that a gym name wraps into a tower. */
  display: grid; grid-template-columns: 70px minmax(60px, 150px) 1fr 90px 110px; gap: 16px; align-items: center;
}
@media (max-width: 760px) {
  .dash-matchrow { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
  .dash-matchrow-teams { flex-basis: 100%; order: -1; }
}

/* — the row under a page title —
   Which way you're looking at the page on the left, what you can take away
   with you on the right. Distinct from .dash-headactions, which sits on the
   title's own line and holds the one thing the page is for. */
.dash-toolbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
  padding: 14px 0; border-bottom: 1px solid var(--color-divider);
}
.dash-toolbar-views { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* The menu trigger is a button here rather than an avatar, so it keeps the
   button's box and only borrows the menu's open/close behaviour. `margin-left`
   holds it against the right edge on its own wrapped line too — the panel
   hangs off its right edge, and a trigger sitting left on a phone would drop
   220px of menu off the side of the screen. */
.dash-toolbar .usermenu { margin-left: auto; }
.dash-toolbar .usermenu-trigger { gap: 6px; }

/* — schedule, calendar view: one column per playing night —
   A season is eight to twelve nights, so the columns can't all be on screen.
   `grid-auto-columns` lets them share the width while they still fit and hold
   a floor of 150px once they don't, at which point the wrapper scrolls. */
.dash-calendar { overflow-x: auto; -webkit-overflow-scrolling: touch; border-top: 2px solid var(--color-text); border-left: 1px solid var(--color-divider); }
.dash-calendar-grid { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(150px, 1fr); }

/* — schedule, bracket view: one column per round — same bargain as above. */
.dash-bracket {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(165px, 1fr); gap: 24px;
  margin-top: 24px; align-items: start; overflow-x: auto; -webkit-overflow-scrolling: touch;
  padding-bottom: 4px;
}

/* — match page: the two names either side of the score — */
.dash-scoreline { display: grid; grid-template-columns: 1fr 120px 1fr; align-items: center; gap: 20px; margin: 18px 0 0; }
.dash-scoreline > * { font-family: var(--font-heading); font-weight: 800; font-size: 34px; letter-spacing: -0.01em; min-width: 0; }
.dash-scoreline-score { text-align: center; color: var(--color-accent); }
.dash-scoreline-away { text-align: right; }
@media (max-width: 600px) {
  /* Side by side there is not enough room for two team names at this size, so
     they stack and centre — the score keeps its place between them. */
  .dash-scoreline { grid-template-columns: 1fr; gap: 2px; }
  .dash-scoreline > * { font-size: 26px; text-align: center; }
}

/* — schedule wizard: the numbered step rail across the top — */
.wizard-steps {
  list-style: none; display: flex; flex-wrap: wrap; gap: 0;
  margin: 18px 0 24px; padding: 0; border-bottom: 2px solid var(--color-text);
}
.wizard-steps li {
  display: flex; align-items: center; gap: 7px;
  padding: 8px 14px 10px 0; font-size: 12px; font-weight: 600;
  color: var(--color-neutral-500);
}
.wizard-steps .is-done { color: var(--color-neutral-700); }
.wizard-steps .is-current { color: var(--color-text); font-weight: 800; }
.wizard-step-n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; font-size: 11px; font-weight: 800;
  background: transparent; color: var(--color-neutral-600);
  border: 1px solid var(--color-divider);
}
/* A filled badge carries its label on a solid fill, so the label takes the
   inverse token. This was #fff, which vanished on --color-text in dark mode. */
.wizard-steps .is-current .wizard-step-n {
  background: var(--color-accent); color: var(--on-accent); border-color: transparent;
}
.wizard-steps .is-done .wizard-step-n {
  background: var(--color-text); color: var(--color-bg); border-color: transparent;
}

/* — schedule preview: a team playing twice the same night —
   The colour is the quick read down a column; the asterisk is what makes it
   survive a mono printout and a reader who can't pick the accent out, and it
   is real text in the cell rather than generated content, so it is spoken. */
.dash-double { color: var(--color-accent-700); }

/* — schedule wizard: what a night's answers actually buy —
   First serve, gym closing and slot length are three numbers that only mean
   something together, so the sum of them sits next to the fields. */
.dash-slotcount {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 14px;
  border: 2px solid var(--color-text); padding: 12px 14px; margin: 18px 0 4px;
  max-width: 620px; font-size: 13px;
}
.dash-slotcount strong { font-family: var(--font-heading); font-weight: 800; font-size: 15px; }
/* The start times take a line of their own — at eight slots they are the
   longest thing in the box. */
.dash-slotcount-times { flex-basis: 100%; color: var(--color-neutral-700); }
/* Whether the night is big enough for what the double header question is
   about to offer. `is-short` is the "no", so it takes the accent. */
.dash-slotcount-fit { flex-basis: 100%; font-weight: 700; }
.dash-slotcount-fit.is-short { color: var(--color-accent-700); }

/* — schedule wizard: the availability grid and its audit —
   Three tables on one step, all of them read by scanning a column rather than
   a row, so they run tighter than a normal table and the numbers line up. */
.dash-avail td, .dash-avail th { padding-top: 7px; padding-bottom: 7px; }
.dash-avail td { font-variant-numeric: tabular-nums; }
/* The footer is a reading of the columns above it, not another team, so it
   sits under a rule of its own. */
.dash-avail tfoot th, .dash-avail tfoot td {
  border-top: 2px solid var(--color-text);
  color: var(--color-neutral-800);
}
/* A team the answers have painted into a corner: no slot left, no games, or a
   match at a time it said it couldn't play. The tint is the scan; the wording
   in the row's last cell is what says which of the three it is. */
.dash-avail tr.is-stuck > th, .dash-avail tr.is-stuck > td {
  background: var(--color-accent-100);
}
/* Three answers per cell, so a select rather than a tick. Sized to the longest
   option so a grid of them still reads as a grid. */
.dash-prefcell {
  width: 100%; min-width: 104px; padding: 4px 6px;
  font-size: 12px; height: auto;
}

/* — match page: one row per set, a stepper for each side — */
.dash-setrow {
  display: grid; grid-template-columns: 80px 1fr 1fr 90px; gap: 16px; align-items: center;
  padding: 12px 0; border-top: 1px solid var(--color-divider);
}
@media (max-width: 600px) {
  /* The two steppers keep their half each; the set number and the winner take
     a full line above and below them. */
  .dash-setrow { grid-template-columns: 1fr 1fr; gap: 8px 12px; }
  .dash-setrow-no, .dash-setrow-win { grid-column: 1 / -1; }
}

/* — paired fields inside a dialog —
   440px of dialog splits in two comfortably; 92vw of a phone does not. */
.dialog-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
/* The full-width fields ask for `1 / -1`, which is the only column here. */
@media (max-width: 480px) { .dialog-grid { grid-template-columns: 1fr; } }

/* — the buttons beside a page title —
   Wrapping rather than compressing: without this the flex items shrink and
   "Add team" breaks across two lines inside its own box. */
.dash-headactions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.dash-headactions .btn-primary, .dash-headactions .btn-secondary { white-space: nowrap; }

/* — micro-label —
   The uppercase caption that names a control group ("Layout", "View") or the
   value under it ("Team code", "Dues"). It was seven copies of the same inline
   declarations across five dash views; this is the one definition. */
.micro-label {
  font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--color-neutral-700);
}
.dash-headactions .micro-label { margin-right: 4px; }

/* — settings sections —
   The stacked, rule-separated blocks a settings page is made of. Was the same
   two inline declarations repeated per section across the settings and match
   pages; the `tight` heading is for a section whose first child is prose
   rather than a field grid. */
.dash-setting-section { padding: 24px 0; border-bottom: 1px solid var(--color-divider); }
.dash-setting-h2 {
  font-family: var(--font-heading); font-weight: 800; font-size: 16px; margin: 0 0 16px;
}
.dash-setting-h2.tight { margin-bottom: 8px; }
.dash-setting-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 560px) { .dash-setting-grid { grid-template-columns: 1fr; } }

/* — the danger block —
   The last section on a settings page: the one action that destroys the thing
   the page is about. Boxed in the accent so it reads as separate from the
   edits above it, and it drops the divider, because nothing follows it. */
.dash-setting-section.danger {
  border: 2px solid var(--color-accent);
  padding: 20px;
  margin: 24px 0 32px;
}
.dash-setting-section.danger .dash-setting-h2 { color: var(--color-accent); }
/* Who runs this league, on the line under the heading — the same boxed
   "current state" note the scoring section uses above its form. */
.dash-directed-by {
  display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
  border: 2px solid var(--color-text); padding: 10px 14px; margin: 0 0 16px; max-width: 620px;
  font-family: var(--font-heading); font-weight: 800; font-size: 15px;
}
.dash-directed-by .micro-label { color: var(--color-accent); }
.dash-danger-actions { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
.dash-danger-actions .field { width: 280px; }
@media (max-width: 560px) { .dash-danger-actions .field { width: 100%; } }

/* — stat strips —
   `.dash-stats` is already two-up under 900px, but 40px numerals don't fit a
   half-width cell on a phone: "$3450.00" was being clipped mid-digit. The
   `sm` modifier is for strips that were already asking for smaller numbers
   inline, where an inline size would have beaten the media query. */
.dash-stats.sm .dash-stat { padding: 18px 20px; }
.dash-stats.sm .dash-stat-value { font-size: 30px; margin-top: 6px; }
@media (max-width: 560px) {
  .dash-stat { padding: 16px 14px 18px; }
  .dash-stat-value { font-size: 30px; }
  .dash-stats.sm .dash-stat { padding: 14px 12px 16px; }
  .dash-stats.sm .dash-stat-value { font-size: 24px; }
}

/* — one layout for wide, another for narrow —
   The teams page offers a table and a set of cards. The table is the better
   read at width and the cards are the only readable option on a phone, so
   when nobody has picked one explicitly both are rendered and the viewport
   decides. An explicit ?view= is honoured at every width. */
.dash-onlynarrow { display: none; }
@media (max-width: 640px) {
  .dash-onlywide { display: none; }
  .dash-onlynarrow { display: block; }
}

/* — the liability waiver: long-form legal prose, a signature, and a record — */
/* Narrower measure than the card it sits in, because reading nine numbered
   sections at full width is what makes people stop reading them. */
.legal { max-width: 68ch; font-size: 14px; line-height: 1.6; }
.legal h2 { font-family: var(--font-heading); font-weight: 800; font-size: 18px; margin: 0 0 .2rem; }
.legal h3 {
  font-family: var(--font-heading); font-weight: 800; font-size: 13px;
  letter-spacing: 0.08em; text-transform: uppercase;
  margin: 1.6rem 0 .4rem; padding-top: .9rem; border-top: 1px solid var(--color-divider);
}
.legal .legal-sub { color: var(--color-neutral-700); margin: 0 0 1.2rem; }
.legal p { margin: 0 0 .8rem; }
.legal ul { margin: 0 0 .8rem; padding-left: 1.2rem; }
.legal li { margin-bottom: .25rem; }
.legal blockquote {
  margin: 0 0 .8rem; padding: .6rem .9rem;
  border-left: 3px solid var(--color-accent); background: var(--color-neutral-100);
}

/* Each certification is a checkbox against a paragraph, so the box has to sit
   at the top of the text rather than centred against it (.check's default). */
.waiver-check {
  align-items: flex-start; gap: .7rem; font-weight: 400; font-size: 13px; line-height: 1.5;
  border: 1px solid var(--color-divider); padding: .8rem;
}
.waiver-check input { margin-top: .2rem; flex: none; }
.waiver-sign { margin-top: 1.6rem; padding-top: 1.4rem; border-top: 2px solid var(--color-text); max-width: 68ch; }

/* A typed signature should read as a signature, not as another text field. */
.signature-ink { font-family: 'Snell Roundhand', 'Apple Chancery', 'Segoe Script', cursive; font-size: 20px; }

/* Label/value pairs for a signature on file. Stacks on a phone. */
.deflist { display: grid; grid-template-columns: max-content 1fr; gap: .35rem 1.2rem; margin: 0; }
.deflist dt {
  font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--color-neutral-700); align-self: center;
}
.deflist dd { margin: 0; font-weight: 700; overflow-wrap: anywhere; }
@media (max-width: 520px) {
  .deflist { grid-template-columns: 1fr; gap: 0; }
  .deflist dt { margin-top: .7rem; }
}

/* ---- Where dues go, and who referees -------------------------------------
   The "Pay dues to" band sits under the stats on Payments and on a team page,
   and reads as one line: the label, the handle, and the thing to tap. On a
   phone the three stack rather than squeeze, because the handle is the part
   that must stay legible — it's what somebody types into another app. */
.dash-payto {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  border: 2px solid var(--color-text); padding: 12px 16px; margin-top: 22px;
}
.dash-payto-handle {
  font-family: var(--font-heading); font-weight: 800; font-size: 16px;
  overflow-wrap: anywhere;
}
.dash-payto-note { font-size: 12px; color: var(--color-neutral-700); }
.dash-payto-action { font-size: 12px; margin-left: auto; }
@media (max-width: 520px) {
  .dash-payto { flex-direction: column; align-items: stretch; gap: 8px; }
  .dash-payto-action { margin-left: 0; align-self: flex-start; }
}

/* The three officiating answers, in League settings. One column: they are
   read as alternatives and each carries a line of explanation, so side by
   side would only shorten the wrong thing. */
.officiating-choices { display: flex; flex-direction: column; gap: 8px; }
.officiating-choice {
  align-items: flex-start; gap: 9px;
  border: 1px solid var(--color-divider); padding: 12px 14px; font-size: 13px;
}
.officiating-choice-note {
  font-size: 12px; color: var(--color-neutral-700); font-weight: 400;
}

/* ---- Editable registry tables --------------------------------------------
   /account/players and /account/leagues are the spreadsheet view of what's on
   file, and the Owner types into them. A cell control has to read as part of
   the grid rather than as a form dropped on top of one: no chrome until it's
   focused, and the accent border once a cell holds something unsaved. */
.cell-input {
  width: 100%; min-width: 6rem; max-width: 16rem; margin: 0;
  padding: .2rem .35rem; font: inherit; font-size: .85rem;
  background: transparent; color: inherit;
  border: 1px solid transparent; border-radius: 0;
}
.cell-input:hover { border-color: var(--line); }
.cell-input:focus {
  border-color: var(--accent); outline: 2px solid var(--accent); outline-offset: -2px;
  background: var(--color-bg);
}
.cell-input.is-changed {
  border-color: var(--accent); background: var(--color-accent-100); font-weight: 700;
}
.table td .cell-input { display: block; }
/* A date cell needs its whole control, and a number reads right-aligned like
   the column it sits in. */
.cell-input[type="date"] { min-width: 9rem; }
.table td.num .cell-input { text-align: right; font-variant-numeric: tabular-nums; }

/* The save bar under an editable table. Sticky, because the table it belongs
   to is longer than the screen and the button has to stay in reach. */
.registry-form { margin-bottom: var(--space-6); }
.registry-save {
  position: sticky; bottom: 0; z-index: 2;
  display: flex; align-items: center; gap: .8rem; flex-wrap: wrap;
  padding: .7rem .8rem; margin-top: .6rem;
  background: var(--card); border: 2px solid var(--line-strong);
}
.registry-save-count { font-size: .82rem; color: var(--muted); }
.registry-save-count.is-live { color: var(--accent-2); font-weight: 700; }

/* The off team: who has the whistle on this match. Deliberately quieter than
   the two teams playing — it is who else is standing on the court, not part of
   the match-up — and it reads the same wherever it appears, on the schedule,
   the match page, the public list and the wizard's preview. */
.dash-ref {
  font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
  color: var(--color-neutral-700); white-space: nowrap;
}
.table td.dash-ref { font-size: 12px; }
