/* ============================================================================
   City Limit Sports — main.css
   The single consolidated stylesheet. Everything below :root derives from the
   tokens; no color or spacing value is hardcoded outside this first block.
   Mobile-first base + exactly two real breakpoints (768px, 1100px). Bootstrap
   Icons supplies icons only; no other framework CSS loads, so this file is the
   single source of truth. Base element rules use :where() (zero specificity) so
   component classes always win — no selector cancellation wars.
   ============================================================================ */

/* --- Barlow (self-hosted, not hot-linked from Google) -----------------------
   Only the two weights the type scale allows (400 + 600), latin & latin-ext.
   font-display: swap paints text immediately in the fallback, then swaps. */
@font-face {
  font-family: 'Barlow';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/barlow-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Barlow';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/barlow-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Barlow';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/barlow-600-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Barlow';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/barlow-600-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --- Overpass (self-hosted) — the display face, shared with the landing page.
   Body copy stays Barlow (above); headings use Overpass so the whole site reads
   as one dark cinematic design. Weights 600/700 only. ------------------------- */
@font-face{font-family:'Overpass';font-style:normal;font-weight:600;font-display:swap;src:url('/fonts/overpass-600-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face{font-family:'Overpass';font-style:normal;font-weight:600;font-display:swap;src:url('/fonts/overpass-600-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face{font-family:'Overpass';font-style:normal;font-weight:700;font-display:swap;src:url('/fonts/overpass-700-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face{font-family:'Overpass';font-style:normal;font-weight:700;font-display:swap;src:url('/fonts/overpass-700-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}

:root {
  /* Brand palette — DARK cinematic theme (2026-07-21: dark site-wide, uniting the
     interior pages with the landing page). Re-derived for a dark ground; the old
     paper-era hexes and their on-paper contrast notes are gone. --crimson/--plum are
     lightened so they read as text on dark; --coral is unchanged and carries a DARK
     label. Still the single source referenced everywhere. */
  --crimson:   #DA76A0; /* brand accent: links, nav active, fixture labels (AA 4.66 on charcoal) */
  --plum:      #D488AE; /* lighter plum: link hover (AA on charcoal)            */
  --coral:     #D36D51; /* THE accent: one CTA per view, live cues, colour-pop  */
  --ink:       #F5F1EC; /* primary text — warm off-white on the dark ground     */
  --paper:     #2E2C30; /* page background — charcoal (team pick 2026-08-01)      */
  --on-accent: #0D0B0C; /* near-black label ON coral/crimson/danger fills (AA)   */
  --warm-gray: #8A7B7E; /* hairlines, mid-dot separators, decorative marks      */

  /* Derived neutrals, lifted off the dark --paper (warm, never cold gray). */
  --surface:    #3A363C; /* input fill / raised surface (a lift ABOVE charcoal --paper) */
  --hairline:   rgba(245,241,236,0.14); /* rule between fixture rows/sections */
  --row-hover:  rgba(245,241,236,0.05); /* subtle row / ghost-button hover   */
  --row-active: rgba(245,241,236,0.09); /* pressed background                */

  /* Metadata text. A light warm gray that clears WCAG AA on the dark ground
     (~8:1). --warm-gray is kept for hairlines, dots and separators only. */
  --meta: #B1A5A7;

  /* Coral CTA states. The label on coral is the DARK --paper (5.7:1); a light
     label on coral fails AA, so it is never used. Hover lifts, active deepens,
     both keeping the dark label AA. */
  --coral-hover:  #DD7659;
  --coral-active: #C86550;

  /* Semantic status — lightened to clear AA as text on the dark ground (7.7 / 8.7 /
     6.4). The danger fill (its one filled button) pairs with the dark --paper label. */
  --ok:     #4FB586; /* registration open   */
  --warn:   #D9A441; /* opens soon          */
  --danger: #EE746C; /* closed / form error (AA on charcoal) */

  /* Disabled (WCAG-exempt; must simply read as inactive). */
  --disabled-bg: #262428;
  --disabled-fg: #6F6569;

  /* Focus — the lightened crimson reads clearly on dark and keeps --coral reserved. */
  --focus-ring: var(--crimson);

  /* Type. Barlow for body and forms; Overpass (self-hosted above) for the display
     face, so the headings echo the landing page's cinematic look. Hierarchy still
     comes from size + weight; --font-display is the one knob for the heading face. */
  --font-sans: Barlow, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: 'Overpass', var(--font-sans);
  --text-display: 3rem;     /* 48px — hero H1 only     */
  --text-h2:      1.75rem;  /* 28px                    */
  --text-row:     1.25rem;  /* 20px — fixture-row name  */
  --text-body:    1rem;     /* 16px                    */
  --text-meta:    0.875rem; /* 14px                    */
  --weight-regular: 400;
  --weight-strong:  600;

  /* Spacing — the ONLY permitted margin/padding/gap values. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 16px;
  --space-4: 24px;
  --space-5: 40px;

  --radius: 4px;              /* one radius; controls only, rows stay square */
  --container-max: 68rem;
  --transition: 150ms ease-out;

  /* Hero sizing. The homepage hero is one full-bleed photo slot, deliberately
     SHORTER than the viewport by this much, so the fixture list below always
     crests the fold — a visitor must see that a list exists without scrolling.
     It has to cover everything stacked above the hero (nav + weather banner)
     plus the section padding and the h2 itself.

     Mobile-first, and the mobile value is the bigger one: at 360px the nav wraps
     to ~103px and the weather banner to ~90px, so 193px sits above the hero
     before the list even starts. The brief specified 220px, which is the right
     number once the nav and banner are single-line — but at 360 it leaves the
     h2 56px BELOW the fold, breaking the very invariant it exists to hold. So
     220px moves to the 768px block where it is true, and mobile takes the value
     the measurements actually demand.

     Note the requirement is independent of screen height: the fold test is
     chrome + (100svh - offset) + padding + h2 < 100svh, and the 100svh cancels.
     One number therefore holds for every phone, not just the one tested.

     Measured at 360px: 276px is the minimum with the usual one-line status
     message (banner 90px), and 320px with a two-line one (banner 134px). 340px
     covers that whole range with slack, and still leaves a 300px hero on the
     smallest phone.

     KNOWN AND DELIBERATE: a very long status message can still push the heading
     under the fold, because the banner grows with text Simon types and no fixed
     offset can out-guess arbitrary content. That failure mode is the right way
     round — on the day he writes three lines about a washed-out court, the
     message IS the thing that should own the fold, not the fixture list. */
  --hero-offset: 340px;

  /* Scrim over the hero photo. Ink at opacity — the one place an alpha colour is
     needed, because the hero text has to stay legible over an arbitrary photo
     that Simon supplies later. Declared here so it stays a token rather than a
     literal buried in a rule.

     0.45 is measured, not guessed. The worst case is --paper text over the coral
     end of the gradient, which is only 3.22:1 unscrimmed — fine for the 48px h1
     (large text needs 3:1) but a fail for the 16px lede (needs 4.5:1). At 0.45
     that same worst case computes to 6.88:1, which clears AA with enough headroom
     left for a bright photo to land here later without a redesign. */
  --scrim: rgba(36, 16, 25, 0.45);
}

/* --- Reset ---------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; }
img, svg { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }

/* --- Base ----------------------------------------------------------------- */
body {
  font-family: var(--font-sans);
  font-size: var(--text-body);
  line-height: 1.5;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
}
:where(h1, h2, h3) { font-family: var(--font-display); font-weight: var(--weight-strong); line-height: 1.2; }
/* h1/h2 take the landing's cinematic treatment: uppercase Overpass at 700, tight
   tracking. Big headings only — h3, eyebrows and labels stay sentence case so nothing
   becomes a shouting all-caps eyebrow (the AI tell CLAUDE.md flags). */
:where(h1, h2) { text-transform: uppercase; font-weight: 700; letter-spacing: -0.01em; }
:where(h1) { font-size: var(--text-display); }
:where(h2) { font-size: var(--text-h2); }
:where(p)  { max-width: 65ch; }
:where(a) {
  color: var(--crimson);
  text-decoration: underline;
  text-underline-offset: 2px;
  transition: color var(--transition);
}
:where(a:hover) { color: var(--plum); }

/* Single functional focus ring for every interactive element (and the forced
   .is-focus demo state). No shadows anywhere else. */
:where(a, button, .btn, .fixture-row, input, select, textarea, [tabindex]):focus-visible,
.is-focus {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

.skip-link {
  position: absolute;
  left: var(--space-3);
  top: -3rem;
  z-index: 10;
  background: var(--ink);
  color: var(--paper);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  text-decoration: none;
  transition: top var(--transition);
}
.skip-link:focus { top: var(--space-3); }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Reduced motion: swap to static, don't merely pause. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* --- Layout --------------------------------------------------------------- */
.container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--space-3);
}
.section { padding-block: var(--space-5); }
.section__title { margin-bottom: var(--space-3); }

/* --- Hero ------------------------------------------------------------------
   One full-bleed photo slot. Height is viewport-minus-offset so the section
   below always crests the fold. svh (small viewport height) is the unit that
   ignores mobile browser chrome collapsing — 100vh would jump as the URL bar
   hides, resizing the hero mid-scroll.

   No photo has been supplied yet, so .hero__media carries the brand
   magenta→coral gradient. This is the ONE gradient site-wide (the hero/logo
   moment) — it must not reappear on a button, header, or any other background.
   When a photo lands, add background-image to .hero__media; the scrim below
   already guarantees the h1 stays legible over it. */
.hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  height: calc(100svh - var(--hero-offset));
  /* Floor for a landscape phone, where the calc can go near zero and the h1 and
     lede would collide with the edges. Deliberately low: at 20rem it started
     WINNING on an ordinary 640px-tall portrait phone and re-broke the fold
     promise it was supposed to be unrelated to. 12rem clears the h1 + lede +
     padding (~98px of content in 192px) and otherwise stays out of the way. */
  min-height: 12rem;
  overflow: hidden;
}
/* THE gradient. The only one in the codebase — see the rule above. */
.hero__media {
  position: absolute;
  inset: 0;
  background-color: var(--crimson);
  background-image: linear-gradient(140deg, var(--crimson), var(--coral));
  background-size: cover;
  background-position: center;
}
/* Scrim: the text sits on a photo, so legibility must not depend on what the
   photo happens to be. A flat overlay rather than a second gradient — the
   gradient budget is spent, and a solid scrim is what makes the contrast
   predictable across the whole slot instead of only where the fade is darkest. */
.hero__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background-color: var(--scrim);
}
.hero__body {
  position: relative;
  width: 100%;
  padding-block: var(--space-5);
}
.hero__title { font-size: var(--text-display); max-width: 16ch; color: var(--ink); }
.hero__lede { margin-top: var(--space-3); max-width: 48ch; color: var(--ink); }

/* --- Site navigation ------------------------------------------------------ */
.site-nav { border-bottom: 1px solid var(--hairline); }
.site-nav__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-4);
  padding-block: var(--space-3);
}
.site-nav__brand { font-family: var(--font-display); font-size: var(--text-row); font-weight: var(--weight-strong); color: var(--ink); text-decoration: none; }
.site-nav__links { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.site-nav__link { color: var(--meta); text-decoration: none; padding-block: var(--space-1); }
.site-nav__link:hover { color: var(--ink); }
.site-nav__link.is-active { color: var(--crimson); font-weight: var(--weight-strong); }
/* Sign in / Your account sits opposite the primary links; it wraps under them
   on narrow screens rather than squeezing the nav. When signed in it holds the
   Player Profile link and the Log out button side by side, so it is a flex row. */
.site-nav__auth { margin-inline-start: auto; display: flex; align-items: center; gap: var(--space-3); }
.site-nav__logout { margin: 0; }

/* --- Weather / play-status banner ------------------------------------------
   A quiet strip under the nav carrying Simon's manual light and, when it is
   available, the NWS forecast. No tint or fill: the status light's dot and label
   carry the colour, exactly as they do in a fixture row. */
.weather-banner { border-bottom: 1px solid var(--hairline); }
.weather-banner__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding-block: var(--space-2);
}
.weather-banner__status { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-2); }
.weather-banner__message { font-size: var(--text-meta); color: var(--meta); }
.weather-banner__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-2);
  margin-inline-start: auto;
  font-size: var(--text-meta);
  color: var(--meta);
}

/* --- Footer --------------------------------------------------------------- */
.site-footer { border-top: 1px solid var(--hairline); margin-top: var(--space-5); padding-block: var(--space-4); }
.site-footer__venue { color: var(--ink); }
.site-footer__links { margin-top: var(--space-2); font-size: var(--text-meta); }
.site-footer__sep { color: var(--warm-gray); }
.site-footer__meta { color: var(--meta); font-size: var(--text-meta); margin-top: var(--space-2); }

/* --- Fixture rows (no cards; hairline-separated, whole row clickable) ------ */
.fixture-list { border-top: 1px solid var(--hairline); }

/* Small date header separating month groups in a merged chronological list.
   Sized at meta, not at a heading scale: it is a divider, not a section title,
   and must not compete with the event names it sits above. Its heading LEVEL is
   set per page (h2 or h3) so the outline never skips — see fixture-list.php. */
.fixture-list__month {
  font-family: var(--font-sans);
  font-size: var(--text-meta);
  font-weight: var(--weight-strong);
  color: var(--meta);
  padding: var(--space-3) var(--space-2) var(--space-1);
  border-bottom: 1px solid var(--hairline);
}

.fixture-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--hairline);
  color: var(--ink);
  text-decoration: none;
  transition: background-color var(--transition);
}
.fixture-row:hover, .fixture-row.is-hover { background: var(--row-hover); }
.fixture-row:active, .fixture-row.is-active { background: var(--row-active); }

/* "League" / "Tournament" — crimson is the brand's fixture-label colour
   (5.77:1 on paper). Sentence case, never all-caps: an eyebrow label shouting
   over a headline is the exact AI tell CLAUDE.md calls out. */
.fixture-row__type {
  font-size: var(--text-meta);
  font-weight: var(--weight-strong);
  color: var(--crimson);
  line-height: 1.25;
}
.fixture-row__name { font-family: var(--font-display); font-size: var(--text-row); font-weight: var(--weight-strong); line-height: 1.25; color: var(--ink); }
/* The metadata is two quiet lines, not one run-on: line 1 is the day and dates,
   line 2 is format + price (see fixture-list.php). Stacking them lets a wordy
   season blurb wrap on its own line without disturbing the scannable top line.
   Both lines share --meta so a real sentence never drops below AA contrast. */
.fixture-row__meta {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--text-meta);
  line-height: 1.4;
  color: var(--meta);
  font-variant-numeric: tabular-nums;
}

/* The live registration cue — plain text, never a pill. Same semantic tokens as
   .status-light, so "registration open" reads the same colour whether it appears
   inline in a metadata line (league/tournament detail pages) or as the standalone
   left-column label on the listings (.fixture-row__status, below).
   Not coral: coral on paper is 3.22:1 and fails AA at 14px. */
.fixture-row__cue { font-weight: var(--weight-strong); }
.fixture-row__cue--open   { color: var(--ok); }
.fixture-row__cue--soon   { color: var(--warn); }
.fixture-row__cue--closed { color: var(--danger); }

/* On the listings the status cue IS the row's left-column label (it replaces the
   redundant "League" / "Tournament", since each listing is one kind of event).
   Meta scale, like the label it stands in for. On mobile it is the coloured
   eyebrow above the event name; from 768px up the grid moves it into the label
   column (see the media queries near the foot of this file). */
.fixture-row__status { font-size: var(--text-meta); line-height: 1.25; }

/* Quiet action links under a static fixture row (the /me dashboard's "Team page" /
   "Find a sub"). Meta scale so they read as secondary to the fixture name. */
.fixture-row__actions { font-size: var(--text-meta); }

/* --- Status light (dot + label; never a pill) ----------------------------- */
.status-light {
  --dot: var(--warm-gray);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-meta);
  font-weight: var(--weight-strong);
  color: var(--dot);
}
.status-light__dot { width: var(--space-2); height: var(--space-2); border-radius: 50%; background: var(--dot); flex: none; }
.status-light--open   { --dot: var(--ok); }
.status-light--soon   { --dot: var(--warn); }
.status-light--closed { --dot: var(--danger); }
/* Play status has its own vocabulary (the weather light is green/yellow/red, not
   open/soon/closed) but maps onto the same three semantic tokens. These exist so
   the weather banner never renders a modifier-less .status-light, which would
   fall back to --warm-gray and quietly fail AA. */
.status-light--green  { --dot: var(--ok); }
.status-light--yellow { --dot: var(--warn); }
.status-light--red    { --dot: var(--danger); }

/* --- Buttons -------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  font-weight: var(--weight-strong);
  line-height: 1.25;
  border: 1px solid transparent;
  border-radius: var(--radius);
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--transition), border-color var(--transition), color var(--transition);
}
/* Primary — coral fill, dark (--paper) label. */
.btn--primary { background: var(--coral); color: var(--on-accent); }
.btn--primary:hover, .btn--primary.is-hover  { background: var(--coral-hover); color: var(--on-accent); }
.btn--primary:active, .btn--primary.is-active { background: var(--coral-active); color: var(--on-accent); }
/* Secondary — ink outline, no fill. */
.btn--secondary { background: transparent; color: var(--ink); border-color: var(--ink); }
.btn--secondary:hover, .btn--secondary.is-hover  { background: var(--row-hover); color: var(--ink); }
.btn--secondary:active, .btn--secondary.is-active { background: var(--row-active); color: var(--ink); }
/* Disabled — both variants. */
.btn:disabled, .btn.is-disabled {
  background: var(--disabled-bg);
  color: var(--disabled-fg);
  border-color: transparent;
  cursor: not-allowed;
}

/* --- Form fields ---------------------------------------------------------- */
.field { display: flex; flex-direction: column; gap: var(--space-1); }
.field__label { font-size: var(--text-meta); font-weight: var(--weight-strong); }
.field__input {
  padding: var(--space-2) var(--space-3);
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--warm-gray); /* 3.74:1 — meets the 3:1 non-text floor */
  border-radius: var(--radius);
  transition: border-color var(--transition);
}
.field__input::placeholder { color: var(--meta); }
.field__input:hover, .field__input.is-hover { border-color: var(--ink); }
.field__input:focus-visible, .field__input.is-focus { border-color: var(--crimson); }
/* Textarea: same field treatment, but it must be resizable vertically and must
   not inherit the single-line height. */
.field__input--multiline { resize: vertical; min-height: 8rem; line-height: 1.5; }
.field__hint  { font-size: var(--text-meta); color: var(--meta); }
.field__error { font-size: var(--text-meta); color: var(--danger); }
.field--error .field__input { border-color: var(--danger); }
.field--error .field__label { color: var(--danger); }
.field__input:disabled, .field__input.is-disabled { background: var(--disabled-bg); color: var(--disabled-fg); cursor: not-allowed; }

/* --- Single-column pages (sign in, account, system messages) ---------------
   One narrow measure with even rhythm between blocks. The owl selector is the
   single source of truth for that rhythm — children set no margins of their
   own, so nothing here can cancel anything else out. */
.measure { max-width: 34rem; }
.measure > * + * { margin-top: var(--space-3); }

.form { display: flex; flex-direction: column; gap: var(--space-3); }
.form__actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.form__aside { font-size: var(--text-meta); color: var(--meta); }

/* System message block: a quiet bordered strip, not a card — no shadow, no tint
   beyond the standard row hover, one radius shared with the controls. */
.notice {
  padding: var(--space-3);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--row-hover);
}

/* --- Section titles -------------------------------------------------------- */
/* The ruled variant: a 1px hairline under a section heading. Used for the
   homepage's "Leagues and tournaments" and the detail-page sections. */
.section__title--ruled {
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--hairline);
}

/* Breadcrumb-ish link back to a listing, above a detail page's h1. Not an
   all-caps eyebrow — it is a real link at metadata scale. */
.page-eyebrow { font-size: var(--text-meta); margin-bottom: var(--space-2); }

.lede { color: var(--meta); max-width: 60ch; }

/* --- Empty states ----------------------------------------------------------
   Never a bare "no data": a statement plus a way onward. No skeletons, no
   shimmer — CLAUDE.md wants real states, not decorative placeholders. */
.empty-state {
  padding: var(--space-4) var(--space-2);
  border-bottom: 1px solid var(--hairline);
}
.empty-state__heading { font-size: var(--text-row); font-weight: var(--weight-strong); }
.empty-state__body { color: var(--meta); margin-block: var(--space-2); max-width: 60ch; }

/* --- Registered-teams list (league detail) ---------------------------------
   The "who is in" list a captain wants the moment they register, long before a
   standings row exists. The same square hairline ledger as every list here:
   names only, no fill, no tiles. */
.team-list { list-style: none; padding: 0; border-top: 1px solid var(--hairline); }
.team-list__item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-1) var(--space-2);
  padding: var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--hairline);
  font-weight: var(--weight-strong);
  font-size: var(--text-row);
}
/* The roster sits in the blank space to the right of the team name, quiet metadata
   next to the strong name. On a narrow screen it wraps below the name rather than
   crowding it. */
.team-list__roster {
  flex: 1 1 auto;
  min-width: 0;
  text-align: right;
  font-weight: 400;
  font-size: var(--text-meta);
  color: var(--meta);
  line-height: 1.4;
}

/* --- Past-seasons archive (leagues / tournaments) --------------------------
   Deliberately quieter than a fixture row: a finished season has no price and no
   coral cue, so this is a plain linked ledger row. The whole row is the link and
   hover is a background shift only, as the fixture rows do. */
.archive-list { list-style: none; padding: 0; border-top: 1px solid var(--hairline); }
.archive-list__item { border-bottom: 1px solid var(--hairline); }
.archive-list__link {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-3) var(--space-2);
  color: var(--ink);
  text-decoration: none;
  transition: background-color var(--transition);
}
.archive-list__link:hover { background: var(--row-hover); }
.archive-list__name { font-weight: var(--weight-strong); }
.archive-list__meta { color: var(--meta); font-size: var(--text-meta); }

/* --- Offseason mailing-list box (homepage) ---------------------------------
   The empty state that does something: one email field and a button, reusing
   .field and .btn so nothing new is themed. Only the input-plus-button row is a
   new layout. It wraps to a stacked, full-width button on a narrow screen. */
.season-signup { padding: var(--space-4) var(--space-2); border-bottom: 1px solid var(--hairline); }
.season-signup__heading { font-size: var(--text-row); font-weight: var(--weight-strong); }
.season-signup__body { color: var(--meta); margin-block: var(--space-2); max-width: 60ch; }
.season-signup__form { margin-top: var(--space-3); max-width: 34rem; }
.season-signup__row { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: stretch; }
.season-signup__row .field__input { flex: 1 1 16rem; }

/* --- Tables (standings) ----------------------------------------------------
   Standings are genuinely tabular, so this is a real <table> with header cells —
   a screen reader announces "Team, wins 5" instead of reading loose numbers.
   Styled as the same hairline ledger as every other list here: square, no fill,
   no zebra striping. */
.table-scroll { overflow-x: auto; }
.table {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
}
.table th, .table td {
  padding: var(--space-2);
  border-bottom: 1px solid var(--hairline);
}
.table thead th {
  font-size: var(--text-meta);
  font-weight: var(--weight-strong);
  color: var(--meta);
  white-space: nowrap;
}
.table__team { font-weight: var(--weight-strong); }
.table__num { text-align: right; font-variant-numeric: tabular-nums; }
/* A quiet second line inside a cell (a roster under a team, a date under an event). */
.table__sub { display: block; font-size: var(--text-meta); font-weight: var(--weight-regular); color: var(--meta); }
.table__muted { color: var(--meta); }
/* The column the table is currently sorted by, so the eye lands on it. */
.table__num--lead { font-weight: var(--weight-strong); }

/* Live-updating regions (standings that refresh without a reload). The caption is
   inserted by live-standings.js, so it shows only when the auto-update is running;
   with JavaScript off it is absent and the page still updates on a manual refresh. */
.live-note { margin-block: 0 var(--space-2); color: var(--meta); font-size: var(--text-meta); }
.live-note::before {
  content: ""; display: inline-block; width: 8px; height: 8px;
  margin-inline-end: var(--space-1); border-radius: 50%;
  background: var(--warm-gray); vertical-align: middle;
}
/* A brief settle when a region's contents change, so the eye notices the update. */
@media (prefers-reduced-motion: no-preference) {
  [data-live-region] { transition: background-color 300ms ease-out; }
  [data-live-updated] { background-color: var(--row-hover); }
}

/* The "Saving… / Saved / error" line next to an async form (async-save.js). It is
   empty until a save runs, so it takes no room on a fresh page; meta-sized so it sits
   quietly beside a Save button rather than shouting. */
.async-status {
  font-size: var(--text-meta);
  color: var(--meta);
  margin-inline-start: var(--space-1);
}
.async-status:empty { display: none; }
.async-status--ok    { color: var(--ok); }
.async-status--error { color: var(--danger); }
.async-status--pending { color: var(--meta); }

/* The "Save all" bar at the foot of a multi-row admin section (scores, standings,
   schedule). A single primary button plus its own status line, set off from the
   rows above with a little space. Its status line is not empty-collapsed, so the
   left margin stays consistent while a batch runs. */
.save-all {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-top: var(--space-2);
}

/* --- Rankings -------------------------------------------------------------- */
/* The published formula: a .notice strip carrying the rule in plain English, its
   list tightened so it reads as one block, not a bulleted form. */
.rankings-formula > * + * { margin-top: var(--space-2); }
.rankings-formula ul { margin: 0; padding-left: var(--space-3); }
.rankings-formula li + li { margin-top: var(--space-1); }

/* Search: one field and a button on a line, wrapping to stacked on a narrow screen. */
.rankings-search { margin: var(--space-4) 0; max-width: 34rem; }
.rankings-search__row { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: stretch; margin-top: var(--space-1); }
.rankings-search__row .field__input { flex: 1 1 12rem; }

/* --- Finalise-results point grid (admin) ----------------------------------- */
.results-points { margin: var(--space-3) 0; }
.results-points summary { cursor: pointer; font-weight: var(--weight-strong); }
.results-points__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--space-3);
  margin-top: var(--space-3);
}

/* --- Match rows (schedule / bracket) --------------------------------------- */
.match-list { border-top: 1px solid var(--hairline); list-style: none; padding: 0; }
.match-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--hairline);
}
.match-row__teams { font-weight: var(--weight-strong); }
.match-row__vs { color: var(--meta); font-weight: var(--weight-regular); }
.match-row__meta { font-size: var(--text-meta); color: var(--meta); font-variant-numeric: tabular-nums; }
.match-row__score { margin-inline-start: auto; font-variant-numeric: tabular-nums; }
.match-row__tag { font-size: var(--text-meta); font-weight: var(--weight-regular); color: var(--crimson); }

/* The living-schedule team filter on the league page: a wrapped row of links, the
   current one filled in the brand crimson. Links (not a JS select) so it works with
   JavaScript off and every filtered view is a real, shareable URL. */
.schedule-filter { display: flex; flex-wrap: wrap; gap: var(--space-1); margin-bottom: var(--space-3); }
.schedule-filter__link {
  font-size: var(--text-meta);
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  color: var(--ink);
  text-decoration: none;
}
.schedule-filter__link:hover, .schedule-filter__link.is-hover { border-color: var(--ink); }
.schedule-filter__link.is-active { background: var(--crimson); border-color: var(--crimson); color: var(--on-accent); }

/* While a filtered view is being fetched in place (schedule-filter.js), dim the
   region slightly so the change registers. Fast and quiet; reduced-motion safe
   as it is a plain opacity swap, not an animation. */
.schedule-live[data-loading] { opacity: 0.55; transition: opacity 150ms ease-out; }

/* --- Player list -----------------------------------------------------------
   "Check who's playing". Names only — first name + last initial, enforced in
   PHP. Plain text in columns rather than rows: a 40-player tournament as 40
   bordered rows would be a wall, and there is nothing to click. */
.player-list {
  list-style: none;
  padding: 0;
  margin-top: var(--space-3);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
  gap: var(--space-2) var(--space-3);
}
.player-list__item { font-variant-numeric: tabular-nums; }
/* The player's self-rating (AA/A/BB/B/Rec) beside their name: quiet metadata, so the
   name stays primary and the level reads as a note (owner 2026-08-08). */
.player-list__skill { color: var(--meta); font-size: var(--text-meta); margin-left: var(--space-1); }
/* The waitlist / not-yet-confirmed group under the confirmed players: a quieter
   subheading and muted names, so it reads as "signed up, not counting toward the cap
   yet" without hiding anyone (owner 2026-08-08). */
.section__title--sub { font-size: var(--text-row); margin: var(--space-4) 0 0; }
.player-list__count { color: var(--meta); font-weight: var(--weight-regular); }
.player-list--pending { color: var(--meta); }

/* --- Tournament divisions --------------------------------------------------
   Pool play runs inside two divisions; each is a standings table with its own
   games beneath it. Stacked on every width — the pack asks for pools as stacked
   tables, not a side-by-side grid that would crush at 360px. */
.division + .division { margin-top: var(--space-4); }
.division__title { font-size: var(--text-row); font-weight: var(--weight-strong); margin-bottom: var(--space-2); }
.division__games { margin-top: var(--space-2); }

/* --- Tournament bracket -----------------------------------------------------
   The combined single-elim playoff, seeded from both divisions. Rounds are
   columns laid left to right; the whole ladder scrolls sideways inside its own
   box (.bracket-scroll) so a deep bracket never widens the page. Match nodes are
   bordered rows with the one site radius, never floating tiles. */
.bracket-scroll { overflow-x: auto; padding-bottom: var(--space-2); }
.bracket {
  display: flex;
  gap: var(--space-4);
  align-items: stretch;
  list-style: none;
  margin: 0;
  padding: 0;
  min-width: min-content;
}
.bracket__round {
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  gap: var(--space-3);
  flex: 0 0 auto;
  min-width: 12rem;
}
.bracket__round-title {
  font-size: var(--text-meta);
  font-weight: var(--weight-strong);
  color: var(--meta);
  margin: 0 0 var(--space-1);
}
.bracket__matches {
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  gap: var(--space-3);
  flex: 1 1 auto;
  list-style: none;
  margin: 0;
  padding: 0;
}
.bracket__match {
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
.bracket__team {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2);
  color: var(--meta);
  font-variant-numeric: tabular-nums;
}
.bracket__team + .bracket__team { border-top: 1px solid var(--hairline); }
.bracket__team--won { color: var(--ink); font-weight: var(--weight-strong); }
.bracket__team--won .bracket__score { color: var(--crimson); }
.bracket__name--tbd { font-style: italic; }
.bracket__note {
  margin: 0;
  padding: 0 var(--space-2) var(--space-2);
  font-size: var(--text-meta);
  color: var(--meta);
}

/* --- Draw preview (admin) ---------------------------------------------------
   The proposed teams from a blind draw, shown as bordered cards in a responsive
   grid. Admin-only, so this is the one place a plain grid of boxes is right: it is
   a working view, not a public listing. */
.draw-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: var(--space-3);
  margin-top: var(--space-2);
}
.draw-team {
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: var(--space-3);
}
.draw-team__name { font-weight: var(--weight-strong); margin-bottom: var(--space-2); }
/* Editable team name. The input wears the same weight as the .draw-team__name it
   replaces and hides its own chrome, so it reads as the heading until you hover or
   focus it, when a border says "this is a field". The Save button is folded away by
   team-rename.js until the name actually changes (and shows, working, with no JS). */
.draw-rename { display: flex; align-items: center; gap: var(--space-1); margin-bottom: var(--space-2); }
.draw-rename__input {
  flex: 1 1 auto;
  min-width: 0;
  font: inherit;
  font-weight: var(--weight-strong);
  color: var(--ink);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: var(--space-1);
  margin: 0;
}
.draw-rename__input:hover { border-color: var(--hairline); }
.draw-rename__input:focus { outline: none; border-color: var(--crimson); background: var(--row-hover); }
.draw-rename__save { flex: none; }
.draw-team__players { list-style: none; margin: 0; padding: 0; }
.draw-team__players li + li { margin-top: var(--space-2); }
.draw-team__level { display: block; font-size: var(--text-meta); color: var(--meta); }
/* The per-player "move to another team" control on a saved draft. */
.draw-move { display: flex; gap: var(--space-1); margin-top: var(--space-1); }
.draw-move .field__input--select { font-size: var(--text-meta); padding: var(--space-1); }

/* --- Honeypot --------------------------------------------------------------
   Off-screen rather than display:none — some bots skip hidden inputs, and this
   only works if they fill it in. aria-hidden + tabindex="-1" in the markup keep
   it away from screen readers and the keyboard, so no real visitor meets it. */
.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Key/value rows, same hairline ledger idiom as the fixture list. */
.detail-list { border-top: 1px solid var(--hairline); }
.detail-list__row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  padding-block: var(--space-2);
  border-bottom: 1px solid var(--hairline);
}
.detail-list__term { flex: none; min-width: 8rem; font-size: var(--text-meta); color: var(--meta); }
.detail-list__value { min-width: 0; overflow-wrap: anywhere; }
.detail-list__note { display: block; font-size: var(--text-meta); color: var(--meta); }

/* --- FAQ ("Common questions" on an event page) ----------------------------
   Q&A sourced from the event's attached content blocks, and the source of the
   page's FAQPage structured data. A plain hairline-separated list, the same
   idiom as the detail and team lists — deliberately NOT an accordion: no motion,
   no JavaScript, and open text is what answer engines read most reliably. */
.faq { border-top: 1px solid var(--hairline); }
.faq__item {
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--hairline);
}
.faq__q { font-weight: var(--weight-strong); margin-bottom: var(--space-1); }
.faq__a { margin: 0; max-width: 70ch; } /* dd carries a default indent; the hairline list owns the rhythm */

/* ==========================================================================
   Registration — forms, roster, running total, checkout.
   Every value below is a token. No new colours, no new spacing steps.
   ========================================================================== */

/* Quiet button: the "Remove" on a roster row. Not a third button style for its
   own sake — a row needs a control that is reachable but does not compete with
   the one primary action on the page, so it is metadata-coloured text with a
   button's hit area. Inherits the global focus ring like any other .btn. */
.btn--quiet {
  padding: var(--space-1) var(--space-2);
  background: transparent;
  color: var(--meta);
  font-size: var(--text-meta);
  font-weight: var(--weight-regular);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.btn--quiet:hover, .btn--quiet.is-hover { color: var(--danger); }

/* Native select, wearing the .field__input treatment. appearance:none strips the
   OS widget so the border and radius match every other control; the arrow is an
   inline SVG data URI (no extra request, no icon font dependency for a form to
   be usable). Its stroke is the literal value of --meta (#B1A5A7): a data URI cannot
   read a CSS custom property, so if --meta ever changes, change this hex to match.
   The old #6E5F62 was a light-theme leftover and read as near-invisible on the dark
   input fill. padding-inline-end keeps the text off the arrow. */
.field__input--select {
  appearance: none;
  -webkit-appearance: none;
  padding-inline-end: var(--space-5);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23B1A5A7' stroke-width='1.5'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  background-size: 1rem;
}

/* A shown-but-unpickable option, which the sub picker uses for anyone the skill or
   sex rule blocks: they stay in the list so their absence is never a mystery, greyed
   so it is obvious they are not an answer. Browsers already grey a disabled option,
   but the exact tone is the OS's rather than ours, and on the dark fill some of them
   land close enough to the live options to be missed. --meta is the token the rest of
   the site uses for "present, secondary", which is exactly this. */
.field__input--select option:disabled { color: var(--meta); }

/* An error-toned notice. Same bordered strip as .notice, retoned — a page-level
   "some fields need another look" must not look like a neutral hint. */
.notice--error { border-color: var(--danger); }

/* Registration forms are long, so they group into titled sections with more air
   between them than between fields. The gap on .form handles within-section
   rhythm; this handles between-section. */
.form--register { gap: var(--space-5); }
.form__section { display: flex; flex-direction: column; gap: var(--space-3); }
.form__section-title {
  font-size: var(--text-row);
  font-weight: var(--weight-strong);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--hairline);
}
.form__section-note { font-size: var(--text-meta); color: var(--meta); max-width: 60ch; }

/* Roster: a dense list, so bordered rows — never rounded tiles (CLAUDE.md). */
.roster { list-style: none; padding: 0; margin: 0; border-top: 1px solid var(--hairline); }
.roster__row {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--hairline);
}
.roster__index { font-size: var(--text-meta); font-weight: var(--weight-strong); color: var(--meta); }
.roster__remove { display: flex; justify-content: flex-end; }
.roster__add { margin-top: var(--space-3); }

/* The running total. A bordered strip like .notice, not a card: no shadow, no
   tint beyond the standard row hover, the one shared radius. */
.quote {
  padding: var(--space-3);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--row-hover);
}
.quote__title { font-size: var(--text-row); font-weight: var(--weight-strong); }
.quote__lines { margin-top: var(--space-3); }
.quote__line {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-1) var(--space-3);
  padding-block: var(--space-2);
  border-bottom: 1px solid var(--hairline);
}
.quote__label { min-width: 0; }
.quote__detail { display: block; font-size: var(--text-meta); color: var(--meta); }
.quote__multiplier { color: var(--meta); font-variant-numeric: tabular-nums; }
.quote__amount { font-variant-numeric: tabular-nums; white-space: nowrap; }
.quote__total {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  align-items: baseline;
  padding-top: var(--space-3);
}
.quote__total-label { font-weight: var(--weight-strong); }
/* The total is the number the whole form exists to produce, so it gets the
   fixture-row size — the largest thing in the block, without a colour that would
   spend the coral reserved for the CTA below it. */
.quote__total-amount {
  font-size: var(--text-row);
  font-weight: var(--weight-strong);
  font-variant-numeric: tabular-nums;
}
.quote__aside { margin-top: var(--space-2); font-size: var(--text-meta); color: var(--meta); }
.quote__aside--stale { color: var(--warn); }
/* The free agent variant: no lines, no total, just the explanation. */
.quote--none .quote__aside { margin-top: var(--space-2); }

/* Waiver. The release text sits in a quiet bordered strip above the tick, the
   same .notice treatment used elsewhere: readable, not a card, no shadow. The
   checkbox and its label then sit on one line, with the label clickable — a 16px
   box is a small target, and the label is the rest of the hit area. */
.waiver__terms {
  padding: var(--space-3);
  margin-bottom: var(--space-3);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--row-hover);
  font-size: var(--text-meta);
}
.waiver__terms > * + * { margin-top: var(--space-2); }
.waiver__heading { font-weight: var(--weight-strong); }
.waiver__list { margin: 0; padding-left: var(--space-3); }
.waiver__list > li + li { margin-top: var(--space-1); }
.waiver__row { display: flex; align-items: flex-start; gap: var(--space-2); }
.waiver__box { flex: none; margin-top: var(--space-1); width: var(--space-3); height: var(--space-3); accent-color: var(--crimson); }
.waiver__label { cursor: pointer; }
.field--error .waiver__label { color: var(--danger); }

/* Checkout block. Provider-rendered markup (see src/Payments/), so these classes
   are a contract with those classes — grep before renaming. */
.checkout {
  padding: var(--space-3);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
.checkout__title { font-size: var(--text-h2); }
.checkout__facts { margin-top: var(--space-3); border-top: 1px solid var(--hairline); }
.checkout__fact {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  padding-block: var(--space-2);
  border-bottom: 1px solid var(--hairline);
}
.checkout__term { flex: none; min-width: 8rem; font-size: var(--text-meta); color: var(--meta); }
.checkout__value { min-width: 0; overflow-wrap: anywhere; }
.checkout__value--strong { font-weight: var(--weight-strong); font-variant-numeric: tabular-nums; }
/* "required" against the note label. Not a pill — plain small text, per the
   metadata rules — but in --danger, because a Venmo payment that arrives without
   this note is genuinely unmatchable. */
.checkout__required { color: var(--danger); font-weight: var(--weight-regular); }
/* The note itself is the one thing on the page that must be copied verbatim, so
   it is set apart as code: monospace, on the surface fill, selectable. */
.checkout__note {
  display: inline-block;
  padding: var(--space-1) var(--space-2);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  user-select: all;
}
.checkout__hint { display: block; margin-top: var(--space-1); font-size: var(--text-meta); color: var(--meta); }
.checkout__actions { margin-top: var(--space-3); }
.checkout__aside { margin-top: var(--space-3); font-size: var(--text-meta); color: var(--meta); }
.checkout--paid { border-color: var(--ok); }

/* Folded-away checkout on the /me dashboard: a pending registration can re-open its
   Venmo instructions without the block dominating a list of otherwise-paid rows.
   The .checkout inside keeps its own styling; this only handles the disclosure. */
.reg-checkout { margin-top: var(--space-2); }
.reg-checkout__summary {
  font-size: var(--text-meta);
  font-weight: var(--weight-strong);
  color: var(--crimson);
  cursor: pointer;
}
.reg-checkout__body { margin-top: var(--space-2); }

/* Numbered "what happens next" list. Bordered rows again, not tiles. */
.next-steps { margin: 0; padding-inline-start: var(--space-4); border-top: 1px solid var(--hairline); }
.next-steps__step { padding-block: var(--space-2); border-bottom: 1px solid var(--hairline); }

/* The register CTA on an event page. No border and no fill — it is the one coral
   button on the view and boxing it in would only compete with it. */
.register-cta { display: flex; flex-direction: column; gap: var(--space-3); }
.register-cta__aside { font-size: var(--text-meta); color: var(--meta); }

/* The "log in and we'll fill this in" invitation, and the copy-a-roster list.
   Both are .notice strips — an offer sitting beside the form, not a step in it. */
.login-nudge { font-size: var(--text-meta); }
.login-nudge__text { font-weight: var(--weight-strong); }

.roster-copy__lead { font-size: var(--text-meta); font-weight: var(--weight-strong); }
.roster-copy__list { list-style: none; padding: 0; margin-top: var(--space-2); }
.roster-copy__list > li + li { margin-top: var(--space-1); }
.roster-copy__meta { font-size: var(--text-meta); color: var(--meta); }

/* A TBD spot: legible as deliberately empty rather than unfinished. The label
   carries the signal; the row itself stays a plain bordered row like the others,
   because tinting it would read as an error. */
.roster__optional {
  font-weight: var(--weight-regular);
  color: var(--meta);
  text-transform: none;
}
.roster__row--tbd .roster__index { color: var(--meta); }

/* Roster ledger on the team page. Bordered rows, same idiom as the fixture list. */
.roster-list { list-style: none; padding: 0; border-top: 1px solid var(--hairline); }
.roster-list__row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding-block: var(--space-2);
  border-bottom: 1px solid var(--hairline);
}
.roster-list__name { font-weight: var(--weight-strong); }
.roster-list__meta { font-size: var(--text-meta); color: var(--meta); overflow-wrap: anywhere; }
.roster-list__state { margin-inline-start: auto; font-size: var(--text-meta); }
.roster-list__action { font-size: var(--text-meta); }
.roster-list__pending { color: var(--meta); }
.roster-list__row--empty .roster-list__name { color: var(--meta); font-weight: var(--weight-regular); }

/* The join link. The input carries the link as real selectable text — the button
   is a convenience on top, so this stacks rather than making the input a slave to
   it. Monospace so a captain can eyeball the token if they ever need to. */
.share-link { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.share-link__input {
  flex: 1 1 18rem;
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--text-meta);
}
.share-link__button { flex: none; }

/* A fixture row that is not a link (a team you play for but do not captain).
   Shares the row's layout and hairline, but none of its interactive affordances —
   no hover shift, no pointer, because there is nothing to click. */
.fixture-row--static { cursor: default; }
.fixture-row--static:hover { background: transparent; }

/* --- Past-performances toggle (Player Profile) -----------------------------
   A CSS-only tab switch: two radios drive which panel shows, so Leagues vs
   Tournaments works with no JavaScript and no page reload. The radios are the
   real control (visually hidden, still focusable and arrow-key navigable); the
   labels are the visible tabs, styled like the schedule filter. */
.perf__radio { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.perf__tabs { display: flex; flex-wrap: wrap; gap: var(--space-1); margin-bottom: var(--space-3); }
.perf__tab {
  font-size: var(--text-meta);
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  color: var(--ink);
  cursor: pointer;
}
.perf__tab:hover { border-color: var(--ink); }
.perf__panel { display: none; }
/* The checked radio lights its tab and reveals its panel (both later siblings). */
#perf-tab-leagues:checked ~ .perf__tabs .perf__tab[for="perf-tab-leagues"],
#perf-tab-tournaments:checked ~ .perf__tabs .perf__tab[for="perf-tab-tournaments"] {
  background: var(--crimson); border-color: var(--crimson); color: var(--on-accent);
}
#perf-tab-leagues:checked ~ .perf__panel--leagues,
#perf-tab-tournaments:checked ~ .perf__panel--tournaments { display: block; }
/* Focus rides the hidden radio; mirror a visible ring onto its own tab. */
#perf-tab-leagues:focus-visible ~ .perf__tabs .perf__tab[for="perf-tab-leagues"],
#perf-tab-tournaments:focus-visible ~ .perf__tabs .perf__tab[for="perf-tab-tournaments"] {
  outline: 2px solid var(--focus-ring); outline-offset: 2px;
}

/* ==========================================================================
   Admin.

   Same design system, no exceptions: the five spacing values, the one radius, no
   shadows, no new colours. It reuses .fixture-row, .table, .field and .notice
   wherever they fit — what is here is only what genuinely had no equivalent.

   The one thing this area does differently is DENSITY. A public page is read; an
   admin page is scanned, at speed, looking for the one row that needs a decision.
   So the rows are tighter and the metadata is tabular — but they are still
   bordered rows, never cards, per CLAUDE.md.
   ========================================================================== */

.admin-nav { border-bottom: 1px solid var(--hairline); background: var(--row-hover); }
.admin-nav__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding-block: var(--space-2);
}
/* Not the coral: this is a label, not a call to action, and the accent is spent
   on the one primary button per view. Crimson is the brand's own labelling job. */
.admin-nav__mark {
  font-weight: var(--weight-strong);
  color: var(--crimson);
  font-size: var(--text-meta);
  letter-spacing: 0.02em;
}
.admin-nav__link { color: var(--meta); text-decoration: none; font-size: var(--text-meta); padding-block: var(--space-1); }
.admin-nav__link:hover { color: var(--ink); }
.admin-nav__link.is-active { color: var(--crimson); font-weight: var(--weight-strong); }
.admin-nav__who { margin-inline-start: auto; font-size: var(--text-meta); color: var(--meta); overflow-wrap: anywhere; }
/* A queue count beside a nav link (subs waiting, unpaid holds). Plain, per the brief:
   a count in the brand crimson, not a red-alert bubble. */
.admin-nav__badge {
  display: inline-block; min-width: 1.25rem; padding: 0 var(--space-1);
  border-radius: var(--radius); background: var(--crimson); color: var(--on-accent);
  font-size: var(--text-meta); font-weight: var(--weight-strong); text-align: center;
}

/* The play-status strip at the top of the ops home: the current light, its message, and
   a one-click way to change it. A hairline row, not a card (rows, not tiles). */
.ops-status {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-3);
  margin-top: var(--space-3); padding: var(--space-2) 0; border-block: 1px solid var(--hairline);
}
.ops-status__msg { font-weight: var(--weight-strong); }
.ops-status__set { margin-inline-start: auto; }

/* A page heading with an action parked on the right (New event, Duplicate). */
.admin-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
}
.admin-head__actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* Collapsible admin section. The draw tool stacks a lot in one column — a preview, the
   physical-draw recorder, the buckets, and a roster grouped bucket by bucket — so each
   block folds behind its own heading and the page opens short. Native <details>: no
   JavaScript, keyboard reachable, and nothing to animate, so reduced motion is honoured
   for free. The summary is styled to read as the same ruled h2 it stands in for
   (uppercase Overpass, hairline under), and keeps its native disclosure triangle as the
   open/closed cue. */
.admin-collapse > summary {
  cursor: pointer;
  font-family: var(--font-display);
  font-size: var(--text-h2);
  font-weight: 700;
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--hairline);
  list-style: none; /* the native triangle is small and low-contrast; drawn chevron below */
}
.admin-collapse > summary::-webkit-details-marker { display: none; }
/* A drawn chevron in the heading's OWN colour, in place of the muted native triangle,
   so it reads as a control rather than being missed. Points right when closed, rotates
   down when open (the global reduced-motion reset snaps it instead of animating). */
.admin-collapse > summary::before {
  content: "";
  display: inline-block;
  flex: none;
  width: 0.5em;
  height: 0.5em;
  margin-right: var(--space-2);
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--transition);
  vertical-align: middle;
  position: relative;
  top: -0.09em;
}
.admin-collapse[open] > summary::before { transform: rotate(45deg); }
.admin-collapse > summary:hover { color: var(--crimson); }
/* The headcount rides along in the heading but stays body-scale metadata, not part of
   the shout. */
.admin-collapse__count {
  font-family: var(--font-sans);
  font-size: var(--text-meta);
  font-weight: var(--weight-regular);
  text-transform: none;
  letter-spacing: 0;
  color: var(--meta);
}
/* Space under the heading only while the section is open, so a closed row is just the
   ruled title with nothing dangling below it. */
.admin-collapse[open] > summary { margin-bottom: var(--space-3); }

/* A collapsible nested one level down — the per-bucket rosters folded inside the
   Buckets section. Its heading is quieter than the section's: row scale and sentence
   case, not the uppercase shout, so the nesting reads at a glance. */
.draw-roster { margin-top: var(--space-3); }
.draw-roster > .form__aside { margin-bottom: var(--space-2); }
.admin-collapse--nested { margin-top: var(--space-3); }
.admin-collapse--nested > summary {
  font-size: var(--text-row);
  font-weight: var(--weight-strong);
  text-transform: none;
  letter-spacing: 0;
}
.admin-collapse--nested[open] > summary { margin-bottom: var(--space-2); }

/* Previous/next-step nav at the foot of each event workflow page (partial:
   admin-workflow-nav.php). A ruled band: the direction in quiet metadata, the stage
   name at row scale so the eye lands on where it is going. The two ends hold their
   place even when one is empty (first/last stage), so the other never jumps sides. */
.workflow-nav { border-top: 1px solid var(--hairline); padding-top: var(--space-3); }
.workflow-nav__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
}
.workflow-nav__link {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  flex: 1 1 8rem;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--ink);
}
a.workflow-nav__link:hover { background: var(--row-hover); }
a.workflow-nav__link:hover .workflow-nav__step { color: var(--crimson); }
.workflow-nav__link--next { text-align: right; }
/* The placeholder for a missing neighbour: no border, no hover, but the same flex
   basis so the real link opposite it keeps its edge of the row. */
.workflow-nav__link--empty { border-color: transparent; background: none; pointer-events: none; }
.workflow-nav__dir { font-size: var(--text-meta); color: var(--meta); }
.workflow-nav__step { font-family: var(--font-display); font-size: var(--text-row); font-weight: var(--weight-strong); }
.workflow-nav__progress { flex: 0 0 auto; font-size: var(--text-meta); color: var(--meta); text-align: center; }

/* The workhorse row. Bordered, whole-row hairline, no tile. */
.admin-list { list-style: none; padding: 0; margin: 0; border-top: 1px solid var(--hairline); }
.admin-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--hairline);
}
.admin-row__name { font-size: var(--text-row); font-weight: var(--weight-strong); color: var(--ink); text-decoration: none; }
a.admin-row__name:hover { color: var(--crimson); }
.admin-row__meta { font-size: var(--text-meta); color: var(--meta); font-variant-numeric: tabular-nums; }
.admin-row__meta--block { flex-basis: 100%; }
.admin-row__actions { margin-inline-start: auto; display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }

/* A captain's per-player cover form, folded behind the player's name. One form each
   because the eligible subs differ per player (level and sex both compare against the
   person going missing), so a roster of four is four lists, not one. Folded so it
   reads as four names rather than four forms. */
.cover-picker { border-bottom: 1px solid var(--hairline); }
.cover-picker summary {
  cursor: pointer;
  padding-block: var(--space-2);
  font-weight: var(--weight-strong);
}
.cover-picker summary:hover { color: var(--crimson); }
.cover-picker__level { font-weight: 400; color: var(--meta); font-size: var(--text-meta); }
.cover-picker[open] { padding-block-end: var(--space-3); }

/* The sub swap: who is out, who is in. Reuses .detail-list wholesale — it is already
   the key/value hairline idiom and this is a key/value pair. The only thing needed on
   top is the full-width break inside .admin-row's flex, and a narrower term column:
   "Out" and "In" do not want the 8rem the detail lists use for "Registration state". */
.sub-swap { flex-basis: 100%; border-top: 0; }
.sub-swap .detail-list__row { padding-block: var(--space-1); border-bottom: 0; }
.sub-swap .detail-list__term { min-width: 3rem; }

/* "Someone else": the candidate select, folded away when a name is already on the
   request. A closed <details> still submits the select inside it, so approving a
   proposal stays one click and no JavaScript is involved. */
.sub-swap__form { flex-basis: 100%; min-width: 0; }
.sub-swap__picker { min-width: 0; }
.sub-swap__picker summary {
  cursor: pointer;
  font-size: var(--text-meta);
  color: var(--meta);
  padding-block: var(--space-1);
}
.sub-swap__picker summary:hover { color: var(--crimson); }
.sub-swap__picker[open] summary { margin-block-end: var(--space-1); }
/* width:100% because .field__input carries no width of its own — everywhere else on
   the site a .field flex column or .admin-search__field's `flex: 1 1 16rem` is what
   stretches it. Here the select sits in ordinary block flow inside a <details>, so it
   would fall back to its INTRINSIC width, and a select's intrinsic width is its
   longest option: "Isabel Cruz (BB) — too strong: BB is stronger than B" would push
   the row off a 360px screen. Same trap as .score-row__pair above. */
.sub-swap__picker .field__input { width: 100%; }
.sub-swap__form .btn { margin-block-start: var(--space-2); }

/* Event status. Plain text in the semantic tokens, never a pill — same idiom as
   .fixture-row__cue, which these deliberately mirror. */
.admin-state { font-size: var(--text-meta); font-weight: var(--weight-strong); }
.admin-state--open      { color: var(--ok); }
.admin-state--draft     { color: var(--warn); }
.admin-state--waitlist  { color: var(--crimson); }
.admin-state--closed,
.admin-state--completed { color: var(--meta); }
.admin-state--voided,
.admin-state--cancelled { color: var(--danger); }

/* An unpaid hold's age. The 48h+ signal is carried by the WORDS ("51 hours"),
   with colour and weight only reinforcing it — colour is never the only channel,
   so this still reads correctly in greyscale and to a screen reader. */
.hold-age { font-variant-numeric: tabular-nums; }
.hold-age--stale { color: var(--danger); font-weight: var(--weight-strong); }

/* Money, right-aligned and tabular so columns of it line up and can be added by
   eye. The single most-scanned thing on the dashboard. */
.admin-money { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* The builder. One column on a phone; two where there is room (see 768px). */
.admin-form { display: flex; flex-direction: column; gap: var(--space-5); }
/* min-width: 0 is load-bearing, not defensive. Every browser ships a UA default of
   min-inline-size: min-content on <fieldset> — the ONE element that has it — so a
   fieldset silently refuses to shrink below its widest child and pushes the page
   sideways. Measured at 360px: the builder's fieldsets rendered 347px and 350px
   inside a 328px container, scrolling the whole page 6px. Zeroing border/padding
   does not touch it; only resetting the min size does. */
.admin-fieldset { border: 0; padding: 0; margin: 0; min-width: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.admin-fieldset__legend {
  font-size: var(--text-row);
  font-weight: var(--weight-strong);
  padding: 0 0 var(--space-2);
  border-bottom: 1px solid var(--hairline);
  width: 100%;
}
.admin-fieldset__note { font-size: var(--text-meta); color: var(--meta); max-width: 60ch; }
/* minmax(0, 1fr), NOT 1fr. A bare 1fr means minmax(AUTO, 1fr), and that auto floor
   forbids the track from shrinking below its widest item's min-content width. For a
   <select> min-content is the width of its LONGEST OPTION — so "Captains register a
   whole team" set a 350px floor on the whole column, and every field in the grid
   inherited it. At 360px that scrolled the builder sideways.
   The min-width: 0 on the children is the other half: a grid item's own automatic
   minimum would re-impose the same floor even once the track is free to shrink. */
.admin-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-3); }
.admin-grid > *, .admin-grid .field { min-width: 0; }
/* An input/select may then be narrower than its content wants; the text ellipsises
   or scrolls inside the control rather than the page scrolling. */
.admin-grid .field__input { min-width: 0; max-width: 100%; }
.admin-grid__wide { grid-column: 1 / -1; }

/* Checkbox lists: the content blocks, and the blast opt-in. Bordered rows again. */
.admin-checks { list-style: none; padding: 0; margin: 0; border-top: 1px solid var(--hairline); }
.admin-check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding-block: var(--space-2);
  border-bottom: 1px solid var(--hairline);
}
.admin-check__box { flex: none; margin-top: var(--space-1); width: var(--space-3); height: var(--space-3); accent-color: var(--crimson); }
.admin-check__label { cursor: pointer; }
.admin-check__note { display: block; font-size: var(--text-meta); color: var(--meta); }

/* Score entry: one compact form per match. The two score boxes are small on
   purpose — a 21 does not need a 20rem field, and narrow boxes read as a score. */
.score-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-2) var(--space-3);
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--hairline);
}
.score-row__teams { flex: 1 1 14rem; min-width: 0; }
.score-row__team { font-weight: var(--weight-strong); }
.score-row__vs { color: var(--meta); font-weight: var(--weight-regular); }
.score-row__meta { display: block; font-size: var(--text-meta); color: var(--meta); font-variant-numeric: tabular-nums; }
/* One set: its label, then the two score boxes. Best of three, so three of these
   sit in a row and wrap onto their own line on a phone. */
.score-row__set { flex: none; display: flex; flex-direction: column; gap: var(--space-1); }
.score-row__set-no { font-size: var(--text-meta); color: var(--meta); white-space: nowrap; }
/* The target ("to 21") is the quiet half of the label — it tells a coordinator what
   the box expects before the validator has to. */
.score-row__target { color: var(--warm-gray); margin-inline-start: var(--space-1); }
.score-row__pair { display: flex; align-items: center; gap: var(--space-1); }
.score-row__dash { color: var(--meta); }
/* width: 100% because these inputs are NOT inside a .field wrapper — that flex
   column is what stretches an input everywhere else on the site. A bare
   <input type="number"> keeps its intrinsic UA width (~150px) instead, bursting
   out of its box and scrolling the page at 360px. 3rem fits a two-digit score
   plus the number spinner. */
.score-row__pair .field__input {
  width: 3rem;
  padding-inline: var(--space-1);
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.score-row__state { flex: none; width: 10rem; max-width: 100%; }
.score-row__state .field__input { width: 100%; }
/* Two small controls that belong together (the divisions editor's division + game-time
   pair): they wrap as ONE unit under the team name on a narrow screen, never one on
   each line, and are sized so the whole row fits a .measure column on one line. */
.score-row__controls { flex: none; display: flex; flex-wrap: wrap; gap: var(--space-2); max-width: 100%; }
.score-row__state--narrow { width: 8rem; }

/* --- League schedule builder ----------------------------------------------
   A lead line, then a stack of tool blocks (divisions, generate, flip). Space
   the lead off the first block and the blocks off each other so each reads as
   its own step. */
.builder-lead { margin-bottom: var(--space-4); }
.schedule-builder > .measure + .measure { margin-top: var(--space-5); }

/* A ruled section heading already draws a divider; an .admin-list directly under
   it must not draw its own top border on top of that (it read as a double line). */
.section__title--ruled + .admin-list { border-top: 0; }

/* A group label inside an .admin-list (a pool/division heading in the match list).
   The first one needs no rule of its own: the section heading above it is the
   divider, so it is not stacked into a second line. */
.admin-list__group {
  list-style: none;
  padding: var(--space-2) 0 var(--space-1);
  margin-top: var(--space-1);
  border-top: 1px solid var(--hairline);
  font-size: var(--text-meta);
  font-weight: var(--weight-strong);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--meta);
}
.admin-list__group:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

/* The flip panel: each night's courts, each shown with its opening and closing game
   (the two a flip swaps) so the effect is legible without re-reading the schedule. */
.flip-night__date { font-weight: var(--weight-strong); margin-bottom: var(--space-1); }
.flip-court {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding-block: var(--space-2);
}
.flip-court + .flip-court { border-top: 1px solid var(--hairline); }
.flip-court__meta { min-width: 0; }
.flip-court__title { font-weight: var(--weight-strong); }
.flip-court__pool { font-weight: var(--weight-regular); font-size: var(--text-meta); color: var(--meta); }
.flip-court__game { font-size: var(--text-meta); color: var(--meta); }
.flip-court__when { display: inline-block; min-width: 2.75rem; color: var(--warm-gray); }

/* The optional best-of-three extension on a tournament bracket game. A tournament game
   is one to 21; the second and third sets fold away so they are there for a championship
   that goes the distance without cluttering every pool game. Sits in the score row where
   sets 2-3 would be; opens to a small row of the two extra sets. */
.score-row__more { flex: none; align-self: flex-end; }
.score-row__more > summary {
  cursor: pointer;
  font-size: var(--text-meta);
  color: var(--meta);
  white-space: nowrap;
  padding-block: var(--space-1);
}
.score-row__more > summary:hover { color: var(--crimson); }
.score-row__more-note { color: var(--warm-gray); }
.score-row__more[open] > summary { margin-bottom: var(--space-1); }
.score-row__more-sets { display: flex; flex-wrap: wrap; gap: var(--space-3); }

/* The blast panel. A .notice-style strip, tinted only by the standard row hover —
   it is a warning about consequences, not a card. */
.blast {
  padding: var(--space-3);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--row-hover);
}
.blast--armed { border-color: var(--danger); }
.blast__count { font-weight: var(--weight-strong); }

/* Search field + button on one line. */
.admin-search { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: flex-end; }
.admin-search__field { flex: 1 1 16rem; min-width: 0; }

/* A destructive button. Outlined in danger rather than filled: filling it would
   make "void" the loudest thing on a page full of them, and the coral fill is
   reserved for the one primary CTA per view anyway. */
.btn--danger { background: transparent; color: var(--danger); border-color: var(--danger); }
.btn--danger:hover, .btn--danger.is-hover { background: var(--danger); color: var(--on-accent); }
.btn--danger:active, .btn--danger.is-active { background: var(--danger); color: var(--on-accent); }

/* Buttons that sit inline in a dense row need to not tower over it. */
.btn--small { padding: var(--space-1) var(--space-2); font-size: var(--text-meta); }

/* ==========================================================================
   Breakpoints — genuine overrides only.
   ========================================================================== */

/* 768px+: wider gutter; the type label moves into its own left-hand column so
   the event names line up as a column the eye can run down. */
@media (min-width: 768px) {
  /* The nav and weather banner stop wrapping here, so the chrome above the hero
     collapses to roughly a third of its mobile height — this is where the
     brief's 220px is the correct figure. */
  :root { --hero-offset: 220px; }

  .container { padding-inline: var(--space-4); }
  .fixture-row {
    display: grid;
    grid-template-columns: 6rem 1fr;
    align-items: baseline;
    column-gap: var(--space-4);
  }
  .fixture-row__type { grid-column: 1; grid-row: 1 / span 2; }
  .fixture-row__name { grid-column: 2; }
  .fixture-row__meta { grid-column: 2; }

  /* Listings put the status cue in that same left-hand label column, and widen it
     from the 6rem a "League"/"Tournament" word needed so a phrase like
     "Registration closed" or "6 team spots left" fits on one line. Scoped to the
     listing (.fixture-list--status) so the /me and /account ledgers, whose short
     labels fit 6rem, keep their narrower column. */
  .fixture-list--status .fixture-row { grid-template-columns: 9rem 1fr; }
  .fixture-row__status { grid-column: 1; grid-row: 1 / span 2; }

  /* Roster rows resolve into a ledger: the row label in its own left column, then
     name and email side by side. Below this they stay stacked — two inputs across
     a 360px screen is two inputs nobody can type into. */
  .roster__row {
    display: grid;
    grid-template-columns: 6rem 1fr 1fr auto;
    align-items: start;
    column-gap: var(--space-3);
  }
  .roster__index { grid-column: 1; padding-top: var(--space-2); }
  .roster__remove { grid-column: 4; align-self: center; }

  /* The builder resolves into two columns. Below this it stays stacked — a date
     field and a price field side by side on a 360px screen are two fields nobody
     can use. Fields that need the room keep the full width via .admin-grid__wide.
     minmax(0, 1fr) again, for the reason given on the base rule. */
  .admin-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); column-gap: var(--space-4); }
}

/* 1100px+: widest gutter; rows resolve into a 3-column ledger — type, name, and
   the metadata line reading across. */
@media (min-width: 1100px) {
  .container { padding-inline: var(--space-5); }
  .fixture-row {
    grid-template-columns: 6rem minmax(14rem, 20rem) 1fr;
    column-gap: var(--space-5);
  }
  /* .fixture-row__name keeps grid-column: 2 from the 768px block — one source of
     truth per property, so it is not restated here. */
  .fixture-row__type { grid-row: auto; }
  .fixture-row__meta { grid-column: 3; }

  .fixture-list--status .fixture-row { grid-template-columns: 9rem minmax(14rem, 20rem) 1fr; }
  .fixture-row__status { grid-row: auto; }
}
