/* Tournament — Hermès-inspired design system.
   Palette: signature orange on ivory/cream, warm neutrals, near-black ink, gold hairlines.
   Type: system serif display for headings/hero, clean system sans for body/UI.
   Feel: generous whitespace, hairline rules, restrained borders, understated motion.
   No external resources — system fonts + inline SVG/data-URIs only. Mobile-first. */

:root {
  /* Signature accents */
  --orange: #ea5b0c;         /* primary fill (Hermès orange, deepened for contrast) */
  --orange-deep: #c2410c;    /* text/links on cream (AA) */
  --orange-bright: #f37021;  /* decorative glints, hero rule */
  --orange-soft: #fbe9dc;    /* warm tint background */
  --orange-line: #f0cdb2;    /* tinted hairline */

  /* Grounds & ink */
  --cream: #faf6f0;
  --paper: #fffdfa;
  --sand: #f6f1e9;           /* table head / inset panels */
  --ink: #1f1a16;
  --ink-soft: #6b6259;
  --ink-faint: #9a9086;
  --line: #e7ded2;           /* hairline rule */
  --line-strong: #d9cdbc;

  /* Semantic */
  --gold: #b58a3c;
  --green: #2e6e4e;
  --red: #a83232;

  /* Refinement tokens (decorative washes & hairlines) */
  --gold-soft: #f7efdd;      /* champion row wash */
  --gold-line: #d9c092;      /* gold hairline */
  --zebra: #fbf8f2;          /* even-row wash on data-heavy tables */
  --row-hover: #f8f2e9;      /* softer, warmer row hover */
  --sand-deep: #f0e9db;      /* table-head gradient base */
  --paper-bright: #fffefc;   /* card top-light */
  --orange-veil: rgba(234, 91, 12, 0.045); /* hero warm wash */
  --shadow-btn: 0 6px 14px -6px rgba(63, 42, 24, 0.35);

  /* ---- Scales -------------------------------------------------------------
     Three scales so spacing, type and motion are chosen from a set instead of
     invented per component. New and edited rules snap onto these; older rules
     that predate them are migrated as they are touched, never wholesale. */

  /* Spacing — seven steps. */
  --s1: 0.25rem;
  --s2: 0.5rem;
  --s3: 0.75rem;
  --s4: 1rem;
  --s5: 1.5rem;
  --s6: 2rem;
  --s7: 3rem;

  /* Type. --fs-2xs is the uppercase micro-caps size (eyebrows, chips, table
     heads) where smallness is the point; nothing below it. Form controls must
     resolve to ≥16px, so they take an explicit 16px, never a token below 1rem. */
  --fs-2xs: 0.66rem;
  --fs-xs: 0.78rem;
  --fs-sm: 0.85rem;
  --fs-base: 1rem;
  --fs-lg: 1.12rem;
  --fs-xl: 1.3rem;
  --fs-2xl: 1.5rem;
  --fs-3xl: 2rem;

  /* Motion. Every duration is disabled by the prefers-reduced-motion rule. */
  --dur-fast: 150ms;
  --dur: 250ms;
  --dur-slow: 500ms;

  /* Form language */
  --radius: 3px;
  --radius-lg: 8px;
  --radius-pill: 999px;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --shadow-sm: 0 1px 2px rgba(63, 42, 24, 0.06);
  --shadow: 0 2px 4px rgba(63, 42, 24, 0.05), 0 14px 34px -16px rgba(63, 42, 24, 0.2);
  --shadow-lg: 0 2px 5px rgba(63, 42, 24, 0.06), 0 28px 60px -24px rgba(63, 42, 24, 0.3);

  --serif: Georgia, "Iowan Old Style", "Palatino Linotype", "Book Antiqua", "Times New Roman", serif;
  --sans: "Optima", "Avenir Next", "Segoe UI", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

h1, h2, h3, .serif { font-family: var(--serif); font-weight: 500; letter-spacing: 0.005em; color: var(--ink); }
h1 { font-size: var(--fs-3xl); line-height: 1.15; margin: 0 0 0.4rem; }
h2 { font-size: 1.45rem; line-height: 1.2; margin: var(--s6) 0 var(--s3); }
h3 { font-size: var(--fs-lg); margin: var(--s4) 0 var(--s2); }
a { color: var(--orange-deep); text-decoration: none; transition: color var(--dur-fast) var(--ease); }
/* <summary> is an interactive control everywhere it appears, not just inside
   the components that remembered to say so. */
summary { cursor: pointer; }
a:hover { color: var(--orange); text-decoration: underline; text-underline-offset: 2px; }
p { margin: 0.4rem 0; }
::selection { background: var(--orange-soft); color: var(--ink); }

/* ---------- Top bar ---------- */
.topbar {
  display: flex; align-items: center; gap: 1rem;
  padding: calc(0.75rem + env(safe-area-inset-top)) 1.4rem 0.75rem;
  background: rgba(255, 253, 250, 0.92);
  backdrop-filter: saturate(1.4) blur(8px);
  -webkit-backdrop-filter: saturate(1.4) blur(8px);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 20;
  flex-wrap: wrap;
}
.brand { display: flex; align-items: baseline; gap: 0.5rem; color: var(--ink); transition: opacity 0.18s var(--ease); }
.brand:hover { text-decoration: none; opacity: 0.78; }
.brand__mark { color: var(--orange); font-size: 1.3rem; line-height: 1; }
.brand__word { font-family: var(--serif); font-size: 1.4rem; letter-spacing: 0.14em; text-transform: uppercase; }
.nav { display: flex; gap: 1.25rem; margin-left: 0.75rem; flex-wrap: wrap; }
.nav a {
  color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.13em;
  font-size: 0.72rem; padding: 0.35rem 0; border-bottom: 2px solid transparent;
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease);
}
.nav a.is-active, .nav a:hover { color: var(--ink); border-bottom-color: var(--orange); text-decoration: none; }
.topbar__user { margin-left: auto; display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap; }
.topbar__name { font-size: 0.85rem; color: var(--ink-soft); }
.inline { display: inline; }

.role-chip {
  font-size: 0.64rem; letter-spacing: 0.11em; text-transform: uppercase;
  padding: 0.22rem 0.6rem; border: 1px solid var(--line-strong); border-radius: var(--radius-pill); color: var(--ink-soft);
  white-space: nowrap;
}
.role-chip--admin { border-color: var(--gold); color: var(--gold); }
.role-chip--manager { border-color: var(--orange); color: var(--orange-deep); }

.rolecheck-group { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 0.7rem 0.9rem; margin: 0; }
.rolecheck-group legend { font-size: 0.7rem; letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink-soft); padding: 0 0.3rem; }
.rolecheck { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; }
.rolecheck input { width: auto; margin: 0; }

.joincode {
  font-family: var(--serif); font-size: 2.3rem; letter-spacing: 0.36em; font-weight: 600;
  text-align: center; padding: 0.9rem 0 0.65rem 0.36em; color: var(--orange-deep);
  font-variant-numeric: tabular-nums;
  /* Engraved plate: framed double hairline (border + inset ring) on a soft
     sand-to-paper sheen, with a letterpress lift under the digits. */
  margin: 0.5rem 0 0.3rem;
  background: linear-gradient(180deg, var(--sand), var(--paper));
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: inset 0 0 0 3px var(--paper), inset 0 0 0 4px var(--line);
  text-shadow: 0 1px 0 var(--paper-bright);
}
/* The player's side of the same object: where the manager READS the code off a
   plate, the player TYPES it into one. Same serif, same tracking, same engraved
   double hairline — sized as a control rather than as display type. The digits
   sit at 1.5rem (24px), comfortably over the 16px floor below which iOS Safari
   zooms the page on focus, and text-indent replaces the trailing letter-space
   so six digits read optically centred (the .joincode plate does the same with
   padding-left). */
.joincode-input {
  font-family: var(--serif);
  font-size: var(--fs-2xl);
  font-weight: 600;
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--orange-deep);
  padding: var(--s3) var(--s2);
  background: linear-gradient(180deg, var(--sand), var(--paper));
  border: 1px solid var(--line-strong);
  box-shadow: inset 0 0 0 3px var(--paper), inset 0 0 0 4px var(--line);
}
/* --ink-soft, not --ink-faint: a placeholder is text and has to clear AA
   (5.5:1 vs 2.9:1). It still reads as a prompt, not a value — the typed code is
   orange and bold. */
.joincode-input::placeholder { color: var(--ink-soft); opacity: 1; font-weight: 400; }
/* Keep the engraved ring while focused — the base input:focus shadow would
   otherwise replace it with the plain halo. */
.joincode-input:focus {
  border-color: var(--orange);
  box-shadow: inset 0 0 0 3px var(--paper), inset 0 0 0 4px var(--orange-line), 0 0 0 3px var(--orange-soft);
}
.tag { display: inline-block; font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.09em; padding: 0.14rem 0.5rem; border-radius: var(--radius-pill); border: 1px solid var(--line-strong); vertical-align: middle; color: var(--ink-soft); }
.tag--guest { border-color: var(--orange); color: var(--orange-deep); }
.skill { display: inline-block; font-size: 0.68rem; text-transform: capitalize; padding: 0.14rem 0.55rem; border-radius: var(--radius-pill); border: 1px solid var(--line-strong); color: var(--ink-soft); }
.skill--beginner { color: #3f8f6b; border-color: #a9d6c1; }
.skill--intermediate { color: var(--orange-deep); border-color: var(--orange); }
.skill--advanced { color: #b04434; border-color: #e0a89f; }
.login-card--wide { max-width: 680px; }
.join-options .card { text-align: left; }

/* ---------- Page ---------- */
.page { max-width: 1080px; margin: 0 auto; padding: 1.9rem 1.3rem 3.5rem; }
.footer {
  border-top: 1px solid var(--line); color: var(--ink-faint);
  text-align: center; font-size: 0.68rem; letter-spacing: 0.18em;
  text-transform: uppercase; font-style: normal;
  padding: 2.5rem 1rem calc(1.7rem + env(safe-area-inset-bottom));
  /* Centered hairline-and-diamond motif beneath the top rule. */
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 96 8'%3E%3Cg%3E%3Cpath d='M0 4 H38 M58 4 H96' stroke='%23d9cdbc' stroke-width='1'/%3E%3Cpath d='M48 0.8 L51.2 4 L48 7.2 L44.8 4 Z' fill='%23b58a3c'/%3E%3C/g%3E%3C/svg%3E") no-repeat top 1.1rem center / 96px 8px;
}

.hero {
  padding: 2.6rem 0 1.7rem;
  border-bottom: 1px solid var(--line);
  margin-bottom: 1.9rem;
  /* Quiet maison backdrop: concentric court-line arcs anchored in the top-right
     corner (inline SVG, strokes at 0.06 opacity — never impairs text contrast)
     over a faintly warmer diagonal wash. */
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cg fill='none' stroke='%23ea5b0c' stroke-opacity='0.06'%3E%3Ccircle cx='400' cy='0' r='70'/%3E%3Ccircle cx='400' cy='0' r='130'/%3E%3Ccircle cx='400' cy='0' r='190'/%3E%3Ccircle cx='400' cy='0' r='250'/%3E%3Ccircle cx='400' cy='0' r='310'/%3E%3Ccircle cx='400' cy='0' r='370'/%3E%3Cpath d='M400 400 L120 0' stroke-opacity='0.05'/%3E%3C/g%3E%3C/svg%3E") right top / 380px 380px no-repeat,
    linear-gradient(135deg, var(--orange-veil), transparent 62%);
}
.hero h1 { font-size: 2.6rem; letter-spacing: -0.01em; }
.hero .lede { color: var(--ink-soft); max-width: 46rem; font-size: 1.06rem; line-height: 1.6; }
.hero__rule { width: 68px; height: 3px; background: linear-gradient(90deg, var(--orange), var(--orange-bright)); margin-top: 1.1rem; border-radius: 2px; }

.eyebrow {
  text-transform: uppercase; letter-spacing: 0.2em; font-size: 0.7rem; font-weight: 600;
  color: var(--orange-deep); margin-bottom: 0.5rem;
}

/* ---------- Cards & grids ---------- */
/* `minmax(0, 1fr)`, never a bare `1fr`. A grid track's automatic minimum is
   min-content, so ONE long <option> ("Badminton Manager (@badminton)") set the
   floor for its <select>, then for its .field, then for the track — and the
   whole PAGE scrolled sideways at 320px. The companion `min-width: 0` rules
   below release the same floor on the items themselves. Genuinely wide content
   (tables) still scrolls inside its own .table-wrap, which is the point. */
.grid { display: grid; gap: 1.1rem; grid-template-columns: minmax(0, 1fr); }
.grid > *, .stack > *,
label.field > input, label.field > select, label.field > textarea { min-width: 0; }
@media (min-width: 640px) {
  .grid--2 { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.card {
  background: linear-gradient(180deg, var(--paper-bright), var(--paper) 58%);
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: 1.2rem 1.3rem; box-shadow: var(--shadow-sm);
  transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.card h3 { margin-top: 0; }
/* A card that is its own disclosure: the heading is the control, so it needs a
   real tap target (44px on touch-sized viewports, per the rest of the app).
   `display` is left alone so the UA keeps drawing the disclosure marker. */
details.card > summary { min-height: 40px; padding: var(--s1) 0; -webkit-tap-highlight-color: transparent; }
@media (max-width: 919px) { details.card > summary { min-height: 44px; } }
/* Accent bar rendered as a gradient painted into the (transparent) top border. */
.card--accent {
  border-top: 3px solid transparent;
  background:
    linear-gradient(90deg, var(--orange), var(--orange-bright)) top / 100% 3px no-repeat border-box,
    linear-gradient(180deg, var(--paper-bright), var(--paper) 58%) border-box;
}
.card__meta { color: var(--ink-soft); font-size: 0.84rem; display: flex; gap: 0.9rem; flex-wrap: wrap; }
.card a.card-link { color: inherit; }
/* Lift only interactive cards (those wrapping a link). */
.card:has(a.card-link):hover { box-shadow: var(--shadow); transform: translateY(-2px); border-color: var(--line-strong); }

.stat { text-align: center; padding: 1.3rem 0.6rem; }
.stat__value { font-family: var(--serif); font-size: 2.3rem; line-height: 1; color: var(--orange-deep); font-variant-numeric: tabular-nums; }
.stat__label { text-transform: uppercase; letter-spacing: 0.13em; font-size: 0.66rem; color: var(--ink-soft); margin-top: 0.4rem; }

/* ---------- Badges ---------- */
.badge {
  display: inline-block; font-size: 0.64rem; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 600;
  padding: 0.24rem 0.65rem; border-radius: var(--radius-pill); border: 1px solid;
}
.badge--draft { color: var(--ink-soft); border-color: var(--line-strong); background: var(--sand); }
.badge--active { color: #fff; border-color: var(--orange); background: var(--orange); }
.badge--completed { color: var(--green); border-color: #bcd7c7; background: #eaf3ee; }

.live-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--orange); margin-right: 0.4rem; box-shadow: 0 0 0 0 rgba(234, 91, 12, 0.5); animation: pulse 1.8s ease-out infinite; vertical-align: 1px; }
@keyframes pulse {
  0%   { opacity: 1; box-shadow: 0 0 0 0 rgba(234, 91, 12, 0.45); }
  70%  { opacity: 0.55; box-shadow: 0 0 0 6px rgba(234, 91, 12, 0); }
  100% { opacity: 1; box-shadow: 0 0 0 0 rgba(234, 91, 12, 0); }
}

/* ---------- Buttons & forms ---------- */
.btn {
  display: inline-block; border: 1px solid var(--ink); background: var(--ink); color: var(--cream);
  font-family: var(--sans); font-size: var(--fs-xs); letter-spacing: 0.12em; text-transform: uppercase; font-weight: 500;
  padding: 0.68rem 1.4rem; border-radius: var(--radius); cursor: pointer;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.btn:hover { background: var(--orange); border-color: var(--orange); color: #fff; text-decoration: none; box-shadow: var(--shadow-btn); transform: translateY(-1px); }
.btn:active { transform: translateY(0); box-shadow: none; }
.btn:focus-visible { outline: 2px solid var(--orange-deep); outline-offset: 2px; box-shadow: 0 0 0 4px var(--orange-soft); }
/* Primary carries a quiet top-light sheen. */
.btn--primary { background: linear-gradient(180deg, var(--orange-bright), var(--orange)); border-color: var(--orange); }
.btn--primary:hover { background: linear-gradient(180deg, var(--orange), var(--orange-deep)); border-color: var(--orange-deep); }
.btn--outline { background: transparent; color: var(--ink); }
.btn--outline:hover { color: #fff; }
.btn--ghost { background: transparent; border-color: transparent; color: var(--ink-soft); }
.btn--ghost:hover { background: transparent; border-color: transparent; color: var(--orange-deep); box-shadow: none; transform: none; }
.btn--sm { padding: 0.42rem 0.85rem; font-size: var(--fs-2xs); }
.btn--danger { border-color: var(--red); background: var(--red); }
.btn--danger:hover { background: #8f2727; border-color: #8f2727; }
/* Disabled is its own state, not a dimmed enabled button. `opacity: 0.45` alone
   left the label as low as 1.8:1, and because `.btn:hover` above never resets
   `background` — same specificity, so it still wins — hovering a disabled button
   painted it primary orange and it read as clickable. Explicit colours instead,
   with hover and active neutralised. #7d7268 on --sand is 4.33:1. */
.btn:disabled,
.btn[disabled] {
  opacity: 1;
  background: var(--sand);
  border-color: var(--line-strong);
  color: #7d7268;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}
.btn:disabled:hover, .btn[disabled]:hover,
.btn:disabled:active, .btn[disabled]:active {
  background: var(--sand);
  border-color: var(--line-strong);
  color: #7d7268;
  box-shadow: none;
  transform: none;
}

/* A stack is a stack whether it is a <form> or a <div> — the element-qualified
   selector meant `<div class="stack">` silently got no grid and no gap at all
   (the join page's "Create an account" card sat beside an identical-looking
   form that did). */
.stack { display: grid; gap: var(--s4); }
.stack--tight { gap: var(--s2); }
label.field { display: grid; gap: 0.35rem; font-size: 0.74rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-soft); }
/* One shape for every control that takes the free width of a toolbar row: one
   floor (14rem), one cap (26rem), so search boxes, filters and message inputs
   are no longer three hand-picked widths. The floor can never exceed the row,
   so a 320px screen wraps the row instead of scrolling the page.
   .searchfield is the same shape, named for the three "Search" boxes. */
.field--grow, .searchfield { flex: 1 1 14rem; min-width: min(14rem, 100%); max-width: 26rem; }
input, select, textarea {
  font: inherit;
  /* `font: inherit` alone pulled label.field's 0.74rem micro-caps size into
     every control it wraps: above 600px each text box, select and textarea in
     the app computed to 11.84px — unreadable, and small enough that iOS Safari
     zooms the page on focus. Five components (.setrow, .scoreentry, .courtname,
     .resume-link, and a blanket ≤600px rule) had each patched themselves back
     to 16px one at a time; this sets the control size once, for all of them.
     A literal 16px, not a token: the floor is an absolute device threshold, so
     it must not scale with a root font-size the user may have shrunk. */
  font-size: 16px;
  color: var(--ink); background: var(--paper);
  border: 1px solid var(--line-strong); border-radius: var(--radius); padding: 0.65rem 0.75rem;
  width: 100%;
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
input:hover, select:hover, textarea:hover { border-color: var(--ink-faint); }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--orange); box-shadow: 0 0 0 3px var(--orange-soft); }
input[type="checkbox"] { width: auto; accent-color: var(--orange); transform: scale(1.15); }
/* `label.field--inline` and not just `.field--inline`: it is only ever used as
   `class="field field--inline"`, and `label.field` (0,1,1) outranked a lone
   class (0,1,0) — so `display: flex` never applied. Every checkbox row was a
   one-column grid instead: the box stretched to the full column (and, scaled
   1.15, pushed the manager console 4px past the viewport at 768px) with its
   label stranded on a second line. Same specificity as label.field, declared
   after it, so it now wins. */
label.field--inline, .field--inline { display: flex; align-items: center; gap: var(--s2); text-transform: none; letter-spacing: 0; font-size: 0.92rem; color: var(--ink); }
.hint { font-size: var(--fs-xs); color: var(--ink-soft); text-transform: none; letter-spacing: 0; max-width: 68ch; }
.form-row { display: grid; gap: var(--s4); grid-template-columns: 1fr; }
@media (min-width: 560px) { .form-row { grid-template-columns: 1fr 1fr; } }

.alert { padding: var(--s3) var(--s4); border-radius: var(--radius); font-size: 0.9rem; margin: var(--s4) 0; border-left: 3px solid; }
.alert--error { background: #f9e8e8; border-color: var(--red); color: var(--red); }
.alert--ok { background: #eaf3ee; border-color: var(--green); color: var(--green); }
/* Tucked under the control it explains rather than floating in its own band. */
.alert--tight { margin-top: var(--s2); }
/* Champion banner: the name reads first, the results link sits at the end on a
   wide screen and wraps under it on a phone rather than squeezing the name. */
.winner { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); justify-content: space-between; }
.winner .btn { flex: none; }

/* ---------- Tables ---------- */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; border-radius: var(--radius-lg); }
table.tbl { width: 100%; border-collapse: collapse; background: var(--paper); border: 1px solid var(--line); font-size: 0.92rem; }
.tbl th {
  text-align: left; font-weight: 600; text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.66rem;
  color: var(--ink-soft); padding: 0.65rem 0.85rem; border-bottom: 2px solid var(--line-strong);
  background: linear-gradient(180deg, var(--sand), var(--sand-deep));
  white-space: nowrap;
}
.tbl td { padding: 0.65rem 0.85rem; border-bottom: 1px solid var(--line); vertical-align: middle; }
.tbl tbody tr { transition: background 0.15s var(--ease); }
.tbl tbody tr:hover { background: var(--row-hover); }
.tbl tr:last-child td { border-bottom: none; }
.tbl .num { text-align: right; font-variant-numeric: tabular-nums; }
.tbl .winner { font-weight: 600; }
/* Batch-select column: as narrow as the box, and the ticked row takes the warm
   tint (not --row-hover, which hovering already uses) so a multi-row selection
   is readable at a glance before it is deleted. */
.tbl th.tbl__pick, .tbl td.tbl__pick { width: 1%; padding-right: 0; text-align: center; }
.tbl tbody tr.is-picked, .tbl tbody tr.is-picked:hover { background: var(--orange-soft); }
.is-champion .name::after { content: " 🏆"; }
.legend { margin-top: 0.5rem; }
/* Standings read tighter: a natural-width table keeps the figures beside the
   names instead of stranding them across a full-width row. */
.tbl--standings { width: auto; min-width: min(100%, 32rem); }

/* ---------- Bracket / schedule ---------- */
.rounds { display: flex; gap: 1.4rem; overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 0.6rem; margin-top: 1.3rem; }
.round-col { min-width: 230px; flex: 0 0 auto; }
.round-col h4 {
  font-family: var(--sans); text-transform: uppercase; letter-spacing: 0.14em;
  font-size: 0.68rem; color: var(--ink-soft); margin: 0 0 0.8rem;
  padding-bottom: 0.5rem; border-bottom: 1px solid var(--line);
}
.matchcard {
  background: var(--paper); border: 1px solid var(--line); border-left: 3px solid var(--line-strong);
  border-radius: var(--radius); padding: 0.6rem 0.75rem; margin-bottom: 0.7rem; font-size: 0.88rem;
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease), transform 0.2s var(--ease);
}
.matchcard:hover { box-shadow: var(--shadow-sm); transform: translateY(-1px); }
.matchcard--done { border-left-color: var(--green); }
.matchcard--live { border-left-color: var(--orange); }
.matchcard__row { display: flex; justify-content: space-between; gap: 0.6rem; padding: 0.14rem 0; }
.matchcard__row .score { font-variant-numeric: tabular-nums; color: var(--ink-soft); white-space: nowrap; }
.matchcard__row.winner { font-weight: 650; color: var(--ink); }
.matchcard__meta { font-size: 0.72rem; color: var(--ink-soft); margin-top: 0.35rem; display: flex; justify-content: space-between; gap: 0.5rem; }
.tbd { color: var(--ink-faint); font-style: italic; }

/* ---------- Connected bracket graph ---------- */
.bracket-graph {
  display: flex; gap: 2rem; align-items: stretch;
  overflow-x: auto; -webkit-overflow-scrolling: touch; padding: 0.2rem 0 0.8rem;
}
.bg-col { display: flex; flex-direction: column; min-width: 230px; flex: 0 0 auto; }
.bg-col > h4 {
  font-family: var(--sans); text-transform: uppercase; letter-spacing: 0.13em;
  font-size: 0.66rem; color: var(--ink-soft); margin: 0 0 0.6rem;
}
.bg-cells { display: flex; flex-direction: column; justify-content: space-around; flex: 1 1 auto; gap: 0.7rem; }
.bg-cell { position: relative; display: flex; align-items: center; }
.bg-cell .matchcard { margin-bottom: 0; width: 100%; }
/* Hairline connectors bridging the gap between rounds. */
.bg-col:not(:last-child) .bg-cell::after {
  content: ''; position: absolute; top: 50%; right: -1rem;
  width: 1rem; height: 1px; background: var(--line-strong);
}
.bg-col:not(:first-child) .bg-cell::before {
  content: ''; position: absolute; top: 50%; left: -1rem;
  width: 1rem; height: 1px; background: var(--line-strong);
}

/* ---------- Drag-to-reseed list ---------- */
.reseed-list { list-style: none; margin: 0.4rem 0 0.8rem; padding: 0; }
.reseed-item {
  /* Wraps. A pair's entry name ("Ada Guest & Zoe Guest") is a PLAYER NAME and
     is never truncated, so when it no longer fits beside the ↑ ↓ ✕ controls the
     controls drop to a second line instead of the row pushing the page sideways
     — it overflowed a 320px viewport by 7px before this. */
  display: flex; align-items: center; gap: 0.55rem; flex-wrap: wrap;
  padding: 0.55rem 0.65rem; margin-bottom: 0.45rem;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius);
  cursor: grab; transition: box-shadow 0.18s var(--ease), border-color 0.18s var(--ease);
}
.reseed-item:hover { border-color: var(--line-strong); box-shadow: var(--shadow-sm); }
.reseed-item.is-dragging { opacity: 0.5; cursor: grabbing; box-shadow: var(--shadow); }
.reseed-handle { color: var(--ink-faint); font-size: 0.85rem; cursor: grab; }
.reseed-seed {
  min-width: 1.4rem; text-align: center; font-variant-numeric: tabular-nums;
  color: var(--orange-deep); font-weight: 650;
}
.reseed-name { font-weight: 600; flex: 1 1 7rem; min-width: 0; overflow-wrap: anywhere; }
.reseed-players { margin-left: 0.1rem; min-width: 0; overflow-wrap: anywhere; }
.reseed-actions { margin-left: auto; flex: 0 0 auto; display: flex; align-items: center; gap: 0.15rem; }

/* ---------- Factual data table ---------- */
.data-table td.mono, .tbl .mono {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.data-table th, .data-table td { padding: 0.45rem 0.65rem; font-size: 0.85rem; }

.progressbar { height: 6px; background: var(--orange-soft); border-radius: var(--radius-pill); overflow: hidden; margin: 0.6rem 0 0.2rem; }
.progressbar__fill { height: 100%; background: linear-gradient(90deg, var(--orange), var(--orange-bright)); border-radius: var(--radius-pill); transition: width 0.6s var(--ease); }

/* ---------- Message / prize panel ---------- */
.proclamation {
  background: var(--paper); border: 1px solid var(--gold); border-radius: var(--radius);
  padding: 1.5rem 1.6rem; margin: 1.3rem 0; position: relative;
}
.proclamation::before {
  content: ""; position: absolute; inset: 5px; border: 1px solid var(--line); pointer-events: none; border-radius: 1px;
}
.proclamation .serif { font-size: 1.18rem; line-height: 1.65; font-style: italic; }
.prize { color: var(--gold); font-family: var(--serif); font-size: 1.05rem; margin-top: 0.6rem; font-style: normal; }

.advice { border-left: 3px solid var(--orange); background: var(--paper); border-radius: 0 var(--radius) var(--radius) 0; padding: 1.1rem 1.3rem; margin: 1.1rem 0; }
.advice ul { margin: 0.4rem 0 0; padding-left: 1.1rem; }
.advice li { margin: 0.38rem 0; font-size: 0.92rem; color: var(--ink-soft); }

.estimate {
  display: flex; gap: 1.5rem; align-items: baseline; flex-wrap: wrap;
  background: var(--orange-soft); border: 1px solid var(--orange-line); border-radius: var(--radius); padding: 1rem 1.2rem;
}
.estimate strong { font-family: var(--serif); font-size: 1.5rem; color: var(--orange-deep); font-variant-numeric: tabular-nums; }
.estimate span { font-size: 0.84rem; color: var(--ink-soft); }

/* ---------- Login / join ---------- */
.login-wrap { max-width: 420px; margin: 6vh auto 0; }
/* Wide cards (join page) size their wrapper instead of being clamped to 420px —
   the two join options sit side by side on any screen ≥640px, stacked below. */
.login-wrap:has(.login-card--wide) { max-width: 680px; }
.login-card {
  background:
    linear-gradient(90deg, var(--orange), var(--orange-bright)) top / 100% 3px no-repeat border-box,
    linear-gradient(180deg, var(--paper-bright), var(--paper) 58%) border-box;
  border: 1px solid var(--line); border-top: 3px solid transparent;
  padding: 2.2rem; box-shadow: var(--shadow); border-radius: var(--radius-lg);
}
.login-card h1 { text-align: center; letter-spacing: 0.06em; }
.demo-creds { font-size: 0.78rem; color: var(--ink-soft); margin-top: 1.3rem; border-top: 1px dashed var(--line-strong); padding-top: 0.9rem; }
.demo-creds code { background: var(--sand); padding: 0.1rem 0.35rem; border-radius: 2px; font-family: var(--mono); }

/* ---------- Misc ---------- */
/* One toolbar and three modifiers. Every bar of controls in the app is built
   from these — no per-site margins or alignment. Declared immediately after
   .toolbar so the modifiers win on order at equal specificity. */
.toolbar { display: flex; gap: var(--s3); align-items: center; flex-wrap: wrap; margin: var(--s4) 0; }
.toolbar .spacer { flex: 1; }
/* Belongs to the thing directly above it (a plate, a lede, a heading) rather
   than to the page rhythm. Collapses the old 0.2 / 0.55 / 0.6rem one-offs. */
.toolbar--tight { margin: var(--s2) 0 0; }
.toolbar--center { justify-content: center; }
/* Carries labelled fields: their inputs sit on the same line as the action
   button beside them instead of the button floating up to the label. */
.toolbar--fields { align-items: flex-end; }
.muted { color: var(--ink-soft); }
.small { font-size: var(--fs-sm); }
.center { text-align: center; }
.mt { margin-top: var(--s5); }
/* Read by assistive tech, invisible on screen — table captions, the accessible
   half of an icon-only control. Not `display:none`, which removes it entirely. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  border: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.section-rule { border: none; border-top: 1px solid var(--line); margin: var(--s6) 0; }

/* ============================================================================
   Typography & spacing polish
   ----------------------------------------------------------------------------
   Conservative measure/rhythm pass for the dense management + live pages.
   Everything here is a cap (max-width) or a small margin nudge — additive, so
   text already constrained inside narrow cards/columns is unaffected and no
   fixed widths are introduced. Extends the care already given to .hint (68ch)
   and .lede to the remaining page-level running text.
   ========================================================================== */

/* Comfortable measure (~70ch) for page-level running paragraphs so they don't
   stretch edge-to-edge across the 1080px column. max-width is a cap: the same
   classes inside narrow cards stay put (already narrower than 70ch). Centered
   one-liners are excluded so they keep centering across their own box (e.g. the
   join-page capacity line) — they opt out with .center. The exclusion used to
   be `:not([style])`, which the CSP had already made meaningless: inline styles
   are dropped, so every such paragraph matched anyway. */
p.muted.small:not(.center),
p.small.muted:not(.center) { max-width: 70ch; }
.advice li { max-width: 70ch; }

/* A little air under a page <h1> so the intro note beneath it — and the first
   stacked section — doesn't read as cramped. Hero ledes keep their own rhythm. */
h1 + p:not(.lede) { margin-top: 0.6rem; margin-bottom: 0.9rem; }

/* Overflow safety: grid/flex items default to min-width:auto and refuse to
   shrink below their content, which can push the page wider than the viewport
   at tight widths. Let the two-column form fields (and their inputs) and the
   order-of-play team cell shrink instead. No visual change at comfortable
   widths; .scoreentry/.setrow inputs already carry min-width:0. */
.form-row > .field,
.form-row input,
.form-row select,
.form-row textarea { min-width: 0; }
.op-teams { min-width: 0; }

/* ---------- Motion: scroll progress + reveal (effects.js contract) ---------- */
/* A thin fixed "moving line" at the very top of the viewport, above the sticky
   topbar (z-index 20). effects.js drives its width via transform: scaleX().
   Only created when motion is allowed, so nothing to neutralize here. */
#scroll-progress {
  position: fixed; top: 0; left: 0; right: 0; height: 3px;
  transform: scaleX(0); transform-origin: left center;
  background: linear-gradient(90deg, var(--orange), var(--orange-bright));
  z-index: 60; pointer-events: none; will-change: transform;
}
/* Gentle fade-up on scroll — gated behind data-anim so that without JS (no flag)
   .reveal elements are simply visible. Under reduced-motion, effects.js never
   sets data-anim, so this never hides content. */
html[data-anim] .reveal {
  opacity: 0; transform: translateY(10px);
  transition: opacity var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease);
}
html[data-anim] .reveal.in-view { opacity: 1; transform: none; }

/* ---------- Account dropdown (usermenu) ---------- */
.usermenu { position: relative; margin-left: auto; }
.usermenu__trigger {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.3rem 0.55rem; border-radius: var(--radius-pill);
  transition: background 0.18s var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.usermenu__trigger::-webkit-details-marker { display: none; }
.usermenu__trigger:hover { background: var(--sand); }
.usermenu__trigger:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.usermenu__caret { color: var(--ink-faint); font-size: 0.7rem; line-height: 1; transition: transform 0.2s var(--ease); }
.usermenu__caret::after { content: "\25BE"; }
.usermenu[open] .usermenu__caret { transform: rotate(180deg); }

.usermenu__panel {
  position: absolute; right: 0; top: 100%; margin-top: 0.45rem;
  min-width: 180px; max-width: min(240px, calc(100vw - 1.5rem));
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: var(--shadow); padding: 0.35rem; z-index: 30;
  display: flex; flex-direction: column;
}
.usermenu__panel a, .usermenu__item {
  display: block; width: 100%; text-align: left;
  padding: 0.5rem 0.7rem; border-radius: var(--radius);
  font-size: 0.85rem; color: var(--ink); text-decoration: none; white-space: nowrap;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.usermenu__panel a:hover, .usermenu__item:hover { background: var(--sand); color: var(--ink); text-decoration: none; }
.usermenu__panel a:focus-visible, .usermenu__item:focus-visible { outline: 2px solid var(--orange); outline-offset: -2px; }
/* Reset the sign-out button so it reads like the link rows. */
button.usermenu__item {
  background: transparent; border: none; font-family: inherit; letter-spacing: 0; text-transform: none;
  cursor: pointer;
}
button.usermenu__item:hover { color: var(--red); background: var(--sand); }

/* ---------- Role / area tags ---------- */
.role-tag {
  display: inline-block; vertical-align: middle;
  font-size: 0.6rem; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 600;
  padding: 0.16rem 0.55rem; border-radius: var(--radius-pill);
  border: 1px solid var(--line-strong); background: var(--sand); color: var(--ink-soft);
}
.role-tag--manager { border-color: var(--orange); color: var(--orange-deep); background: var(--orange-soft); }
.role-tag--admin { border-color: var(--gold); color: var(--gold); }
/* Highlight the current user among tags. */
.tag--you { border-color: var(--orange); background: var(--orange); color: #fff; }

/* Shared muted "vs" separator (order-of-play + quickscore names). */
.vs { color: var(--ink-faint); font-weight: 400; font-style: italic; }

/* ---------- Order-of-play board ---------- */
/* .op-card is a .card--accent and mostly inherits. */
.op-list { list-style: none; margin: 0.6rem 0 0; padding: 0; display: flex; flex-direction: column; }
.op-item {
  display: flex; align-items: center; gap: 0.85rem; flex-wrap: wrap;
  padding: 0.7rem 0; border-top: 1px solid var(--line);
}
.op-item:first-child { border-top: none; }
.op-court {
  flex: 0 0 auto; min-width: 3.4rem; text-align: center;
  font-size: 0.66rem; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 600;
  font-variant-numeric: tabular-nums;
  padding: 0.3rem 0.5rem; border-radius: var(--radius);
  color: var(--orange-deep); background: var(--orange-soft); border: 1px solid var(--orange-line);
}
.op-teams { flex: 1 1 12rem; display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; font-weight: 600; }
.op-round { flex: 0 0 auto; margin-left: auto; text-align: right; font-size: 0.75rem; color: var(--ink-soft); }

/* ---------- Quick self-score form ---------- */
.quickscore { display: grid; gap: 0.85rem; }
.quickscore__teams {
  display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; flex-wrap: wrap;
  font-weight: 650; font-size: 1.02rem;
}
.setrow { display: grid; grid-template-columns: auto 1fr auto 1fr; align-items: center; gap: 0.5rem 0.6rem; }
.setrow__n {
  min-width: 3.2rem; font-size: 0.62rem; letter-spacing: 0.11em; text-transform: uppercase;
  font-weight: 600; color: var(--ink-soft);
}
.setrow__vs { text-align: center; color: var(--ink-faint); font-size: 0.95rem; }
.setrow input {
  min-width: 0; min-height: 44px; text-align: center; font-size: 16px;
  font-variant-numeric: tabular-nums; padding: 0.4rem 0.5rem;
}

/* ---------- Competitor's own match ---------- */
.yourmatch { border-left: 3px solid var(--orange); background: var(--orange-soft); }

/* ---------- Score entry grid (manager panel, self-score, live) ----------
   The two entrants are column headers above their own inputs, each row is a
   labelled set, and the winner's name reads green with a check. */
.scoreentry { display: grid; gap: 0.4rem; margin: 0.7rem 0 0.2rem; }
.scoreentry__row {
  display: grid; grid-template-columns: 3rem 1fr 1fr; gap: 0.5rem; align-items: center;
}
.scoreentry__head { padding-bottom: 0.15rem; border-bottom: 1px solid var(--line); margin-bottom: 0.15rem; }
.scoreentry__lbl {
  font-size: 0.62rem; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-soft); font-weight: 600;
}
.scoreentry__name {
  display: flex; align-items: center; justify-content: center; gap: 0.3rem;
  text-align: center; font-weight: 650; font-size: 0.9rem; line-height: 1.2;
  min-width: 0; overflow-wrap: anywhere;
}
.scoreentry__name.winner { color: var(--green); }
/* "you" is three letters; it must never break across two lines inside a name
   column. The NAME still wraps freely — only the chip is held together. */
.scoreentry__name .tag--you { white-space: nowrap; }
.scoreentry__won {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.05rem; height: 1.05rem; border-radius: 50%;
  background: var(--green); color: #fff; font-size: 0.66rem; flex: 0 0 auto;
}
.scoreentry input {
  min-height: 42px; text-align: center; font-size: 16px;
  font-variant-numeric: tabular-nums; padding: 0.4rem 0.5rem; min-width: 0;
}

/* Manager result card wrapper. */
.scorecard { padding: 0.9rem 1rem; }
.scorecard__top { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.scorecard__round { font-size: 0.7rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-soft); font-weight: 600; }
.scorecard__summary { font-size: 0.85rem; margin: 0.5rem 0 0.7rem; }
.scorecard .btn { width: 100%; }
@media (max-width: 480px) {
  .scoreentry__row { grid-template-columns: 2.4rem 1fr 1fr; gap: 0.35rem; }
}

/* ---------- Resume / copyable link field ---------- */
.resume-link {
  font-family: var(--mono); font-size: 16px; color: var(--ink);
  background: var(--sand); border: 1px solid var(--line-strong); border-radius: var(--radius);
  padding: 0.55rem 0.7rem; width: 100%; cursor: pointer;
  overflow-x: auto; white-space: nowrap;
}
.resume-link:focus { background: var(--paper); }

/* ---------- Alert: caution (amber/orange) ---------- */
.alert--warn { background: var(--orange-soft); border-color: var(--orange); color: var(--orange-deep); }

/* ---------- Definition list (account info) ---------- */
.deflist { display: grid; grid-template-columns: auto 1fr; gap: 0.45rem 1.1rem; align-items: baseline; margin: 0.6rem 0; }
.deflist dt { font-size: 0.66rem; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 600; color: var(--ink-soft); }
.deflist dd { margin: 0; color: var(--ink); }

/* ---------- Result pills (match history) ---------- */
.result {
  display: inline-block; font-size: 0.62rem; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 600;
  padding: 0.16rem 0.55rem; border-radius: var(--radius-pill); border: 1px solid var(--line-strong);
  color: var(--ink-soft); font-variant-numeric: tabular-nums;
}
.result--win { color: var(--green); border-color: #bcd7c7; background: #eaf3ee; }
.result--loss { color: var(--red); border-color: #e0a89f; background: #f9e8e8; }

/* ---------- 4-up stat grid ---------- */
@media (min-width: 560px) { .grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 900px) { .grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* ---------- Manual pairing row ---------- */
.team-pair { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; }
.team-pair select { width: auto; min-width: 8rem; max-width: 15rem; flex: 1 1 8rem; }
.team-pair .btn { flex: 0 0 auto; }

/* ---------- Small-screen hardening (new components) ---------- */
@media (max-width: 600px) {
  .usermenu__panel { min-width: 168px; max-width: calc(100vw - 1.5rem); }
  .team-pair select { flex: 1 1 100%; max-width: none; }
}

@media (max-width: 480px) {
  .hero { padding: 1.9rem 0 1.3rem; }
  .hero h1 { font-size: 1.95rem; }
  h1 { font-size: 1.7rem; }
  .topbar { gap: 0.6rem; padding-left: 1rem; padding-right: 1rem; }
  .brand__word { font-size: 1.15rem; }
  .page { padding: 1.2rem 1rem 2.6rem; }
  .btn { padding: 0.62rem 1.05rem; }
  .joincode { font-size: 1.9rem; letter-spacing: 0.28em; padding-left: 0.28em; max-width: 100%; }
  .usermenu__panel { min-width: 160px; }
  .quickscore__teams { font-size: 0.95rem; }
  .op-item { gap: 0.6rem; }
  /* Reclaim horizontal room inside the framed surfaces. Deliberately placed in
     this early block so the padding written by later variants (.sidepanel,
     .emptystate, .op-edit, .lockbanner…) still wins — only the generic cards
     and the two oversized ones below are pulled in. */
  .card { padding: 1rem 1.05rem; }
  .login-card { padding: 1.5rem 1.2rem; }
  .proclamation { padding: 1.25rem 1.2rem; }
  .hero .lede { font-size: 1rem; }
}

/* Respect reduced-motion preferences. */
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; scroll-behavior: auto !important; }
}

/* ============================================================================
   Bracket connectors — "cyber" winner-feed arrows (bracket.js)
   ----------------------------------------------------------------------------
   bracket.js draws an absolutely-positioned inline-SVG overlay per .bracket-graph
   with thin, softly-glowing orange connector paths + arrowheads running from each
   match to the one it feeds. This section makes room for that overlay, lifts the
   cards above it, retires the old CSS hairline stubs once JS has taken over, and
   owns the marching-dash "flow" animation (disabled under reduced motion).
   No-JS keeps the stubs; ≤600px stacks the bracket vertically and drops the
   (now meaningless) arrows — bracket.js clears the overlay at that width.
   All colours derive from existing tokens.
   ========================================================================== */

.bracket-graph { position: relative; }

/* SVG overlay: spans the full scrollable content, sits beneath the cards, and
   never intercepts pointer events. bracket.js sizes it to scrollWidth/Height. */
.bracket-svg {
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none; overflow: visible;
}
/* Cards (and their cells) ride above the connector layer so lines tuck behind. */
.bracket-graph .bg-cell { position: relative; z-index: 1; }
.bracket-graph .matchcard { position: relative; z-index: 1; }

/* Once bracket.js draws real connectors it tags the graph `js-bracket`; retire
   the pseudo-element hairline stubs so they don't double up. Without JS the class
   is absent and the stubs remain, keeping a legible bracket. */
.bracket-graph.js-bracket .bg-col:not(:last-child) .bg-cell::after,
.bracket-graph.js-bracket .bg-col:not(:first-child) .bg-cell::before {
  display: none;
}

/* Flowing "current" along the winner lines — restrained marching dashes, not
   neon. bracket.js adds .bg-flow to winner paths (skipped under reduced motion);
   dash length/gap + speed are tuned to feel like a slow, elegant signal. */
.bracket-svg .bg-flow {
  stroke-dasharray: 5 7;
  animation: bracket-flow 1.15s linear infinite;
}
@keyframes bracket-flow {
  to { stroke-dashoffset: -24; }
}

/* A faint "cyber" corner tick on live bracket matches — an on-brand orange
   angle at the top-right, understated (not a glow, not a neon frame). */
.bracket-graph .matchcard--live::after {
  content: ""; position: absolute; top: -1px; right: -1px;
  width: 9px; height: 9px;
  border-top: 2px solid var(--orange);
  border-right: 2px solid var(--orange);
  border-top-right-radius: 3px;
  opacity: 0.6; pointer-events: none;
}

/* Mobile stack: rounds become full-width sections (label above its cards)
   instead of a horizontal scroll. bracket.js skips/clears arrows at this width. */
@media (max-width: 600px) {
  .bracket-graph {
    flex-direction: column; gap: 0;
    overflow-x: visible; padding-bottom: 0;
  }
  .bracket-graph .bg-col {
    min-width: 0; width: 100%;
    padding: 1rem 0; border-top: 1px solid var(--line);
  }
  .bracket-graph .bg-col:first-child { border-top: none; padding-top: 0.2rem; }
  .bracket-graph .bg-cells { justify-content: flex-start; }
  /* Sideways hairline stubs make no sense stacked — hide them (JS or not). */
  .bracket-graph .bg-cell::before,
  .bracket-graph .bg-cell::after { display: none; }
  /* Keep the round-robin schedule comfortable on a 375px viewport. */
  .rounds { gap: 0.8rem; }
  .round-col { min-width: 200px; }
}

/* Reduced motion: freeze the flow into clean, solid winner lines. */
@media (prefers-reduced-motion: reduce) {
  .bracket-svg .bg-flow { animation: none; stroke-dasharray: none; }
}

/* ==========================================================================
   Notifications: bell + badge, list, and mobile-friendly toasts
   ========================================================================== */
.topbar__end { margin-left: auto; display: flex; align-items: center; gap: 0.55rem; }
.topbar__end .usermenu { margin-left: 0; }

.bell {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  min-width: 40px; min-height: 40px; color: var(--ink-soft); font-size: 1.15rem;
  border-radius: var(--radius-pill); text-decoration: none; transition: background 0.18s var(--ease), color 0.18s var(--ease);
}
.bell:hover { color: var(--orange-deep); background: var(--sand); text-decoration: none; }
.bell:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.bell__badge {
  position: absolute; top: 2px; right: 2px;
  min-width: 1.05rem; height: 1.05rem; padding: 0 0.24rem;
  background: var(--orange); color: #fff; border: 1px solid var(--paper);
  font-size: 0.6rem; font-weight: 700; font-variant-numeric: tabular-nums;
  border-radius: var(--radius-pill); display: inline-flex; align-items: center; justify-content: center;
}

.notif-list { display: flex; flex-direction: column; gap: 0.55rem; margin-top: 1.1rem; }
.notif {
  display: flex; gap: 0.85rem; align-items: flex-start; padding: 0.8rem 1rem;
  background: var(--paper); border: 1px solid var(--line); border-left: 3px solid var(--line-strong);
  border-radius: var(--radius); color: var(--ink); text-decoration: none;
  transition: box-shadow 0.18s var(--ease), border-color 0.18s var(--ease);
}
a.notif:hover { border-color: var(--line-strong); box-shadow: var(--shadow-sm); text-decoration: none; }
.notif--unread { border-left-color: var(--orange); background: var(--orange-soft); }
.notif__icon { font-size: 1.05rem; flex: 0 0 auto; width: 1.7rem; text-align: center; line-height: 1.4; }
.notif__body { display: flex; flex-direction: column; gap: 0.12rem; min-width: 0; }
.notif__label { font-size: 0.62rem; letter-spacing: 0.11em; text-transform: uppercase; color: var(--ink-soft); font-weight: 600; }
.notif__msg { font-size: 0.92rem; overflow-wrap: anywhere; }
/* Tabular figures so a column of "12 min ago" / "3 h ago" does not jitter. */
.notif__time { font-size: 0.72rem; font-variant-numeric: tabular-nums; }

#toast-root {
  position: fixed; left: 50%; bottom: calc(1rem + env(safe-area-inset-bottom));
  transform: translateX(-50%); z-index: 80;
  display: flex; flex-direction: column; gap: 0.5rem; align-items: center;
  width: min(94vw, 30rem); pointer-events: none;
}
.toast {
  width: 100%; background: var(--ink); color: var(--cream);
  padding: 0.75rem 1.1rem; border-radius: var(--radius); border-left: 3px solid var(--orange);
  box-shadow: var(--shadow-lg); font-size: 0.9rem; overflow-wrap: anywhere;
  opacity: 0; transform: translateY(12px); transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.toast--in { opacity: 1; transform: none; }

/* ==========================================================================
   Wave 3: access control, team swaps, play-queue editing, players & banners
   ========================================================================== */
.pw-banner {
  background: var(--orange-soft); border-bottom: 1px solid var(--orange-line);
  color: var(--orange-deep); font-size: 0.85rem; text-align: center;
  padding: 0.55rem 1rem;
}
.pw-banner a { font-weight: 600; }

/* Per-member substitution/exchange row in the teams table. */
.team-swap { display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap; padding: 0.2rem 0; }
.team-swap__name { font-weight: 600; min-width: 7rem; }
/* No font-size here: this select is a form control and must stay ≥16px. It used
   to set 0.85rem and — being (0,1,1) — outranked the ≤600px `select { 16px }`
   rule that was written to fix it, so it was 13.6px at every width and zoomed
   iOS on focus. */
.team-swap select { width: auto; min-width: 9rem; max-width: 16rem; flex: 1 1 9rem; padding: 0.4rem 0.5rem; }

/* Access-control page. */
.rolecard .role-tag { margin-bottom: 0.4rem; display: inline-block; }
.rolecheck-group--col { flex-direction: column; gap: 0.35rem; align-items: flex-start; }
.usercell { display: flex; flex-direction: column; gap: 0.25rem; min-width: 0; }
.usercell .badge { margin-right: 0.3rem; }

/* Play-queue editor reuses the order-of-play list inside a card. */
.op-edit { padding: 0.4rem 1.1rem; margin-top: 0.6rem; }

/* Public team-composition cards. */
.teamcard strong { font-family: var(--serif); font-size: 1.02rem; }

@media (max-width: 600px) {
  .team-swap select { flex: 1 1 100%; max-width: none; }
  .team-swap__name { min-width: 0; }
}

/* ============================================================================
   Maison flourishes — decorative refinement pass (additive only)
   ----------------------------------------------------------------------------
   Zebra striping for the dense factual tables, a gold champion treatment,
   a laurel flourish on success notices, engraved gold corner ticks on the
   proclamation frame, and pill-typography normalization. All colours are
   token-based; inline SVGs are self-contained data: URIs (CSP img-src data:).
   Nothing here animates, so no extra reduced-motion handling is needed.
   ========================================================================== */

/* Zebra striping — data-heavy tables only (.data-table, #stat-table); the
   standings table stays clean. Hover restated after so it wins the tie. */
.data-table tbody tr:nth-child(even),
#stat-table tbody tr:nth-child(even) { background: var(--zebra); }
.data-table tbody tr:hover,
#stat-table tbody tr:hover { background: var(--row-hover); }

/* Champion row: a quiet gold wash with a gold rule at the left edge (inset
   shadow, so no layout shift), held steady under hover. */
.tbl tbody tr.is-champion { background: var(--gold-soft); }
.tbl tbody tr.is-champion:hover { background: var(--gold-soft); }
.tbl tr.is-champion td:first-child { box-shadow: inset 3px 0 0 var(--gold); }
.tbl tr.is-champion .name { font-weight: 600; }

/* Success notices carry a faint laurel at the right — tasteful for both the
   champion proclamation and ordinary confirmations. */
.alert--ok {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'%3E%3Cg fill='none' stroke='%232e6e4e' stroke-opacity='0.3' stroke-width='1.5' stroke-linecap='round'%3E%3Cpath d='M14 34 C7 28 5.5 18 9.5 8'/%3E%3Cpath d='M9 12 L5 10.4 M8 17 L4 16.6 M8.4 22 L4.6 23 M10.2 27 L6.9 29 M12.8 31.4 L10.2 34'/%3E%3Cpath d='M26 34 C33 28 34.5 18 30.5 8'/%3E%3Cpath d='M31 12 L35 10.4 M32 17 L36 16.6 M31.6 22 L35.4 23 M29.8 27 L33.1 29 M27.2 31.4 L29.8 34'/%3E%3C/g%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.8rem center;
  background-size: 30px 30px;
  padding-right: 3.4rem;
}

/* Proclamation: engraved gold corner ticks just inside the double frame,
   painted with hairline gradients (two strokes per corner). */
.proclamation {
  background-image:
    linear-gradient(var(--gold-line), var(--gold-line)),
    linear-gradient(var(--gold-line), var(--gold-line)),
    linear-gradient(var(--gold-line), var(--gold-line)),
    linear-gradient(var(--gold-line), var(--gold-line)),
    linear-gradient(var(--gold-line), var(--gold-line)),
    linear-gradient(var(--gold-line), var(--gold-line)),
    linear-gradient(var(--gold-line), var(--gold-line)),
    linear-gradient(var(--gold-line), var(--gold-line));
  background-repeat: no-repeat;
  background-size: 14px 1px, 1px 14px, 14px 1px, 1px 14px, 14px 1px, 1px 14px, 14px 1px, 1px 14px;
  background-position:
    left 10px top 10px, left 10px top 10px,
    right 10px top 10px, right 10px top 10px,
    left 10px bottom 10px, left 10px bottom 10px,
    right 10px bottom 10px, right 10px bottom 10px;
}

/* Pills and chips share one typographic voice: consistent optical height and
   steady figures across badges, tags, skills and results. */
.badge, .tag, .skill, .role-chip, .role-tag, .result {
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
}

/* ============================================================================
   Motion enhancements (effects agent)
   ----------------------------------------------------------------------------
   CSS half of the JS motion layer (effects.js / live.js / bracket.js):
   bell nudge, toast dismiss hairline, live-fragment swap dip, bracket hover
   trace, and the champion sheen. Everything is additive and no-JS-safe — the
   classes below are only ever added by script; without JS nothing changes.
   All animations/transitions are disabled under prefers-reduced-motion (both
   here and by the global reduced-motion rule above; the JS gates them too).
   ========================================================================== */

/* Bell nudge — a tiny 3-swing wiggle (±8°) when the unread count rises.
   effects.js adds .bell--nudge for ~1s, then removes it. */
.bell--nudge { animation: bell-nudge 0.9s var(--ease); transform-origin: 50% 22%; }
@keyframes bell-nudge {
  0%, 90%, 100% { transform: rotate(0deg); }
  12% { transform: rotate(8deg); }
  28% { transform: rotate(-8deg); }
  44% { transform: rotate(6deg); }
  60% { transform: rotate(-6deg); }
  76% { transform: rotate(3deg); }
}

/* Toast dismiss hairline — a thin orange line along the bottom that drains
   over the toast's 6.5s lifetime (scaleX keeps it off the layout thread). */
.toast { position: relative; overflow: hidden; }
.toast::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: linear-gradient(90deg, var(--orange), var(--orange-bright));
  opacity: 0.65; pointer-events: none;
  transform-origin: left center;
  animation: toast-timer 6.5s linear forwards;
}
@keyframes toast-timer {
  from { transform: scaleX(1); }
  to   { transform: scaleX(0); }
}

/* Live fragment swap — live.js adds .live-swap just before replacing
   #live-root's content: the dip lands instantly (no transition), then the
   class is removed and opacity eases back over 160ms. Default opacity is 1,
   so nothing here depends on JS. */
#live-root { transition: opacity 0.16s var(--ease); }
#live-root.live-swap { opacity: 0.55; transition: none; }

/* Bracket hover trace — bracket.js tags a card's outgoing winner path(s) with
   .bg-hot while hovered (desktop pointers only). Opacity needs !important to
   beat the element-style opacity bracket.js sets on undecided lines. */
.bracket-svg path.bg-hot {
  stroke-width: 2.4;
  opacity: 1 !important;
  filter: drop-shadow(0 0 2px rgba(234, 91, 12, 0.5));
}

/* Champion sheen — a slow, very subtle diagonal gold glint across the
   champion notice (.alert--ok containing "Champion:"; class set once by
   effects.js). Gradient overlay only; text and laurel stay untouched. */
.champion-glint { position: relative; overflow: hidden; }
.champion-glint::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(115deg,
    transparent 42%,
    rgba(217, 192, 146, 0.45) 50%,
    transparent 58%);
  background-size: 250% 100%;
  background-repeat: no-repeat;
  animation: champion-glint 4s ease-in-out infinite;
  opacity: 0.5;
}
@keyframes champion-glint {
  0%       { background-position: 130% 0; }
  55%, 100% { background-position: -30% 0; }
}

/* Reduced motion: retire every effect in this section outright. */
@media (prefers-reduced-motion: reduce) {
  .bell--nudge { animation: none; }
  .toast::after { content: none; animation: none; }
  #live-root { transition: none; }
  #live-root.live-swap { opacity: 1; }
  .champion-glint::after { content: none; animation: none; }
}

/* ---------- Teams lock panel (pre-reveal, lives inside #live-root) ----------
   Big, pure, centered — one quiet statement, one large countdown, one date. */
.lockbanner {
  text-align: center;
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
  padding: 2.6rem 1.5rem 2.3rem; margin: 1.1rem 0;
}
.lockbanner__glyph {
  width: 34px; height: 34px; color: var(--orange);
  margin-bottom: 0.8rem; opacity: 0.9;
}
.lockbanner__title {
  font-family: var(--serif); font-weight: 500; letter-spacing: -0.005em;
  font-size: 1.65rem; line-height: 1.2; margin: 0 0 0.9rem; color: var(--ink);
}
.lockbanner__count.countdown-out {
  font-family: var(--serif); font-size: 2.6rem; line-height: 1.1;
  font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
  color: var(--orange-deep); margin: 0 0 0.7rem;
}
.lockbanner__when { margin: 0; font-size: 0.9rem; color: var(--ink-soft); }
@media (max-width: 480px) {
  .lockbanner { padding: 2rem 1rem 1.8rem; }
  .lockbanner__title { font-size: 1.3rem; }
  .lockbanner__count.countdown-out { font-size: 1.8rem; }
}

/* Segmented capacity: one cell per place — filled = taken, hollow = free. */
.capacity__cells { display: flex; gap: 3px; margin-bottom: 0.4rem; }
.capacity__cell {
  flex: 1 1 0; min-width: 5px; height: 9px; border-radius: 2px;
  background: var(--sand-deep); box-shadow: inset 0 0 0 1px var(--line);
  transition: background 0.4s var(--ease);
}
.capacity__cell.is-filled {
  background: linear-gradient(180deg, var(--orange-bright), var(--orange));
  box-shadow: none;
}

/* Custom welcome message on the join page. */
.join-message {
  font-size: 1.05rem; font-style: italic; color: var(--ink-soft);
  text-align: center; margin: 0.2rem 0 0.6rem; line-height: 1.55;
}

/* ==========================================================================
   Tournament page redesign: layout, facts grid, meter, players panel, CTA
   ========================================================================== */
/* Two-column live layout: content + players aside; stacked on phones. */
.t-layout { display: grid; gap: 1.1rem; grid-template-columns: 1fr; align-items: start; }
/* The players panel gets real room so names are never cut; it widens further
   on roomy screens rather than squeezing the list. */
@media (min-width: 920px) { .t-layout { grid-template-columns: minmax(0, 1fr) 340px; } }
@media (min-width: 1180px) { .t-layout { grid-template-columns: minmax(0, 1fr) 380px; } }
.t-main { min-width: 0; }
.t-side { min-width: 0; }
@media (min-width: 920px) { .t-side { position: sticky; top: 76px; } }

/* Labelled facts grid (replaces the run-together one-line meta). Each fact
   reads as a small plaque: caption over an orange tick over the value. */
.factlist {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr));
  gap: 1.1rem 1.8rem; margin: 0.2rem 0;
}
.fact dt {
  font-size: 0.62rem; letter-spacing: 0.15em; text-transform: uppercase;
  color: var(--ink-faint); font-weight: 600; margin-bottom: 0.3rem;
  padding-bottom: 0.3rem; position: relative;
}
.fact dt::after {
  content: ""; position: absolute; left: 0; bottom: 0;
  width: 18px; height: 2px; border-radius: 1px;
  background: linear-gradient(90deg, var(--orange), var(--orange-bright));
  opacity: 0.75;
}
.fact dd {
  margin: 0; font-family: var(--serif); font-size: 1.02rem; line-height: 1.35;
  color: var(--ink); overflow-wrap: anywhere;
}

/* Match-progress meter: thicker track, gradient fill (eased in by effects.js),
   soft moving sheen while the tournament is live. */
.meter { margin-top: 1rem; }
.meter__track {
  height: 10px; background: var(--orange-soft);
  border-radius: var(--radius-pill); overflow: hidden;
  box-shadow: inset 0 1px 2px rgba(63, 42, 24, 0.08);
}
.meter__fill {
  height: 100%; width: 0; border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--orange-deep), var(--orange), var(--orange-bright));
  transition: width 0.9s var(--ease);
  position: relative;
}
.meter--live .meter__fill::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(110deg, transparent 30%, rgba(255, 255, 255, 0.35) 50%, transparent 70%);
  background-size: 220% 100%;
  animation: meter-sheen 2.6s linear infinite;
}
@keyframes meter-sheen { to { background-position: -220% 0; } }
.meter__label { display: inline-block; margin-top: 0.45rem; font-size: 0.84rem; color: var(--ink-soft); }
.meter__label strong { color: var(--ink); font-variant-numeric: tabular-nums; }

/* Players side panel. */
.sidepanel { padding: 1rem 1.1rem; }
.capacity { margin: 0.35rem 0 0.7rem; }
.capacity__bar {
  height: 6px; background: var(--sand-deep); border-radius: var(--radius-pill);
  overflow: hidden; margin-bottom: 0.35rem;
}
.capacity__fill { height: 100%; background: linear-gradient(90deg, var(--orange), var(--orange-bright)); border-radius: var(--radius-pill); }
.capacity__label { font-size: 0.8rem; color: var(--ink-soft); }
.capacity__label strong { color: var(--ink); font-variant-numeric: tabular-nums; }
.playerlist { list-style: none; margin: 0; padding: 0; }
.playerlist li {
  display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap;
  padding: 0.42rem 0; border-top: 1px solid var(--line); font-size: 0.9rem;
}
.playerlist li:first-child { border-top: none; }
.playerlist__name { font-weight: 550; min-width: 0; overflow-wrap: anywhere; }
.sidepanel .btn { width: 100%; text-align: center; }

/* Celebratory join CTA (guest just joined). */
.btn--cta {
  display: block; width: 100%; text-align: center;
  background: linear-gradient(115deg, var(--orange-deep), var(--orange) 45%, var(--orange-bright));
  border-color: var(--orange-deep); color: #fff;
  font-size: 0.9rem; padding: 0.95rem 1.4rem; letter-spacing: 0.1em;
  box-shadow: var(--shadow-btn); position: relative; overflow: hidden;
}
.btn--cta::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(110deg, transparent 35%, rgba(255, 255, 255, 0.3) 50%, transparent 65%);
  background-size: 250% 100%;
  animation: cta-glint 2.8s var(--ease) infinite;
}
@keyframes cta-glint { to { background-position: -250% 0; } }
.btn--cta:hover { filter: brightness(1.05); background: linear-gradient(115deg, var(--orange-deep), var(--orange) 45%, var(--orange-bright)); }

@media (prefers-reduced-motion: reduce) {
  .meter--live .meter__fill::after, .btn--cta::after { animation: none; }
  .meter__fill { transition: none; }
}

/* ==========================================================================
   Notification bell (SVG) + in-place dropdown panel
   ========================================================================== */
.bellwrap { position: relative; }
.bell .bell__icon { width: 20px; height: 20px; display: block; }
.bell {
  color: var(--ink-soft);
}
.bell:hover { color: var(--orange-deep); }
.bell__badge {
  top: 4px; right: 4px;
  min-width: 0.95rem; height: 0.95rem; padding: 0 0.2rem;
  font-size: 0.56rem; letter-spacing: 0;
  box-shadow: 0 1px 3px rgba(63, 42, 24, 0.25);
}

.notifpanel {
  position: absolute; right: 0; top: calc(100% + 8px);
  width: min(360px, calc(100vw - 1.5rem));
  max-height: min(430px, 70vh); overflow-y: auto;
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  padding: 0.45rem; z-index: 40;
}
.notifpanel__head {
  font-size: 0.62rem; letter-spacing: 0.15em; text-transform: uppercase;
  font-weight: 600; color: var(--ink-soft);
  padding: 0.45rem 0.7rem 0.4rem; border-bottom: 1px solid var(--line);
  margin-bottom: 0.3rem;
}
.notifpanel__item { margin: 0.25rem 0; padding: 0.55rem 0.7rem; border-radius: var(--radius); }
.notifpanel__item .notif__msg { font-size: 0.86rem; }
.notifpanel__item .notif__time { font-size: 0.68rem; }
.notifpanel__empty { color: var(--ink-soft); font-size: 0.88rem; text-align: center; padding: 1.2rem 0.5rem; margin: 0; }
.notifpanel__all {
  display: block; text-align: center; font-size: 0.8rem;
  padding: 0.55rem 0.5rem 0.4rem; border-top: 1px solid var(--line); margin-top: 0.3rem;
}

/* ---------- Join page: tournament info + collapsible rules ---------- */
.join-info {
  margin: 1rem 0 1.2rem; padding: 1.1rem 0 0.35rem;
  border-top: 1px solid var(--line);
}
.join-info .factlist { grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr)); gap: 0.9rem 1.3rem; }
.join-info__prize { text-align: center; margin: 0.9rem 0 0.2rem; font-size: 1rem; }
.rulesdrop { margin-top: 0.8rem; border-top: 1px solid var(--line); }
.rulesdrop summary {
  cursor: pointer; list-style: none; padding: 0.65rem 0.2rem;
  font-size: 0.68rem; letter-spacing: 0.16em; text-transform: uppercase;
  font-weight: 600; color: var(--orange-deep);
  display: flex; align-items: center; gap: 0.45rem;
  -webkit-tap-highlight-color: transparent;
}
.rulesdrop summary::-webkit-details-marker { display: none; }
.rulesdrop summary::after {
  content: "\25BE"; color: var(--ink-faint); font-size: 0.8rem;
  transition: transform 0.2s var(--ease);
}
.rulesdrop[open] summary::after { transform: rotate(180deg); }
.rulesdrop ul { margin: 0 0 0.7rem; padding-left: 1.15rem; }
.rulesdrop li { margin: 0.35rem 0; font-size: 0.88rem; color: var(--ink-soft); text-align: left; }

/* ---------- Profile: personal details ---------- */
.profile__bio {
  font-family: var(--serif); font-size: 1.12rem; font-style: italic; line-height: 1.55;
  color: var(--ink-soft); max-width: 44rem; margin: 0.5rem 0 0.1rem;
}
.profile__details { display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap; margin: 0.75rem 0 0.2rem; }
.profile__actions { display: flex; align-items: center; gap: 0.8rem; flex-wrap: wrap; margin-top: 0.9rem; }
/* A sport the player practises — quiet counterpart to the skill chip. */
.sportchip {
  display: inline-block; font-size: 0.72rem; letter-spacing: 0.04em;
  padding: 0.16rem 0.6rem; border-radius: var(--radius-pill);
  border: 1px solid var(--line-strong); background: var(--sand); color: var(--ink-soft);
}

/* Player names link to their profile — arrow appears/animates on hover. */
.playerlist__link { color: var(--ink); text-decoration: none; display: inline-flex; align-items: baseline; gap: 0.25rem; }
.playerlist__link:hover { color: var(--orange-deep); text-decoration: none; }
.playerlist__go {
  font-size: 0.7rem; color: var(--ink-faint); opacity: 0.55;
  transition: opacity 0.18s var(--ease), transform 0.18s var(--ease), color 0.18s var(--ease);
}
.playerlist__link:hover .playerlist__go { opacity: 1; color: var(--orange); transform: translate(1px, -1px); }
.playerlist__link:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; border-radius: 2px; }

/* ---------- Court references (wizard) ---------- */
.courtnames { display: grid; grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); gap: 0.5rem; margin-top: 0.35rem; }
.courtname { display: flex; align-items: center; gap: 0.4rem; }
.courtname__n {
  flex: 0 0 auto; min-width: 1.5rem; text-align: center;
  font-size: 0.7rem; font-weight: 700; color: var(--orange-deep);
  font-variant-numeric: tabular-nums;
}
.courtname input { min-width: 0; font-size: 16px; }

/* ---------- Live tournament cards (home) ---------- */
.tcard--live { border-top-color: var(--orange); position: relative; overflow: hidden; }
.tcard--live::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: linear-gradient(90deg, transparent, var(--orange-bright), transparent);
  background-size: 50% 100%; background-repeat: no-repeat;
  animation: live-rail 2.4s linear infinite;
}
@keyframes live-rail { from { background-position: -60% 0; } to { background-position: 160% 0; } }
.livetag {
  display: inline-flex; align-items: center; gap: 0.34rem;
  font-size: 0.62rem; letter-spacing: 0.14em; text-transform: uppercase; font-weight: 700;
  /* --orange-deep, not --orange: white on #ea5b0c is 3.50:1 and fails AA at this
     size, while #c2410c gives 5.18:1. This pill marks a tournament as LIVE, so
     it is exactly the label that must stay legible. */
  color: #fff; background: var(--orange-deep); border-radius: var(--radius-pill);
  padding: 0.24rem 0.6rem 0.24rem 0.5rem; vertical-align: middle;
}
.livetag .live-dot { background: #fff; box-shadow: 0 0 0 0 rgba(255,255,255,0.7); margin: 0; }
.progressbar__fill--live { position: relative; overflow: hidden; }
.progressbar__fill--live::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(110deg, transparent 30%, rgba(255,255,255,0.45) 50%, transparent 70%);
  background-size: 220% 100%; animation: meter-sheen 2.2s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .tcard--live::before, .progressbar__fill--live::after { animation: none; }
}

/* "Joined" marker — a quiet confirmation, deliberately unlike the LIVE pill. */
.tag--joined {
  border-color: #bcd7c7; background: #eaf3ee; color: var(--green);
  font-weight: 600; letter-spacing: 0.08em;
  display: inline-flex; align-items: center; gap: 0.28rem;
}
/* The status badge now carries longer copy ("In preparation") — keep it tidy. */
.badge--draft { white-space: nowrap; }

/* ---------- Whole tournament card is the link ----------
   "Stretched link": the real <a> stays on the title (so it reads correctly to
   screen readers and keyboards), and an invisible overlay extends its hit area
   over the entire card. Scoped to .tcard — manager cards carry their own
   buttons and must not be swallowed. */
.tcard { position: relative; cursor: pointer; }
.tcard .card-link::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  border-radius: inherit;
}
.tcard .card-link:hover { text-decoration: none; }
.tcard:hover { box-shadow: var(--shadow); transform: translateY(-2px); border-color: var(--line-strong); }
.tcard:hover .card-link { color: var(--orange-deep); }
/* Keyboard focus lands on the title link — outline the whole card instead. */
.tcard:focus-within { outline: 2px solid var(--orange); outline-offset: 3px; }
.tcard .card-link:focus-visible { outline: none; }
/* The live rail must stay above the overlay to remain visible. */
.tcard--live::before { z-index: 2; }

/* Empty state: a quiet card that tells you what to do next. */
.emptystate { text-align: center; padding: 1.6rem 1.3rem; }
.emptystate p { margin: 0 0 0.35rem; }
.emptystate .btn { margin-top: 0.7rem; }

/* Notification call-to-action (rows that lead somewhere). */
.notif__cta {
  display: inline-block; margin-top: 0.3rem;
  font-size: 0.74rem; font-weight: 600; letter-spacing: 0.04em;
  color: var(--orange-deep);
}
a.notif:hover .notif__cta { color: var(--orange); }

/* Guest → account upsell: the action, not a footnote. */
.upsell {
  display: flex; align-items: center; gap: 1.1rem; flex-wrap: wrap;
  border-left: 3px solid var(--orange); background: var(--paper);
  margin: 1rem 0 0.4rem; max-width: 46rem;
}
.upsell__body { flex: 1 1 16rem; min-width: 0; }
.upsell__title { font-family: var(--serif); font-size: 1.08rem; display: block; margin-bottom: 0.15rem; }
.upsell__cta { flex: 0 0 auto; width: auto; }
@media (max-width: 560px) { .upsell__cta { width: 100%; } }

/* Manager announcement box. The card is not a .stack (its rows are a toolbar, a
   checkbox and two hints), so the one row that needs separating says so here
   rather than carrying its own margin. */
.announce { border-top: 3px solid var(--gold); }
.announce .field--inline { margin-top: var(--s2); }

/* ============================================================================
   Mobile-first polish pass
   ----------------------------------------------------------------------------
   Additive refinements for 375–768px, layered last so the narrow-viewport
   rules win on cascade order rather than on specificity hacks. Five themes:
     1. the players-panel row (one line, truncating name, chips that never wrap),
     2. a segmented capacity meter that degrades instead of turning to slivers,
     3. ≥44px tap targets and ≥13px body copy below the desktop breakpoint,
     4. phone density — shorter sticky header, two-line order-of-play rows,
     5. the notification dropdown as a bottom sheet instead of a clipped panel.
   The ≥920px desktop layout is untouched except where noted. No class is
   renamed or removed, no JS contract is touched, every colour is a token, and
   nothing new animates (the one transition reused here is already covered by
   the global prefers-reduced-motion rule).
   ========================================================================== */

/* ---------- Players panel: one row = one line ------------------------------
   The bug: `.playerlist li` was `flex-wrap: wrap`, so "long name + guest chip
   + skill chip" pushed the skill onto a second line, leaving ragged two-line
   rows beside single-line ones.

   The fix keeps ONE line for every row instead of forcing two on all of them:
   at 280px the panel simply cannot afford to double its height for 18 players,
   and a name reads better truncated than a row does broken. The row is now
   `nowrap`; the name is the only flexible item (`flex: 0 1 auto; min-width: 0`)
   and truncates with an ellipsis, while both chips are rigid
   (`flex: 0 0 auto; white-space: nowrap`) so they always share the name's line.
   Because the name may shrink all the way to zero, the row can never overflow
   the panel — no clipping, no sideways scroll. With the chips capped at ~140px
   the name still keeps ~100px of a 280px column (and ~165px on a phone).

   Rows read uniformly with 0, 1 or 2 chips: the name is always first and always
   on the baseline, the chips always trail it on the same line.

   The ↗ affordance rides in a reserved lane at the right edge of the *link*
   box (which is shrink-to-fit), so it stays glued to the name — or to the
   ellipsis — instead of being truncated away or drifting across the row. The
   native `title` on the link surfaces the full name when it is cut.

   Scoped to `.playerlist li`: the /t/:id/players table reuses
   `.playerlist__name` inside a <td>, where wrapping is correct.

   REVISED: a player's NAME IS NEVER TRUNCATED. The panel was widened instead
   (see .t-layout below), which fits the realistic worst case — a long name plus
   both chips — on one line. When a name is longer still, the chips wrap beneath
   it rather than the name being cut: a name is identity, chips are metadata, so
   the metadata yields. */
.playerlist li { flex-wrap: wrap; }
.playerlist li > .playerlist__name {
  flex: 1 1 auto; min-width: 0;
  white-space: normal; overflow: visible; text-overflow: clip;
  overflow-wrap: anywhere;
}
.playerlist li > .tag,
.playerlist li > .skill {
  flex: 0 0 auto; white-space: nowrap;
  /* One notch tighter than the page-level chips so the name keeps the column. */
  padding-left: 0.42rem; padding-right: 0.42rem; letter-spacing: 0.06em;
}
/* The arrow flows inline right after the last word, so it stays with the name
   however many lines that name takes. */
.playerlist li > a.playerlist__link { display: inline; }
.playerlist li > .playerlist__link .playerlist__go {
  position: static; display: inline; margin-left: 0.25rem;
}
.playerlist li > .playerlist__link:hover .playerlist__go { transform: translate(1px, -1px); }

/* ---------- Capacity meter: cells, never slivers ---------------------------
   playersPanel renders one .capacity__cell per place, up to 40 — as a single
   flex row that is ~317px of *minimum* width: wider than the 280px desktop
   panel and enough to push a 375px page sideways. Only one of the two markups
   is ever rendered, so the plain bar is not a fallback we can switch to; the
   cells have to survive on their own.

   A grid with auto-fill and an 8px floor keeps every cell legible and wraps the
   remainder onto a second row of identical widths (a wrapped *flex* row would
   grow the short last row and mismatch the first). Caps small enough to fit
   still render as a single row, exactly as before. */
.capacity__cells {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8px, 1fr));
  gap: 3px;
}
.capacity__cell { min-width: 0; }

/* ---------- Overflow guards (all widths) ----------------------------------
   Flex/grid children default to min-width:auto and refuse to shrink below their
   content, which is how a long entrant name or a long email pushes the page
   sideways. Purely defensive — no visual change at comfortable widths. */
.op-teams { overflow-wrap: anywhere; }
.deflist { grid-template-columns: minmax(0, auto) minmax(0, 1fr); }
.deflist dd { min-width: 0; overflow-wrap: anywhere; }
.usercell, .profile__details, .upsell__body { min-width: 0; }

/* ---------- Tap targets below the desktop breakpoint -----------------------
   ≥44px in the constrained dimension wherever it doesn't distort the layout.
   Capped at 919px so the ≥920px two-column desktop keeps its density. */
@media (max-width: 919px) {
  .btn {
    display: inline-flex; align-items: center; justify-content: center;
    min-height: 44px;
  }
  .btn--cta { display: flex; }
  .btn--sm { padding: 0.5rem 1rem; font-size: 0.72rem; }

  .bell { min-width: 44px; min-height: 44px; }
  .usermenu__trigger { min-height: 44px; }
  .usermenu__panel a, .usermenu__item {
    display: flex; align-items: center; min-height: 44px;
  }
  .rulesdrop summary { min-height: 44px; }
  .notifpanel__all {
    display: flex; align-items: center; justify-content: center;
    min-height: 44px; font-size: 0.85rem;
  }

  /* Player rows grow to 44px and the link's padding fills the row exactly —
     the matched negative margins keep the visual rhythm while making sure two
     neighbouring hit areas never overlap onto each other. */
  .playerlist li { padding: 0.7rem 0; min-height: 44px; }
  .playerlist li > a.playerlist__link {
    padding-top: 0.7rem; padding-bottom: 0.7rem; margin: -0.7rem 0;
  }
  .sidepanel a.small { display: inline-block; padding: 0.55rem 0; }

  /* Stacked .t-main → .t-side: the players panel is a section of its own, not
     a trailing afterthought, so it gets a full section gap above it. */
  .t-layout { gap: 1.5rem; }
  .sidepanel { padding: 1.1rem 1.15rem; }
}

/* ---------- Phone layout: header, type floor, dense rows ---------- */
@media (max-width: 600px) {
  /* Sticky header: identity + account on line one, navigation on line two,
     with tighter padding. Two deliberate rows (~95px) instead of two or three
     ragged ones that wrap differently depending on the user's name length.
     .t-side's `position: sticky; top: 76px` is already scoped to ≥920px, so
     nothing sticks to the header down here. */
  .topbar {
    gap: 0.3rem 0.8rem;
    padding: calc(0.45rem + env(safe-area-inset-top)) 1rem 0.45rem;
  }
  .brand { order: 1; }
  .topbar__end, .topbar__user { order: 2; margin-left: auto; }
  .nav { order: 3; flex: 1 0 100%; margin-left: 0; gap: 1.15rem; }
  .nav a { padding: 0.5rem 0.1rem; line-height: 1.35; }
  .topbar__name {
    max-width: 7rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  /* (The 16px control floor that used to live here now sits on the base
     `input, select, textarea` rule, so it holds at every width, not just here.) */

  /* Nothing readable below ~13px. Uppercase micro-labels (.eyebrow, .tag,
     .badge, dt captions, nav) keep their size — that smallness is the point. */
  .hint, .demo-creds { font-size: 0.82rem; }
  .capacity__label, .notif__cta, .matchcard__meta, .op-round { font-size: 0.8rem; }
  .rulesdrop li, .notifpanel__item .notif__msg { font-size: 0.9rem; }

  /* Section rhythm: headings and rules pull in so a stack of panels reads as a
     sequence rather than as islands separated by dead space. */
  h2 { font-size: 1.3rem; margin: 1.7rem 0 0.7rem; }
  .section-rule { margin: 1.6rem 0; }
  .mt { margin-top: 1.1rem; }

  /* Order of play becomes two lines — [court] entrants / round — instead of a
     three-way flex row that wraps the round onto its own right-aligned line. */
  .op-item {
    display: grid; grid-template-columns: auto minmax(0, 1fr);
    gap: 0.15rem 0.7rem; align-items: center;
  }
  .op-court { grid-column: 1; grid-row: 1 / span 2; align-self: center; }
  .op-teams { grid-column: 2; grid-row: 1; }
  .op-round { grid-column: 2; grid-row: 2; margin-left: 0; text-align: left; }
}

@media (max-width: 480px) {
  /* Two facts per line instead of eight stacked plaques: the labelled grid is
     the page's summary and shouldn't cost half a screen. Values keep
     `overflow-wrap: anywhere`, so a long court list still wraps cleanly.
     The join page sets its own (more specific) .join-info .factlist columns. */
  .factlist { grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr)); gap: 0.9rem 1.1rem; }
  .fact dd { font-size: 0.98rem; }
  h2 { font-size: 1.25rem; }
  .brand__word { font-size: 1.05rem; letter-spacing: 0.1em; }
  .stat { padding: 1rem 0.5rem; }
  .stat__value { font-size: 2rem; }
}

/* ---------- Notifications: a sheet, not a clipped dropdown -----------------
   .notifpanel is anchored `right: 0` inside .bellwrap, but the bell sits ~150px
   in from the right edge of a 375px screen — so a `min(360px, 100vw - 1.5rem)`
   panel hangs off the LEFT of the viewport and silently loses content (left
   overflow doesn't even scroll). Below 600px it becomes a bottom sheet pinned
   to the safe area: full width, no anchoring arithmetic, and still nothing but
   a [hidden] toggle for effects.js — outside-click and Escape keep working.
   `display` is deliberately never set on .notifpanel so the UA's
   `[hidden] { display: none }` still governs visibility; the rule below just
   makes that guarantee explicit. */
.notifpanel[hidden] { display: none; }

@media (max-width: 600px) {
  .notifpanel {
    position: fixed; inset: auto 0 0 0;
    width: auto; max-width: none;
    max-height: min(72vh, 30rem);
    overscroll-behavior: contain;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    border-bottom: none;
    padding: 0.5rem 0.75rem calc(0.8rem + env(safe-area-inset-bottom));
    box-shadow: 0 -10px 44px -14px rgba(63, 42, 24, 0.4);
    z-index: 70;
  }
  .notifpanel__head { padding-top: 0.6rem; font-size: 0.66rem; }
  .notifpanel__item { padding: 0.7rem 0.75rem; }
}

/* The badge is toggled with the `hidden` attribute; our `display` declarations
   override the UA's [hidden]{display:none}, so restate it (this is why an
   empty "0" badge was showing). Same guard as .notifpanel[hidden]. */
.bell__badge[hidden] { display: none !important; }

/* ==========================================================================
   Important-announcement dialog — centred, dismissible, device-adaptive
   ========================================================================== */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 90;
  display: flex; align-items: center; justify-content: center;
  padding: 1.2rem;
  padding-bottom: calc(1.2rem + env(safe-area-inset-bottom));
  background: rgba(31, 26, 22, 0.42);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  opacity: 0; transition: opacity 0.22s var(--ease);
}
.modal-backdrop--in { opacity: 1; }
.modal {
  width: min(30rem, 100%);
  max-height: calc(100vh - 3rem);
  max-height: calc(100dvh - 3rem); overflow-y: auto;
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--line); border-top: 4px solid var(--orange);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  padding: 1.7rem 1.6rem 1.4rem; text-align: center;
  transform: translateY(10px) scale(0.98);
  transition: transform 0.24s var(--ease);
}
.modal-backdrop--in .modal { transform: none; }
.modal__eyebrow {
  font-size: 0.64rem; letter-spacing: 0.18em; text-transform: uppercase;
  font-weight: 700; color: var(--orange-deep); margin-bottom: 0.7rem;
}
.modal__msg {
  font-family: var(--serif); font-size: 1.15rem; line-height: 1.5;
  margin: 0 0 1.3rem; overflow-wrap: anywhere;
}
.modal__ok { width: 100%; }
/* Phones: a bottom sheet is easier to reach than a centred box. */
@media (max-width: 600px) {
  .modal-backdrop { align-items: flex-end; padding: 0; }
  .modal {
    width: 100%; border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    border-left: none; border-right: none; border-bottom: none;
    padding: 1.5rem 1.15rem calc(1.3rem + env(safe-area-inset-bottom));
    /* dvh after vh: on iOS `vh` is the LARGE viewport — measured as if the
       Safari toolbar were retracted — so a sheet sized in vh can push its own
       button under the browser chrome. Old Safari keeps the vh line. */
    max-height: 85vh;
    max-height: 85dvh;
    transform: translateY(100%);
  }
  .modal__msg { font-size: 1.05rem; }
}
@media (prefers-reduced-motion: reduce) {
  .modal-backdrop, .modal { transition: none; }
  .modal, .modal-backdrop--in .modal { transform: none; }
}

/* ==========================================================================
   Participants roster — manager console (.tbl--rost inside #live-root)
   --------------------------------------------------------------------------
   The panel was a three-column table whose last column had no header and held
   two unrelated things: a STATUS ("In a team") and a destructive ACTION
   ("Remove"). Rows resolved identity three different ways, so no two rows were
   the same height and nothing lined up.

   Four zones now, each with its own labelled column: identity (name + one piece
   of metadata), level, status, actions. In doubles the status column exists
   because a status exists; in singles it is not rendered at all, so there is
   never an unlabelled column.

   MOBILE-FIRST: the default is NOT a table. Every row is a card — name on its
   own full-width line, level/status/controls on the line beneath — so the panel
   never scrolls sideways and the name never has to compete for width.

   The table is restored at ≥1080px, and that number is measured, not taste.
   This panel is one half of .grid--2 inside a .page capped at 1080px, so the
   table is never wider than ~465px however large the screen is. Subtract the
   level, status and action columns and the name column is ~205px at a 1080px
   viewport but only ~120px at 920px — narrow enough that every single identity
   wraps, which is the ragged rhythm this redesign exists to remove. Below the
   width where four columns genuinely fit, the card list is simply the better
   layout, so it keeps running.

   The panel is re-rendered wholesale by live.js every few seconds: everything
   here is stateless CSS on server-rendered classes — no :target, no sibling
   state, nothing that a swap could reset.
   ========================================================================== */

.tbl--rost { display: block; }
.tbl--rost thead { display: none; }
.tbl--rost tbody { display: block; }
.tbl--rost tr {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 0.35rem 0.55rem;
  padding: 0.7rem 0.8rem;
  border-bottom: 1px solid var(--line);
}
.tbl--rost tr:last-child { border-bottom: none; }
.tbl--rost td { display: block; padding: 0; border-bottom: none; }

/* A name is identity and is NEVER truncated, so the identity block takes the
   full width of the row and level/status/controls share the line below it.
   Every card is therefore the same shape — name, metadata, controls — and only
   a name long enough to wrap adds a line, to its own row alone. */
.rost__c--who { flex: 1 1 100%; }
.rost__c--level, .rost__c--status { flex: 0 0 auto; }
/* Grows to push the controls to the right edge; wraps onto its own line rather
   than squeezing the chips when a row runs out of width at 320px. */
.rost__c--act { flex: 1 1 auto; }
/* Two controls (singles draft: "Add to draw" + remove) always take a line of
   their own. Left to wrap on their own they fit beside a bare "—" but not
   beside a level pill, which is precisely the ragged two-then-three-line rhythm
   this panel exists to fix. Falls back to plain wrapping without :has(). */
.rost__c--act:has(form + form) { flex: 1 1 100%; }

/* The identity block is always name-then-metadata on two lines, never "inline
   if it happens to fit". That is what the old panel got wrong: the same kind of
   row rendered at three different heights depending on how long the name was,
   so the column could not be scanned. Stacking is uniform by construction, and
   it hands the name the FULL width of its row/column — the layout least likely
   to break a name at all. */
.rost__who {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 0.1rem; min-width: 0;
}
.rost__name {
  min-width: 0; max-width: 100%;
  font-size: 0.95rem; font-weight: 550; color: var(--ink);
  overflow-wrap: anywhere;
}
.rost__who > .tag, .rost__handle { flex: 0 0 auto; white-space: nowrap; }
.rost__handle { font-size: 0.82rem; color: var(--ink-soft); }
.rost__none { color: var(--ink-soft); }

/* A status reads as a state, not as a label in an action slot: a marker dot and
   sentence case, distinct in form from the bordered level pill beside it. */
.rost__status {
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-size: 0.82rem; color: var(--ink-soft); white-space: nowrap;
}
.rost__status::before {
  content: ""; flex: 0 0 auto;
  width: 7px; height: 7px; border-radius: 50%; background: currentColor;
}
.rost__status--in { color: var(--green); }        /* 6.0:1 on paper */
.rost__status--wait { color: var(--ink-soft); }   /* 5.5:1 on paper */
.rost__status--wait::before { background: transparent; box-shadow: inset 0 0 0 2px var(--orange); }

.rost__act { display: flex; align-items: center; justify-content: flex-end; gap: 0.3rem; flex-wrap: wrap; }
/* Destructive, so quiet: an unadorned glyph that only turns red on approach,
   never louder than the name it sits beside. The accessible name carries the
   player's name, and the form confirms before anything is deleted. */
.rost__x {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; padding: 0.2rem;
  font-size: 0.95rem; line-height: 1; letter-spacing: 0;
  color: var(--ink-soft); border-radius: var(--radius);
}
.rost__x:hover {
  color: var(--red); background: transparent; border-color: var(--line-strong);
  box-shadow: none; transform: none;
}
/* Colour only — .btn:focus-visible still supplies the ring and the halo. */
.rost__x:focus-visible { color: var(--red); }
.tbl--rost td.rost__empty { color: var(--ink-soft); padding: 0.9rem 0.2rem; }

@media (min-width: 1080px) {
  .tbl--rost { display: table; }
  .tbl--rost thead { display: table-header-group; }
  .tbl--rost tbody { display: table-row-group; }
  .tbl--rost tr { display: table-row; padding: 0; border-bottom: none; }
  .tbl--rost td {
    display: table-cell; padding: 0.6rem 0.55rem;
    border-bottom: 1px solid var(--line); vertical-align: middle;
  }
  .tbl--rost tr:last-child td { border-bottom: none; }
  /* Tight inner gutters, generous outer ones: every pixel saved between the
     metadata columns goes to the name. The panel is a half-width card (~470px
     even on a 1280px screen), so those pixels are the difference between a name
     that sits on one line with its chip and one that wraps. */
  .tbl--rost th { padding-left: 0.55rem; padding-right: 0.55rem; }
  .tbl--rost th:first-child, .rost__c--who { padding-left: 0.9rem; }
  .tbl--rost th:last-child, .rost__c--act { padding-right: 0.9rem; }
  /* Metadata columns shrink to their content (width:1% + nowrap), which hands
     every remaining pixel to the name — the column that must never be cut. */
  .tbl--rost th:not(:first-child),
  .rost__c--level, .rost__c--status, .rost__c--act { width: 1%; white-space: nowrap; }
  .tbl--rost th.rost__c--act, .rost__c--act { text-align: right; }
  /* Keep both controls on one line: a wrapping action group reports a narrower
     min-content width, and `width:1%` would then hand the column too little
     room and stack the ✕ under "Add to draw". */
  .rost__act { flex-wrap: nowrap; }
  /* One pill width for every level — an alignment anchor on both edges instead
     of a ragged right side. 5rem is the natural width of the longest label
     ("intermediate"), so the anchor costs the name column nothing. */
  .rost__c--level .skill { min-width: 5rem; text-align: center; }
  .rost__x { min-width: 40px; min-height: 40px; }
  .tbl--rost td.rost__empty { padding: 0.9rem 0.85rem; }
}

/* ---------- Who manages this tournament ----------
   Roll call of the creator plus any managers they granted access to. Same
   identity rhythm as the participants roster: the name owns its line and never
   truncates, metadata sits beneath it, the destructive control stays quiet. */
.mgrcard__form { margin-top: 1rem; padding-top: 0.9rem; border-top: 1px solid var(--line); }
.mgrcard__form .field { display: block; margin-bottom: 0.35rem; }
.mgrcard__row { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
.mgrcard__row select { flex: 1 1 12rem; min-width: 0; }

.mgrlist { list-style: none; margin: 0.8rem 0 0; padding: 0; }
.mgrlist__row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem;
  padding: 0.6rem 0; border-top: 1px solid var(--line);
}
.mgrlist__row:first-child { border-top: none; }
/* Identity takes the room it needs; the tags and the ✕ yield, never the name. */
.mgrlist__who { flex: 1 1 11rem; min-width: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.mgrlist__name { font-weight: 600; overflow-wrap: anywhere; }
.mgrlist__handle { font-size: 0.78rem; color: var(--ink-soft); overflow-wrap: anywhere; }
.mgrlist__tags { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.tag--shown { border-color: var(--orange); color: var(--orange-deep); }
.mgrlist__act { display: flex; justify-content: flex-end; min-width: 2.5rem; }
.mgrlist__remove {
  min-width: 40px; min-height: 40px; line-height: 1;
  color: var(--ink-soft); border-color: transparent;
}
.mgrlist__remove:hover, .mgrlist__remove:focus-visible { color: var(--red); border-color: var(--red); background: var(--paper); }

@media (max-width: 600px) {
  /* Thumb-sized targets courtside; the ✕ drops onto its own line rather than
     squeezing the name. */
  .mgrlist__act { min-width: 44px; }
  .mgrlist__remove { min-width: 44px; min-height: 44px; }
  .mgrcard__row select, .mgrcard__row .btn { width: 100%; }
}

/* The 6-digit plate is the widest fixed thing on the manager console: at 320px
   it measures 235px of the 272px available (87%), which is safe with the system
   serif but leaves no room if a platform substitutes a wider face. Buy headroom
   on the narrowest phones (iPhone SE) rather than risk pushing the page sideways. */
@media (max-width: 360px) {
  .joincode { font-size: 1.6rem; letter-spacing: 0.2em; padding-left: 0.2em; }
}

/* Ownership transfer is an administrator-only zone inside the managers card. */
.mgrcard__form--admin { border-top-style: dashed; }
.mgrcard__form--admin .field { display: flex; align-items: center; gap: 0.5rem; }

/* ---------- Form label utilities ----------
   `label.field` is a grid, so a bare <span> beside the label text becomes its
   own row — which is why a qualifier like "(optional)" dropped to a second line
   and knocked neighbouring fields out of vertical alignment. Wrapping the text
   and its qualifier in .field__label keeps them on one row, so every field in a
   .form-row has the same two-row shape and their inputs line up. */
.field__label { display: flex; align-items: baseline; gap: 0.4rem; flex-wrap: wrap; }
.field__opt { text-transform: none; letter-spacing: 0.02em; font-weight: 400; color: var(--ink-soft); }

/* Square icon-only control (edit pencil, close, etc). 40px keeps it a real tap
   target; the label lives in aria-label, never in visible text. */
.iconbtn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 40px; min-height: 40px; padding: 0.4rem;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--paper); color: var(--ink-soft); cursor: pointer;
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease), background 0.18s var(--ease);
}
.iconbtn:hover { color: var(--orange-deep); border-color: var(--orange); background: var(--paper-bright); }
.iconbtn:focus-visible { outline: 2px solid var(--orange-deep); outline-offset: 2px; }
.iconbtn svg { width: 18px; height: 18px; display: block; }
@media (max-width: 600px) { .iconbtn { min-width: 44px; min-height: 44px; } }

/* ============================================================================
   Live tournament page (/t/:id) — tabbed, courtside-first
   ----------------------------------------------------------------------------
   The page used to be one long stack of flush panels showing the same pending
   matches twice ("On now" + the round-by-round schedule). It is now:

     alerts → your match / turn banner → tabs [ Play · Standings|Draw · Info ]

   Three mechanics are worth knowing before editing this block:

   1. TABS SURVIVE THE LIVE REFRESH. live.js replaces #live-root's innerHTML
      every few seconds. The tab STATE therefore lives outside it: three
      visually-hidden radios that are siblings of #live-root, panels inside it,
      selected with `#tt-x:checked ~ #live-root .tpanel--x`. A swap cannot touch
      the radios, so the open tab is preserved. No JavaScript is involved at any
      point — labels drive the radios, and the radios are natively focusable and
      arrow-key operable, which also keeps the semantics honest (a real,
      always-accurate checked state rather than a hand-maintained aria-selected).

   2. NO INLINE STYLES. The CSP is `style-src 'self'` with no 'unsafe-inline',
      so `style="width:57%"` is silently dropped and every server-rendered bar
      would read empty without JS. Widths come from `.is-wNN` buckets below.

   3. A PLAYER'S NAME IS NEVER TRUNCATED. Every name box here is
      `overflow-wrap: anywhere` with no ellipsis; chips, courts and metadata are
      the things that yield.
   ========================================================================== */

/* ---------- Bar widths without inline styles (nearest 2%) ------------------
   Declared after .meter__fill / .capacity__fill so equal specificity resolves
   in favour of the bucket. effects.js still animates via element style, which
   outranks both. */
.is-w0{width:0%}.is-w2{width:2%}.is-w4{width:4%}.is-w6{width:6%}.is-w8{width:8%}.is-w10{width:10%}
.is-w12{width:12%}.is-w14{width:14%}.is-w16{width:16%}.is-w18{width:18%}.is-w20{width:20%}.is-w22{width:22%}
.is-w24{width:24%}.is-w26{width:26%}.is-w28{width:28%}.is-w30{width:30%}.is-w32{width:32%}.is-w34{width:34%}
.is-w36{width:36%}.is-w38{width:38%}.is-w40{width:40%}.is-w42{width:42%}.is-w44{width:44%}.is-w46{width:46%}
.is-w48{width:48%}.is-w50{width:50%}.is-w52{width:52%}.is-w54{width:54%}.is-w56{width:56%}.is-w58{width:58%}
.is-w60{width:60%}.is-w62{width:62%}.is-w64{width:64%}.is-w66{width:66%}.is-w68{width:68%}.is-w70{width:70%}
.is-w72{width:72%}.is-w74{width:74%}.is-w76{width:76%}.is-w78{width:78%}.is-w80{width:80%}.is-w82{width:82%}
.is-w84{width:84%}.is-w86{width:86%}.is-w88{width:88%}.is-w90{width:90%}.is-w92{width:92%}.is-w94{width:94%}
.is-w96{width:96%}.is-w98{width:98%}.is-w100{width:100%}

/* ---------- Contrast repairs on this page ---------------------------------
   --ink-faint (#9a9086) is ~2.9:1 on cream and fails AA. These three carry
   meaning a player has to read, so they move to --ink-soft (5.5:1). */
.vs { color: var(--ink-soft); font-weight: 400; font-style: italic; }
.tbd { color: var(--ink-soft); font-style: italic; }
.fact dt { color: var(--ink-soft); }

/* ---------- Tabs ---------------------------------------------------------- */
.ttabs { position: relative; margin-top: 1.7rem; }
/* Off-screen but focusable: the label carries the ring (see :focus-visible). */
.ttabs__radio {
  position: absolute; top: 0; left: 0; width: 1px; height: 1px;
  margin: 0; padding: 0; border: 0; opacity: 0; pointer-events: none;
  -webkit-appearance: none; appearance: none;
}
.ttabs__bar {
  display: flex; gap: 0.2rem;
  border-bottom: 1px solid var(--line-strong);
  margin-bottom: 1.7rem;
}
.ttab {
  flex: 1 1 0; min-width: 0; margin-bottom: -1px;
  display: flex; align-items: center; justify-content: center;
  min-height: 44px; padding: 0.55rem 0.5rem; text-align: center;
  font-size: 0.71rem; letter-spacing: 0.13em; text-transform: uppercase; font-weight: 600;
  color: var(--ink-soft); cursor: pointer; overflow-wrap: anywhere;
  border-bottom: 2px solid transparent;
  border-radius: var(--radius) var(--radius) 0 0;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.18s var(--ease), background 0.18s var(--ease), border-color 0.18s var(--ease);
}
.ttab:hover { color: var(--orange-deep); background: var(--orange-veil); }
/* Selected tab. One rule per id — the ids are fixed by tournamentPage(). */
#tt-play:checked ~ .ttabs__bar .ttab[for="tt-play"],
#tt-table:checked ~ .ttabs__bar .ttab[for="tt-table"],
#tt-info:checked ~ .ttabs__bar .ttab[for="tt-info"] {
  color: var(--ink); border-bottom-color: var(--orange);
  background: linear-gradient(180deg, transparent, var(--orange-veil));
}
/* Keyboard focus lands on the hidden radio; draw the ring on its label. */
#tt-play:focus-visible ~ .ttabs__bar .ttab[for="tt-play"],
#tt-table:focus-visible ~ .ttabs__bar .ttab[for="tt-table"],
#tt-info:focus-visible ~ .ttabs__bar .ttab[for="tt-info"] {
  outline: 2px solid var(--orange-deep); outline-offset: -3px;
  border-radius: var(--radius); color: var(--ink);
}
/* Panels. Only the checked one is shown; #live-root can be swapped freely. */
.tpanel { min-width: 0; }
.tpanel { display: none; }
#tt-play:checked ~ #live-root .tpanel--play,
#tt-table:checked ~ #live-root .tpanel--table,
#tt-info:checked ~ #live-root .tpanel--info { display: block; }

/* ---------- Vertical rhythm inside a tab -----------------------------------
   The owner's complaint was that the boxes sat flush against each other. One
   grid gap owns the spacing between panels, so nothing depends on margin
   collapsing between a card and a bare <section>. */
.tstack { display: grid; gap: 1.5rem; align-content: start; min-width: 0; }
@media (min-width: 920px) { .tstack { gap: 1.9rem; } }

.panel { min-width: 0; }
.panel__title { font-size: 1.16rem; line-height: 1.25; margin: 0 0 0.25rem; }
.panel__count {
  font-family: var(--sans); font-size: 0.64rem; letter-spacing: 0.12em; font-weight: 600;
  color: var(--ink-soft); border: 1px solid var(--line-strong); border-radius: var(--radius-pill);
  padding: 0.14rem 0.5rem; vertical-align: 0.18em;
}
.panel__note { margin: 0 0 0.2rem; font-size: 0.85rem; color: var(--ink-soft); }
.panel__note strong { color: var(--ink); font-variant-numeric: tabular-nums; }
.panel .meter { margin-top: 0.8rem; }

/* ---------- Order of play — the ONE queue ----------------------------------
   Replaces the old .op-card + round-by-round schedule pair. First `courts`
   rows are on court (warm chip), the rest are the waiting line (#3, #4 …).
   Completed matches are in .reslist below, never here. */
.oplist, .reslist { list-style: none; margin: 0.9rem 0 0; padding: 0; }
.opitem {
  display: grid; grid-template-columns: 4.4rem minmax(0, 1fr);
  gap: 0.1rem 0.85rem; align-items: start;
  padding: 0.75rem 0; border-top: 1px solid var(--line);
}
.opitem:first-child, .resitem:first-child { border-top: none; padding-top: 0.15rem; }
.opitem__slot {
  grid-column: 1; align-self: center; text-align: center; overflow-wrap: anywhere;
  font-size: 0.63rem; letter-spacing: 0.09em; text-transform: uppercase; font-weight: 600;
  font-variant-numeric: tabular-nums; line-height: 1.3;
  padding: 0.32rem 0.4rem; border-radius: var(--radius);
  color: var(--ink-soft); background: var(--sand); border: 1px solid var(--line-strong);
}
.opitem--now > .opitem__slot {
  color: #fff; background: var(--orange-deep); border-color: var(--orange-deep);
}
.opitem__body { grid-column: 2; min-width: 0; }
.opitem__vs {
  margin: 0; font-weight: 600; font-size: 1rem; line-height: 1.35;
  overflow-wrap: anywhere; /* a name wraps; it is never cut */
}
.opitem__vs .vs, .resitem__names .vs { margin: 0 0.4rem; }
.opitem__meta, .resitem__meta { margin: 0.15rem 0 0; font-size: 0.81rem; color: var(--ink-soft); }
.opitem__score, .resitem__score { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
/* The viewer's own row, in either list. Padding (not negative margin) so the
   emphasis can never push the card sideways at 320px. */
.opitem--mine, .resitem--mine {
  background: var(--orange-veil); box-shadow: inset 3px 0 0 var(--orange);
  padding-left: 0.7rem; border-radius: var(--radius);
}

/* ---------- Results — done, with the final score --------------------------- */
.resitem { display: grid; gap: 0.1rem; padding: 0.7rem 0; border-top: 1px solid var(--line); }
.resitem__names { margin: 0; line-height: 1.35; overflow-wrap: anywhere; }
.resitem__name { color: var(--ink-soft); }
.resitem__name.is-won { color: var(--ink); font-weight: 650; }
.resitem__won { color: var(--green); margin-left: 0.25rem; }
.resitem__meta { display: flex; flex-wrap: wrap; gap: 0.1rem 0.75rem; }

/* ---------- "Your match" — the pinned, personal panel ----------------------
   Three states, three volumes, so a player can tell which one they are in from
   across the hall before reading a single word:

     .gonow            YOU ARE ON. A plaque carrying the court reference, the
                       biggest thing on the page, over a live pill and the score
                       form. Warm ground, thick accent rail, moving sweep.
     .turnbanner       Still in the line. Quiet sand, no accent fill, a count
                       expressed IN MATCHES (there is no clock in the data).
     .yourmatch--idle  Nothing to do. Quietest of the three.

   Nothing here relies on colour alone: the loud state says "Your turn — live
   now" and "Go to court" in words, the quiet one says "N matches to go". */
.turnbanner {
  border-left: 3px solid var(--line-strong);
  background: linear-gradient(180deg, var(--sand), var(--paper) 72%);
  margin-top: 1.2rem;
}
/* Next in line is the one waiting state that earns the accent. */
.turnbanner--next { border-left-color: var(--orange); }
.turnbanner__lead {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.2rem 0.75rem;
  font-family: var(--serif); font-size: 1.2rem; line-height: 1.3;
  margin: 0.35rem 0 0.55rem; color: var(--ink);
}
/* How far away, in matches — the only honest distance the data supports. */
.turnbanner__count {
  flex: 0 0 auto; min-width: 2.3rem; max-width: 100%;
  padding: 0.06em 0.28em; text-align: center;
  border: 2px solid var(--line-strong); border-radius: var(--radius-lg);
  background: var(--paper); color: var(--ink);
  font-size: 2.1rem; line-height: 1.25; font-variant-numeric: tabular-nums;
}
.turnbanner__count--word { font-size: 1.35rem; letter-spacing: 0.01em; }
.turnbanner--next .turnbanner__count { border-color: var(--orange); color: var(--orange-deep); }
.turnbanner__leadtxt { min-width: 0; overflow-wrap: anywhere; }
.turnbanner__note { margin: 0.55rem 0 0; font-size: var(--fs-xs); color: var(--ink-soft); }
.turnbanner__meta, .yourmatch__meta {
  margin: 0; font-size: 0.88rem; color: var(--ink-soft);
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem 0.7rem;
}
.turnbanner__meta strong { color: var(--ink); overflow-wrap: anywhere; }
.yourmatch { margin-top: 1.2rem; }
.yourmatch--idle { background: var(--paper); border-left-color: var(--line-strong); }
.yourmatch__lead {
  font-family: var(--serif); font-size: 1.3rem; line-height: 1.25; margin: 0.3rem 0 0.45rem;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem 0.7rem;
}
.courtchip {
  display: inline-block; font-size: 0.65rem; letter-spacing: 0.11em; font-weight: 600;
  text-transform: uppercase; font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
  padding: 0.24rem 0.6rem; border-radius: var(--radius-pill);
  color: #fff; background: var(--orange-deep); border: 1px solid var(--orange-deep);
}
/* A place in the queue is NOT an instruction to walk anywhere, so it must not
   wear the same solid orange as a court you have been sent to. */
.courtchip--wait { color: var(--ink-soft); background: var(--paper); border-color: var(--line-strong); }

/* ---------- The call to court ----------------------------------------------
   The single loudest thing on the page, and only ever shown to the one player
   it belongs to. Read at arm's length it is three marks: a live pill, the words
   "Go to court", and a plaque with the reference the venue actually uses. */
.gonow {
  position: relative; overflow: hidden; margin-top: 1.2rem;
  border-color: var(--orange-line); border-left: 6px solid var(--orange-deep);
  background: linear-gradient(180deg, var(--orange-soft), var(--paper) 62%);
  box-shadow: var(--shadow);
}
/* The same marching sweep the live tournament cards wear (@keyframes live-rail).
   Motion is the THIRD carrier only — the pill says "live" in words and the
   plaque is a shape, so nothing is lost when it is switched off below. */
.gonow::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 4px;
  background: linear-gradient(90deg, transparent, var(--orange-bright), transparent);
  background-size: 50% 100%; background-repeat: no-repeat;
  animation: live-rail 2.4s linear infinite;
}
.gonow__flag {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.6rem;
  margin: 0.1rem 0 0.55rem;
}
/* Whose match this is — a name is never truncated, so it wraps. */
.gonow__who {
  min-width: 0; overflow-wrap: anywhere;
  font-size: var(--fs-2xs); font-weight: 700; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-soft);
}
.gonow__cta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.75rem;
  margin: 0 0 0.5rem; font-family: var(--serif); font-weight: 500;
  line-height: 1.1; color: var(--ink);
}
.gonow__go { font-size: clamp(1.45rem, 7vw, 1.95rem); }
.gonow__go--only { font-size: clamp(1.25rem, 6vw, 1.7rem); }
/* The plaque. #fff on --orange-deep is 5.2:1, so the reference reads as TEXT and
   not merely as a coloured shape; tabular figures keep "9" and "10" aligned; a
   named court ("Centre") wraps inside the plaque rather than pushing the page. */
.gonow__ref {
  display: inline-block; max-width: 100%; min-width: 2.1em;
  padding: 0.05em 0.28em; border-radius: var(--radius-lg);
  background: var(--orange-deep); color: #fff;
  font-size: clamp(2.5rem, 15vw, 3.8rem); line-height: 1.18; font-weight: 600;
  text-align: center; font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.gonow__sub {
  margin: 0 0 0.85rem; padding-bottom: 0.85rem;
  border-bottom: 1px solid var(--orange-line);
  font-size: var(--fs-sm); color: var(--ink-soft); overflow-wrap: anywhere;
}
.gonow__sub strong { color: var(--ink); }
/* AA-safe live pill. The plain .livetag sits on --orange (3.5:1) — fine for the
   decorative home cards, not for a label read courtside; --orange-deep is 5.2:1. */
.livetag--now { background: var(--orange-deep); }

@media (prefers-reduced-motion: reduce) {
  /* The global reduce rule freezes an animation on its last frame, which would
     leave the swept rail invisible. Draw it solid instead. */
  .gonow::before { animation: none; background: var(--orange-deep); }
}

/* ---------- Small tidy-ups replacing dropped inline styles ---------------- */
.teamreveal__name { margin: 0.3rem 0; font-size: var(--fs-lg); }
.sidepanel__count { margin: 0.2rem 0 0.5rem; }
.sidepanel__note { margin: 0.35rem 0 0; }
.tpanel .advice { margin: 0; }
.tpanel .proclamation { margin-top: 0.4rem; }

/* ---------- Narrow viewports ---------------------------------------------- */
@media (max-width: 600px) {
  .ttabs { margin-top: 1.3rem; }
  .ttabs__bar { margin-bottom: 1.3rem; }
  .ttab { font-size: 0.68rem; letter-spacing: 0.08em; padding: 0.5rem 0.3rem; }
  .tstack { gap: 1.25rem; }
  .turnbanner__lead, .yourmatch__lead { font-size: 1.18rem; }
  .opitem { grid-template-columns: 3.9rem minmax(0, 1fr); gap: 0.1rem 0.65rem; }
  .opitem__vs { font-size: 0.96rem; }
  .opitem__meta, .resitem__meta { font-size: 0.8rem; }
  /* Standings is a tab of its own now, so let it use the full width it has. */
  .tbl--standings { min-width: 100%; }
}

/* The edit-profile form is a composed card, not a full-width slab: cap its
   measure so paired fields stay balanced in the 1080px page column. Replaces a
   dead inline style="max-width:44rem" the CSP was dropping. */
.form--narrow { max-width: 44rem; }

/* A bar with no width bucket must read EMPTY, not full. Without this the CSP
   drops any inline width and `width:auto` fills the whole track, so a draft
   tournament reported 100% complete. */
.progressbar__fill { width: 0; }
.capacity__fill { width: 0; }

/* ============================================================================
   Manager console sub-navigation (/campaigns/:id/…)
   ----------------------------------------------------------------------------
   The console is five sub-pages — Overview · Players · Schedule · Results ·
   Settings — and this bar is the only thing they share. Three decisions:

   1. IT WRAPS, IT NEVER SCROLLS. A horizontally scrolling strip hides tabs
      exactly where a manager needs them most (one hand, at the side of a
      court). `flex-wrap` instead: at 320px the row breaks after three tabs and
      the second row's cells GROW to fill the width, so nothing is clipped, the
      page never scrolls sideways, and every target stays ≥44px tall.
   2. HAIRLINES ARE THE GAP. A 1px flex gap over a --line-strong background
      draws every divider, so a wrapped row can never leave a half-drawn border.
   3. It is <a> links, not tabs-with-JS: each sub-page is a real URL, so back,
      bookmarking and a POST/Redirect/GET all land exactly where they should.
   ========================================================================== */
.subnav {
  display: flex; flex-wrap: wrap; gap: 1px;
  margin: var(--s4) 0 var(--s5);
  background: var(--line-strong);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.subtab {
  flex: 1 1 6rem; min-width: 0; min-height: 46px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.05rem; padding: var(--s2) var(--s1);
  background: var(--paper); color: var(--ink-soft); text-align: center;
  font-size: var(--fs-2xs); letter-spacing: 0.1em; text-transform: uppercase; font-weight: 600;
  overflow-wrap: anywhere;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.subtab:hover { color: var(--orange-deep); background: var(--sand); text-decoration: none; }
.subtab:focus-visible { outline: 2px solid var(--orange-deep); outline-offset: -3px; }
/* The page you are on: warm wash + an orange underline drawn inside the cell,
   so the marker survives the row wrapping. */
.subtab.is-current {
  color: var(--ink);
  background: linear-gradient(180deg, var(--orange-soft), var(--paper));
  box-shadow: inset 0 -3px 0 var(--orange);
}
/* Live counter on a tab (people joined, results recorded). --ink-soft, never
   --ink-faint: this is text a manager reads, so it must clear AA. */
.subtab__n {
  font-size: var(--fs-2xs); letter-spacing: 0.04em; font-weight: 600;
  color: var(--ink-soft); font-variant-numeric: tabular-nums;
}
.subtab.is-current .subtab__n { color: var(--orange-deep); }

/* ---------- Overview: the one thing to do next ---------------------------- */
.nextact { border-left: 3px solid var(--orange); }
.nextact__lead {
  font-family: var(--serif); font-size: var(--fs-xl); line-height: 1.3;
  margin: 0.1rem 0 var(--s2); overflow-wrap: anywhere;
}
.nextact .toolbar { margin-bottom: 0; }
.nextact .btn { flex: 0 1 auto; }

/* ---------- Schedule: court references as the players know them ----------- */
.courtrefs {
  display: flex; flex-wrap: wrap; gap: var(--s2);
  list-style: none; margin: var(--s3) 0 0; padding: 0;
}

/* ---------- Results: land on the card you just saved ----------------------
   The score POST redirects to #m<id>. Clear the sticky topbar so the card is
   not hidden under it, and tint it briefly so the eye finds it. */
.scorecard { scroll-margin-top: 5.5rem; }
.scorecard:target {
  border-color: var(--orange);
  box-shadow: 0 0 0 3px var(--orange-soft);
}

/* Below the width where all five tabs fit on one line, lock the row to exactly
   three: `calc(33.333% - 1px)` + the 1px gaps makes four impossible, so the bar
   always breaks 3 + 2 (the pair growing to fill) instead of leaving a lone
   orphan tab on the second row at common phone widths. */
@media (max-width: 560px) {
  .subnav { margin: var(--s3) 0 var(--s4); }
  .subtab { flex-basis: calc(33.333% - 1px); letter-spacing: 0.06em; padding: var(--s2) 0.15rem; }
  .nextact__lead { font-size: var(--fs-lg); }
}

/* ---------- Order of play — two named groups, one queue --------------------
   The board reads top-down as three states: ON COURT NOW (one block per busy
   court, the court name loudest), UP NEXT (the waiting line, blocked by round)
   and — in its own panel below — Results. Each state is announced by a real
   heading, so none of them relies on colour to be told apart. */
.opgroup__head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.3rem 0.6rem;
  font-family: var(--sans); font-size: var(--fs-2xs); font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-soft);
  margin: var(--s5) 0 var(--s2); padding-bottom: var(--s2);
  border-bottom: 1px solid var(--line-strong);
}
.opgroup__head .panel__count { letter-spacing: 0.08em; text-transform: none; }
.opgroup__note { margin: 0 0 var(--s3); }
/* The dot is a flex item here, so vertical-align cannot place it. */
.opgroup__head--live .live-dot { align-self: center; margin-right: 0; }

/* On court now: the court reference is the loudest thing in the panel —
   courtside it is the only instruction that matters. Auto-fit, so the columns
   answer to the container and never to the viewport.
   LIVE is signalled four ways over: the word "Live" in a filled pill, the
   sentence "Being played right now", a pulsing dot, and a swept rail along the
   top edge. Remove colour, remove motion, and two of the four still stand. */
.oncourt { display: grid; gap: var(--s3); grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr)); }
.oncourt__item {
  position: relative; overflow: hidden;
  padding: var(--s3) var(--s4); padding-top: var(--s4);
  border-left: 5px solid var(--orange-deep); box-shadow: var(--shadow-sm);
}
.oncourt__item::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: linear-gradient(90deg, transparent, var(--orange-bright), transparent);
  background-size: 50% 100%; background-repeat: no-repeat;
  animation: live-rail 2.4s linear infinite;
}
.oncourt__flag {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem 0.55rem;
  margin: 0 0 0.35rem;
}
.oncourt__court {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.1rem 0.55rem;
  margin: 0 0 0.35rem; font-family: var(--serif); font-weight: 500;
  line-height: 1.1; color: var(--orange-deep); overflow-wrap: anywhere;
}
.oncourt__kicker {
  font-family: var(--sans); font-size: var(--fs-2xs); font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-soft);
}
.oncourt__ref {
  font-size: clamp(2rem, 9vw, 2.6rem); line-height: 1.05;
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere; min-width: 0;
}
.oncourt__ref--plain { font-size: var(--fs-xl); }
.oncourt__state {
  font-family: var(--sans); font-size: var(--fs-2xs); font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-soft);
}
.oncourt__vs { margin: 0; font-size: var(--fs-lg); font-weight: 650; line-height: 1.3; overflow-wrap: anywhere; }
.oncourt__vs .vs { margin: 0 0.4rem; }
.oncourt__meta { margin: 0.25rem 0 0; font-size: var(--fs-xs); color: var(--ink-soft); }
.oncourt__meta strong { color: var(--ink); font-variant-numeric: tabular-nums; }
/* Same veil as .opitem--mine. NOT --orange-soft: --orange-deep on that is only
   4.39:1 and the "Your match" label would fail AA. */
.oncourt__item--mine { border-left-width: 7px; background: var(--orange-veil); }

@media (prefers-reduced-motion: reduce) {
  .oncourt__item::before { animation: none; background: var(--orange-deep); }
}

/* The waiting line, blocked by round: 12 rows read as four blocks of three. */
.opround__head {
  font-family: var(--sans); font-size: var(--fs-2xs); font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-soft);
  margin: var(--s4) 0 0;
}
.opqueue > .opround:first-child > .opround__head { margin-top: var(--s2); }
.opround__list { margin-top: 0.2rem; }

/* The viewer's anchor. .eyebrow supplies the type; this only tightens it so the
   label costs one line, not two, at 320px. */
.opmine { margin: 0 0 0.1rem; letter-spacing: 0.12em; }
/* When the anchor is an INSTRUCTION ("Your match — go to court 9 now") it stops
   being a label and becomes a sign: #fff on --orange-deep, 5.2:1. */
.opmine--go {
  display: inline-block; margin: 0 0 0.4rem; padding: 0.28rem 0.6rem;
  border-radius: var(--radius); letter-spacing: 0.1em;
  color: #fff; background: var(--orange-deep); overflow-wrap: anywhere;
}

/* Version tag beside "About" in the account menu: present for anyone who needs
   to quote a build, quiet enough to ignore otherwise. */
.usermenu__meta {
  margin-left: auto; padding-left: var(--s3);
  font-family: var(--mono); font-size: var(--fs-2xs);
  color: var(--ink-soft); font-variant-numeric: tabular-nums;
}
.usermenu__panel a { display: flex; align-items: baseline; }

/* Results button on the finish announcement, above "Close". Stacked, because
   at 320px two buttons side by side wrap into a ragged pair. */
.modal__go { display: block; width: 100%; text-align: center; margin-top: var(--s3); }
.modal__go + .modal__ok { display: block; width: 100%; margin-top: var(--s2); }

/* ---------- Podium ----------
   Place is spelled out ("1st · Champion"), never signalled by colour alone. */
.podium { list-style: none; margin: var(--s3) 0 0; padding: 0; display: grid; gap: var(--s3); }
.podium__step {
  display: grid; gap: 0.2rem; padding: var(--s3) var(--s4);
  background: var(--paper); border: 1px solid var(--line); border-left: 5px solid var(--line-strong);
  border-radius: var(--radius);
}
.podium__step--gold { border-left-color: var(--gold); background: var(--gold-soft); }
.podium__step--silver { border-left-color: #9aa0a6; }
.podium__step--bronze { border-left-color: #b07d4a; }
.podium__place { display: flex; align-items: baseline; gap: 0.5rem; }
.podium__num { font-family: var(--serif); font-size: var(--fs-xl); font-weight: 600; color: var(--ink); }
.podium__word {
  font-size: var(--fs-2xs); font-weight: 700; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-soft);
}
.podium__name { font-size: var(--fs-lg); font-weight: 650; overflow-wrap: anywhere; }
.podium__record { font-size: var(--fs-xs); color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.podium__prize { margin-top: 0.2rem; font-size: var(--fs-xs); color: var(--orange-deep); }
.podium__step--first .podium__name { font-size: var(--fs-xl); }
@media (min-width: 640px) { .podium { grid-template-columns: repeat(3, 1fr); align-items: start; } }
