/*===========================================================================
Gateway Chess Proprietary. Do not reproduce without permission in writing.
Copyright © 2026-2026, Gateway Chess League, Inc.  All rights reserved.
===========================================================================*/

/* Dark mode: theme.js sets data-theme="light"/"dark" on <html> as soon as
   the page starts loading (before first paint, since it's a blocking
   <head> script — see that file) based on a saved override in
   localStorage, falling back to the OS/browser's prefers-color-scheme.
   Colors that differ between the two themes are CSS variables here;
   colors that don't (status dots, the tooltip popup, school-swatch
   legend colors — all already self-contained/high-contrast regardless of
   page background) are left as plain hex values, unchanged from before
   dark mode existed. The @media block below is a pure progressive-
   enhancement fallback for the (currently nonexistent in this app)
   no-JS case — theme.js normally always sets data-theme itself, so the
   :root[data-theme="dark"] rule further down is what actually applies in
   practice. color-scheme tells the browser to draw native widgets
   (scrollbars, the default look of <select>/<input>/<button> before this
   file's own styling, form-control focus rings) in a dark-appropriate
   palette too, instead of only recoloring what this stylesheet touches
   directly. */
:root {
  --color-bg: #fff;
  --color-text: #222;
  --color-border: #ddd;
  --color-border-strong: #ccc;
  --color-surface: #f4f4f4;
  --color-surface-alt: #fafafa;
  --color-muted-bg: #ddd;
  --color-muted-text: #444;
  --color-danger: #a00;
  --color-live-dot-static: #aaa;
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --color-bg: #1b1b1b;
    --color-text: #e6e6e6;
    --color-border: #444;
    --color-border-strong: #555;
    --color-surface: #2a2a2a;
    --color-surface-alt: #242424;
    --color-muted-bg: #3a3a3a;
    --color-muted-text: #ccc;
    --color-danger: #ff6b6b;
    --color-live-dot-static: #777;
    color-scheme: dark;
  }
}

:root[data-theme="light"] {
  --color-bg: #fff;
  --color-text: #222;
  --color-border: #ddd;
  --color-border-strong: #ccc;
  --color-surface: #f4f4f4;
  --color-surface-alt: #fafafa;
  --color-muted-bg: #ddd;
  --color-muted-text: #444;
  --color-danger: #a00;
  --color-live-dot-static: #aaa;
  color-scheme: light;
}

:root[data-theme="dark"] {
  --color-bg: #1b1b1b;
  --color-text: #e6e6e6;
  --color-border: #444;
  --color-border-strong: #555;
  --color-surface: #2a2a2a;
  --color-surface-alt: #242424;
  --color-muted-bg: #3a3a3a;
  --color-muted-text: #ccc;
  --color-danger: #ff6b6b;
  --color-live-dot-static: #777;
  color-scheme: dark;
}

/* A real, sitewide bug found while checking match.php's scheduled/
   friendly toggle: the browser's own built-in `[hidden] { display: none }`
   rule is an author-overridable UA-stylesheet rule, so any element that
   both gets `hidden` set dynamically AND has a class setting its own
   `display` (`.field-row`'s `display: flex`, in the case that surfaced
   this) stays visually displayed regardless — the `hidden` attribute
   itself was still correctly set by the JS, it just had no visual
   effect. `!important` is deliberate here, not a shortcut: this is
   exactly the sanctioned use for it — a single, low-specificity global
   correctness rule that should always win over any component-level
   `display` override, rather than chasing this down class-by-class. */
[hidden] {
  display: none !important;
}

body {
  font-family: system-ui, sans-serif;
  max-width: 1040px;
  margin: 40px auto;
  /* margin:auto only centers (and thereby adds side space) once the
     viewport is wider than max-width — below that, width:auto fills the
     viewport exactly and the auto margins collapse to 0, leaving every
     page's nav/headings/tables flush against the browser edge with no
     breathing room at all. This padding guarantees side space at any
     width; box-sizing keeps it from adding to max-width's own 1040px. */
  box-sizing: border-box;
  padding: 0 20px;
  background: var(--color-bg);
  color: var(--color-text);
}

/* A 3-column grid, per the user: nav must stay *between* the logo and
   the Dark Mode button — not on a separate row below both — even when
   it wraps. Two earlier flex-based attempts this same session both
   failed that requirement in different ways: grouping logo+nav (button
   alone wraps away) let nav's own row-mate change based on viewport;
   grouping logo+button with nav always on its own full row below put
   nav *under* both instead of between them. Grid solves this directly —
   logo and button each get a fixed-width (auto) column and stay pinned
   to the row's left/right edges no matter what; nav's own column is
   `1fr` (whatever's left in the middle) and narrows right along with
   the header, so its link text soft-wraps across more internal lines
   as needed — but that only makes nav's own column *taller*, never
   moves logo or button off the row. See CLAUDE.md's dated Status
   entries for the full history of all three attempts. */
.site-header {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: 16px;
  margin-bottom: 24px;
}

.site-logo {
  height: 48px;
  width: auto;
}

/* Bigger than the base .site-logo (2026-08-21, per the user) — scoped to
   just the header's own two logos, not the footer's (which also uses the
   plain .site-logo class and wasn't part of this ask). */
.site-header .site-logo {
  height: 56px;
}

/* Middle column (2026-08-21): the title/season link stacked above nav,
   both centered, grouped in their own flex column so the grid's own
   1fr column can hand them a single height — see the site-header
   comment above for why grid (not flex) owns the outer 4 columns. */
.site-header-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 0;
}

.site-header-title {
  color: var(--color-text);
  text-align: center;
  line-height: 1.3;
  font-weight: bold;
}

/* Two separate links (Data Hub / league) now share this span rather than
   the old single <a class="site-header-title">; anchors need their own
   color rule since the UA stylesheet's link color otherwise wins over the
   inherited one (2026-08-23, per the user). */
.site-header-title a {
  color: inherit;
}

.site-header-season {
  text-align: center;
  line-height: 1.3;
}

/* min-width: 0 overrides a grid item's default min-width: auto (based on
   its content's min-content size) — without it, nav's column would
   refuse to shrink below that width and could force the header wider
   than the viewport instead of actually wrapping its own text. */
.site-header nav {
  margin-bottom: 0;
  min-width: 0;
  text-align: center;
}

#theme-toggle {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  color: var(--color-text);
  white-space: nowrap;
  /* Less side padding than the sitewide button { padding: 8px 16px }
     default, per the user — the button's text ("🌙 Dark mode") is
     already long enough that the wide default padding read as
     oversized. */
  padding: 6px 8px;
  /* Smaller than the sitewide button { font-size: 1em } default
     (2026-08-21, per the user, once the header logos grew — the button
     was reading oversized next to everything else in the header). */
  font-size: 0.85em;
}

/* Site-wide footer (2026-08-19), modeled on www.gatewaychess.org's own
   footer — logo, then copyright, then the org-site legal links, all
   centered below the app's own content. */
.site-footer {
  margin-top: 40px;
  padding-top: 20px;
  border-top: 1px solid var(--color-border);
  text-align: center;
  color: var(--color-muted-text);
}

.site-footer-logos {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 16px;
  margin-bottom: 8px;
}

.site-footer-logos .site-logo {
  margin-bottom: 0;
}

.site-footer p {
  margin: 0 0 8px;
}

.site-footer nav {
  margin-bottom: 0;
}

.site-footer nav a {
  color: var(--color-text);
}

nav {
  margin-bottom: 24px;
}

/* white-space: nowrap keeps a multi-word link (e.g. "Live Match
   Reporting") from breaking across two lines when nav's own grid column
   narrows — wrapping still happens between separate links/the "|"
   separators, just never inside one link's own text. */
nav a {
  color: var(--color-text);
  white-space: nowrap;
}

/* Jumps to each expandable section on index.php — a plain flex row so a
   conditionally-hidden link (Playoffs/Ranking Rules/Regular Season
   Standings — Overall, all hidden until the season's published) just
   disappears via its own [hidden] attribute without leaving a stray gap
   or separator behind. */
.section-jump-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
}

table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 24px;
}

th, td {
  text-align: left;
  padding: 8px 12px;
  border-bottom: 1px solid var(--color-border);
}

th {
  background: var(--color-surface);
}

/* A <th scope="row"> used purely to give a cross-table/wall-chart-style
   row its accessible name (2026-09-14, accessibility fix: a row's own
   player-identifying cell needs to be a real header, not a <td>, for
   scope="row" to apply) should render identically to the plain <td> it
   replaces -- this is a semantic-only change, not a visual one. Shared
   across all three apps via this stylesheet (Grand Prix/Tournaments both
   load it wholesale). */
.row-header-cell {
  background: inherit;
  font-weight: normal;
}

section {
  margin-bottom: 24px;
}

.inline-form, .field-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-bottom: 12px;
}

/* A little extra breathing room after the "Round N / scheduled date"
   heading on admin.php's match-date-edit row — the plain .inline-form
   gap read as too tight once that heading became two lines tall. */
#match-edit-scheduled-date-text {
  margin-right: 12px;
}

/* Keeps a CAPTCHA question and its answer field paired on one line —
   without this, .inline-form's flex-wrap can independently wrap the
   answer input onto its own row once the earlier fields fill the
   first line, leaving the question stranded above it. */
.captcha-row {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

/* team.php's Team Branding form: "Save branding" always sits on its own
   row below the nickname/color fields, left-aligned at its normal size
   (per the user, 2026-08-09) — not trailing inline after the fields, and
   not stretched full-width either (a first pass put flex-basis: 100% on
   the button itself, which forced the button element to actually fill
   that width; the user asked for it back to normal size). The
   flex-basis: 100% instead lives on a small wrapping div
   (.branding-form-submit-row) — that's what forces the line break within
   the form's flex-wrap layout — while the button inside it stays a
   normal block-level element sized to its own content. Scoped to this
   one form, not a change to .inline-form itself, since every other form
   sharing that class still wants its normal inline wrapping. */
.branding-form-submit-row {
  flex-basis: 100%;
}

.field-row label {
  display: flex;
  flex-direction: column;
  font-size: 0.9em;
  gap: 4px;
}

/* match.php's/submit.php's mode-picker rows (.start-mode-row): the
   radio label and its picker are vertically centered against each other
   (plain .field-row default), which is only visually stable because the
   picker-reservation rule right below keeps both rows' heights constant
   across mode changes — see that rule's own comment. (An earlier
   flex-start attempt fixed the radio's position within its own row, but
   didn't address the *other* row's position shifting — the real fix
   turned out to be freezing row height, at which point plain centering
   is correct and stays put.) Per the user, 2026-08-03. */

/* The scheduled/friendly picker that belongs to the *inactive* mode is set
   `hidden` (see match.js's/submit.js's setStartMode()) — which, via the
   sitewide `[hidden] { display: none !important }` rule, removes it from
   layout entirely. That's correct for the row it's inside (that row
   collapses to just its own radio label, as intended), but it also meant
   each .start-mode-row's own height changed depending on which mode was
   selected — and since the "Friendly" row sits directly below the
   "Pick a scheduled match" row, a shrinking/growing first row visibly
   shifted the second row (and everything below it) up or down every time
   the mode was toggled. Overriding to `visibility: hidden` instead (still
   `!important`+higher specificity to beat the sitewide rule) keeps each
   picker's own natural space reserved whether or not it's the active
   mode, so both rows' heights — and everything below them — stay put
   regardless of which mode is selected. visibility: hidden also already
   removes the element from the tab order and click targets on its own,
   same practical non-interactivity the `hidden` attribute gave it. Per
   the user, 2026-08-03. */
#start-scheduled-picker[hidden],
#start-friendly-picker[hidden],
#scheduled-picker[hidden],
#friendly-picker[hidden] {
  display: flex !important;
  visibility: hidden;
}

/* Fixed-width column for the two mode radios (.start-mode-option) — "Pick
   a scheduled match" and "Friendly (not on schedule)" render at different
   natural text widths (176px vs 189px, measured), so without this each
   row's picker started at a different x, breaking the visual "second
   column" a picker is meant to line up in. 230px comfortably fits the
   longer label's radio circle + gap + text (207px total) without
   wrapping — 200px was too tight and wrapped "Friendly (not on
   schedule)" onto a second line. Per the user, 2026-08-03. */
.start-mode-option {
  flex: 0 0 230px;
}

/* Generic override for a label whose text should sit *beside* its own
   input instead of above it — plain .field-row label stacks a label's
   children in a column (built for the "text above its own select/input"
   pattern used in most of this app's forms), which breaks for a label
   that's meant to read inline (a radio's circle-then-text, or "In
   -progress match" beside its own dropdown). Used by match.php's/
   submit.php's mode-picker radios (.start-mode-option, since the radio
   labels are nested inside a .field-row there — see that layout's own
   Status entry for why this needed fixing twice) and match.php's
   "Continue an in-progress match" label (.inline-label), per the user. */
.field-row label.start-mode-option,
.field-row label.inline-label {
  flex-direction: row;
  align-items: center;
  gap: 6px;
}

/* match.php's/submit.php's friendly-mode fields (Home team/Away team/
   Match date): plain .field-row label defaults to align-items: stretch,
   which stretches both the label text and the select/input to the same
   (widest-child-driven) width — since the text itself renders
   left-aligned within that stretched box by default, a label whose text
   is narrower than its own select (or vice versa) ends up visibly
   off-center from it, confirmed by measuring the two elements' actual
   horizontal centers (up to ~42px apart on "Away team"). align-items:
   center instead centers each child's own natural-width box within the
   column, which centers the text over the select (or vice versa)
   without needing to touch every other .field-row label in the app —
   scoped to this specific row's labels only, not a sitewide change. */
.friendly-fields label {
  align-items: center;
}

/* A small "?" next to a field that needs a one-line explanation beyond
   what its placeholder can hold — e.g. First name accepting a middle
   name/initial too. Tooltip is pure CSS (:hover/:focus + content:
   attr(data-tooltip)) rather than JS, and tabindex="0" on the span makes
   it keyboard-focusable so the tooltip isn't mouse-only. */
.help-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--color-muted-bg);
  color: var(--color-muted-text);
  font-size: 0.75em;
  font-weight: bold;
  cursor: help;
  position: relative;
  flex: none;
}

.help-icon:hover::after,
.help-icon:focus::after {
  content: attr(data-tooltip);
  position: absolute;
  left: 0;
  top: 100%;
  margin-top: 6px;
  background: #333;
  color: #fff;
  padding: 6px 10px;
  border-radius: 4px;
  font-size: 0.85em;
  font-weight: normal;
  white-space: normal;
  width: 220px;
  z-index: 10;
}

/* A small, muted line under a field/checkbox (e.g. team.php's "SMS OK?"
   disclaimer note) — deliberately its own block, not folded into a
   .help-icon tooltip, since it's meant to always be visible rather than
   only on hover. */
.field-note {
  display: block;
  font-size: 0.85em;
  color: var(--color-muted-text);
}

select, input {
  padding: 6px;
  font-size: 1em;
}

/* The spinner up/down arrows on a number input overlay its own content
   box rather than adding to the input's width, so text (placeholder or
   typed) can render right underneath them and get visually covered —
   seen on the "Grade" placeholder in the roster manager's "Add player"
   form. Padding alone chased a moving target (spinner width/position
   differs enough between browsers — this looked fixed in Chromium
   testing but the user still saw it covered, almost certainly in
   Firefox) so remove the spinner outright instead: these are all small,
   short-range fields (grade, rating) people type into directly rather
   than click tiny arrows on anyway. */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

input[type="number"] {
  -moz-appearance: textfield;
}

/* Browsers auto-size a number input with min/max by the digit count of
   the *value* range (1-12 → 2 digits), not by its placeholder text —
   "Grade" (5 characters) doesn't reliably fit in that narrow a box
   without relying on exactly how tight each browser renders it. Used on
   both team.php's and match.js's "Add player" forms. */
#player-grade,
.player-grade {
  min-width: 70px;
}

button {
  padding: 8px 16px;
  font-size: 1em;
  cursor: pointer;
}

.roster-managers {
  display: flex;
  gap: 16px;
  margin-bottom: 16px;
}

.roster-manager {
  flex: 1;
  min-width: 0;
  overflow-x: hidden;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: 4px 12px;
}

/* Real root cause of a reported layout bug (2026-07-31): <fieldset> has
   a browser-default min-width: min-content (confirmed via
   getComputedStyle) that a plain <div> doesn't — so even though
   .roster-manager above has min-width: 0 to let it shrink as a flex
   item, .roster-manager-lock-fieldset nested inside it was still
   refusing to shrink below its own widest content's natural width
   (e.g. the "Add player" form's inputs), silently overflowing the
   panel's real allocated width at narrower viewports. This only
   surfaced there, not at .roster-manager itself, which is why the
   outer overflow-x: hidden above only clipped the symptom rather than
   fixing it — content was still overflowing internally, just no longer
   visible spilling into the sibling panel. Overriding min-width here is
   the actual fix; the outer overflow-x: hidden stays too as a defensive
   backstop for anything else that might someday not shrink properly. */
.roster-manager-lock-fieldset {
  min-width: 0;
}

/* admin.php's lock fieldset now only wraps three already-boxed
   <details class="admin-panel"> groups (see below) — its own default
   browser border/padding would just be a second, redundant box around
   all three, so it's reset to purely functional (still gates every
   nested form control's disabled state while the page is read-only,
   just with no visual box of its own). #admin-lock-fieldset-2 (added
   2026-08-27, inside the Site Admin panel — see its own comment in
   admin.php) gets the same reset for the same reason, just wrapping
   several plain unboxed <section>s instead of <details class="admin-panel">
   ones. team.php's own #team-lock-fieldset gets the identical reset
   (2026-08-02, per the user) — same reasoning, its own default border
   was a redundant box around the page's plain (unboxed) sections.
   Scoped individually to each fieldset rather than a sitewide `fieldset`
   reset, in case some future fieldset elsewhere in the app actually
   wants its own border. */
#admin-lock-fieldset,
#admin-lock-fieldset-2,
#team-lock-fieldset {
  border: none;
  margin: 0;
  padding: 0;
  /* A bare <fieldset>'s browser-default min-width is its own content's
     min-content size, not 0 like every other block element (2026-09-05,
     per the user: Tournaments admin's roster-review table "way too wide"
     after uploading a real tournament) -- a wide nested table's own
     min-content (individual-prizes cell, several columns wide) was
     silently forcing this fieldset, and therefore the whole page, past
     body's own max-width: 1040px column, independent of the table's own
     .t-table-scroll overflow-x wrapper (tournaments/style.css) doing its
     job correctly -- that wrapper can only scroll content wider than
     *its own* container, and its container had already been stretched by
     this fieldset before the wrapper ever got a chance to constrain it.
     Same fix as .roster-manager-lock-fieldset above, generalized to every
     admin/team lock fieldset instead of just the one it was first found
     on. */
  min-width: 0;
}

/* team.php's collapsible sections (Team Name, Coach/Sponsor, Roster,
   Team Branding, Change Team Password) — all closed by default (see the
   `<details>` markup), deliberately with no border/background box of
   their own (unlike admin.php's .admin-panel groups), consistent with
   this page's plain, unboxed layout. Originally just Roster's own
   styling (#roster-section); generalized to the shared .team-section
   class 2026-08-08, per the user, once every other section got the same
   collapsible treatment. margin-bottom + summary padding added the same
   day, also per the user, once 5 headers stacked directly against each
   other with no breathing room between them. Reused as-is under
   .plain-collapsible-section (2026-08-10) for index.php's own "Regular
   Season Standings" sections, which default `open` (unlike team.php's
   own closed-by-default sections) since this is the page's main content,
   not an editing form someone opens on demand. */
.team-section,
.plain-collapsible-section {
  margin-bottom: 12px;
}

.team-section summary,
.plain-collapsible-section summary {
  cursor: pointer;
  padding: 8px 0;
}

.team-section summary h2,
.plain-collapsible-section summary h2 {
  display: inline;
  margin: 0;
}

/* admin.php's jump-to-section links — the page has grown long enough
   (several collapsible groups, each with its own sub-forms) that a quick
   table of contents at the top is worth it. Plain "a | a | a" links, same
   convention as .site-header's own <nav>, just boxed for visual separation
   from the page content below. */
#admin-toc {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: 8px 12px;
  margin-bottom: 16px;
  font-size: 0.9em;
}

/* admin.php's collapsible section groups (Conferences / Schools & Teams /
   Schedule) — same bordered-box-with-a-clickable-summary-heading pattern
   as .roster-manager above, just for grouping several whole <section>s
   instead of one roster-editing panel. Closed by default (no `open`
   attribute in the markup) to keep the page from reading as one long
   wall of forms. */
.admin-panel {
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: 4px 16px 16px;
  margin-bottom: 16px;
}

.admin-panel summary {
  cursor: pointer;
  padding: 8px 0;
}

.admin-panel summary h2 {
  display: inline;
  margin: 0;
}

/* The Schedule group's own "Schedule" sub-panel (the actual match table,
   as opposed to the Schedule-a-match/Generate-round-robin forms next to
   it) nests one more collapsible level down — its own smaller bordered
   box so it doesn't read as flush with its parent's border. */
.admin-subpanel {
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: 4px 12px 12px;
  margin-top: 8px;
}

/* admin.js's "View Result Log" panel (2026-08-22) — a plain bordered box
   around the per-match hsl_match_result_logs table. overflow-x: auto is
   the safety net for the table's Report column (below), which is wide
   enough on its own to push a narrow viewport past the panel's border. */
#match-edit-result-log {
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: 8px 12px;
  margin-top: 8px;
  overflow-x: auto;
}

/* Each result-log row's full report text (2026-08-25, per the user — "a
   text box should be sufficient", no expand/collapse click needed; moved
   into its own full-width row below the event row, also per the user,
   rather than sharing space in a column). Fixed height so one long
   report doesn't blow out the row; the textarea's own scrollbars handle
   the rest, and resize: vertical lets the admin grow it if they want more
   at once. Explicit bg/color/border (not the browser default) so it
   matches the app's own light/dark theme instead of the OS's. */
.match-result-log-report {
  width: 100%;
  height: 120px;
  font-family: 'Courier New', Courier, monospace;
  font-size: 11px;
  background: var(--color-bg);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  resize: vertical;
  box-sizing: border-box;
}

/* admin.js's "Edit or delete match results" panel — Delete Match Results
   sits in the same row as Save changes (2026-08-23, per the user; briefly
   had its own row below at the panel's bottom-right, 2026-08-22, before
   this), red text (the same --color-danger used sitewide, e.g.
   #match-edit-playoff-reason-row above) so it doesn't read as just
   another plain button despite sharing the row. */
#match-edit-delete-button {
  color: var(--color-danger);
  margin-left: auto;
}

.admin-subpanel summary {
  cursor: pointer;
  padding: 6px 0;
}

.admin-subpanel summary h3 {
  display: inline;
  margin: 0;
}

.roster-manager summary {
  cursor: pointer;
  font-weight: bold;
  padding: 8px 0;
}

.roster-manager .roster-manager-body h4 {
  margin-bottom: 6px;
}

/* The roster table (and its inline-edit row of several small inputs) is
   often wider than a side-by-side roster-manager panel can fit — scope
   the overflow to this wrapper so a too-wide table scrolls in place
   instead of visually spilling past the panel's own border into
   whatever's next to it (which, since it paints after this panel in DOM
   order, would otherwise sit on top and swallow clicks meant for the
   overflowing content — see match.js's roster-table-wrap usage). */
.roster-manager .roster-table-wrap {
  overflow-x: auto;
}

/* This table shares half the page width with its sibling team's panel
   (see .roster-managers above), so the default site-wide button padding
   (8px 16px, sized for full-width pages) makes the action column overflow
   more than it needs to — shrink just these buttons rather than the
   sitewide default, small enough that Edit+Delete fit on one line instead
   of stacking (which was inflating row height further). */
.roster-manager .roster-table-wrap button {
  padding: 1px 4px;
  font-size: 0.75em;
}

/* Save/Cancel (the edit-mode action column) used to sit side by side like
   Edit does in read mode — in this narrow, side-by-side panel that left
   Cancel squeezed almost out of view next to Save and the row's other
   inputs. Stacked instead, each shorter than the shared button rule above
   (tighter padding/line-height/font-size) so the extra row this adds
   doesn't cost much height — the row growing a little taller to fit both
   is an accepted trade-off for Cancel actually being visible. `margin: 0
   auto` (not `width: 100%`, tried first) keeps each button its own
   natural compact size and centers it — matching the read-mode Edit
   button's own look, rather than stretching to fill the whole action
   column the way a first pass did. */
.roster-manager .roster-table-wrap .save-player,
.roster-manager .roster-table-wrap .cancel-edit {
  display: block;
  width: fit-content;
  margin: 0 auto;
  padding: 0 3px;
  line-height: 1.4;
  font-size: 0.7em;
}

.roster-manager .roster-table-wrap .save-player {
  margin-bottom: 2px;
}

/* The action column itself (Edit in read mode, Save/Cancel in edit mode)
   — centering it here too keeps Edit's own position consistent with
   Save/Cancel above, rather than relying on Edit happening to look
   roughly centered by coincidence in a column barely wider than it is. */
.roster-manager .roster-table-wrap td:last-child {
  text-align: center;
}

.roster-manager .roster-table-wrap th,
.roster-manager .roster-table-wrap td {
  padding: 2px 3px;
  font-size: 0.85em;
}

/* Headers (e.g. "USCF ID") are still allowed to wrap onto two lines
   to save column width — only td needs nowrap, and only because two
   inline <input>s in the edit-mode name cell otherwise wrap onto
   separate lines whenever the column comes up short, which pushed
   Save/Cancel far enough right that they scrolled out of the wrapper's
   visible area with no visible scrollbar hinting they were still there. */
.roster-manager .roster-table-wrap td {
  white-space: nowrap;
}

.roster-manager .roster-table-wrap input {
  padding: 3px 4px;
  /* The HTML `size` attribute turned out unreliable here — Chromium
     ignores it outright on type="number" without min/max (see
     match.js's rosterManagerPlayerRowHtml comment), and even where it
     is honored it doesn't map to a predictable pixel width across input
     types. Explicit widths give precise control instead, sized just
     large enough to type into (not necessarily to show a long value
     in full — text still scrolls within the input itself). */
  width: 28px;
}

.roster-manager .roster-table-wrap .edit-uscf-id,
.roster-manager .roster-table-wrap .edit-lichess-id {
  width: 38px;
}

/* Player name and Lichess handle are the only two read-mode columns
   with genuinely unbounded-length content — a long name or handle was
   otherwise free to force the whole table wider, pushing later columns
   off the visible edge with no scrollbar to hint more content existed
   (same underlying problem as the edit-row overflow above, just from
   read-mode data instead of input widths). Truncating with an ellipsis
   keeps the column width fixed regardless of what's in it. */
.roster-manager .roster-table-wrap .roster-cell-truncate {
  display: inline-block;
  max-width: 90px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

.roster-manager .roster-table-wrap .edit-grade {
  width: 28px;
}

/* Never wrap the Edit/Delete buttons onto a second line even if the
   column is a little tight — they're small enough now to fit on one
   line at any realistic panel width, and wrapping was the main
   contributor to excess row height. */
.roster-manager .roster-table-wrap td:last-child {
  white-space: nowrap;
}

/* USCF ID and Lichess are centered in both header and data rows —
   including a plain "—" for a player with none on file, which is what
   the user actually flagged (a bare dash with no alignment rule of its
   own defaulted to the sitewide left-align) — but real values get the
   same treatment too, for the same reason team_public.php's own roster
   table centers this column outright rather than only the empty case:
   alternating alignment between "has a value" and "doesn't" would look
   like a layout bug of its own. */
.roster-manager .roster-table-wrap .roster-uscf-header,
.roster-manager .roster-table-wrap .roster-uscf-cell,
.roster-manager .roster-table-wrap .roster-lichess-header,
.roster-manager .roster-table-wrap .roster-lichess-cell {
  text-align: center;
}

.roster-manager .roster-table-wrap .roster-grade-cell,
.roster-manager .roster-table-wrap .roster-rating-cell {
  text-align: right;
}

.roster-manager .roster-table-wrap .roster-grade-cell input,
.roster-manager .roster-table-wrap .roster-rating-cell input {
  text-align: right;
}

/* team.php's own roster table (#roster-section — a separate table from
   the embedded .roster-table-wrap above, not sharing its markup) had the
   same "wide edit-row inputs move the whole table around" problem
   reported live (2026-08-26, per the user — most noticeable editing a
   player near the bottom of a long roster, where the row-height jump
   shifts everything below it). Same fix: explicit `ch`-based widths
   instead of the `size` attribute (dropped from the HTML — same
   reliability note as the roster-manager block above), sized to what
   each field can actually hold rather than an arbitrary guess — grade
   is at most 2 digits, USCF ID always exactly 8. First/last name get a
   narrower-but-still-readable width and `white-space: nowrap` on their
   shared cell so they can't wrap onto two lines, which was the specific
   "keep first and last name on one line" ask. Rating narrowed the same
   way (max 3000, so 4 digits); Lichess ID just gets a modest reduction
   from its old default rather than a precise fit — its real limit (30
   chars) is long enough that sizing to it would defeat the point. */
#roster-body td {
  white-space: nowrap;
}

#roster-body .edit-first-name,
#roster-body .edit-last-name {
  width: 8ch;
}

#roster-body .edit-grade {
  width: 3ch;
}

#roster-body .edit-uscf-id {
  width: 9ch;
}

#roster-body .edit-uscf-rating {
  width: 5ch;
}

#roster-body .edit-lichess-id {
  width: 8ch;
}

/* A save/cap error on this row (2026-08-26, per the user — reported
   live: #player-message, up by the Add-player form, isn't visible
   without scrolling back up when editing a player near the bottom of a
   long roster) — a full-width row right under the edit row, one line,
   right-justified (per the user, revising a first version that put it
   in the actions cell instead). Stays `white-space: nowrap` (inherited
   from the #roster-body td rule above) since the whole point is one
   line — this cell spans every column, so it has far more room than
   the narrow actions cell the first version was cramped into. */
#roster-body .edit-row-message-row td {
  text-align: right;
  color: var(--color-danger);
}

/* Suppresses the normal td border-bottom (see the sitewide th/td rule)
   on the edit row itself once it has a message showing right below it
   (2026-08-26, per the user — a divider line between the row and its
   own error read as an unwanted extra separator). team.js toggles this
   class on only while a message is actually visible — the border stays
   on normally (no message row rendered below at all in that case), so
   the divider between one player's row and the next is unaffected. The
   message row's own border-bottom (unset above, so it keeps the
   sitewide default) is what still marks the bottom of this row+message
   pair. */
#roster-body tr.edit-row-has-message td {
  border-bottom: none;
}

#roster-body .edit-row-message {
  margin: 0;
}

/* "Player updated." save confirmation (2026-08-26, per the user) — same
   full-width, one-line, right-justified, no-divider-above treatment as
   the edit-row error above, just the default text color instead of
   .edit-row-message-row's red, since this isn't an error. */
#roster-body .player-message-row td {
  text-align: right;
}

#roster-body tr.player-row-has-message td {
  border-bottom: none;
}

#roster-body .player-row-message {
  margin: 0;
}

#live-score, #watch-score, #watch-tiebreak-pending, .running-score {
  font-weight: bold;
  font-size: 1.2em;
}

#rr-round-dates {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 12px;
}

#rr-round-dates label {
  display: flex;
  flex-direction: column;
  font-size: 0.85em;
  gap: 4px;
}

#rr-message {
  white-space: pre-line;
}

.group-row td {
  background: var(--color-surface);
  padding-top: 14px;
}

.round-row td {
  background: var(--color-surface-alt);
  font-style: italic;
}

/* conference_results.php's schedule table: a "Week" column spanning
   every row belonging to that week via rowspan, instead of a separate
   <h3>/<thead> per week group (2026-08-01, per the user — reclaims the
   vertical space those repeated per-group headers used).
   conference_results.js's weekRowsHtml()/toggleMatchDetail() keep the
   rowspan count in sync with however many of that week's detail rows
   are currently expanded, so this stays visually correct regardless.
   Centered both ways (vertical-align: middle) so the label re-centers
   itself within the group's current height as rows expand/collapse,
   rather than staying pinned to wherever the group's original top was. */
.conference-week-cell {
  background: var(--color-surface);
  font-weight: bold;
  text-align: center;
  vertical-align: middle;
  border-right: 1px solid var(--color-border-strong);
}

/* Expand triangle's own column (2026-08-02, per the user — see
   .match-results-expand-col's own comment for the general rule this
   follows): a scheduled/double-forfeit row here has nothing to expand, so
   without this the triangle's inline margin would shift that row's Date
   text left relative to every clickable row's. */
.conference-schedule-expand-col {
  width: 1%;
}

/* Marks the boundary between one week's group of matches and the next
   (2026-08-01, per the user — the rowspan'd Week column alone made this
   boundary too easy to miss while scanning down the table). A plain
   top border on every <td> in the week's first row, heavier/higher
   -contrast than the normal 1px var(--color-border) row dividers within
   a single week — conference_results.js only applies the .week-start
   class to a week group's first row, which is always the same row the
   Week cell's own rowspan starts on, so this border and that cell's top
   edge always land in the same place with no offset. */
.match-row.week-start > td {
  border-top: 3px solid var(--color-text);
}

/* admin.php's Playoffs "Wildcard rankings" table — marks the boundary
   between the two teams that actually took a wildcard seed (7/8, always
   the table's first 2 rows) and everyone else shown below them for
   context only (2026-08-09, per the user: "put a stronger line between
   the wildcard and non-wildcard teams"). Same weight/color convention
   as the conference schedule's own week-boundary divider above, reused
   rather than inventing a second one. */
.wildcard-boundary-row > td {
  border-top: 3px solid var(--color-text);
}

:root[data-theme="dark"] .wildcard-boundary-row > td {
  border-top: 2px solid #888;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .wildcard-boundary-row > td {
    border-top: 2px solid #888;
  }
}

/* The 3px var(--color-text) divider above reads fine in light mode
   (#222 on white), but per the user, in dark mode var(--color-text) is
   the near-white #e6e6e6 — at 3px that reads as a much heavier, glowing
   bar than its light-mode counterpart, not just an equally-weighted
   divider in an inverted palette. Toned down for dark mode specifically
   rather than changing the light-mode version, which the user confirmed
   is fine as-is. First pass used var(--color-border-strong) (#555), which
   the user then asked to lighten a bit further — but not all the way to
   var(--color-text)'s near-white #e6e6e6 — so this is a plain literal
   hex value (#888) sitting between the two, not one of the existing
   themed variables. */
:root[data-theme="dark"] .match-row.week-start > td {
  border-top: 2px solid #888;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .match-row.week-start > td {
    border-top: 2px solid #888;
  }
}

/* admin.php's "Pending Playoff Tiebreak Draws" panel (2026-08-09, per the
   user) — one card per still-unresolved tie, each with its own "Draw"
   button. Bordered/padded like a small standalone unit, since several of
   these can appear stacked at once. */
.pending-tiebreak-card {
  border: 1px solid var(--color-border-strong);
  border-radius: 6px;
  padding: 10px 14px;
  margin-bottom: 12px;
  background: var(--color-surface);
}

.pending-tiebreak-card p {
  margin: 4px 0;
}

.pending-tiebreak-card ul {
  margin: 4px 0;
}

/* Draw-method dropdown + manual reorder editor (2026-08-10, per the user
   — "add the choice for the draw 'Computer picks' or 'User picks' with a
   pulldown"), and the permanent "how/when this was decided" line on a
   resolved card (same panel, "leave it with the result" — see the
   Status entry on this). */
.tiebreak-method-row {
  margin: 8px 0 4px;
}

.tiebreak-resolved-info {
  font-style: italic;
  color: var(--color-muted-text);
}

.tiebreak-order-list {
  margin: 4px 0;
  padding-left: 0;
}

.tiebreak-order-list li {
  list-style: none;
  padding: 2px 0;
}

.tiebreak-order-position {
  display: inline-block;
  width: 22px;
  font-weight: bold;
}

.tiebreak-order-move {
  padding: 1px 6px;
  margin-left: 4px;
  font-size: 0.85em;
}

/* Per the user, the page's other dark-mode dividers — the sitewide
   1px row divider (th,td's own border-bottom, normally var(--color-
   border), #444 in dark) and the Week column's vertical rule
   (.conference-week-cell's border-right, normally var(--color-border-
   strong), #555 in dark) — read too dim next to the now-brighter #888
   week-start divider above, and should match it. Scoped to this page's
   two containers only (not a sitewide var(--color-border)/var(--color-
   border-strong) change, which would affect every other page's tables
   too), same container-scoping technique as this page's own font-size/
   padding density rule above. */
:root[data-theme="dark"] #conference-standings-container th,
:root[data-theme="dark"] #conference-standings-container td,
:root[data-theme="dark"] #conference-schedule-container th,
:root[data-theme="dark"] #conference-schedule-container td {
  border-bottom-color: #888;
}

:root[data-theme="dark"] #conference-schedule-container .conference-week-cell {
  border-right-color: #888;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) #conference-standings-container th,
  :root:not([data-theme]) #conference-standings-container td,
  :root:not([data-theme]) #conference-schedule-container th,
  :root:not([data-theme]) #conference-schedule-container td {
    border-bottom-color: #888;
  }

  :root:not([data-theme]) #conference-schedule-container .conference-week-cell {
    border-right-color: #888;
  }
}

#schedule-legend {
  margin-bottom: 12px;
  font-size: 0.9em;
}

/* conference_results.php's week-by-week schedule table — per the user,
   slightly smaller and less vertically padded than the sitewide defaults
   (font-size: 1em / th,td padding: 8px 12px) to cut down on page height,
   since a full season's worth of matches can run long. Only the vertical
   half of the padding is reduced (horizontal stays at the sitewide 12px)
   — same scoped-container technique as board_standings.php's/
   board_cross_table.php's own density reductions. */
#conference-schedule-container {
  font-size: 0.9em;
}

#conference-schedule-container th,
#conference-schedule-container td {
  padding: 5px 12px;
}

/* SCHOOL_COLOR_PALETTE (admin.js) is a fixed set of light pastels used as
   a background regardless of theme — var(--color-text) would go light-on
   -light in dark mode, so these two get a pinned dark text color instead,
   same reasoning as leaving the tooltip/live-dot colors above unthemed. */
.school-swatch,
.school-travel-cell {
  color: #222;
}

.school-swatch {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 4px;
  margin-right: 4px;
}

#teams-table {
  display: block;
  overflow-x: auto;
}

#teams-table th:nth-child(2n+1),
#teams-table td:nth-child(2n+1) {
  border-left: 2px solid var(--color-border);
}

.center-cell {
  text-align: center;
}

/* justify-content: flex-start (not center) — nothing else on this page is
   horizontally centered, so a full row of conferences shouldn't be
   either; it should sit flush left like every other element. When a
   conference wraps onto its own shorter line below a fuller row, that
   line is still flex-start (flush left) by default — app.js's
   markStandingsLineStarts() applies a JS-computed margin-left to center
   that shorter line specifically under the wider line(s) above it,
   rather than under the page. (CSS justify-content: center can't do
   this alone — flex-wrap lays out each line centered within the row's
   full available width, not relative to a sibling line's width, which
   would have also un-lefted the fuller line above.) */
.standings-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  align-items: stretch;
}

.standings-group {
  box-sizing: border-box;
  /* Fixed, non-growing, non-shrinking width: growing let a lone
     conference on its own wrapped line stretch to fill the row (its
     table columns spread out absurdly); shrinking let three conferences
     get squeezed narrower than their table content needed once the
     window was too narrow to fit them but not yet narrow enough to
     trigger a wrap, so text collided instead of wrapping cleanly. A
     fixed width sidesteps both: three groups either fit at their normal
     width or wrap, never squeeze or stretch. justify-content:center
     above then centers whatever ends up on each wrapped line — three
     across, or one alone. */
  flex: 0 0 320px;
  padding: 0 16px;
}

.standings-group:first-child {
  padding-left: 0;
}

.standings-group:not(:first-child) {
  border-left: 1px solid var(--color-border-strong);
}

/* Repeats .standings-group so this beats the border rule above on
   specificity ((0,2,0) vs (0,2,0) tie broken by source order) — a plain
   .standings-group--line-start alone is (0,1,0) and silently lost that
   fight, so the class was being applied correctly but never actually
   suppressing the divider. */
.standings-group.standings-group--line-start {
  border-left: none;
}

.standings-group h2 {
  margin-bottom: 8px;
  font-size: 1.1em;
  text-align: center;
}

.standings-group table {
  margin-bottom: 0;
}

.standings-group th,
.standings-group td {
  padding: 4px 6px;
  text-align: right;
  /* Plain proportional-font digits render at different widths per glyph
     (a "1" is narrower than an "8"), which right-alignment alone doesn't
     fix — two rows with the same digit *count* (e.g. Avg's "26.7" vs
     "20.3") can still land their decimal points at different x positions
     because the specific digits differ in total rendered width.
     tabular-nums forces every digit to the same fixed width, so a column
     with a consistent character count (as Avg's toFixed(1) always
     produces per integer-digit-count) lines up exactly. Per the user,
     2026-08-03. */
  font-variant-numeric: tabular-nums;
}

.standings-group th {
  text-align: center;
}

.standings-group td:first-child {
  text-align: left;
  white-space: nowrap;
}

.standings-group .board-pts-header {
  font-size: 0.8em;
}

/* Small per-team logo thumbnail in front of the team name, standings
   table only — a school with no logo uploaded falls back to the Gateway
   Chess League logo (see app.js's loadStandings()), same rule
   team_public.php's own banner already follows. */
.standings-team-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.standings-team-logo {
  width: 20px;
  height: 20px;
  object-fit: contain;
  border-radius: 3px;
  flex-shrink: 0;
}

/* index.php's "no schedule generated yet" standings replacement
   (2026-08-21, per the user) — reuses .standings-team-link/-logo above
   for a consistent look, just swaps the W-L-D table for a plain list
   since there's no schedule/results data to show yet. */
.no-schedule-team-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.no-schedule-team-list li {
  padding: 4px 6px;
}

/* Recent Matches / Match Results (renderMatchesTable()) — a small logo
   to the left of the home team's name and to the right of the away
   team's, per the user, so the two logos sit on the outside of the row
   with the score in the middle. Also reused by the standings page's
   Matches in Progress pane (loadInProgress()) for its own Home/Away
   cells. Sized to match .standings-team-logo (20px) — originally 16px,
   widened per the user, 2026-08-03 ("a bit bigger"). */
.match-team-logo {
  width: 20px;
  height: 20px;
  object-fit: contain;
  vertical-align: middle;
}

/* Matches in Progress pane (loadInProgress()) — center every column,
   headers and data alike, overriding the sitewide left-align default.
   Per the user, 2026-08-03. */
#in-progress-container th,
#in-progress-container td {
  text-align: center;
}

/* Sizes to its own content instead of the sitewide table { width: 100% }
   default (2026-08-12, per the user — "don't stretch those tables once
   everything is on one line") — this table, unlike .match-results-table
   below, never picked up its own width: auto override before. */
#in-progress-container table {
  width: auto;
}

/* No cell here wraps (this table's own equivalent of .match-results-
   table's identical rule below — a real, wide-content table like this
   one, sitting right above Recent Match Results, needs the exact same
   fix for the exact same reason: the Started column's full timestamp
   can otherwise wrap at a narrow viewport, and without a scrollable
   container the table then bleeds past its own box into page-level
   horizontal overflow instead of cleanly scrolling on its own). */
#in-progress-container {
  overflow-x: auto;
}

#in-progress-container th,
#in-progress-container td {
  white-space: nowrap;
}

/* Home/Away override the blanket center-everything rule above (2026-08-10,
   per the user — "do the team name/logo alignment the same as the recent
   match results"): right-justified/left-justified with the logo on the
   outside edge, matching .match-results-table td.team-home/.team-away
   exactly (see below) rather than this pane's own previous center-
   everything treatment for these two columns specifically. The extra
   class on top of the plain #in-progress-container td selector above is
   what wins the specificity fight regardless of source order. */
#in-progress-container td.team-home,
#in-progress-container td.team-away {
  white-space: nowrap;
}

#in-progress-container td.team-home {
  text-align: right;
}

#in-progress-container td.team-away {
  text-align: left;
}

/* The auto-update toggle button's two states ("⟳ Auto-updating (click to
   pause)" vs "⏸ Auto-update paused (click to resume)") render at
   different natural widths (268px vs 313px, measured in this container's
   Chromium) — min-width alone only pins the *shorter* state to match the
   longer one in whichever browser's font metrics happen to agree with
   that measurement; a browser rendering the text wider than the fixed
   floor (real desktop Firefox, per the user, 2026-08-03) has nothing
   stopping the button from wrapping onto a second line instead of simply
   growing wider — changing its *height*, not just its width, and
   shifting everything below it. white-space: nowrap is the actual fix:
   it guarantees the button is always exactly one line tall regardless of
   which browser/font renders the text wider, so height never changes;
   min-width is kept alongside it purely to minimize *width* flicker in
   browsers where 316px happens to be enough for both states. */
#in-progress-auto-toggle {
  min-width: 316px;
  white-space: nowrap;
}

.team-home .match-team-logo {
  margin-right: 4px;
}

.team-away .match-team-logo {
  margin-left: 4px;
}

/* Blank rows app.js pads a shorter conference's <tbody> with so every
   conference's Board Standings link lands on the same table row (see
   loadStandings()) are pure spacing, not real ranked rows — no visible
   divider line under them. */
.standings-group tr.standings-blank-row td {
  border-bottom: none;
}

/* Keeps the Board Pts column's ones digit aligned at the same horizontal
   spot on every row regardless of whether that row also has a trailing
   ½ (a draw's half-board-point contribution) — see app.js's
   boardPointsCellHtml(). Same fixed-width-inline-block technique as
   .score-value/.score-dash below, just right-aligned as a pair since
   this whole group sits inside an already-right-aligned td. 32px (not
   the original 22px) — a full season's real board-point totals run into
   3 digits (e.g. "158"), which measured at ~29px wide and was
   overflowing into the adjacent ½ span at the old, too-narrow width,
   visually overlapping the last digit. */
.board-pts-int {
  display: inline-block;
  width: 32px;
  text-align: right;
}

.board-pts-half {
  display: inline-block;
  width: 10px;
  text-align: left;
}

/* Without this, a narrow standings-group column lets the browser insert a
   soft line break between .board-pts-int and .board-pts-half (they're
   two separate inline-block siblings), wrapping the ½ onto its own line
   under the number instead of staying beside it — caught when widening
   .board-pts-int above (from 22px to 32px) to fix the overlap bug tipped
   this cell's total content width over the column's available space. */
.board-pts-cell {
  white-space: nowrap;
}

/* The auto-update toggle swaps between two icon glyphs (⟳ / ⏸) whose
   natural line-height differs slightly depending on the font, which
   otherwise shifts the rest of the page a pixel or two on every click. */
#watch-auto-toggle {
  line-height: 1;
}

/* Matches in Progress pane's Home/Away columns — a longer team name
   ("Belleville East") was otherwise free to wrap onto a second line,
   which looked cramped next to the pane's other single-line columns. */
.team-name-cell {
  white-space: nowrap;
}

.live-icon-link {
  display: inline-flex;
}

.live-icon {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #2a7;
}

/* Pulses while the "Matches in Progress" pane is actually auto-updating
   (see app.js's updateLiveIcons()) so the Live column visually agrees
   with the pane's own auto-update status line rather than just being a
   static "this match is live" marker. */
.live-icon--animated {
  animation: live-icon-pulse 1.4s ease-in-out infinite;
}

.live-icon--static {
  background: var(--color-live-dot-static);
}

@keyframes live-icon-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* The whole-page edit-lock banner (admin.php, team.php — see lock.js's
   initLockBanner()) and the roster-manager panel's own smaller lock
   message on match.php (see match.js) share this same "you're read-only,
   here's why" visual treatment: a tinted bar so it reads as page status,
   not just another paragraph of body text. */
#admin-lock-banner p,
#team-lock-banner p {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: 8px 12px;
  margin-bottom: 16px;
}

#admin-lock-banner input,
#team-lock-banner input {
  margin-right: 4px;
}

.roster-manager-lock-message {
  font-style: italic;
  color: var(--color-danger);
}

.roster-manager-lock-message:empty {
  display: none;
}

/* Shared red-text treatment for any message informing the user a page/
   resource is currently locked by someone else (or that their own lock
   was lost to someone else) — lock.js's whole-page banner, match.js's
   match-picker lock-conflict message, and admin.js's match-edit Extra/JV
   lock message all wrap just the locked-by-someone-else text in a span
   with this class, rather than coloring the whole containing message
   element (which is often shared with plain, non-lock validation text
   that shouldn't turn red too). */
.lock-conflict-message {
  color: var(--color-danger);
}

/* admin.js's match-edit score-swing preview: highlights the "if saved"
   score once it actually differs from what's currently recorded, so a
   pending board correction's standings-affecting swing isn't easy to
   miss in a plain scorer-facing <select>. Same red as
   .lock-conflict-message but a distinct class — this isn't a "locked by
   someone else" message, just reusing the same attention-getting color
   for a different kind of "look before you save" signal. */
.match-edit-score-pending {
  color: var(--color-danger);
  font-weight: bold;
}

/* team.js's roster panel: warns a multi-team school hasn't finished
   designating its 5 "locked to A" (and, at a 3-team school, 5 "locked to
   A or B") players yet (2026-08-26, per the user). Same red as
   .lock-conflict-message/.match-edit-score-pending but its own class —
   not a lock-conflict message, just the same "look before you save"
   color. */
.eligibility-warning {
  color: var(--color-danger);
}

/* team.js's roster panel: "(Locked)" second line on the Eligibility
   column header once a coach can no longer edit that field at all
   (2026-08-27, per the user — a coach trying to edit it used to just get
   a generic "Incorrect or missing password" error with no indication the
   field itself was the problem). Informational, not a warning — muted
   rather than .eligibility-warning's danger color. */
.eligibility-header-locked {
  display: block;
  font-weight: normal;
  font-size: 0.85em;
  color: var(--color-muted-text);
}

/* Shown in place of a roster-manager panel's real content when it
   belongs to a team other than whoever logged into match.php itself —
   see match.js's needsTeamPasswordGate(). */
.roster-manager-password-gate {
  padding: 8px 0;
}

.roster-manager-password-gate p {
  margin-top: 0;
}

/* match.js's roster-manager panel, shown instead of the normal roster
   content when a multi-team school hasn't finished its eligibility-lock
   designations yet (2026-08-26, per the user) — same shell as
   .roster-manager-password-gate above, just a different reason the
   panel's real content isn't shown. */
.roster-manager-restrictions-gate {
  padding: 8px 0;
}

.roster-manager-restrictions-gate p {
  margin-top: 0;
  color: var(--color-danger);
}

/* match.js's Start Match boards table, shown instead of the actual board
   -by-board lineup pickers when either side's school hasn't finished its
   eligibility-lock designations yet (2026-08-27, per the user — "You
   shouldn't even be able to set a roster until the eligibility list is
   set", extending the same rule .roster-manager-restrictions-gate above
   already enforced on the roster-manager panel to the boards table
   itself). */
.start-boards-restrictions-gate {
  text-align: left;
  color: var(--color-danger);
}

.start-boards-restrictions-gate p {
  margin: 4px 0;
}

/* :not([colspan]) excludes the empty-state row's own single
   <td colspan="4">No locks currently held.</td> (admin.js) — with no
   other cells in that row, it's trivially both :first-child and
   :last-child too, so without this it wrongly inherited the
   Force-Release column's right alignment instead of this table's normal
   left default (found live 2026-08-27, per the user). */
#locks-table td:last-child:not([colspan]) {
  text-align: right;
}

/* Recent Matches / Match Results tables (app.js's renderMatchesTable()):
   score sits between the two team names, right-justified home side /
   left-justified away side, winner bolded, headers centered. Sized to its
   own content (not the sitewide table's width: 100%) — stretching to the
   full page width was also what pushed the Score column wide enough that
   its (much narrower, fixed-width) content no longer lined up under the
   centered "Score" header.
   width: max-content (2026-08-12, per the user — "recent is still
   stretching," a real, measured bug: plain width: auto on a <table>
   only shrink-wraps when its *containing block* is also
   content-sized — once this table sits inside
   #match-results-container's own overflow-x: auto wrapper (added the
   same session, for the narrow-viewport scroll fix), that container
   itself fills the full available page width, and width: auto on the
   table then resolved to "fill the container" rather than "shrink to
   my own content," spreading every column out with far more blank
   space than its actual content needed — confirmed visually (a plain
   "2026-02-04" / "7" / "West" / "CBC" row with large gaps between each
   value) and by measurement (every Home cell reporting an identical,
   content-independent width). max-content forces the table back to its
   own true natural width regardless of how much room the container
   offers, while the container's overflow-x: auto still catches the
   opposite case — a narrow viewport where the table's natural width
   exceeds what's available. */
.match-results-table {
  width: max-content;
}

/* Never lets the table itself (or the page around it) grow wider than
   the viewport — see .match-results-table's own nowrap rule below for
   why that's now possible at a narrow viewport. */
#recent-matches-container,
#match-results-container {
  overflow-x: auto;
}

/* No cell here ever wraps onto a second line (2026-08-12, per the user —
   "stop stretching recent results once date's wrapping stops") — the
   Date column previously had no explicit rule of its own, so a narrow
   enough viewport could squeeze it into wrapping, which then let the
   table (and the whole page along with it) keep growing wider than the
   viewport instead of cleanly containing the overflow. #recent-matches-
   container/#match-results-container's own overflow-x: auto below is the
   actual safety net once nowrap makes that no longer optional — an
   overly-narrow viewport now scrolls just this table horizontally
   instead of wrapping cell text or stretching the page, same pattern
   team_public.php's Schedule table already established for itself. */
.match-results-table th,
.match-results-table td {
  white-space: nowrap;
}

.match-results-table th {
  text-align: center;
}

/* Expand triangle gets its own narrow leading column (2026-08-02, per the
   user — a general rule for any table mixing expandable and
   non-expandable rows, not just team_public.php's Schedule table this was
   first fixed on): a double-forfeit row here has nothing to expand, so
   without its own column the triangle's inline margin would otherwise
   shift that row's Date text left relative to every clickable row's. Same
   width: 1% shrink-to-content trick as every other expand column in this
   file (.team-roster-expand-col, .team-schedule-expand-col, etc). */
.match-results-expand-col {
  width: 1%;
}

.match-results-table td.team-home,
.match-results-table td.team-away {
  white-space: nowrap;
}

.match-results-table td.team-home {
  text-align: right;
}

.match-results-table td.team-away {
  text-align: left;
}

.match-results-table td.winner {
  font-weight: bold;
}

/* "Round" column (2026-08-10, per the user) — shows matches.round_number,
   or "Friendly" for a match with none (see app.js's matchRoundLabel()).
   The header is already centered for free via .match-results-table th
   above; the data cell needs its own explicit rule since a plain <td>
   otherwise falls back to the sitewide left-align default. Sits right
   after Date (moved there 2026-08-12, per the user — previously this
   table's own leftmost column, ahead of even the expand-triangle
   column). The Matches in Progress pane's own copy of this column
   instead sits right after its leading "Live" column, a different table
   with its own already-centered-by-default td rule
   (#in-progress-container td). */
.match-results-round {
  text-align: center;
}

/* "Conf." column (2026-08-12, per the user), sitting right after Round —
   same centering treatment, same reasoning as .match-results-round
   above. */
.match-results-conference {
  text-align: center;
}

/* Every piece of the score is a fixed-width inline-block regardless of its
   content (digit count, ½ glyph, or an empty indicator slot) specifically
   so the dash between the two scores lands at the same horizontal spot on
   every row — a table column's width is already shared across all its
   rows, so fixed-width children here is the only piece needed to keep
   the whole group, and therefore the dash within it, from drifting.
   Centered so it still lines up under the "Score" header regardless of
   how much wider the column ends up than the group's own content.
   Tighter left/right padding than the sitewide 12px (2026-08-12, per the
   user — "less padding between teams and arrow") — this cell's own
   content is already centered/self-contained, so it doesn't need as much
   breathing room from its neighboring team-name cells as a normal cell. */
.score-cell {
  white-space: nowrap;
  text-align: center;
  padding-left: 4px;
  padding-right: 4px;
}

.score-indicator {
  display: inline-block;
  width: 12px;
  text-align: center;
}

/* Custom-drawn "=" for a draw (2026-08-13, per the user: "we need an
   equals sign with the same weight and horizontal size as the triangles
   used to indicate winners"), replacing the bare Unicode "=" character
   that used to sit directly inside .score-indicator. A real, measured
   problem, not a subjective nitpick: at the same font-size, the ◀/▶
   triangle glyphs and "=" are nowhere near visually equivalent — the
   triangle's own ink measures ~10×13px, "="'s own ink only ~8×4px (its
   two strokes are thin, short lines, nothing like a triangle's solid
   filled shape) — and scaling the Unicode "=" up via font-size to
   compensate for the width gap also blows out its line-height (~1.7em
   was needed to reach the triangle's own ~10px ink width, which grows
   the character's own box to ~36px tall, well past a table row's normal
   height). A small, fixed-size two-bar shape drawn via pseudo-elements
   sidesteps both problems at once: sized/weighted independently of any
   glyph's own font metrics, and contained within a fixed height that
   doesn't disturb row height. Used in place of a bare "=" wherever a
   draw is indicated (app.js/team_public.js/watch.js's shared
   match-detail.js, and conference_results.js's own separate copy). */
.score-equals {
  position: relative;
  display: inline-block;
  width: 12px;
  height: 1em;
  vertical-align: -3px;
}

/* Dialed back once more (2026-08-13, same day, third and final pass, per
   the user: "the equals sign is too heave and the bottom bar looks larger
   than the top") — the immediately-prior 11×5px/0.5px-left version
   over-corrected on weight, and (per the user's own report) read visibly
   asymmetric between the two bars despite getComputedStyle confirming
   both pseudo-elements always shared byte-identical width/height/left —
   the only plausible cause left is inconsistent anti-aliasing from the
   0.5px subpixel `left` value (and the resulting non-integer `top`
   offsets), which different browsers/zoom levels can round differently
   per bar. Every value here is now a whole pixel — `left`, `width`,
   `height`, and both `top` offsets all resolve to integers (this box's
   own height is always exactly 1em = 16px at the sitewide default font
   -size, confirmed via getComputedStyle) — so there's nothing left for a
   renderer to round asymmetrically between the two bars, regardless of
   browser. Verified via a side-by-side synthetic comparison against the
   real ▶ glyph before picking these values: bold enough to still read
   clearly as an "=" (unlike the original 10×3px version this whole
   thread started from), lighter than the immediately-prior 11×5px
   version, with a full 3px gap between the two bars so they read as two
   distinct strokes rather than a near-solid block. */
/* Switched from the two-bar "=" above to a single centered bar
   (2026-08-14, per the user — tried as a side-by-side comparison
   first ("the equals sign looks too heavy, let's temporarily try just
   a same-width dash instead to see how it looks"), then confirmed:
   "I prefer the single dash for a draw"). Same 10px width/4px height as
   each of the two bars it replaces — this is deliberately still called
   `.score-equals` (and `SCORE_EQUALS_HTML` in match-detail.js/
   conference_results.js) despite no longer drawing an actual "=" shape;
   renaming would touch several files' class/constant names purely for
   internal-naming tidiness with no visible effect, so it was left
   as-is. The 2026-08-13 history above (the two-bar design, its own
   weight/asymmetry fixes) is kept for the reasoning trail even though
   superseded — that shape is what this replaces, not what's live now. */
.score-equals::before {
  content: '';
  position: absolute;
  left: 1px;
  width: 10px;
  height: 4px;
  background: currentColor;
  top: calc(50% - 2px);
}

/* No explicit width (2026-08-12, per the user — superseding two earlier
   attempts, first a 48px fixed box sized around "Forfeit" text, then a
   30px box sized around a right-aligned int/half pair) — app.js's
   scoreValueHtml() now always renders the exact same three-piece shape
   (a leading digit, a "ones" character, a trailing ½ — each either real
   or invisibility:hidden) for every row, and each of those three pieces
   has its own fixed width below, so this outer wrapper's own total
   rendered width comes out identical on every row without needing a
   forced width of its own to guarantee it. */
.score-value {
  display: inline-block;
}

/* app.js's scoreValueHtml() always renders these three spans together,
   using .score-value-hidden on whichever ones are standing in for
   content that isn't actually there (a 1-digit score's own leading
   digit; a whole-number score's own trailing ½) — visibility: hidden
   (not display: none) is what keeps a hidden piece occupying exactly
   the same width a real one would. */
.score-value-hidden {
  visibility: hidden;
}

/* Fixed widths on all three pieces (2026-08-12, per the user — "equal on
   the two sides") — a real, measured gotcha caught after an initial,
   width-less version of this rule: every real digit 0-9 renders at an
   identical width in this font (confirmed by measurement), but the
   forfeited side's "F" character does not (~9.8px vs a digit's ~8.9px)
   — so a row with "F" on one side and a real 2-digit score on the other
   rendered its dash about 1px off from every purely-numeric row's,
   since .score-value-ones's own *content* width differed by glyph, not
   just by digit count. Explicit fixed widths here (not just the hidden
   -placeholder trick above) are what make a score's total rendered
   width — and therefore the gap to whichever neighbor sits beside it —
   truly identical regardless of which specific character occupies each
   slot, not just whether one is present. */
.score-value-lead,
.score-value-ones {
  display: inline-block;
  width: 10px;
  text-align: center;
}

.score-value-half {
  display: inline-block;
  width: 14px;
  text-align: center;
}

/* Bolds just the winning side's number within a score group (Recent
   Matches/Match Results' matchScoreCellHtml(), including a forfeit's real
   score) — same "highlight the winner" intent as .match-results-table
   td.winner above, which already bolds the winning team's *name*; this
   is the matching treatment for the *number* sitting right next to it.
   font-weight is inherited, so this still reaches the nested
   .score-value-lead/.score-value-ones/.score-value-half spans without
   needing its own rule. */
.score-value.winner {
  font-weight: bold;
}

/* The expanded per-board detail table's Result column header (app.js's
   detailResultHeaderHtml()) carries the whole match's total score as a
   smaller, muted second line under the plain "Result" label — bolded
   per the user, so the score itself still reads as the emphasized part
   of that subtitle even though it's smaller/muted-colored than the
   "Result" label above it. */
.detail-total-score {
  font-weight: bold;
  font-size: 0.85em;
  color: var(--color-muted-text);
}

/* Equal margin on both sides (2026-08-12, per the user — "equal on the
   two sides") — safe to rely on now that both neighboring .score-value
   groups render at a content-independent width (see .score-value-hidden
   above), so this margin is the *only* thing controlling the gap on
   either side; it's no longer competing with variable leftover box
   space the way an earlier attempt's fixed-width int/half boxes were. */
.score-dash {
  display: inline-block;
  width: 14px;
  text-align: center;
  margin: 0 3px;
}

/* Disambiguates a drawn Armageddon board from a genuine outright win
   (2026-08-15, per the user: "Since a draw and black win produces the
   same score, we need extra text on the result to disambiguate") — draw
   odds (see playoff_stage_aggregate() in api/db.php) award the full
   tiebreak point to Black on a drawn game, so its score/arrow render
   identically to Black having actually won the game outright. `display:
   block` puts this on its own line below the score group (the parent
   .score-cell's own `white-space: nowrap` only governs its own inline
   text flow, not a block child's layout, so this doesn't fight that rule
   or force the row wider). Muted/italic, same "this qualifies the result,
   it isn't the result itself" treatment as .md-pending-cell's "Pending". */
.armageddon-draw-odds-note {
  display: block;
  margin-top: 2px;
  font-size: 0.85em;
  font-style: italic;
  color: var(--color-muted-text);
  white-space: normal;
}

/* team_public.php's roster table: Grade/Rating right-justified, same
   alignment convention as the inline roster-manager's own
   .roster-grade-cell/.roster-rating-cell (scoped differently there since
   that table lives inside .roster-manager and this one doesn't). */
.team-roster-grade,
.team-roster-rating {
  text-align: right;
}

.team-roster-board-pts,
.team-roster-record,
.team-roster-uscf,
.team-roster-playoff-record {
  text-align: center;
}

/* Lichess column stays left-aligned for a real handle (readable in a
   left edge like Player/USCF ID's own real values), but the empty-state
   dash centers itself instead of sitting awkwardly at the left edge of
   an otherwise-empty cell. */
.team-roster-lichess-empty {
  display: block;
  text-align: center;
}

/* team_public.js's boardPtsCellHtml() — "Board/Pts" ("5 / 2½"), the
   player's Board Standings eligibility. Same fixed-width-inline-block
   alignment technique as .board-pts-int/.board-pts-half (app.js's
   standings table) and .board-standings-score-int/-half
   (board_standings.js), just with a board-number span and a "/"
   separator added in front so the slash itself lands at the same x
   position on every row regardless of digit count on either side — a
   board number is always a single digit (1-5), so its own span only
   needs to be wide enough for that. white-space: nowrap on the cell
   stops the browser inserting a soft line break between these sibling
   inline-block spans on a narrow column (the same wrap bug
   .board-pts-cell/.board-standings-score already had to guard against). */
.team-roster-board-pts {
  white-space: nowrap;
}

/* Left-aligned (not right, unlike .board-pts-int/.team-roster-board-pts-int
   below) — this box is always exactly one digit (a board number is always
   1-5), so alignment here doesn't affect the slash's own guaranteed fixed
   x-position at all (that's purely a function of this span's own fixed
   width); left-aligning just leaves visible breathing room between the
   digit and the "/" that follows, instead of crowding them together. */
.team-roster-board-num {
  display: inline-block;
  width: 10px;
}

/* Fixed CSS padding rather than literal space characters in the "/"
   content itself — a rendered space glyph's own visual width isn't
   guaranteed symmetric on both sides of the slash, where padding is. */
.team-roster-board-slash {
  display: inline-block;
  padding: 0 3px;
}

.team-roster-board-pts-int {
  display: inline-block;
  width: 14px;
  text-align: right;
}

.team-roster-board-pts-half {
  display: inline-block;
  width: 10px;
  text-align: left;
}

/* team_public.php's per-player game-log detail (togglePlayerDetail()) —
   Board column shows either a real board number or a 'JV'/'Extra' label
   (2026-08-02, per the user), centered same as every other bare-number
   "which board" column elsewhere in this app (board-detail-board-pts,
   etc). */
.player-games-board {
  text-align: center;
}

/* Round column (2026-08-16, per the user) — same centering treatment as
   every other "Round" column elsewhere in this app (.match-results-round
   etc), header and data alike. */
.player-games-round {
  text-align: center;
}

/* Schedule table's Home/Away column, per the user. */
.team-schedule-home-away {
  text-align: center;
}

/* Date column, per the user — left-aligned in both the header (overriding
   .match-results-table th's own text-align: center below) and the data
   cells, so header and data line up. Data cells were already left-aligned
   by the sitewide th/td default, but the header wasn't until this. Needs
   the #team-schedule-container-scoped selector specifically — a plain
   .team-schedule-date class alone has lower specificity than
   ".match-results-table th" (one class + one type selector) and loses to
   it regardless of source order. */
#team-schedule-container .team-schedule-date {
  text-align: left;
}

/* Opponent-school logo in front of the opponent's name in the Schedule
   table's Opponent column, per the user — same fallback rule as every
   other per-team logo in this app (schoolLogoUrl() above). Sized to
   match .standings-team-logo (20px) rather than the smaller 16px this
   started at, per the user's "could be a little bigger" follow-up
   (2026-08-02). */
.team-schedule-opponent-logo {
  width: 20px;
  height: 20px;
  object-fit: contain;
  vertical-align: middle;
  margin-right: 4px;
}

/* board_standings.php's whole dynamic content area (every "Board N"
   heading, leaderboard table, and expanded per-player game-log table) —
   scaled down ~20% from the sitewide defaults (font-size: 1em/th,td
   padding: 8px 12px), per the user, since this page is denser (up to
   ~30 rows per board) than most others in the app. Scoped to this one
   container rather than a global change, so no other page is affected. */
#board-standings-container {
  font-size: 0.8em;
}

#board-standings-container th,
#board-standings-container td {
  padding: 6px 10px;
}

/* The sitewide table { margin-bottom: 24px } rule (see that rule's own
   comment) is meant to separate one top-level table from the next
   section of the page — applied unscoped, it also lands on the nested
   .player-games-table sitting inside a player's expanded detail row,
   leaving a stray 24px gap between the game log's last row and the next
   player row in the outer leaderboard table (2026-08-02, per the user:
   "Remove the extra line at the bottom of the expansion"). */
#board-standings-container .player-games-table {
  margin-bottom: 0;
}

/* board_standings.php's per-board leaderboard tables, and app.js's link
   into it from the bottom of each conference's standings table. Sized to
   its own content (not the sitewide table { width: 100% }) — same
   stretch-to-page-width bug already hit and fixed on the standings
   page's Match Results table (see .match-results-table above). */
.board-standings-table {
  width: auto;
}

/* Boards 1-3 in the left column, 4-5 in the right, per the user — the
   page read as too long with all 5 stacked in one column. align-items:
   flex-start so a shorter column doesn't stretch to match a taller one;
   flex-wrap lets the right column drop below the left on a narrow
   viewport instead of squeezing both into an unreadable width. */
.board-standings-columns {
  display: flex;
  gap: 32px;
  align-items: flex-start;
  flex-wrap: wrap;
}

.board-standings-col {
  flex: 1 1 320px;
  min-width: 280px;
}

/* Small team logo next to each player's team name, per the user —
   falls back to the sitewide league logo (see playerTeamLogoUrl()) same
   as every other per-team logo display in this app. */
.board-standings-team-logo {
  width: 16px;
  height: 16px;
  object-fit: contain;
  vertical-align: middle;
  margin-right: 4px;
}

.board-standings-score,
.board-standings-gp {
  text-align: center;
}

/* Keeps the Score column's units digit aligned at the same horizontal
   spot on every row regardless of a trailing ½ — see
   board_standings.js's boardStandingsScoreHtml(). Same fixed-width-pair
   technique as the standings page's Board Pts column
   (.board-pts-int/.board-pts-half), just narrower since a per-board
   score is always a single digit (at most a full season's game count).
   white-space: nowrap on the <td> itself is required too, not just the
   spans — without it, a narrow column can let the browser insert a soft
   line break between the two spans, wrapping the ½ onto its own line
   (the exact bug already hit and fixed on that same Board Pts column). */
td.board-standings-score {
  white-space: nowrap;
}

.board-standings-score-int {
  display: inline-block;
  width: 14px;
  text-align: right;
}

.board-standings-score-half {
  display: inline-block;
  width: 10px;
  text-align: left;
}

/* Same "give the expand triangle its own narrow column" treatment as
   team_public.php's roster table (see .team-roster-expand-col) — a long
   player name can still wrap in this now-auto-width table, and this
   keeps the wrapped second line landing under the name instead of under
   the triangle. A fixed pixel width, not that rule's own `width: 1%` —
   team_public.php's roster table stays at the sitewide `width: 100%`
   (a definite containing width, so 1% of it is trivial), but this
   table is deliberately shrink-to-fit (`width: auto` above); mixing
   `width: auto` on the table with a `%`-width column sent Chromium's
   automatic table layout into a feedback loop (a real bug caught by
   actually measuring the rendered width, not just reading the CSS —
   the table rendered at 1000px instead of shrinking to its content). */
.board-standings-expand-col {
  width: 20px;
  white-space: nowrap;
}

/* board_cross_table.php: a real HTML table (columns/alignment via table
   layout, no monospace font needed) styled after the reference league's
   own east_cross_table.txt/central_cross_table.txt/west_cross_table.txt.
   Sized to its own content, same reasoning as .board-standings-table
   above; overflow-x: auto on the wrapper is the safety net for a
   narrow viewport, since a season's worth of round columns can add up
   to a table wider than most screens — the columns themselves don't
   wrap or shrink, they just scroll into view. */
.cross-table-wrap {
  overflow-x: auto;
}

.cross-table {
  width: auto;
}

/* Denser than most other pages (many round columns on top of Name/Team/
   Grade/Rating/USCF ID/Place/Pts), so per the user, scaled down ~10% from
   the sitewide defaults (font-size: 1em / th,td padding: 8px 12px) —
   same reasoning and technique as board_standings.php's own
   #board-standings-container rule, just a smaller reduction and scoped
   to this page's own container instead. */
#cross-table-container {
  font-size: 0.9em;
}

#cross-table-container th,
#cross-table-container td {
  padding: 7px 11px;
}

/* A further reduction on top of the container-wide one above, scoped to
   just the tables themselves (not the "Board N" <h3> headers, which the
   user confirmed are already the right size) — per the user, "a little
   more" than the initial ~10% cut. Compounds with the container's own
   0.9em (0.9 × 0.9 ≈ 0.81, ~19% below the sitewide default overall). */
.cross-table {
  font-size: 0.9em;
}

/* #cross-table-container th/td above, not .cross-table th/td alone —
   needed to actually beat that ID-scoped rule's specificity (1,0,1) vs.
   a plain class+element rule's (0,1,1). A real bug caught by measuring
   the computed padding, not just reading the CSS: without the extra
   #cross-table-container prefix, this rule was silently losing that
   fight and never applying at all. */
#cross-table-container .cross-table th,
#cross-table-container .cross-table td {
  padding: 5px 9px;
}

.cross-table-no,
.cross-table-uscf,
.cross-table-score,
.cross-table-round {
  text-align: center;
  white-space: nowrap;
}

/* Grade/Rating/Place right-justified per the user, same convention as
   team_public.php's own .team-roster-grade/.team-roster-rating. */
.cross-table-grade,
.cross-table-rating,
.cross-table-place {
  text-align: right;
  white-space: nowrap;
}

/* board_cross_table.php's header row: page title on the left, the
   notation legend pinned to the upper-right per the user. Wraps below
   the title on a narrow viewport rather than the legend box being forced
   to squeeze down to an unreadable width. margin-bottom tightened per
   the user, so the legend box's bottom edge sits close to the Conference
   picker's own bottom edge right below it. */
.cross-table-header-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 16px;
  margin-bottom: 0;
}

/* h1 keeps its default browser top margin everywhere else in the app,
   but here it pushed the heading's own visible top edge below the
   legend box's top edge (the legend has no equivalent margin, just
   padding starting flush at the flex row's own top) — zeroed out so the
   two tops actually line up, per the user. */
.cross-table-header-row h1 {
  margin-top: 0;
}

.cross-table-legend {
  max-width: 340px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 12px 16px;
  font-size: 0.85em;
}

.cross-table-legend h2 {
  margin-top: 0;
  margin-bottom: 8px;
  font-size: 1em;
}

.cross-table-legend p {
  margin: 4px 0;
  text-align: justify;
}

/* Repeats .standings-group td so this ties .standings-group td:first-child
   above on specificity ((0,2,1) each) and wins on source order instead of
   losing to it outright — a plain .board-standings-link-row alone is
   (0,1,0) and was silently losing that fight, left-aligning this row
   under the single td (this is the only cell in its <tr>, so it's always
   also :first-child) instead of centering it as intended. Same
   class-specificity gotcha (and the same fix shape) as the
   .standings-group.standings-group--line-start rule above. */
.standings-group td.board-standings-link-row {
  text-align: center;
  font-size: 0.9em;
}

/* Shared board-by-board match detail table (match-detail.js's
   mdBoardDetailTableHtml()/mdBoardRowsHtml(), 2026-08-13) — used by
   app.js (Recent Match Results/Match Results), team_public.js (a team's
   own Schedule table), and watch.js's own persistent table shell
   (#watch-boards-table, which reuses just the tbody-row styling below
   rather than this table-level width/header rule, since it keeps its
   own static <thead>). Replaces the two previously-separate, visually
   *different* per-page classes this consolidated (.match-board-detail
   -table's plain-left-aligned player columns, and .board-detail-table's
   right-justified-left/left-justified-right "flows toward the score"
   treatment) — standardized on the plain-left-aligned convention, the
   one 2 of the original 3 pages already used. */
.md-detail-table {
  width: auto;
}

.md-detail-table th {
  text-align: center;
}

/* Board/Points data cells had no alignment class at all on one of this
   table's two predecessors (2026-08-12, per the user) — same class of
   bug already fixed elsewhere in this app (see e.g. team_public.php's
   USCF ID column) where a header is explicitly centered but its own
   data cells fall back to the sitewide left-align default. Scoped to
   tbody (not the whole table) matching the precedent this app already
   established for match.php's/watch.php's own live board tables'
   identical Board/Points centering (see #watch-boards-table's own
   matching rule below, kept separate since that table isn't built by
   mdBoardDetailTableHtml() itself). */
.md-detail-table tbody td:nth-child(1),
.md-detail-table tbody td:nth-child(2) {
  text-align: center;
}

/* Also applies to #watch-boards-table's own tbody, built from the same
   mdBoardRowsHtml() rows (see that table's own separate width/header
   rules below) — the winning side's player name is bolded, same
   convention as the Result column's own score-value.winner bolding the
   winning *number* right next to it (matchScoreCellHtml()'s row-level
   score already pairs the two the same way). Also applies to
   #live-boards-table (match.php's own live-reporting board table,
   2026-08-14, per the user — "when a match is reported, bold the winner
   and use the result symbols next to the name") — that table isn't built
   by mdBoardRowsHtml() at all (it needs a Modify button/interactive
   report controls the shared renderer has no room for), but reuses this
   same winner-bolding convention directly since the visual result should
   read identically wherever a board's outcome is shown. */
.md-detail-table td.winner,
#watch-boards-table td.winner,
#live-boards-table td.winner,
#start-prior-stages-table td.winner {
  font-weight: bold;
}

/* A still-unreported board/extra-game's Result cell (2026-08-13) — muted
   and italic so "Pending" reads as a state, not a result. */
.md-pending-cell {
  color: var(--color-muted-text);
  font-style: italic;
}

/* Wraps both the live-reporting and live-viewing board tables — neither
   table stretches with the browser anymore (width: auto below), so on a
   narrow viewport a wide one (match.php's, with its selects/buttons, or
   watch.php's own min-width floor) needs to scroll within its own box
   instead of forcing the whole page to scroll horizontally. */
.boards-table-wrap {
  overflow-x: auto;
}

/* match.php's lineup-setup table (before "Start Match" is clicked) — per
   the user: Board/Points columns centered, same treatment #live-boards-table
   gets below. The Home player/Away player headers were tried centered too
   but reverted per the user — they stay left-justified, matching the
   player <select>s underneath them. Scoped to thead/tbody, not the tfoot —
   same reasoning as #live-boards-table's own thead/tbody scoping just
   below: the tfoot's Coach/Sponsor row leads with a colspan="2" cell, so a
   plain nth-child selector would misalign against it. */
#start-boards-table thead th:nth-child(1),
#start-boards-table tbody td:nth-child(1),
#start-boards-table thead th:nth-child(2),
#start-boards-table tbody td:nth-child(2) {
  text-align: center;
}

/* Live match reporting's board table — per the user: smaller font, less
   horizontal padding (this table has a lot of columns competing for
   space once the trailing Remove column exists), Board/Points/Result
   centered (Home player/Away player are the only columns that stay
   left-reading text, matching the player <select>s above them), and the
   two Result <select>s (a varsity board's .report-result and a JV/extra
   game's own .extra-game-result) forced to the same width regardless of
   their own options' text length — .report-result's longest option
   ("Away wins by forfeit") is much wider than .extra-game-result's
   ("Home win"/"Draw"/"Away win"), which otherwise left the JV row's
   pulldown visibly narrower than a board row's. */
/* Sizes to its own content instead of the sitewide table { width: 100% }
   stretching it to the full page width as the browser resizes — same
   fix already applied to several other tables in this app (Match
   Results, Board Standings, the cross table). */
/* #start-prior-stages-table (2026-08-15) is a second, always-read-only
   table sharing the exact same shape — see match.php's own comment on it
   — so it picks up the same sizing/centering/dash-spacing rules
   throughout this section rather than duplicating them. */
#live-boards-table,
#start-prior-stages-table {
  width: auto;
  font-size: 0.9em;
}

#live-boards-table th,
#live-boards-table td,
#start-prior-stages-table th,
#start-prior-stages-table td {
  padding: 8px 6px;
  white-space: nowrap;
}

/* Scoped to thead/tbody only — the tfoot's Coach/Sponsor row leads with a
   colspan="2" cell, so its *own* 2nd <td> (the home coach name) is really
   under the Home player column, not Points. A plain td:nth-child(2)
   selector doesn't know about colspan and would wrongly center-align that
   coach name instead of leaving it flush with the player names above it —
   a real bug caught by the user (the coach name visibly didn't line up
   with the column of player names right above it). Column 4 (Result) is
   included here too (2026-08-13, per the user — "the 'Pending' still
   looks uncentered in the live match reporting") — this table never
   actually centered its Result column at all (a bare <td>, no class,
   left-aligned by the sitewide default, for every one of its states: the
   report select+button, a decided board's plain result text, and a
   still-pending JV/extra game's "Pending" note), unlike every other page
   in this app showing the same column (index.php/team_public.php/
   watch.php/conference_results.php all center it via the shared
   .score-cell class this table's own cells never carry). Centering the
   whole column — not just the "Pending" state — is what actually brings
   it in line with those, and reads fine for the interactive select
   +button too (a centered inline-block group, same as any other
   centered form control in this app). */
#live-boards-table thead th:nth-child(1),
#live-boards-table tbody td:nth-child(1),
#live-boards-table thead th:nth-child(2),
#live-boards-table tbody td:nth-child(2),
#live-boards-table thead th:nth-child(4),
#live-boards-table tbody td:nth-child(4),
#start-prior-stages-table thead th:nth-child(1),
#start-prior-stages-table tbody td:nth-child(1),
#start-prior-stages-table thead th:nth-child(2),
#start-prior-stages-table tbody td:nth-child(2),
#start-prior-stages-table thead th:nth-child(4),
#start-prior-stages-table tbody td:nth-child(4) {
  text-align: center;
}

#live-boards-table select {
  width: 180px;
}

/* Tighter dash margin than the sitewide .score-dash rule (2026-08-15, per
   the user: "too much space around the dash in the score") — scoped to
   just this table rather than the shared .score-dash class, since no
   other page's own score display was flagged. Paired with match.js's own
   scoreValueHtml() dropping its always-hidden leading-digits span (a
   board's weight never reaches two digits), which was the bigger
   contributor to the gap; this narrows the dash's own margin on top of
   that. */
#live-boards-table .score-dash,
#start-prior-stages-table .score-dash {
  margin: 0 1px;
}

/* Smaller than the sitewide button { padding: 8px 16px } default
   (2026-08-15, per the user: "make the report button smaller") — sized
   for the trailing-column Modify button, a decided varsity board's own
   action (both the varsity board's own select and, as of 2026-08-19,
   the Extra/JV game row's own select report directly on change with no
   button of their own at all — see match.js's .report-result/
   .extra-game-result change-listener comments). Per the user: "Modify
   button should be same height as report button" — without this it was
   still on the sitewide 8px 16px default, taller than its row's
   neighbors. .extra-game-modify (2026-08-19, per the user: a decided
   JV/Extra game needs both Modify and Remove, not just Remove) gets the
   same treatment, sitting next to .extra-game-remove in the same
   trailing cell — .extra-game-remove pulled in here too so the two
   don't mismatch in height now that they're side by side. */
#live-boards-table .modify-button,
#live-boards-table .extra-game-modify,
#live-boards-table .extra-game-remove {
  padding: 4px 10px;
}

/* A decided board's Modify button now lives in the table's own trailing
   action column (2026-08-14, per the user: "move the modify button to
   the right of the whole row") rather than inline inside the Result
   cell — same trailing column extra/JV games' own Remove button already
   uses, so the arrow/score/dash/score/arrow group in the Result cell
   stays plainly centered with nothing else competing for its space. */
#live-boards-table th:last-child,
#live-boards-table td:last-child,
#start-prior-stages-table th:last-child,
#start-prior-stages-table td:last-child {
  width: 1%;
}

/* Coach/Sponsor label, on both this table and #start-boards-table's own
   matching tfoot row. */
#live-boards-table tfoot td:first-child,
#start-boards-table tfoot td:first-child {
  font-weight: bold;
}

/* A stage-transition heading row (2026-08-14, "Blitz Tiebreak"/
   "Armageddon Tiebreak"/"Normal Time") — only rendered at all once a
   playoff match actually has more than one stage, see match.js's own
   renderLiveBoards(). Centered and set off from the surrounding board
   rows with a muted background, same visual weight as a section divider
   rather than a normal data row. */
#live-boards-table .live-board-stage-heading td,
#start-prior-stages-table .live-board-stage-heading td {
  text-align: center;
  background: var(--color-muted-bg);
  padding-top: 10px;
  padding-bottom: 10px;
}

/* Same treatment, for the shared match-detail.js primitive used by
   app.js/team_public.js/watch.js's own read-only board-detail tables
   (2026-08-14) — see mdBoardRowsHtml()'s own comment for why this needed
   to exist at all (a tiebreak-decided playoff match's boards now span
   more than one stage there too, not just on match.php's live view). */
.md-board-stage-heading td {
  text-align: center;
  background: var(--color-muted-bg);
  padding-top: 10px;
  padding-bottom: 10px;
}

/* watch.php's read-only counterpart of the same table — same font-size/
   padding/Board+Points-centering treatment, minus the select-width and
   trailing-column rules above (nothing here is interactive). */
/* min-width per the user, ~30% wider than this table's own natural
   shrink-to-fit width on a real match (measured ~397px on the Lift for
   Life-B vs. Althoff match) — width: auto alone let it get too narrow
   on a match with short player names/no JV rows. */
#watch-boards-table {
  width: auto;
  min-width: 516px;
  font-size: 0.9em;
}

#watch-boards-table th,
#watch-boards-table td {
  padding: 8px 6px;
}

#watch-boards-table th:nth-child(1),
#watch-boards-table td:nth-child(1),
#watch-boards-table th:nth-child(2),
#watch-boards-table td:nth-child(2) {
  text-align: center;
}

#extra-games-section,
#extra-games-body {
  margin-top: 1em;
}

/* submit.php's "Submit result" button — per the user, needs clear
   vertical separation from "Add JV Game" right above it so the two
   aren't read as one button group. */
.submit-button-row {
  margin-top: 1.5em;
}

.extra-game-row {
  display: flex;
  align-items: center;
  gap: 0.75em;
  padding: 0.3em 0;
  border-bottom: 1px solid var(--color-border);
}

/* Add Game button stacked below the two side pickers, per the user,
   rather than inline at the end of the same row as the away side's
   controls. */
.extra-game-add-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5em;
  margin-top: 0.5em;
}

.extra-game-pickers {
  display: flex;
  align-items: center;
  gap: 0.75em;
}

/* admin.php's own copy of this same row keeps Add Game inline with the
   two side pickers instead, per the user (2026-08-07) — an ID-scoped
   override (ID beats the shared .extra-game-add-row class regardless of
   source order) so match.php's own stacked layout above is untouched. */
#match-edit-extra-game-add-row {
  flex-direction: row;
  align-items: center;
  flex-wrap: wrap;
}

.extra-game-side {
  display: flex;
  align-items: center;
  gap: 0.4em;
}

/* match.php's own Add Game row only — see the header markup in that file
   (Home/Away Player + team name, added per the user 2026-08-09). An
   ID-scoped override rather than changing the shared .extra-game-side
   rule itself, since admin.php's identically-classed Add Game row has no
   header and stays a plain single row (same reasoning as
   #match-edit-extra-game-add-row's own override right above). */
#extra-game-add-row .extra-game-side {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.3em;
}

.extra-game-side-header {
  font-weight: bold;
}

.extra-game-side-controls {
  display: flex;
  align-items: center;
  gap: 0.4em;
}

.extra-game-side label {
  font-weight: normal;
  white-space: nowrap;
}

/* team.php's own live logo preview (branding editor) — unrelated to
   team_public.php's banner logo below, which is sized within .team-banner
   instead since it now shares a box with the team name. */
#branding-logo-preview {
  max-height: 80px;
  max-width: 200px;
}

/* team_public.php's "Select Team" heading + dropdown share one row instead
   of the dropdown sitting on its own line below — the heading text itself
   already says what the control is for, so a separate "Team" <label>
   above it would just be redundant. Extra margin-bottom (beyond the
   heading's own default) since this row sits directly above the banner,
   which otherwise looked cramped right under it. Font-size reduced from
   the browser's default h1 size (2026-07-30, per the user) — a full-size
   h1 towered over the small inline <select> next to it. */
.team-select-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 32px;
}

.team-select-row h1 {
  margin: 0;
  font-size: 1.3em;
}

/* team_public.php's team banner (2026-07-30, per the user): team/nickname
   name on the left, logo on the right, in a rounded box whose background
   is a gradient built from the school's own two branding colors (dark
   upper-left, fading quickly to light — set inline per-team in
   team_public.js, since the actual colors vary by school). White text +
   a shadow keeps the name readable regardless of exactly how dark/light
   the two configured colors are; a school with no colors configured yet
   falls back to a flat neutral theme-aware gradient instead of an empty
   background (see team_public.js). */
.team-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  border-radius: 16px;
  padding: 20px 28px;
  /* The gradient's light end often lands close to the page background
     color (especially a school's light color being literal white) —
     without this, the box's own boundary disappears on that side. */
  border: 1px solid var(--color-border-strong);
}

.team-banner h2 {
  margin: 0;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}

.team-banner .team-logo {
  max-height: 64px;
  max-width: 160px;
  flex: none;
}

/* Bold + centered directly under the banner, matching the current
   record/place figures shown there (see team_public.js's
   teamStandingText()). */
.team-standing {
  font-weight: bold;
  text-align: center;
  margin-top: 8px;
}

.coach-display {
  font-weight: bold;
}

/* team_public.php's roster defaults to just this team's own players (see
   team_public.js's renderRoster()) — the expand/collapse toggle for the
   rest of the school's shared roster sits beside the "Roster" heading
   (2026-08-08, per the user — moved up from its own row below the
   table) and is now a plain, real-looking button (the sitewide default
   `button` styling below), not the underlined-text-link look this used
   to have. */
/* justify-content: space-between (rather than relying on the natural gap
   after a variable-width heading) pins the button to the row's own
   right edge regardless of which heading wording is showing — the
   heading itself changes length between "Varsity Roster — {team}" and
   "School Roster — {school}" (see updateRosterHeadings()), which
   otherwise shifted the button's x position even once its own size was
   fixed (2026-08-08, per the user — "same size and location"). */
.roster-heading-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  /* Space before the table below — padding, not margin, per the
     "Edit Team" bottom-spacing fix's own lesson right above: a margin
     here risks collapsing with the table's own (likely-zero) top
     margin instead of reliably producing visible space. */
  padding-bottom: 12px;
}

.roster-heading-row h3 {
  margin: 0;
}

/* The toggle's two label states ("Show full {school} roster" /
   "Show only {team} players") are different lengths, which previously
   let the button itself resize (and, since it sits in a flex row next
   to the heading, shift its own click target) every time it was
   clicked — same class of bug already fixed once on the standings
   page's "Matches in Progress" auto-update toggle. Fixed with a fixed
   min-width (sized to the longest real label sitewide — "Show only
   Lift for Life‑A players", measured at 256px, plus a small buffer —
   rather than the widest label for whichever team happens to be
   selected) and `white-space: nowrap` so the button can never wrap
   its own text onto a second line and grow taller instead, same
   structural-guarantee reasoning as that earlier fix. 2026-08-08, per
   the user. */
#roster-toggle-button {
  min-width: 270px;
  white-space: nowrap;
}

/* The new "Edit Team" button (2026-08-08, per the user) sits below the
   whole roster table, with room underneath it too rather than ending
   flush at the very bottom of the page. `padding-bottom`, not
   `margin-bottom` — a bottom margin here is the section's/body's last
   child, so it would just collapse into `body`'s own 40px bottom
   margin (margins collapse to the larger of the two, not their sum),
   producing no visible extra space at all; padding never collapses. */
.edit-team-row {
  margin-top: 16px;
  padding-bottom: 24px;
}

/* Expand/collapse affordance for clickable rows (team_public.php's
   Schedule and Roster tables — toggleScheduleDetail()/
   togglePlayerDetail()): a small triangle pointing right by default,
   rotating to point down once that row's detail panel is expanded (the
   toggling row itself gets .row-expanded — see team_public.js). A plain
   inline-block + CSS transform rotation, not a second glyph swap, so the
   rotation itself animates instead of just snapping between two symbols. */
.expand-triangle {
  display: inline-block;
  transition: transform 0.15s ease;
  color: var(--color-muted-text);
  font-size: 0.75em;
}

.row-expanded .expand-triangle {
  transform: rotate(90deg);
}

/* The roster table's triangle gets its own narrow column (2026-07-30,
   per the user) rather than sitting inline in front of the player's
   name — otherwise a name long enough to wrap would have its second
   line sit under the triangle instead of under the first name itself.
   width: 1% is the standard table trick for "shrink this column to its
   content's minimum width" in a table that doesn't have table-layout:
   fixed. The Schedule table's own triangle stays inline (see
   .match-results-table below) since that table's columns never wrap at
   all, so the same misalignment risk doesn't apply there. */
.team-roster-expand-col {
  width: 1%;
  white-space: nowrap;
}

/* schools-players/index.php's three-level expandable list (schools ->
   roster -> per-player game log) — same narrow-triangle-column technique
   as .team-roster-expand-col above, reused directly since it's generic. */
.sp-expand-col {
  width: 1%;
  white-space: nowrap;
}

#schools-players-container .player-games-table,
#schools-players-container .sp-roster-table {
  margin-bottom: 0;
}

/* Each expansion level a bit denser than the one it's nested inside
   (2026-08-30, per the user) — the school -> roster table's font-size is
   slightly smaller than the outer schools table's sitewide default
   (font-size: 1em / th,td padding: 8px 12px), and the roster -> game-log
   table is smaller again, same "denser nested content" reasoning as
   board_standings.php's own reduction above but two steps instead of one
   here. .player-games-table's own font-size below is relative to its
   already-reduced .sp-roster-table ancestor (font-size isn't reset
   between them), so the two 0.9em steps compound to ~0.81em of the page's
   own base size by the game-log level — padding isn't inherited, so each
   level sets its own px values instead. */
/* Full sitewide width (the plain `table` rule's own default) left every
   short column -- USCF ID, Grade, even Record -- stretched out with a lot
   of empty space (2026-09-06, per the user: "Rows are too wide"). Plain
   `width: auto` alone doesn't fix this the way it does for a top-level
   table like .t-prize-table -- nested inside a parent <td> (this table
   sits inside the outer schools table's own expanded detail row),
   table-layout: auto's default column-sizing still stretches to fill
   that <td>'s own (wide) available width rather than shrinking to this
   table's actual content. table-layout: fixed plus an explicit width on
   every column sidesteps that entirely: with every column's width
   pinned, there's no "remaining space" for the auto algorithm to
   distribute, so the table's own auto width becomes simply the sum of
   its columns -- narrow, and no longer dependent on how wide its
   container happens to be. */
#schools-players-container .sp-roster-table {
  table-layout: fixed;
  width: auto;
  font-size: 0.9em;
}

#schools-players-container .sp-roster-table th,
#schools-players-container .sp-roster-table td {
  padding: 6px 10px;
}

#schools-players-container .sp-roster-table .sp-expand-col {
  width: 2ch;
}
#schools-players-container .sp-roster-table th:nth-child(2),
#schools-players-container .sp-roster-table td:nth-child(2) {
  width: 16ch;
}
#schools-players-container .sp-roster-table th:nth-child(3),
#schools-players-container .sp-roster-table td:nth-child(3) {
  width: 9ch;
}
#schools-players-container .sp-roster-table .sp-grade-col {
  width: 5ch;
}
#schools-players-container .sp-roster-table th:nth-child(5),
#schools-players-container .sp-roster-table td:nth-child(5) {
  width: 10ch;
}

.sp-grade-col {
  text-align: center;
}

/* Sizes to its own content and keeps each game on one line where the
   viewport allows it (2026-09-05, per the user) -- this table sits nested
   inside .sp-roster-table's own fixed-layout <td> (colspan across its 5
   narrow columns), which would otherwise cap it at that same narrow width
   and wrap "Detail"/"Opponent" text onto 2-3 lines despite plenty of
   unused page width to the right. width: max-content lets it (and the
   .sp-roster-table ancestor it's nested in, since that one's own
   table-layout: fixed + width: auto only fixes column *proportions*, not
   a hard pixel cap) grow to fit its longest row; white-space: nowrap on
   its own cells is what actually stops the mid-row wrapping, not the
   width alone. #schools-players-container's own overflow-x: auto below is
   the fallback for the rare row too wide for the viewport even so -- it
   scrolls instead of breaking the page. */
#schools-players-container {
  overflow-x: auto;
}

#schools-players-container .player-games-table {
  font-size: 0.9em;
  width: max-content;
}

#schools-players-container .player-games-table th,
#schools-players-container .player-games-table td {
  padding: 5px 8px;
  white-space: nowrap;
}

/* schools-players/index.php's own subtle links -- the USCF ID under each
   player's name and, since 2026-08-30, each game's opponent name too --
   blend into the surrounding text at rest instead of the usual permanent
   underline, and only reveal themselves as a link on hover (2026-08-30,
   per the user, originally introduced as a look-and-feel test on the
   USCF ID link alone, then extended to opponent links the same way). */
.sp-subtle-link {
  color: inherit;
  /* Was `text-decoration: none` (2026-09-14, accessibility fix, per the
     user) -- at-rest links were visually indistinguishable from plain
     text, so a keyboard/touch user (anyone not currently hovering) had
     no cue this was a link at all. A plain underline in the *same* color
     as the surrounding text (not the hover blue) keeps the "blends in"
     look while still being a real, always-visible non-color signal. */
  text-decoration: underline;
}

.sp-subtle-link:hover {
  /* var(--color-muted-text) was tried first and was too close to
     var(--color-text) to actually read as a change at a glance; this
     is a plain hex rather than a themed variable since one mid-blue
     already has workable contrast against both the light and dark
     --color-bg. */
  color: #3b7dd8;
}

/* Schools/Players: the High School League Team Page link(s) column
   (2026-09-14, moved into its own column 2026-09-15, per the user) --
   deliberately NOT .sp-subtle-link (that convention is for blending a
   secondary detail into the surrounding text; this is a real navigation
   link to a whole other page, so it keeps the plain default link
   styling/color, just smaller like this page's other sub-line notes).
   Centered (2026-09-15, per the user: "centered with the 'High School
   League' text above") so a single-team school's one-line link and a
   multi-team school's two stacked lines both line up the same way down
   the column, not just the two-line case's own lines against each
   other. */
.sp-hsl-team-cell {
  text-align: center;
  font-size: 0.85em;
}

.sp-hsl-team-label {
  color: var(--color-muted-text);
}

/* team_public.php's Schedule table (renderSchedule() in team_public.js)
   — column data should never wrap (2026-07-30, per the user), unlike the
   Roster table above where a long player name is still allowed to wrap.
   overflow-x: auto on the container is the safety net for a narrow
   viewport, same pattern as .roster-manager .roster-table-wrap
   elsewhere in this file — nowrap columns just make the table wider
   rather than wrap, so something has to absorb that on a small screen. */
#team-schedule-container {
  overflow-x: auto;
}

/* Extra horizontal padding (up from the sitewide 12px), per the user —
   the collapsed table was noticeably narrower than the expanded
   per-board detail table nested inside an opened row, so expanding a
   match made the whole table (and the page around it) visibly jump
   wider. Widening the collapsed columns up front keeps that jump much
   smaller. */
#team-schedule-container th,
#team-schedule-container td {
  white-space: nowrap;
  padding: 8px 24px;
}

/* Schedule table's expand triangle now gets its own dedicated column
   (2026-08-02, per the user), same technique as the Roster table's own
   .team-roster-expand-col above — a clickable row's triangle used to sit
   inline right before the Date text, which meant a non-clickable
   (merely scheduled) row's date started at a different horizontal
   position than a clickable row's, since only the latter had a leading
   triangle+margin taking up space. Giving the triangle its own column
   removes that inconsistency, so the Date column itself is consistently
   left-aligned regardless of whether that row happens to be clickable. */
#team-schedule-container .team-schedule-expand-col {
  width: 1%;
}

/* password_gate.js's password block — inserted right after the page's
   own <h1> (see hidePageBelowHeading() in that file), with everything
   else on the page display:none underneath it, so the site header and
   page heading stay visible instead of being blanked behind a
   full-screen overlay. A normal in-flow block, not fixed/positioned. */
#password-gate-block {
  margin: 20px 0;
}

/* Optional per-page note (password_gate.js's gateNoteHtml/data-gate-note
   — e.g. submit.php's "use Live Match Reporting instead" pointer) sitting
   directly above the login block itself, per the user. Wider than the
   login card below it (max-width: 340px) since this is a full sentence
   plus a link, not a compact form. */
#password-gate-note {
  max-width: 480px;
  margin: 20px 0 0;
  color: var(--color-muted-text);
}

#password-gate-form {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 24px 28px;
  max-width: 340px;
  text-align: center;
}

#password-gate-form h2 {
  margin-top: 0;
}

#password-gate-form label {
  display: block;
  margin-bottom: 12px;
}

#password-gate-form input,
#password-gate-form select {
  display: block;
  width: 100%;
  margin-top: 6px;
  box-sizing: border-box;
}

#password-gate-error {
  color: var(--color-danger);
  min-height: 1.2em;
}

/* password_gate.js's persistent "Logged in as X" line (2026-08-07),
   inserted right after the page's own <h1> once a login actually
   resolves — small and muted, an informational aside rather than
   something competing with the page's real heading for attention. */
#password-gate-status {
  color: var(--color-muted-text);
  font-size: 0.9em;
  margin: 0 0 16px;
}

/* lock.js's "Are you still here?" inactivity-warning banner (2026-08-07,
   per the user) — sticky, not fixed: prepended as the page's own first
   body child (see showInactivityWarning()'s own comment) so it occupies
   real document-flow space and pushes the site header/nav down instead
   of overlapping it, then stays pinned to the top of the viewport via
   position: sticky if the page is scrolled, since it's genuinely
   time-sensitive (a countdown to the lock actually going away). Fixed,
   non-themed amber, not a --color-* custom property — same reasoning
   already used elsewhere in this file for a handful of self-contained
   status indicators (the live-match dot, the help-icon tooltip popup):
   a bright, high-contrast warning color reads correctly against either
   theme's page background without needing a light/dark pair of its own. */
#inactivity-warning-banner {
  position: sticky;
  top: 0;
  z-index: 1000;
  background: #ffcc00;
  color: #222;
  text-align: center;
  padding: 10px 16px;
  margin-bottom: 12px;
  border-radius: 6px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
  font-weight: 600;
}

#inactivity-warning-banner button {
  margin-left: 12px;
}

/* coaches.php's own small in-page reveal widget — deliberately not
   reusing #password-gate-block/-form (those are password_gate.js's
   whole-page-blocking gate; this page's base table stays public, so it
   needs a plain in-flow widget instead, not an overlay/full card). */
#coaches-reveal-widget {
  margin-bottom: 20px;
}

#coaches-reveal-error {
  color: var(--color-danger);
  min-height: 1.2em;
  margin: 4px 0 0;
}

#coaches-reveal-status {
  color: var(--color-muted-text);
}

/* index.php's Overall Standings + Playoffs sections (2026-08-05) — same
   width:auto override every other standings-shaped table in this app
   needs to avoid stretching to the sitewide table{width:100%} default. */
.overall-standings-table,
.playoffs-seeds-table {
  width: auto;
}

/* Breathing room below the playoff seed list before whatever follows it
   (2026-08-12, per the user). */
.playoffs-seeds-table {
  margin-bottom: 16px;
}

.overall-standings-table th,
.overall-standings-table td,
.playoffs-seeds-table th,
.playoffs-seeds-table td {
  padding: 6px 12px;
  text-align: left;
}

.playoffs-bracket-note {
  color: var(--color-muted-text);
  font-style: italic;
}

/* board_standings.php/board_cross_table.php's shared disclaimer, right
   above Board 1 (2026-08-17, per the user) — a heading rather than a
   muted/italic note like .playoffs-bracket-note above, since this is
   scoping the whole page's data, not annotating one specific state. */
.regular-season-note {
  margin: 0 0 12px;
  font-size: 1em;
  color: var(--color-muted-text);
}

#tiebreak-rules-content {
  max-width: 700px;
}

/* admin.php's "Edit or delete match results" Extra/JV Games table — marks
   a game staged locally via "Add Game" but not yet actually created
   (2026-08-04, see admin.js's matchEditStagedNewExtraGames comment). */
.match-edit-extra-game-unsaved {
  color: var(--color-muted-text);
  font-style: italic;
  font-size: 0.9em;
}

/* A row marked for removal (2026-08-04, per the user) — grayed out until
   "Save changes" actually deletes it (or "Restore" undoes the mark); its
   own fields are also disabled at that point (see admin.js's
   toggleMatchEditExtraGameRowRemoval()), so this is purely a visual cue
   on top of that, not the only thing preventing further edits. Opacity
   is scoped to every cell except the last (the Restore button's own) —
   per the user, the button itself shouldn't be dimmed along with the
   rest of the row, since it's the one thing on a removed row that still
   needs to read clearly as the way to undo it. */
.match-edit-extra-game-row-removed {
  background: var(--color-muted-bg);
}

.match-edit-extra-game-row-removed td:not(:last-child) {
  opacity: 0.5;
}

/* A superseded tiebreak stage's own board rows on the "Edit or delete
   match results" panel — read-only once a LATER stage exists (2026-08-15,
   per the user: "Normal results uneditable if blitz active. Blitz
   uneditable if Armageddon active."). Every select in the row is also
   disabled (see admin.js's matchEditRefreshRowReadOnlyState()); this is
   purely the visual cue on top of that, same muted-background convention
   as .match-edit-extra-game-row-removed just above. */
.match-edit-board-row-readonly {
  background: var(--color-muted-bg);
}

.match-edit-board-row-readonly select {
  opacity: 0.6;
}

/* Repeats the "Plays White" column header right on the Armageddon board's
   own cell (2026-08-15, per the user — see admin.js's matchEditBoardRowHtml()
   own comment) — the table's real <th> can be scrolled well out of view by
   the time this one row (the last of up to 11) is reached. */
.match-edit-white-side-label {
  display: block;
  font-size: 0.8em;
  color: var(--color-muted-text);
  white-space: nowrap;
}

/* Playoff Bracket (2026-08-11) — shared between the read-only public view
   (app.js, index.php's Playoffs section) and the editable admin view
   (admin.js, admin.php's own "Playoff Bracket" subpanel). Plain flex
   columns rather than SVG/canvas connector lines (matching this app's
   plain-HTML+CSS aesthetic elsewhere) — one column per round, each column
   vertically centering its own boxes via space-around so a shorter round
   (Semifinals: 2 boxes, Championship: 1) reads as roughly centered next
   to the round(s) feeding it, without needing real connector lines drawn
   between them. */
.playoff-bracket-columns {
  display: flex;
  gap: 24px;
  align-items: stretch;
  flex-wrap: wrap;
}

.playoff-bracket-column {
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  gap: 12px;
  min-width: 220px;
}

.playoff-bracket-column h4 {
  text-align: center;
  margin: 0 0 4px;
}

.playoff-bracket-match {
  border: 1px solid var(--color-border-strong);
  border-radius: 6px;
  padding: 8px 12px;
  background: var(--color-surface);
}

.playoff-bracket-side {
  padding: 2px 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.playoff-bracket-side .standings-team-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

/* Public view only (2026-08-12, per the user) — two rows, "<logo> #<seed>
   <team>" then "at <logo> #<seed> <team>", replacing the old two-stacked-
   .playoff-bracket-side rows admin.php's editable view still uses. Each
   row leads with a fixed-width .playoff-bracket-at-label column (blank on
   the top row, "at" on the bottom) so both rows' logos start at the same
   x regardless of the "at" text — which then reads as sticking out to the
   left of that aligned logo column, rather than pushing it rightward. */
.playoff-bracket-team-row {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 1px 0;
  white-space: nowrap;
  overflow: hidden;
}

.playoff-bracket-at-label {
  /* Wide enough for "vs." (measured ~17.1px at this font-size) as well as
     "at" (~11.7px — 2026-08-12, per the user: "vs." shows here until both
     sides of a match are known real teams, "at" once they are), with a
     small buffer past "vs." itself. Tightened from 26px to 20px
     (2026-08-13, per the user: "Too much padding to the left of the seed
     number" — a real 26px was noticeably more than either label actually
     needs, most visible on the away row, which shows no label text at
     all and so was just leaving a wide blank gap before the logo). */
  flex: 0 0 20px;
  text-align: right;
  color: var(--color-muted-text);
  font-size: 0.85em;
}

.playoff-bracket-team-row .standings-team-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.playoff-bracket-tbd {
  color: var(--color-muted-text);
  font-style: italic;
}

.playoff-bracket-status {
  display: block;
  margin-top: 4px;
  color: var(--color-muted-text);
  font-size: 0.9em;
}

.playoff-bracket-seed-notes {
  margin-top: 16px;
  /* Padding before the "Playoff Seeds" card that follows it (2026-08-12,
     per the user) — collapses with that card's own margin-top below to
     the larger of the two when both are present, same as any two
     adjacent block margins. */
  margin-bottom: 16px;
}

.playoff-bracket-seed-notes ul {
  margin: 4px 0;
}

/* The playoff seed table's own collapsible card (2026-08-12, per the
   user — "Put seeding as a separate collapsable card inside the Playoffs
   card"). Same bordered-box look as admin.php's .admin-subpanel, kept as
   its own rule rather than reusing that class directly — this is a
   public-page card, not an admin one, and the two shouldn't be coupled
   just because they happen to look alike today. Defaults open (see
   app.js's own comment on why), unlike .admin-subpanel's closed default. */
.playoff-seeds-card {
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: 4px 12px 12px;
  margin-top: 8px;
}

.playoff-seeds-card summary {
  cursor: pointer;
  padding: 6px 0;
}

.playoff-seeds-card summary h3 {
  display: inline;
  margin: 0;
}

/* Public bracket view only (2026-08-12, per the user) — pins each round's
   <h4> at the top of its column instead of letting it share the same
   justify-content: space-around distribution as the match boxes below it
   (which is what was throwing off "vertically center Semifinals/
   Championship between their feeder matches": with the title counted as
   one of the flex items, space-around's per-item segment math no longer
   lined up with the actual match pairing). Moving the boxes into their own
   nested flex column, sized to the remaining space below the fixed-height
   title, restores that alignment for free -- for a standard 4-2-1 bracket
   with uniform box heights, space-around's own per-item segment centering
   is mathematically identical to "centered between this item's two feeder
   matches." Scoped to the public container only; admin.php's editable
   bracket view keeps its original single-flex-column markup/behavior. */
/* Also (2026-08-12): the longer "Winner of #A vs #B at Winner of #C vs #D"
   placeholder text this same session introduced is wide enough, at
   white-space: nowrap, to push a column past its natural share of the
   page's ~1000px content width -- with flex-wrap: wrap (the shared
   default) that pushed the Championship column onto its own second row
   instead of staying beside Round 1/Semifinals, a real regression caught
   by measuring actual layout, not assumed from the CSS alone. Fixed by
   keeping all 3 columns on one row (nowrap) and letting each one flex to
   an equal share of the available width with min-width: 0 overriding a
   flex item's own default content-driven floor -- .playoff-bracket-teams'
   existing nowrap/overflow:hidden/ellipsis then truncates cleanly to
   whatever width that leaves it, rather than forcing the column wider.
   overflow-x: auto is the safety net if a column's own min-width floor
   still can't fit (a very narrow viewport), same wrap-and-scroll
   convention used elsewhere in this app rather than reflowing/breaking. */
#playoff-bracket-container .playoff-bracket-columns {
  flex-wrap: nowrap;
  overflow-x: auto;
}

#playoff-bracket-container .playoff-bracket-column {
  justify-content: flex-start;
  flex: 1 1 0;
  /* Raised from 180px (2026-08-13, per the user: "Don't allow the box to
     shrink too much in narrower windows") — 180px let a match box shrink
     down well past what a real team name (e.g. "Belleville East") needs
     to stay legible before falling back to ellipsis-truncation; a match
     box's own real content (a school logo, "#N Team Name" on two rows, a
     score column, and a status column) genuinely needs more room than
     that to read comfortably. The .playoff-bracket-columns row's own
     overflow-x: auto (see its own comment above) is still the safety net
     once the viewport itself is narrower than 3 × this floor — the page
     scrolls horizontally rather than continuing to squeeze the boxes
     thinner. */
  min-width: 260px;
}

#playoff-bracket-container .playoff-bracket-column-matches {
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  flex: 1;
  gap: 12px;
}

/* Per-row score alignment (2026-08-13, per the user): "Parkway West
   should have an 8 to its right and Ladue-B should have a 22" — each
   team's own score sits to the right of that team's own row, a ▶ arrow
   between the winning team's name and its score (no arrow for the loser,
   or for either side when there's no decided winner yet — scheduled,
   in-progress, a draw, or a still-unresolved tied playoff match), and a
   third column, vertically centered across both rows, reads "F" once the
   match is finalized/played or "<n> of 5" otherwise. A 3-column CSS grid
   (every cell placed by its own --away/--home modifier class, not source
   order) makes this hold regardless of team-name or placeholder-text
   length. Scoped to the public container only — .playoff-bracket-match
   is shared with admin.php's own bracket view, which keeps its original
   .playoff-bracket-side/.playoff-bracket-status block layout (a single
   combined score line below both team names) unchanged. */
#playoff-bracket-container .playoff-bracket-match {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  column-gap: 10px;
  row-gap: 2px;
}

.playoff-bracket-team-row--away {
  grid-column: 1;
  grid-row: 1;
  min-width: 0;
}

.playoff-bracket-team-row--home {
  grid-column: 1;
  grid-row: 2;
  min-width: 0;
}

.playoff-bracket-row-score {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 2px;
  white-space: nowrap;
}

.playoff-bracket-row-score--away {
  grid-column: 2;
  grid-row: 1;
}

.playoff-bracket-row-score--home {
  grid-column: 2;
  grid-row: 2;
}

/* display: flex/flex-direction: column (2026-08-13) makes every state's
   content stack as its own line(s) — the plain single-value states ("F",
   "Starting") are just one flex item each, the two-line states
   (playoffBracketScheduleDateHtml()'s weekday+date, and the "Live"
   status below) are each built from two explicit sibling <span>s, not a
   bare <br>, since a <br> mixed into flex content doesn't reliably force
   a line break (it generates no real box for flex layout to stack on)
   the way it does in normal block/inline flow. */
/* Base layout/color/typography, shared by all three states (2026-08-13:
   "F", the live dot+count, and the scheduled date — see
   playoffBracketStatusIndicatorHtml()'s own comment for the full state
   list, trimmed the same day from four states down to three). No fixed
   width here — see .playoff-bracket-status-indicator--aligned below for
   why only two of the three states actually need one. */
.playoff-bracket-status-indicator {
  grid-column: 3;
  grid-row: 1 / span 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
  font-size: 0.85em;
  font-weight: 600;
  color: var(--color-muted-text);
  white-space: nowrap;
  padding-left: 8px;
  border-left: 1px solid var(--color-border);
}

/* Fixed width (2026-08-13, per the user — "The scores of live matches
   should line up with the scores of other matches in the bracket"):
   each .playoff-bracket-match is its own separate CSS grid instance (see
   that selector's own comment), so grid-template-columns: 1fr auto auto
   sizes column 2 (score) and column 3 (the status indicator) independently
   *per box* — nothing keeps them the same across sibling boxes in the
   same round on its own. The score column's own actual width already
   comes out identical regardless of content (see .score-value's own
   fixed-width-pieces comment in this file), but column 3's content used
   to vary a lot by state, which is what actually broke alignment — a
   real, measured case: the live match's own status column ("Live" over
   "0 of 5", ~46px) sat ~31px wider than a finished match's ("F", ~16px),
   which shifted that one box's score ~31px left of every other box's in
   the same round, since column 1 is the only flexible (1fr) track and a
   wider column 3 in one box eats into column 1's share in *that* box
   only. Fixing column 3's width to something both "F" and the live
   state can share removes that variance for the two states that
   actually have a score in the neighboring column to protect — column
   2's own *right* edge (where the score's ones digit lands, via
   flex-end) works out to `box_width − column_3_width − gaps`,
   independent of column 2's own width, so fixing column 3 alone is
   sufficient.
   Deliberately scoped to just these two states via this modifier class,
   not the base .playoff-bracket-status-indicator rule above (2026-08-13,
   per the user: "Do not include the date in what gets aligned") — a
   scheduled match never has a score in column 2 to begin with
   (playoffBracketRowScoreHtml() renders an empty span for
   match.status === 'scheduled'), so there's nothing there for alignment
   to help or hurt, and forcing the date's own two-line text into this
   same narrow width would only clip it for no benefit.
   Tightened from 34px to 14px (2026-08-13, per the user: "Too much
   padding around the F and the green dot") — 34px was sized generously
   past the original four-state design's widest content ("Starting," now
   removed), but the two states that actually use this width today are
   much narrower: "F" measures ~6.5px of actual glyph content, and the
   stacked dot+digit's widest line (the dot itself, .live-icon's own
   fixed 10px) is the true binding constraint — 14px leaves a small
   buffer past that 10px without the wide, obviously-centered gutter the
   old 34px produced. */
.playoff-bracket-status-indicator--aligned {
  width: 14px;
}

/* The live state's own dot+count (2026-08-13, per the user — the "Live"
   label and "of 5" text were both dropped, leaving just the sitewide
   small green pulsing dot (.live-icon--animated, the same one the
   standings page's own Matches-in-Progress pane uses for the identical
   concept) and the bare boards-reported count). Stacked (2026-08-13,
   same day, per the user: "Put the 0 underneath the green dot") rather
   than side by side — flex-direction: column, same as the outer
   .playoff-bracket-status-indicator's own stacking, with align-items:
   center still centering the shorter line (whichever one that is)
   under/over the wider one. */
.playoff-bracket-status-live {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}

.playoff-bracket-note {
  grid-column: 1 / -1;
  margin-top: 2px;
  color: var(--color-muted-text);
  font-size: 0.85em;
}

/* admin.php's bracket-creation seed table (2026-08-11) — sizes to its own
   content instead of the sitewide table { width: 100% } default, same
   fix already applied to several other narrow admin tables in this file. */
#playoff-bracket-seed-table {
  width: auto;
}

/* admin.php's "Bracket Assignments" batch table on the bracket-exists
   view (2026-08-13 — supersedes the brief 2026-08-12 "Seed Assignments"
   consolidation, split back into a separate Seed Assignment/Bracket
   Assignment model; see playoffBracketAssignmentsHtml()'s own comment in
   admin.js). Sizes to its own content, same #playoff-bracket-seed-table
   precedent above. */
.playoff-bracket-position-assignments {
  margin-top: 16px;
}

.playoff-bracket-position-assignments table {
  width: auto;
}

#playoff-bracket-position-assignment-message {
  color: var(--color-danger);
}

/* Shared "Reasons" list widget (2026-08-13) — used by both the Seed
   Assignment (pre-creation, #playoff-bracket-seed-reasons-container) and
   Bracket Assignment (post-creation, .playoff-bracket-position-
   assignments) forms, via admin.js's playoffBracketReasonsListHtml().
   Replaces a design that attached a reason to each affected row —
   see that function's own comment for the full reasoning. */
.playoff-bracket-reasons-label {
  font-weight: bold;
  margin-top: 12px;
}

.playoff-bracket-reasons {
  margin-top: 4px;
}

.playoff-bracket-reason-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
}

.playoff-bracket-reason-input {
  width: 420px;
  max-width: 100%;
  box-sizing: border-box;
}

/* A plain "×" — sized/shaped distinctly from every other button on this
   page so it doesn't compete visually with "Save"/"+ Add another
   reason" for attention; it's a small, easy-to-miss-on-purpose undo for
   a misclick on "+", not a primary action. */
.playoff-bracket-reason-remove {
  padding: 2px 8px;
  line-height: 1;
}

.playoff-bracket-reason-add {
  margin-top: 2px;
}

/* admin.php's "Bracket assignment changes" running log (2026-08-13) —
   same spacing convention as .playoff-bracket-seed-notes above, the
   Seed Assignment notes it sits alongside. */
.playoff-bracket-position-change-notes {
  margin-top: 16px;
  margin-bottom: 16px;
}

.playoff-bracket-position-change-notes ul {
  margin: 4px 0;
}

/* admin.php's "Edit or delete match results" panel — the reason box that
   appears only once a playoff match's live board edits would move the
   decisive winner (2026-08-11, see admin.js's
   matchEditPlayoffWinnerChangePending). Same red used sitewide for "pay
   attention, something's different" (matches .match-edit-score-pending
   right above the score-swing line this sits under). */
#match-edit-playoff-reason-row {
  border: 1px solid var(--color-danger);
  border-radius: 6px;
  padding: 8px 12px;
  margin-bottom: 12px;
}

#match-edit-playoff-reason-row label {
  display: block;
  margin-bottom: 4px;
  color: var(--color-danger);
}

#match-edit-playoff-reason {
  width: 100%;
  box-sizing: border-box;
}

/* Data Hub home page (www/index.php) -- that page has no stylesheet of
   its own, same as schools-players/index.php (see this file's own
   .sp-* rules above), so its page-specific rules live here too. */

/* "Tournaments" section heading link (2026-09-02, per the user: "Make
   Tournament header smaller, and link to main Tournaments page") --
   inherits the surrounding heading's own color instead of the UA
   stylesheet's default blue/underline, same reasoning as .site-header
   -title a above. */
.gc-section-heading-link {
  color: inherit;
  text-decoration: none;
}

.gc-section-heading-link:hover {
  text-decoration: underline;
}

/* Every Data Hub home page section's own supporting content -- the
   tournament-row lists, the High School League/Grand Prix status lines,
   the Schools/Players count (2026-09-08, per the user: "All supporting
   text should be indented like the junior league") -- indented one step
   in from its own h3 heading, same fixed 40px every browser's UA
   stylesheet already gives a plain <ul> by default (the Junior League/
   Missouri State Tournament lists' own look before this rule existed);
   written explicitly here instead of left to that UA default so it's the
   same value across every section (including #tournaments-list below,
   which used to override it to 0) and every browser, not just whatever a
   bare <ul>/<p> happens to inherit. */
.gc-section-body {
  padding-left: 40px;
}

/* Tournaments list (2026-09-02, per the user, revised several times
   since). Two earlier cuts are superseded: a flex-only attempt (each row
   aligned on its own, so the end link's x position drifted per row), then
   a single-row CSS Grid version (date/dash/name/end as 4 shared grid
   columns, so the links column stayed vertically aligned even though it
   sat right after the name on the same line). That version is itself
   superseded now (2026-09-08, per the user: "Gateway Tournaments and MO
   tournament: Remove the pipe before flyer. Move the links to another
   row, indented from the date") -- the end links move to their own
   second, indented line per tournament (mirrors the Tournaments app's own
   already-established .t-row-top/.t-row-line2 pattern in
   tournaments/style.css), so a fixed grid-column count across every row
   is no longer what keeps things aligned -- each row is its own flex line
   with a shared FIXED date width instead (see .gc-tournament-date below),
   which lines up row to row (and list to list -- #tournaments-list/
   #junior-league-list/#non-gateway-tournaments-list all share this same
   class) without needing every row to contribute the same element count.
   .gc-tournament-row is a real block (not display: contents) now that it
   holds two stacked lines instead of contributing single-row grid items. */
.gc-tournament-list {
  list-style: none;
  margin: 0;
  /* padding-left comes from .gc-section-body (2026-09-08, per the user)
     -- this list used to zero it out entirely so its rows started flush
     under the "Gateway Tournaments" heading; now indented one step in,
     same as every other section's own supporting content. */
  /* Don't stretch across the whole page (2026-09-02, per the user) --
     same sitewide "don't stretch" reasoning as tournaments/style.css's
     own several 60em max-widths (.t-tournaments-list included). */
  max-width: 60em;
}

.gc-tournament-row {
  margin-bottom: 8px;
}

.gc-tournament-row:last-child {
  margin-bottom: 0;
}

.gc-tournament-row-top {
  display: flex;
  align-items: center;
  column-gap: 10px;
}

/* Fixed 100px (2026-09-08, per the user: "Missouri State: Date and em
   dash do not line up with the rest of the page") wide enough for the
   longest real date string ("Dec 13, 2025" measures ~99px) -- every list
   sharing this exact width, rather than each sizing its own date column
   off only its own rows' content, is what keeps the dash lined up at the
   same x position list to list, not just row to row within one list. */
/* display: inline-block (2026-09-08, per the user: "em dashes are still
   not lining up") -- a flex item (.gc-tournament-row-top's own children)
   already respects `width` regardless of display type, but this same
   class is also reused bare, outside any flex container, for the Grand
   Prix/High School League status lines' own dateDashHtml() (index.php) --
   a plain inline <span> ignores `width`/`text-align` entirely there,
   which let each status line's own date text (not padded to a fixed
   100px box) end at a slightly different x position than the tournament
   -row lists' own fixed-width date column, throwing the dash after it
   off by a couple px. inline-block makes `width` apply in both contexts
   identically. */
.gc-tournament-date {
  display: inline-block;
  width: 100px;
  flex-shrink: 0;
  text-align: right;
  color: var(--color-muted-text);
}

.gc-tournament-dash {
  flex-shrink: 0;
  color: var(--color-muted-text);
}

/* The end links' own second line (2026-09-08, per the user, see this
   rule block's own header comment) -- indented to 100px date width + 10px
   gap, i.e. "indented from the date", not flush under it. No leading
   pipe/dash of its own any more (the links used to need one to separate
   them from the name on the same line -- on their own line, nothing
   precedes them to separate from). No `> * + * { margin-left }` rule
   between the <a> tags either (2026-09-08, per the user: "put the same
   amount of space on both sides of the pipe separators") -- an earlier
   copy of that rule from tournaments/style.css's own .t-row-line2 (a
   *space*-joined list there, not pipe-joined) stacked an extra 10px onto
   the *right* side only of every " | " text already sitting between the
   links here, since `+` counts only element siblings and skips the text
   node in between -- the pipe's own left/right spacing (from the " | "
   string itself) is already symmetric on its own and needs no help. */
.gc-tournament-row-links {
  margin-top: 2px;
  margin-left: 110px;
  font-size: 0.9em;
}

/* Junior League's own inline links (2026-09-08, per the user: "remove
   the pip before the link") -- unlike Gateway Tournaments/Missouri State
   Tournament above, Junior League hides its own name (see index.php's
   own hideName option) and never has more than one link, so the link(s)
   stay inline right after the dash instead of dropping to their own
   line -- no leading separator, same reasoning as .gc-tournament-row
   -links' own "nothing precedes them" note. */
.gc-tournament-row-inline-links {
  white-space: nowrap;
}

/* The em dash immediately after a date outside a tournament-row list --
   the Grand Prix/High School League status lines (2026-09-08, per the
   user: "Make the spacing around the em dashes next to dates
   consistent") -- explicit 10px margin on each side so it matches
   #tournaments-list's own column-gap: 10px exactly, rather than left to
   whatever a plain " — " text sequence happens to render as (which
   doesn't reliably match a CSS Grid gap). display: inline-block makes
   the margin actually apply the same way padding/gap would (a plain
   inline element's own margin doesn't add space in the inline-flow
   direction the same predictable way). Not used inside a tournament-row
   list itself -- .gc-tournament-dash there already gets its spacing from
   the grid's own column-gap, and adding this on top would double it. */
.gc-date-dash {
  display: inline-block;
  margin: 0 10px;
  color: var(--color-muted-text);
}

/* Schools/Players' own live count, folded directly into its <h3> instead
   of a separate supporting-text line below (2026-09-08, per the user:
   "Since Schools/Players does not have a date, pull the supporting text
   into the header") -- every other section's supporting line leads with
   a date, which is what the .gc-section-body indent/.gc-date-dash
   spacing rules above are built around; Schools/Players has no date to
   lead with, so it reads better folded into the heading itself. Same
   font size as the heading itself (2026-09-08, per the user) -- only
   font-weight/color are toned down, so it still reads as supporting
   detail rather than more heading text, without shrinking. */
.gc-section-heading-detail {
  font-size: 1em;
  font-weight: normal;
  color: var(--color-muted-text);
}

/* Same diagonal green-tinted repeating gradient as the Tournaments app's
   own results page (2026-09-08, per the user: "add the color gradient
   background like for the tournaments") -- see .t-results-page in
   tournaments/style.css for the full design history (135deg diagonal,
   one full fade-and-back cycle every 200vw, both tints mixed from the
   same logo green #29723C). A deliberate copy, not a shared class across
   the two stylesheets -- same per-page CSS/JS duplication convention
   this codebase already uses throughout (index.php has no stylesheet of
   its own; every one of its own page-specific rules already lives here,
   not in tournaments/style.css). Applied to <body class="gc-gradient-bg">
   on www/index.php only -- every other Data Hub page keeps the plain
   `body { background: var(--color-bg) }` look. */
.gc-gradient-bg {
  background: repeating-linear-gradient(135deg,
    #ffffff 0,
    #d0e0d4 100vw,
    #ffffff 200vw);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .gc-gradient-bg {
    background: repeating-linear-gradient(135deg,
      #000000 0,
      #102e18 100vw,
      #000000 200vw);
  }
}

:root[data-theme="dark"] .gc-gradient-bg {
  background: repeating-linear-gradient(135deg,
    #000000 0,
    #102e18 100vw,
    #000000 200vw);
}
