/* Design system — shared tokens every page/CSS file draws from. Palette
   leans into green/purple/yellow-green (see styleguide.md §2), hand-picked
   against wimbledon.com/scores as a direct reference rather than a
   pixel-identical copy of AELTC's own brand colors. Dark-mode is
   intentionally out of scope — this app is charted outdoors in daylight,
   where high contrast on a light background is what actually matters
   (spec: "high contrast, easy to read in outdoor sunlight"). Full rationale
   for every token below lives in styleguide.md — treat that file as the
   source of truth and keep this block in sync with it. */

/* Fonts — self-hosted (wwwroot/fonts) rather than loaded from Google's CDN
   at runtime, so the app has no network dependency on an external host even
   though it's charted courtside on phones, sometimes on spotty signal.

   Montserrat for everything, headings and body: the open-licensed face
   closest to Gotham, which wimbledon.com sets its type in (styleguide.md §1).
   One variable font covers every weight from 400 to 800, with font-display:
   swap so text renders immediately in the fallback stack and swaps in once
   the font loads, never a blank screen.

   Split the way Google Fonts serves it: basic Latin (38 KB, what nearly
   every page needs, and preloaded in _Layout) and Latin Extended (69 KB).
   unicode-range means the browser fetches the second only on a page that
   actually has one of its characters, a player named Šimková or Kőváry, and
   draws just those characters from it. */
@font-face {
    font-family: "Montserrat";
    font-style: normal;
    font-weight: 400 800;
    font-display: swap;
    src: url("/fonts/montserrat-variable.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Montserrat";
    font-style: normal;
    font-weight: 400 800;
    font-display: swap;
    src: url("/fonts/montserrat-variable-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Serif accent — for the handful of eyebrow-style labels that borrow
   wimbledon.com's look (their own "THE CHAMPIONSHIPS" eyebrow is set in
   Adobe Caslon Pro, a commercial/licensed font we can't self-host; Libre
   Caslon Text is the open, freely-licensed Caslon revival closest to it).
   Not a variable font, so regular and bold ship as two separate static
   files rather than one weight range like Montserrat above. Used sparingly —
   see --font-serif below and styleguide.md §2. */
@font-face {
    font-family: "Libre Caslon Text";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/fonts/libre-caslon-text-regular.woff2") format("woff2");
}

@font-face {
    font-family: "Libre Caslon Text";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/fonts/libre-caslon-text-bold.woff2") format("woff2");
}

:root {
    /* Brand — deep court green, re-picked against wimbledon.com/scores as a
       direct reference (see styleguide.md §2): their hero/header green,
       their near-black green hero background, and the pale mint they use to
       highlight a winner row. Close to their values but hand-adjusted, not
       a pixel-identical copy of AELTC's own brand colors. */
    --color-primary: #046a37;
    --color-primary-dark: #034f29;
    --color-primary-darker: #0c2a18;
    --color-primary-light: #dcf1e4;

    /* Same green family, re-tuned for the dark scoreboard/header cards
       (background: --color-primary-darker) rather than the light surface —
       a plain --color-primary reads as almost-black-on-black there, so
       anything meant to stand out against the dark card needs its own
       brighter/washed variant instead of the light-surface tokens above. */
    --color-primary-bright-on-dark: #4ade80;
    --color-primary-muted-on-dark: #9fd0b0;
    --color-primary-faint-on-dark: #b8c4bb;

    /* Tennis-ball accent — for live indicators, "clinched" badges, active
       states, and anywhere something needs to pop. Used sparingly: this is
       a UI accent, not a background color, since it's too light/saturated
       for body text or large fills to stay readable. Always pair with
       --color-accent-ink for text placed on top of it. Re-picked to match
       the yellow-green wimbledon.com uses over its dark green header,
       rather than the previous neon lime. */
    --color-accent: #c8e56a;
    --color-accent-dark: #69782c;
    --color-accent-ink: #323c17;
    --color-accent-light: #f5f9e5;

    /* Visiting-side color — the "visiting team" color everywhere a match/
       score is shown; kept distinct from home-green/accent-yellow on
       purpose (spec requires the two sides be instantly distinguishable at
       a glance). Purple, matching the third color wimbledon.com actually
       leans on (their "THE CHAMPIONSHIPS" eyebrow text, the logo ring) —
       replaces the old clay red-orange, which didn't come from that
       reference at all. */
    --color-visiting: #56198a;
    --color-visiting-light: #f2e7f8;
    --color-visiting-bright-on-dark: #c77dff;

    /* Division colours for the admin scheduling board, where a tile is a fixture
       rather than one side of a score, so home/away is carried by the
       "Visitor @ Host" wording instead of by colour. That frees colour for the
       division, but NOT the green/purple pair: those mean home and visiting
       everywhere else in the app (styleguide §7) and reusing them here would
       make the board contradict every other screen.
       Blue and orange, because after green (home), purple (visiting),
       yellow-green (live), amber (warning), red (danger) and wine (penalty)
       there is not much left, and blue against orange is the most
       colour-blind-legible pair of what remains. Colour is never the only
       signal regardless: every tile also carries a B or G. */
    --color-division-boys: #2a4b8d;
    --color-division-boys-light: #e5ebf6;
    --color-division-girls: #b4530a;
    --color-division-girls-light: #fbeade;

    /* Neutrals — warmed slightly to sit better next to green than the old
       blue-leaning grays did */
    --color-ink: #1c241d;
    --color-text: #2b332c;
    --color-text-muted: #6b756c;
    --color-text-faint: #9aa39b;
    --color-border: #e1e7e2;
    --color-border-strong: #cdd6ce;
    --color-surface: #ffffff;
    --color-surface-alt: #f7f9f7;
    --color-bg: #f3f6f3;
    --color-disabled: #9aa4b2;

    /* Semantic — success reuses the brand green family; danger/warning stay
       conventional so they're never ambiguous with the palette */
    --color-success: #046a37;
    --color-success-bg: #dcf1e4;
    --color-danger: #b3261e;
    --color-danger-dark: #8f1f18;
    --color-danger-bg: #fdecea;
    --color-warning: #7a5b00;
    --color-warning-bg: #fff3cd;

    /* Issue Penalty specifically — a deep wine red rather than .btn-danger's
       conventional orange-leaning red, which fought visually with the green/
       purple palette next to it (chart.css's Undo/Swap/End Match row). A
       penalty call is a serious in-game action, not a destructive delete, so
       it doesn't need to borrow .btn-danger's alarm color; this leans enough
       toward the visiting purple's hue to read as part of the same family
       instead of clashing with it. */
    --color-penalty: #7a1f3d;
    --color-penalty-dark: #5c1730;
    --color-warning-border: #ffc107;

    /* Typography */
    /* One family now; the two tokens stay so headings and body can be told
       apart again without touching every rule that uses them. */
    --font-heading: "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-body: "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-sans: var(--font-body);
    --font-serif: "Libre Caslon Text", Georgia, "Times New Roman", serif;

    /* Fluid type scale — ~1.25 ratio, interpolated 375px -> 1440px viewport */
    --text-xs: clamp(0.75rem, 0.72rem + 0.10vw, 0.8125rem);
    --text-sm: clamp(0.875rem, 0.84rem + 0.15vw, 0.9375rem);
    --text-base: clamp(1rem, 0.96rem + 0.20vw, 1.0625rem);
    --text-lg: clamp(1.125rem, 1.06rem + 0.30vw, 1.25rem);
    --text-xl: clamp(1.375rem, 1.28rem + 0.45vw, 1.5rem);
    --text-2xl: clamp(1.625rem, 1.48rem + 0.70vw, 1.875rem);
    --text-3xl: clamp(2rem, 1.78rem + 1.05vw, 2.375rem);
    --text-4xl: clamp(2.5rem, 2.16rem + 1.60vw, 3rem);

    /* Spacing scale */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-5: 1.5rem;
    --space-6: 2rem;
    --space-8: 3rem;

    /* Radius */
    --radius-sm: 6px;
    --radius-md: 8px;
    --radius-lg: 13px;
    --radius-pill: 999px;

    /* Shadow */
    --shadow-sm: 0 1px 3px rgba(16, 24, 40, 0.08);
    --shadow-md: 0 4px 12px rgba(16, 24, 40, 0.10);
    --shadow-lg: 0 10px 30px rgba(16, 24, 40, 0.16);

    /* Icons */
    /* The smallest size carries the row-level actions (lineup, scoreboard,
       chart, edit, delete) that get tapped on a phone at the side of a
       court, so it's set a little above the 1rem a caption-sized glyph
       would take. */
    --icon-size-sm: 1.15rem;
    --icon-size-md: 1.5rem;
    --icon-size-lg: 2rem;
    --icon-size-xl: 3rem;
}

/* Every page here toggles sections on and off via the `hidden` attribute/
   property rather than a visibility class, on the assumption that `hidden`
   just works. It doesn't, by default: a plain author rule like
   `.foo { display: flex; }` sits at the same specificity as the browser's
   built-in `[hidden] { display: none }`, and normal author rules always beat
   normal user-agent ones regardless of which was written "later" — so any
   class that sets display unconditionally silently cancels `hidden` on
   itself. This was caught three times independently in one afternoon
   (.point-entry, .game-entry, .set-entry all showing at once regardless of
   which charting level was picked) before it was clear it wasn't three bugs
   but one systemic one — grepping turned up the same defect already latent
   in .add-player-form, .charting-level-change-btn, .team-picker-bar,
   .set-entry-row, and .btn (meaning every hideable button using it, not just
   a specific instance).
   One rule, unconditionally, closes the whole class of bug rather than
   chasing the next class that happens to set display and later gets a
   `hidden` toggle. !important is deliberate and safe here — there's no
   legitimate reason a hidden element should ever actually render, so nothing
   should ever need to out-rank this. */
[hidden] {
    display: none !important;
}

/* Icons — monochrome SVGs referenced from wwwroot/icons/sprite.svg via
   <svg class="icon"><use href="@Icons.Url#icon-name"/></svg> (markup) or
   `${ICON_SPRITE}#icon-name` (script), the version-stamped URL.
   fill: currentColor so every icon inherits its surrounding text color —
   never set a fill on an individual icon usage. See styleguide.md §9. */
.icon {
    display: inline-block;
    width: var(--icon-size-md);
    height: var(--icon-size-md);
    fill: currentColor;
    flex-shrink: 0;
}

.icon-sm { width: var(--icon-size-sm); height: var(--icon-size-sm); }
.icon-lg { width: var(--icon-size-lg); height: var(--icon-size-lg); }
.icon-xl { width: var(--icon-size-xl); height: var(--icon-size-xl); }

/* Icon buttons — plain-background small square buttons for an edit/delete
   pair next to a list row. Monochrome via .icon's currentColor. */
.icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text-muted);
    cursor: pointer;
    transition: background-color 0.15s, color 0.15s;
}

.icon-btn:hover {
    background: var(--color-surface-alt);
    color: var(--color-text);
}

.icon-btn-danger:hover {
    background: var(--color-danger-bg);
    color: var(--color-danger);
}

.icon-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* A small copy button set in the corner of a block of copyable text (the
   match report preview): quiet until hovered, and a check in the success
   color for a moment once it has copied. */
.copy-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text-muted);
    cursor: pointer;
    transition: background-color 0.15s, color 0.15s, border-color 0.15s;
}

.copy-btn:hover {
    background: var(--color-surface-alt);
    border-color: var(--color-border);
    color: var(--color-text);
}

.copy-btn:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
}

.copy-btn.copy-btn-done {
    color: var(--color-success);
}

.icon-btn:disabled:hover {
    background: transparent;
    color: var(--color-text-muted);
}

/* Modal — generic overlay/panel used anywhere the app needs a popup dialog
   (report preview, penalty entry, admin edit forms, delete confirmation).
   Reused as-is; page-specific content goes inside .modal-panel. */
.modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 1.5rem 1rem;
    overflow-y: auto;
    z-index: 1000;
}

.modal-panel {
    background: var(--color-surface);
    border-radius: var(--radius-lg);
    padding: 1.25rem;
    width: 100%;
    max-width: 480px;
    box-shadow: var(--shadow-lg);
}

/* Wider variant for forms with a full-sentence field (e.g. the Issue
   Penalty reason textarea), so its text isn't cramped into a narrow column. */
.modal-panel-wide {
    max-width: 640px;
}

/* For a table that gains columns from its own data and no longer fits 640
   (the travel report on a season with doubled-up rivalries). Held under the
   viewport so it still behaves on a narrow window, where the table's own
   horizontal scroll takes over. */
.modal-panel-widest {
    max-width: min(860px, 94vw);
}

/* Marks whichever player/pair is currently serving — used next to a name on
   the charting page's own scoreboard (Pages/Matches/Chart) and on every
   live-scoreboard view (wwwroot/js/set-score-table.js). Shared here since
   both draw from the same server-computed value (Match.CurrentServerSide). */
.serving-dot {
    display: inline-block;
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 50%;
    background: var(--color-accent);
    border: 1px solid var(--color-accent-dark);
    margin-left: 0.4rem;
    vertical-align: middle;
}

/* Set score table: two rows (home/visiting), one column per set (blank past
   however many have been played, so every table is the same width), then a
   wider "current game" column with a distinct background. Used inside a
   .position-row (compact, scores.css) on the dual-match/admin list views,
   standalone on the single-match detail view (.set-score-table-lg,
   scores.css), and on the charting page's own scoreboard (chart.css,
   equally .set-score-table-lg — it's the page's whole focus too). Lives
   here rather than in scores.css since the charting page needs it and
   doesn't load that stylesheet. See wwwroot/js/set-score-table.js. */

.set-score-table {
    width: 100%;
    border-collapse: collapse;
    /* Fixed layout + an explicit width on every score column (below) is
       what makes the 4 score columns line up across different position
       rows' *separate* <table> elements — each table's own content would
       otherwise size its columns independently (e.g. a table with no
       serving dot yet, or shorter names, ending up narrower), so nothing
       would visually line up from row to row. */
    table-layout: fixed;
}

/* A flex row rather than a plain cell, so the player name is the only thing
   allowed to shrink: the school code and the serving dot both sit at their
   natural width and survive a long doubles pair, which is exactly when
   knowing the school matters most. */
.set-score-name {
    text-align: left;
    font-size: 0.88rem;
    font-weight: 700;
    color: var(--color-ink);
    padding: 0.1rem 0.5rem 0.1rem 0;
    white-space: nowrap;
    overflow: hidden;
    display: flex;
    align-items: baseline;
}

.set-score-name-text {
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

/* On a phone the name column is about 66px once the set and game columns
   have their share, which cut even a short name to "Peter Ly…". Two lines
   instead: a name wraps at its spaces, and only a pairing too long for two
   lines is still cut short. */
@media (max-width: 480px) {
    .set-score-name {
        white-space: normal;
        align-items: center;
    }

    .set-score-name-text {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        line-clamp: 2;
        line-height: 1.2;
        overflow-wrap: break-word;
    }

    /* Doubles: surnames only. The full pair stays in the accessibility
       tree (visually hidden, not display:none) so screen readers still
       read both full names. */
    .set-score-name-full {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

.set-score-name-short {
    display: none;
}

@media (max-width: 480px) {
    .set-score-name-short {
        display: inline;
    }
}

/* The school a name belongs to, as an annotation rather than part of the
   name: quieter and smaller so the player still reads first, but always
   present, because home-then-visiting row order alone doesn't say which
   school is which, least of all on the admin board, where several dual
   matches are stacked. Same code the emailed box score uses (School.CodeFor).
   flex-shrink: 0 keeps it from being the thing that gets ellipsed away when
   a long doubles pair squeezes the cell. */
.set-score-school {
    /* Its own margin rather than a flex gap on the parent: this same class is
       reused outside the score table (the chart match card's "A vs B" row),
       where there is no flex container to provide one. The serving dot brings
       its own margin, so a gap here would double-space it. */
    margin-left: 0.4em;
    font-size: 0.78em;
    font-weight: 600;
    color: var(--color-text-muted);
    letter-spacing: 0.02em;
    flex-shrink: 0;
}

.set-score-cell {
    text-align: center;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    font-size: 0.92rem;
    color: var(--color-ink);
    padding: 0.1rem 0.4rem;
    width: 2rem;
}

.set-score-cell-empty {
    color: var(--color-text-faint);
    font-weight: 500;
}

/* A completed set's winning side reads bolder than the losing side; an
   in-progress set (Complete: false on the DTO) doesn't get either — nobody's
   "won" it yet, so both sides stay the plain .set-score-cell weight
   regardless of who's currently ahead. */
.set-score-cell-set-winner {
    font-weight: 800;
}

.set-score-cell-set-loser {
    font-weight: 500;
    color: var(--color-text-muted);
}

/* The tiebreak loser's point count (e.g. the "4" in "7-6(4)"), shown next
   to the losing side's game count instead of in parentheses — a table cell
   doesn't have room for "(4)" without crowding the column. */
.set-score-tiebreak {
    font-size: 0.62em;
    font-weight: 700;
    vertical-align: super;
    margin-left: 1px;
}

.set-score-cell-current {
    background: var(--color-accent-light);
    color: var(--color-accent-dark);
    border-radius: var(--radius-sm);
    font-weight: 800;
    /* Wider than a set column — "Ad"/"40" and higher tiebreak point counts
       need more room than a single set's game count ever does. */
    width: 2.6rem;
}

/* Fluid rather than the fixed 1.15rem this used to be. The wider score
   columns below (needed for a double-digit tiebreak superscript) leave this
   column less room on a narrow phone, so it shrinks a little there instead
   of truncating names more aggressively than before. Stays full-size from
   roughly tablet width up. */
.set-score-table-lg .set-score-name {
    /* Saturates at the old fixed 1.15rem by ~700px viewport, same as before
       there. Only phone widths below that actually shrink. */
    font-size: clamp(0.95rem, 0.72rem + 0.98vw, 1.15rem);
    max-width: none;
}

.set-score-table-lg .set-score-cell {
    /* Fluid with a lower floor than --text-2xl (which this used to use
       directly). Shrinking a little on a narrow phone both keeps a
       double-digit tiebreak superscript comfortably clear of the column
       edge and gives the name column above back some of the room the wider
       columns below take from it. Still a large, clearly legible score
       digit at every width (1.35rem is well above body text size).
       Saturates at the old max (1.875rem) by ~900px viewport, so nothing
       narrower than a phone actually renders smaller than before. */
    font-size: clamp(1.35rem, 0.975rem + 1.6vw, 1.875rem);
    /* Horizontal padding trimmed from 0.6rem to claw back interior room for
       the wider case below, rather than only growing the column outward.
       The name column (no fixed width; see .set-score-table's comment) eats
       whatever this table gains, and on a 375px phone it's already down to
       ~40px. Vertical padding untouched, since that's row height/spacing,
       not what overflowed. */
    padding: 0.4rem 0.35rem;
    /* Was 3.2rem, too narrow once a match tiebreak's raw point total (each
       side's own th/tv, not the usual single-digit "(4)" of a 6-6 tiebreak)
       runs to two digits, e.g. a 12-10 breaker deciding the 3rd set. The
       superscript was overflowing the column in that case. */
    width: 3.75rem;
}

.set-score-table-lg .set-score-cell-current {
    width: 4.4rem;
}

/* A finished court listed among courts still playing: no point score to
   show, but the column keeps its width so this court's sets line up with
   theirs (buildSetScoreTable's pointColumn: 'blank'). */
.set-score-cell-current-blank {
    visibility: hidden;
}

/* The odometer (setScoreCellValue in set-score-table.js): a window one line
   tall over a strip of two values, which slides to show the new one. Up for
   an increase, down for a drop. */
/* The window is sized by an invisible copy of the new value sitting in the
   text flow (.odometer-sizer), so it has exactly the width, height and
   baseline a plain score would, and a rolled score sits level with an
   unrolled one beside it. The strip is laid over it at twice its height and
   slides by half, which is one window. Clipped with clip-path, not
   overflow: hidden, which would make the inline-block's bottom edge its
   baseline and lift the whole window above the line. */
.odometer {
    position: relative;
    display: inline-block;
    clip-path: inset(0);
}

.odometer-sizer {
    visibility: hidden;
}

.odometer-track {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    height: 200%;
    display: flex;
    flex-direction: column;
}

.odometer-track > span {
    flex: 1;
    text-align: center;
}

.odometer-up {
    animation: odometer-roll-up 0.45s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

.odometer-down {
    animation: odometer-roll-down 0.45s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

@keyframes odometer-roll-up {
    from { transform: translateY(0); }
    to { transform: translateY(-50%); }
}

@keyframes odometer-roll-down {
    from { transform: translateY(-50%); }
    to { transform: translateY(0); }
}

/* No reduced-motion rule needed here: for anyone who asks for less motion,
   setScoreCellValue never builds an odometer in the first place. */

/* At phone width the widths above left the charting page's name column
   about 34px of text, so even "Andy" had to break mid-word. The digits
   there are at their 1.35rem floor, where a set column needs about 40px
   (a game count plus a two-digit tiebreak superscript) and the game column
   about 40px ("Ad", "40"), so the score columns give back what they don't
   use and the name gets roughly three times the room. */
@media (max-width: 480px) {
    .set-score-table-lg .set-score-cell {
        width: 2.75rem;
        padding: 0.4rem 0.2rem;
    }

    .set-score-table-lg .set-score-cell-current {
        width: 3.2rem;
    }
}

.modal-panel h2 {
    font-size: 1.2rem;
    font-weight: 700;
    margin-bottom: 1rem;
}

/* .modal-actions is what admin-common.js builds its modals with;
   .form-actions is the hand-written equivalent in the charting, trainer,
   review and dashboard markup. They're the same thing — a row of buttons
   closing a modal form — and the second had no rule at all, so those modals
   were rendering an unstyled block of buttons while the admin ones lined up
   correctly. Same treatment for both rather than a second definition to keep
   in sync. */
.modal-actions,
.form-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.6rem;
    margin-top: 1.25rem;
}

/* A button set apart at the start of the row, away from Save, e.g. Delete
   in the roster's Edit Player dialog. */
.modal-actions .modal-action-start {
    margin-right: auto;
}

.confirm-delete-message {
    color: var(--color-text);
    line-height: 1.5;
}

.confirm-delete-detail {
    color: var(--color-text-muted);
    font-size: 0.88rem;
    margin-top: 0.4rem;
}

/* confirmTypedDelete's "type X to confirm" field. */
.confirm-typed-delete-label {
    display: block;
    margin-top: 1rem;
    font-weight: 600;
    font-size: 0.88rem;
}

.confirm-typed-delete-label input {
    display: block;
    margin-top: 0.4rem;
    width: 100%;
}

/* General Styles */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/* Flex column rather than a fixed calc() on main (see the old comment
   there): that approach guessed at the combined height of the header and
   footer and reserved exactly that much, so any page where the real header
   (nav wrapping to two lines, the impersonation banner) or footer differed
   from the guess left a gap of leftover background below the footer on a
   short page, or forced a scrollbar on a tall one. flex: 1 0 auto on main
   below has it absorb exactly whatever's left, so the footer always lands
   at the bottom of the viewport with nothing to tune by hand. */
body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    font-family: var(--font-body);
    font-size: var(--text-base);
    background-color: var(--color-bg);
    color: var(--color-text);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

/* Form controls don't inherit font-family from the page by default in the
   browser's UA stylesheet — without this they'd silently fall back to
   Arial regardless of the --font-body token above. */
button, input, select, textarea {
    font-family: inherit;
}

h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-heading);
    font-weight: 700;
    color: var(--color-ink);
    line-height: 1.25;
}

h1 { font-size: var(--text-3xl); margin-top: var(--space-4); }
h2 { font-size: var(--text-2xl); }
h3 { font-size: var(--text-xl); }
h4 { font-size: var(--text-lg); }

a {
    color: var(--color-primary);
}

/* Horizontal-only padding, deliberately not the shorthand: <main
   class="container"> is both `main` and `.container` on the same element,
   and a shorthand `padding` here would silently win the whole property
   (top/bottom included) over main's own vertical padding below regardless
   of which was more specific for the sides that actually matter. Same
   cascade trap as the [hidden]/display one documented in styleguide.md §10,
   just with padding instead of display. Longhand keeps this rule out of
   main's top/bottom padding entirely. */
/* The scheduling board is the one page in this app that wants the whole screen:
   it is a calendar with a tray on one side and a summary table on the other,
   and it is desktop-only by nature (see scheduling.css). Everything else is
   phone-first and caps far below this. Opting out here rather than moving the
   cap keeps that the exception it is. :has() is already relied on above for
   .pill-filter, so this adds no new dependency. */
body:has(.scheduling-page) .container {
    max-width: none;
}

.container {
    /* Explicit rather than left to default block sizing: <main
       class="container"> is a direct flex child of body's sticky-footer
       column (see body, above), and a flex item with auto cross-axis
       margins — which margin: 0 auto below computes to here — is exempt
       from the default stretch-to-fill behavior per the flexbox spec.
       Without this, main sized itself to its own max-content width
       instead of the available column width, which happened to be
       exactly narrow enough to collapse the dashboard's auto-fit tile
       grid down to a single column even on a full-width desktop screen.
       width: 100% opts back into filling the available width (still
       capped by max-width and centred by the auto margins below). */
    width: 100%;
    max-width: 1200px;
    margin: 0 auto;
    padding-left: 20px;
    padding-right: 20px;
}

/* Navigation */
.navbar {
    background-color: var(--color-primary-darker);
    color: white;
    padding: 0.9rem 0;
    box-shadow: var(--shadow-md);
}

.navbar-brand {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    font-size: 1.4rem;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: white;
    text-decoration: none;
}

.navbar-logo {
    height: 2.4rem;
    width: auto;
    flex-shrink: 0;
}

/* The bar is the site's name and the menu button, at every width; every
   link lives in the menu, in groups split by a line (_Layout.cshtml). It used
   to be inline links that collapsed into the menu only on a phone, which
   meant a different place to look on each device and a bar that grew a
   link with every role. */
.navbar-inner {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: nowrap;
    gap: 0.75rem;
}

.navbar-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    padding: 0.3rem;
    border-radius: var(--radius-sm);
    color: white;
    cursor: pointer;
}

.navbar-toggle:hover {
    background: rgba(255, 255, 255, 0.1);
}

.navbar-toggle .icon {
    width: 1.5rem;
    height: 1.5rem;
}

/* Closed by default; .navbar-links-open (toggled in site.js) shows it. A
   dropdown under the button on a wide screen, the bar's full width on a
   phone (below). Above ordinary page content, below .modal-overlay's 1000. */
.navbar-links {
    display: none;
    position: absolute;
    top: calc(100% + 0.9rem);
    right: 0;
    width: 17rem;
    max-height: calc(100vh - 5rem);
    overflow-y: auto;
    flex-direction: column;
    background: var(--color-primary-darker);
    border-radius: 0 0 var(--radius-md) var(--radius-md);
    box-shadow: var(--shadow-md);
    padding: 0.35rem 0 0.6rem;
    z-index: 900;
}

.navbar-links.navbar-links-open {
    display: flex;
}

.navbar-group {
    display: flex;
    flex-direction: column;
    padding: 0.35rem 0;
}

.navbar-group + .navbar-group {
    border-top: 1px solid #2f4a38;
}

.navbar-link {
    display: block;
    padding: 0.5rem 1.25rem;
    color: #a9c9b3;
    text-decoration: none;
    font-size: 0.92rem;
    font-weight: 600;
    transition: color 0.15s, background-color 0.15s;
}

.navbar-link:hover,
.navbar-link:focus-visible {
    color: white;
    background: rgba(255, 255, 255, 0.06);
}

/* A count beside a menu item (Reporting: matches waiting to be reported),
   in the same iOS badge red as the dashboard tile's, since it means the same
   thing. */
.navbar-link-with-badge {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}

.navbar-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.4rem;
    height: 1.4rem;
    padding: 0 0.4rem;
    border-radius: 999px;
    background: #ff3b30;
    color: #ffffff;
    font-size: 0.75rem;
    font-weight: 700;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

/* display:inline-flex above outranks the hidden attribute's display:none. */
.navbar-badge[hidden] {
    display: none;
}

.navbar-user {
    color: white;
    font-weight: 700;
}

.navbar-logout-btn {
    margin: 0.35rem 1.25rem 0;
    background: none;
    border: 1px solid #2f4a38;
    color: #a9c9b3;
    border-radius: var(--radius-sm);
    padding: 0.45rem 0.8rem;
    font-size: 0.85rem;
    font-weight: 600;
    text-align: center;
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s;
}

.navbar-logout-btn:hover {
    color: white;
    border-color: white;
    background: none;
}

/* Impersonation banner: persistent across every page while an admin is
   viewing as a Coach, Statistician or Athletics Director (_Layout.cshtml, UserEndpoints.Impersonate).
   Full-strength accent, since this is exactly what that token is for:
   something that needs to stay noticeable the whole time this state is
   active, not just on first load. */
.impersonation-banner {
    background: var(--color-accent);
    color: var(--color-accent-ink);
}

/* Also carries .container for its max-width/horizontal padding. Longhand
   here so this doesn't re-fight that shared rule for the sides it doesn't
   care about (see styleguide.md §10's [hidden]/padding writeup). */
.impersonation-banner-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
    font-size: 0.88rem;
    font-weight: 600;
}

.impersonation-banner-inner .btn-secondary {
    min-height: 32px;
    padding: 0.3rem 0.9rem;
    font-size: 0.82rem;
}

/* Main Layout */
main {
    flex: 1 0 auto;
    padding: 2rem 0;
}

/* Reachable from every page on purpose: a coach, a parent or a school's
   counsel should be able to find what the site says about student information
   without hunting for it. */
.footer-link {
    margin-left: 0.75rem;
    color: inherit;
    text-decoration: underline;
}

.footer-link:hover {
    color: #fff;
}

/* A quieter second line than the copyright above it. This is a credit rather
   than a notice, so it should be findable without competing with the privacy
   and terms links people occasionally go looking for. Its own link rule rather
   than .footer-link, which carries a left margin for sitting in a row of
   links and would indent this one mid-sentence. */
.footer-credit {
    margin: 0.35rem 0 0;
    font-size: 0.72rem;
    opacity: 0.75;
}

.footer-credit a {
    color: inherit;
    text-decoration: underline;
}

.footer-credit a:hover {
    color: #fff;
}

footer {
    background-color: var(--color-primary-darker);
    color: #7fa38c;
    text-align: center;
    padding: 1.5rem 0;
    margin-top: 3rem;
    font-size: 0.85rem;
}

/* Buttons */
.btn {
    display: inline-block;
    padding: 0.7rem 1.4rem;
    border: none;
    border-radius: var(--radius-sm);
    font-size: 1rem;
    font-weight: 600;
    /* A <button> has no inherited line-height of its own in the UA
       stylesheet (it computes to "normal", ~1.15 of its font size) while an
       <a> inherits the page's 1.6 — same padding and font-size, but a
       shorter box, which is what made a .btn-sm link and a .btn-sm button
       sitting side by side (admin-schools.js's Google Maps row) come out a
       few pixels different in height. Pinning it here makes every .btn the
       same height regardless of which element renders it. */
    line-height: 1;
    cursor: pointer;
    text-decoration: none;
    text-align: center;
    transition: background-color 0.15s, box-shadow 0.15s, transform 0.05s;
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
}

.btn:active {
    transform: translateY(1px);
}

.btn:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
}

/* Flat neutral gray regardless of variant (primary/secondary/danger/success)
   — a disabled button should read as obviously inert at a glance, not just
   a slightly-dimmer version of its normal color. Wins over the variant
   background rules below on specificity alone (pseudo-class + class beats
   a single class), no !important needed. */
.btn:disabled {
    background-color: var(--color-border-strong);
    color: var(--color-text-faint);
    cursor: not-allowed;
    box-shadow: none;
}

.btn:disabled:active {
    transform: none;
}

.btn-primary {
    background-color: var(--color-primary);
    color: white;
}

.btn-primary:hover {
    background-color: var(--color-primary-dark);
}

.btn-secondary {
    background-color: #667085;
    color: white;
}

.btn-secondary:hover {
    background-color: #545c6e;
}

.btn-danger {
    background-color: var(--color-danger);
    color: white;
}

.btn-danger:hover {
    background-color: var(--color-danger-dark);
}

/* A destructive action offered alongside a form rather than as its point,
   e.g. Delete in the roster's Edit Player dialog: outlined in red rather
   than filled, so it doesn't compete with Save. */
.btn-danger-outline {
    background-color: transparent;
    color: var(--color-danger);
    box-shadow: inset 0 0 0 1px var(--color-danger);
}

.btn-danger-outline:hover {
    background-color: var(--color-danger-bg);
}

/* Forms */
.form-group {
    margin-bottom: 1.5rem;
}

/* A group of buttons that answer one question (which side, which format),
   named by its legend. Looks like any other labelled field. */
.form-fieldset {
    border: 0;
    padding: 0;
    min-width: 0;
}

.form-fieldset > legend {
    padding: 0;
    margin-bottom: 0.5rem;
    font-weight: 600;
    color: var(--color-text);
}

label {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: 600;
    color: var(--color-text);
    font-size: 0.92rem;
}

input[type="text"],
input[type="email"],
input[type="url"],
input[type="tel"],
input[type="search"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="datetime-local"],
select,
textarea {
    width: 100%;
    padding: 0.7rem 0.85rem;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-sm);
    font-size: 1rem;
    font-family: inherit;
    color: var(--color-text);
    background: var(--color-surface);
    transition: border-color 0.15s, box-shadow 0.15s;
}

/* A 2px outline in the brand green, not a color shift alone (styleguide.md
   §7): the old 3px glow in --color-primary-light (#E7F3EB) was close to
   invisible on white in sunlight, which is where this is used. */
input:focus,
select:focus,
textarea:focus {
    outline: 2px solid var(--color-primary);
    outline-offset: 1px;
    border-color: var(--color-primary);
}

/* ---- Toasts (showAlert in site.js) ----
   Tinted background with dark text in the semantic pairs, rather than
   white on a saturated fill: every pair here passes AA. Stacked, so two
   messages close together don't sit on top of each other. */
.toast-stack {
    position: fixed;
    top: var(--space-4);
    right: var(--space-4);
    left: var(--space-4);
    z-index: 1100;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--space-2);
    pointer-events: none;
}

.toast {
    pointer-events: auto;
    max-width: 28rem;
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-md);
    border-left: 4px solid currentColor;
    box-shadow: var(--shadow-md);
    font-weight: 600;
    cursor: pointer;
    background: var(--color-primary-light);
    color: var(--color-primary-darker);
}

.toast-success { background: var(--color-success-bg); color: var(--color-success); }
.toast-error   { background: var(--color-danger-bg);  color: var(--color-danger); }
.toast-warning { background: var(--color-warning-bg); color: var(--color-warning); }

/* ---- Connection banner (charting page) ----
   Fixed to the bottom of the screen so it never pushes the scoring buttons
   around when it appears (styleguide.md §10). */
.connection-banner {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1050;
    padding: var(--space-3) var(--space-4);
    background: var(--color-warning-bg);
    color: var(--color-warning);
    border-top: 2px solid var(--color-warning);
    font-weight: 600;
    text-align: center;
}

/* Respect a request for less motion: hover lifts and transitions become
   instant rather than animated. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* Responsive */
@media (max-width: 768px) {
    .container {
        padding-left: 15px;
        padding-right: 15px;
    }

    .navbar-brand {
        font-size: 1.2rem;
        gap: 0.5rem;
    }

    .navbar-logo {
        height: 2rem;
    }

    .card {
        margin-bottom: 1rem;
    }

    /* On a phone the menu spans the screen, edge to edge, with taller rows
       to tap: anchored to the full-width bar rather than to the row inside
       it, whose edges depend on the page's padding. */
    .navbar {
        position: relative;
    }

    .navbar-inner {
        position: static;
    }

    .navbar-links {
        top: 100%;
        left: 0;
        right: 0;
        width: auto;
        border-radius: 0;
    }

    .navbar-link {
        padding: 0.7rem 20px;
    }

    .navbar-logout-btn {
        margin-left: 20px;
        margin-right: 20px;
    }
}

/* Inline form feedback — used on any page with an auto-saving or ad-hoc
   form (dashboard, charting's penalty form, etc.) */
.form-status {
    font-size: 0.88rem;
    font-weight: 600;
}

.form-status-error { color: var(--color-danger); }
.form-status-success { color: var(--color-success); }


/* Reaches screen readers but takes no space on screen. For a field whose
   visible context already names it (a second number box beside a labelled
   first, a search box next to its button) but which a screen reader would
   otherwise announce unnamed. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ---- Back links ---- */

/* The small muted link that sits above a page title and returns the visitor
   to wherever they came from (see renderBackLink in site.js). Every page's
   is this one: the admin and coach pages used to wrap theirs in a bordered
   .admin-subnav bar, which never held anything but the back link itself and
   drew each one a shade fainter than the public pages' equivalent. */
.back-link {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    margin-bottom: 1rem;
    color: var(--color-text-muted);
    font-weight: 600;
    font-size: 0.9rem;
    text-decoration: none;
}

.back-link:hover { color: var(--color-primary); }

/* ---- Pill filters ---- */

/* A radio group drawn as pills, for the handful of pages that segment a list
   by one exclusive choice (admin's Teams, the scoreboard, standings). Lives
   here rather than in any one page's stylesheet: it started out copied
   between admin.css and scores.css, and standings would have made three. */
.pill-filter {
    display: flex;
    gap: 0.4rem;
}

.pill-filter label {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.4rem 0.9rem;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-pill);
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--color-text-muted);
    background: var(--color-surface);
    cursor: pointer;
}

.pill-filter input {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
}

.pill-filter input:checked + span {
    color: inherit;
}

.pill-filter label:has(input:checked) {
    background: var(--color-primary);
    border-color: var(--color-primary);
    color: white;
}

.pill-filter input:focus-visible {
    outline: none;
}

.pill-filter label:has(input:focus-visible) {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
}

/* ---- Status pills ---- */

/* Not started / Live / Suspended / Final, on a position or on a whole dual
   match. Here rather than in scores.css because five pages across three
   stylesheets show them and only two of those load that file. The vocabulary
   itself is POSITION_STATUS_TAGS in site.js. */
.status-tag {
    display: inline-block;
    margin-left: 0.4rem;
    padding: 0.1rem 0.4rem;
    border-radius: var(--radius-sm);
    font-size: 0.62rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    vertical-align: middle;
}

.status-tag-suspended {
    background: var(--color-warning-bg);
    color: var(--color-warning);
}

.status-tag-not-started {
    background: var(--color-surface-alt);
    color: var(--color-text-muted);
}

.status-tag-live {
    background: var(--color-accent);
    color: var(--color-accent-ink);
}

.status-tag-final {
    background: var(--color-success-bg);
    color: var(--color-success);
}

/* ---- Dual match score cells (see renderDualMatchScore in site.js) ---- */

/* The score itself, wherever a dual match's result is shown next to a
   status pill: the public scores list, the coach schedule, admin's Dual
   Matches. Tabular figures so the numbers line up down a column. */
.score-numbers {
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    color: var(--color-ink);
}

/* .status-tag carries a leading margin for the usual case, where it trails
   a label. In these cells it's a flex item with its own gap instead. */
.schedule-score .status-tag {
    margin-left: 0;
}

/* Keeps the name to one line beside the shield on a typical phone; the very
   narrowest still wrap, as they did before the shield. After the 768px block
   so it wins there. */
@media (max-width: 420px) {
    .navbar-brand {
        font-size: 1.02rem;
        gap: 0.4rem;
    }

    .navbar-logo {
        height: 1.8rem;
    }
}

/* ---- Dragging tiles (tile-drag.js) ----
   One look for every drag in the app: the roster's reorder, the lineup's
   seats and Sitting Out list, and the admin scheduling board. The lifted
   copy floats under the pointer, the tile it came from stays behind as a
   dashed slot, the place it would land is marked the same way, and a tile
   that has just landed flashes once. The !important on the slot and target
   is deliberate: they are momentary states that have to win over each
   page's own tile colors (a division fill, a roster row) for a second. */

.tile-drag-ghost {
    position: fixed;
    z-index: 1000;
    margin: 0;
    box-sizing: border-box;
    pointer-events: none; /* elementFromPoint sees what's underneath, not the copy */
    outline: 2px solid var(--color-primary);
    outline-offset: -1px;
    box-shadow: 0 14px 28px rgba(16, 24, 40, 0.22), 0 3px 8px rgba(16, 24, 40, 0.12);
    scale: 1.03;
    cursor: grabbing;
    will-change: translate;
    transition: scale 150ms ease-out, box-shadow 150ms ease-out;
}

/* The page's own grip glyph (roster handle, lineup chip grip) turns green
   on the lifted copy. */
.tile-drag-ghost .roster-drag-handle,
.tile-drag-ghost .lineup-chip-grip {
    color: var(--color-primary);
}

/* Released: drop the lift and glide onto the tile's new place. */
.tile-drag-ghost.tile-drag-gliding {
    scale: 1;
    box-shadow: var(--shadow-sm);
    transition: translate 200ms cubic-bezier(0.2, 0, 0, 1), width 200ms cubic-bezier(0.2, 0, 0, 1),
        height 200ms cubic-bezier(0.2, 0, 0, 1), scale 200ms ease-out, box-shadow 200ms ease-out;
}

/* Where the tile was picked up from (and, on the roster, where it will land). */
.tile-drag-source {
    border-style: dashed !important;
    border-color: var(--color-primary) !important;
    background: var(--color-primary-light) !important;
    box-shadow: none !important;
    opacity: 1 !important;
}

.tile-drag-source > *,
.tile-drag-landing {
    visibility: hidden;
}

/* The place under the pointer that would take the tile. */
.tile-drop-target {
    outline: 2px dashed var(--color-primary) !important;
    outline-offset: -2px;
    background-color: var(--color-primary-light) !important;
}

.tile-drag-settled {
    animation: tile-drag-settle 600ms ease-out;
}

@keyframes tile-drag-settle {
    from {
        background-color: var(--color-primary-light);
        border-color: var(--color-primary);
        box-shadow: 0 0 0 2px var(--color-primary);
    }
}

body.tile-drag-active,
body.tile-drag-active * {
    cursor: grabbing !important;
    user-select: none;
    -webkit-user-select: none;
}

@media (prefers-reduced-motion: reduce) {
    .tile-drag-ghost,
    .tile-drag-ghost.tile-drag-gliding {
        scale: 1;
        transition: none;
    }

    .tile-drag-settled {
        animation: none;
    }
}
