/* ═══════════════════════════════════════════════════════════
   ProElite Sports — Custom Styles
   Brand colors and components that extend Tailwind CSS
   ═══════════════════════════════════════════════════════════ */

/* The Plus Jakarta Sans @import that used to sit here moved into every page's
   <head> as preconnect + <link> (2026-10-12). An @import inside a stylesheet is
   SERIAL: the browser cannot ask Google for the font CSS until this whole file
   has arrived, and cannot fetch the .woff2 until that answers — three
   dependent round trips before any text renders, on a path measured at 230 ms
   each. A <link> in the HTML is discovered by the preload scanner alongside
   this file and fetched in parallel. A new page that loads app.css needs the
   three tags next to its app.css link; bin/test-font-link.php checks. */

/* ═══════════════════════════════════════════════════════════
   App-wide sleek scrollbars
   Chrome / Edge / Safari get the sleek rounded pill from the
   ::-webkit-scrollbar rules below. Firefox has no ::-webkit-scrollbar, so
   it gets the standard scrollbar-width / scrollbar-color — but ONLY inside
   the Firefox-only @supports gate below.

   Why the gate matters (this WAS the "ugly OS scrollbar in modals" bug):
   in Chromium 121+, the moment scrollbar-width OR scrollbar-color is set on
   an element, the browser throws away that element's ::-webkit-scrollbar
   styling and paints the plain native scrollbar instead. The old global
   `* { scrollbar-width: thin }` therefore silently killed the pill
   everywhere in Chrome/Edge — cache-busting never stood a chance, the rules
   were loading fine, just being ignored.
   RULE: never set scrollbar-width/-color outside this @supports block, or
   you re-break the pill on that element in Chromium.
   ═══════════════════════════════════════════════════════════ */
@supports (-moz-appearance: none) {
    * {
        scrollbar-width: thin;
        scrollbar-color: rgba(15, 23, 42, 0.22) transparent;
    }
}
*::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}
*::-webkit-scrollbar-track {
    background: transparent;
}
*::-webkit-scrollbar-thumb {
    background: rgba(15, 23, 42, 0.18);
    border-radius: 8px;
    border: 2px solid transparent;
    background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover {
    background: rgba(15, 23, 42, 0.32);
    background-clip: content-box;
    border: 2px solid transparent;
}
*::-webkit-scrollbar-corner {
    background: transparent;
}
@supports (-moz-appearance: none) {
    [data-theme="dark"] * {
        scrollbar-color: rgba(255, 255, 255, 0.22) transparent;
    }
}
[data-theme="dark"] *::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.18);
    background-clip: content-box;
}
[data-theme="dark"] *::-webkit-scrollbar-thumb:hover {
    background: rgba(255, 255, 255, 0.32);
    background-clip: content-box;
}

:root {
    /* Typography — the single place the app's two families are named.
       Every font-family in app.css and in the app pages' style blocks points
       here, so a swap is these two lines plus the @import above. */
    --pe-font:         'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --pe-font-display: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

    --pe-blue:       #38bdf8;
    --pe-blue-dark:  #0ea5e9;
    --pe-blue-light: #e0f2fe;
    --pe-charcoal:   #111827;
    --pe-gray:       #6b7280;
    --pe-gray-light: #f5f6f8;
    --pe-red:        #e94560;
    /* --pe-danger is the semantic error/required red, distinct from --pe-red
       (#e94560, the brand rose). Used 41 times and never defined, so its 17
       fallback-less uses — all `color` — were inheriting instead of going red.
       #ef4444 is what 23 of the 24 fallbacks in the codebase already name, and
       what .req and the four *-required classes use. */
    --pe-danger:     #dc2626;
    --pe-green:      #22c55e;
    --pe-orange:     #f59e0b;
    --pe-bg:         #f0f2f5;
    --pe-white:      #ffffff;
    /* Tells the browser which way its OWN chrome should go: date and time
       pickers, select dropdowns, scrollbars, number spinners, the caret and the
       autofill wash. Without it every one of those stayed light while the app
       was dark — most visible on the facility hours drawer and the campaign send
       times, which are nothing but native pickers (2026-09-14). */
    color-scheme: light;
    /* --pe-card is the card/input surface. Referenced 198 times, never defined;
       its 6 fallback-less uses are all `background` and so were transparent —
       four inline-styled inputs in league-settings, a segmented-button active
       state in tournament.html, and one dark dropdown in teams-view. Unlike
       --pe-card-bg, these are NOT all dark-scoped, so it needs a value in both
       themes. #fff is what all 154 light fallbacks name. */
    --pe-card:       #ffffff;
    /* Design refresh (2026-08-05): softer, layered card shadows for depth.
       Backup: docs/_design-backup/app.css.bak · tag pre-design-refresh-20260805 */
    --pe-shadow:     0 1px 2px rgba(17,22,36,0.04), 0 12px 28px -14px rgba(17,22,36,0.14);
    --pe-shadow-md:  0 6px 20px -8px rgba(17,22,36,0.14);
    --pe-shadow-lg:  0 24px 48px -18px rgba(17,22,36,0.22);
    --pe-radius:     10px;

    /* Sidebar */
    --sidebar-w:         240px;
    --sidebar-w-collapsed: 68px;
    --sidebar-bg:        #1a1a2e;
    --sidebar-bg-hover:  rgba(255,255,255,0.06);
    --sidebar-bg-active: rgba(56,189,248,0.12);
    --sidebar-text:      rgba(255,255,255,0.55);
    --sidebar-text-hover: rgba(255,255,255,0.9);
    --sidebar-text-active: #38bdf8;
    --sidebar-border:    rgba(255,255,255,0.08);
    --sidebar-ease:      0.2s cubic-bezier(0.4,0,0.2,1);

    /* Borders & surfaces */
    --pe-border:         #e6e8ee;
    --pe-border-light:   #eef0f3;
    --pe-border-medium:  #dde1e7;
    --pe-surface:        #fafbfc;
    /* --pe-bg-subtle is the tint behind a section header, a segmented-control
       track or a group row — a shade OFF the card, never the card itself. It is
       referenced 20 times across 8 files and was never defined, so all 20 fell
       back to a hardcoded light grey. In dark mode that painted white bars
       across a dark page: the DETAILS and AREAS headers on facility-profile,
       the same headers on referee-profile and division-profile, the Group By
       rows in every data table (Robb, 2026-09-16).
       The fallbacks authors wrote are #f8fafc (most common), #f1f5f9, #f5f6f8,
       #f1f4f8, #f7f8fa, #f0f2f5 and #eef1f5 — all the same idea one or two
       steps apart. #f3f5f8 sits in the middle of them and, unlike #f8fafc,
       stays visible against both --pe-card (#fff) and --pe-bg (#f0f2f5).
       The dark value is one step UP from --pe-surface (#1e2028) rather than
       down, so a header still reads as a header on a dark card. */
    --pe-bg-subtle:      #f3f5f8;

    /* Text hierarchy */
    --pe-text:           var(--pe-charcoal);
    --pe-text-secondary: #64748b;
    /* --pe-text-muted is an alias, not a new stop. It is referenced 737 times
       across 50 files and was never defined, so every one of those colour
       declarations was invalid and silently inherited its parent instead.
       Authors wrote three different fallbacks for it (#94a3b8, #64748b,
       #6b7280); #94a3b8 is the most common but sits at 2.6:1 on white, which
       fails AA for text, and ~727 of the uses are text. Aliasing to
       text-secondary keeps them legible and follows the precedent already set
       in this file. var() resolves at use, so this tracks the dark theme too. */
    --pe-text-muted:     var(--pe-text-secondary);
    --pe-text-tertiary:  #9ca3af;
    --pe-text-placeholder: #b0b7c3;

    /* Alert backgrounds */
    --pe-red-light:    #fef2f2;
    --pe-green-light:  #f0fdf4;
    --pe-orange-light: #fffbeb;

    /* Top bar */
    --topbar-h:      56px;

    /* Bottom tab bar */
    --tab-h:          64px;
    --tab-bg:         #ffffff;
    --tab-border:     #e5e7eb;
    --tab-text:       #9ca3af;
    --tab-text-active: #38bdf8;
}

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

body {
    font-family: var(--pe-font);
    color: var(--pe-charcoal);
    background: var(--pe-bg);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
}

h1, h2, h3, h4 {
    font-family: var(--pe-font-display);
    font-weight: 700;
    line-height: 1.2;
}

/* ── Auth Pages (Login, Register) ─────────────────────── */

.auth-page {
    min-height: 100vh;
    display: flex;
    background: linear-gradient(135deg, #eef2f7 0%, #dce3ec 50%, #c8d6e0 100%);
    position: relative;
    /* overflow-x clips the ::before/::after decorative blobs (positioned
       at -200px/-150px offsets) so they don't create a horizontal scroll.
       overflow-y is left default so long forms (like Register, which has
       ~7 fields + password rules) can scroll vertically on short viewports.
       Was `overflow: hidden` — that clipped the submit button below the
       fold on laptops with viewport height < ~800px. */
    overflow-x: hidden;
}

/* The gradient above is a light-mode literal, so until now every auth page
   (login, register, reset, verify, the team-register family — 12 in all) kept a
   pale backdrop in dark mode while the card on top of it went dark. Same three
   stops and same 135deg, walked down the dark palette and landing on --pe-bg's
   #0f1117. The ::before/::after blobs are translucent (rgba 0.12/0.08) and read
   as a soft glow on the dark ground, so they are left alone. */
[data-theme="dark"] .auth-page {
    background: linear-gradient(135deg, #171b26 0%, #12151d 50%, #0f1117 100%);
}

.auth-page::before {
    content: '';
    position: absolute;
    top: -200px;
    right: -200px;
    width: 600px;
    height: 600px;
    background: radial-gradient(circle, rgba(90,143,168,0.12) 0%, transparent 70%);
    pointer-events: none;
}

.auth-page::after {
    content: '';
    position: absolute;
    bottom: -150px;
    left: -150px;
    width: 400px;
    height: 400px;
    background: radial-gradient(circle, rgba(90,143,168,0.08) 0%, transparent 70%);
    pointer-events: none;
}

.auth-container {
    margin: auto;
    width: 100%;
    max-width: 440px;
    padding: 24px;
    position: relative;
    z-index: 1;
}

.auth-card {
    background: var(--pe-white);
    border-radius: 16px;
    padding: 0;
    box-shadow: 0 8px 32px rgba(56,189,248,0.25), 0 4px 16px rgba(0,0,0,0.1);
    overflow: hidden;
}

.auth-card-header {
    background: #333333;
    padding: 28px 40px 24px;
    display: flex;
    justify-content: center;
    border-bottom: 2px solid #38bdf8;
}

.auth-card-body {
    padding: 32px 40px 40px;
}

.auth-logo {
    /* Height trimmed from 60→50 to offset the padding crop (the mark now fills
       its box), keeping the login logo the same visual size as before. */
    display: block;
    height: 50px;
    width: auto;
    max-width: 100%;
    margin: 0;
}

.auth-title {
    text-align: center;
    font-size: 22px;
    color: var(--pe-charcoal);
    margin-bottom: 4px;
}

.auth-subtitle {
    text-align: center;
    color: var(--pe-gray);
    font-size: 14px;
    margin-bottom: 28px;
}

/* ── Form Controls ────────────────────────────────────── */

.form-group {
    margin-bottom: 18px;
}

.form-group label {
    display: block;
    /* Labels sit one step back from the values they name: slightly smaller and
       in secondary text, so a stacked form reads as label-then-value rather
       than two rows of equal weight. */
    font-size: 12.5px;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--pe-text-secondary);
    margin-bottom: 5px;
}

/* Required marker. Labels are muted, so the asterisk needs its own colour or it
   disappears into the label it qualifies. #ef4444 is what this codebase already
   uses for it in .bac-required, .sal-required, .lp-form-req and the two local
   .req rules in checkout.html and contact.html. */
.req {
    color: var(--pe-danger);
}

/* `.pe-input` is the same control, OUTSIDE a .form-group.
   Every rule below is scoped to .form-group, so a bare <select> in a custom
   row (a repeater, a compact grid) falls back to the near-native widget and
   reads as a different, older app right beside a styled one — which is what
   the event drawer's Facility + Area row looked like (Robb, 2026-09-19).
   Add the class rather than copying the declarations: .rw-field, .rn-rate and
   #evSessionsBuilder had each already made their own copy, and a fourth would
   be a fourth thing to keep in step with this block. */
.form-group input,
.form-group select,
.form-group textarea,
.pe-input {
    width: 100%;
    padding: 11px 14px;
    background: var(--pe-gray-light);
    border: 1.5px solid var(--pe-border);
    border-radius: 10px;
    font-family: inherit;
    font-size: 14px;
    color: var(--pe-text);
    transition: all 0.2s ease;
}

/* Native date/time controls render their value in a monospace UA font when
   they sit OUTSIDE a .form-group (custom rows like the schedule session builder
   and a facility's operating hours). Make every one inherit the site font so
   the value text matches the rest of the app. */
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"],
input[type="week"] {
    font-family: inherit;
}

.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus,
.pe-input:focus {
    outline: none;
    background: var(--pe-white);
    border-color: var(--pe-blue);
    box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.12);
}

.form-group input::placeholder,
.pe-input::placeholder {
    color: var(--pe-text-placeholder);
}

/* ── Password Toggle ─────────────────────────────────── */

.input-password-wrap {
    position: relative;
}

.input-password-wrap input {
    padding-right: 44px;
}

.password-toggle {
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    cursor: pointer;
    padding: 4px;
    color: var(--pe-gray);
    transition: color 0.2s;
}

.password-toggle:hover {
    color: var(--pe-charcoal);
}

.password-toggle svg {
    width: 20px;
    height: 20px;
    display: block;
}

/* Hide browser's built-in password reveal (Edge, Chrome) */
input[type="password"]::-ms-reveal,
input[type="password"]::-ms-clear {
    display: none;
}

input[type="password"]::-webkit-credentials-auto-fill-button,
input[type="password"]::-webkit-clear-button {
    display: none;
}

.form-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

/* Three across. Lived in events.html's page styles, so `class="form-row-3"` on
   any other page silently did nothing — which is what wrapped Gender onto its
   own line in the season drawer (2026-09-20). Shared now; one column below
   1024px, where three fields stop fitting. */
.form-row-3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    /* TOP-align, not bottom. This was `align-items: end` so a label carrying a
       .field-tip (whose icon overhung its line box by a fraction) would not
       sit Sport a pixel above Division beside it - but bottom-aligning the
       whole CELL lifts any field that has text UNDER its control: the season
       drawer's Gender grew a "This division is Boys." hint and the label and
       select floated above the two beside it (Robb, prod, 2026-10-23). The
       overhang is fixed at its source instead: the shared rule below pins the
       label's line box tall enough to hold the icon. */
    align-items: start;
}
@media (max-width: 1023px) { .form-row-3 { grid-template-columns: 1fr; } }

/* A label's line box is 18px whether or not it carries a 15px .field-tip, so
   fields on one row line up from the TOP and a hint under one control cannot
   move its neighbours. Covers .form-row-3, -ident and -4, all top-aligned. */
.form-row-3 > .form-group > label,
.form-row-ident > .form-group > label,
.form-row-4 > .form-group > label { line-height: 18px; }

/* The division drawer's identity line: Sport | Division Name | Colour. Not
   .form-row-3, because equal thirds give the one REQUIRED field on the line the
   same 190px as the two optional ones beside it — a division name is routinely
   "Baseball - Coaches Pitch" and a sport is one word. `minmax(0, …)` on every
   track so a long value cannot push the row wider than the drawer. */
.form-row-ident {
    grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.6fr) minmax(0, 1fr);
    align-items: start;             /* see .form-row-3 */
}
@media (max-width: 1023px) { .form-row-ident { grid-template-columns: 1fr; } }

/* Four across, for the one row that needs it: the event drawer's Pricing +
   per-player price + per-team price + Capacity, when the mode is "Player +
   team". Top-aligned like .form-row-3, for the same reason: the Pricing label
   carries a .field-tip, and the shared label line-height rule above is what
   keeps it level with its neighbours.
   NO media query of its own, deliberately. `.form-row` collapses to one column
   at 1023px further down this file, and that rule is LATER with the same
   one-class specificity — so it wins here whatever this says. A tablet rule
   written up here did nothing at all and measured as a single column at 768px.
   Out-specifying it with `.form-row.form-row-4` would work and would also make
   this the one form row in the app that behaves differently at tablet width,
   for no reason the admin would guess. So it inherits the house behaviour:
   four across above 1024, one column below, exactly like .form-row-3. */
.form-row-4 {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    align-items: start;
}

.form-row-address {
    display: flex;
    gap: 12px;
}

.form-row-address > .form-group { min-width: 0; }

/* ── Colour field (SWATCH, assets/js/swatch-picker.js) ──
   A trigger that shows the current colour and a popover holding the palette.
   Shared, not division-specific: the facility and tournament colour inputs are
   the same job done with a bare <input type="color">, and this is where they go
   if they ever want the same curated set. See the module header in the JS for
   why the palette is a popover rather than an inline strip. */

.pe-colorfield { position: relative; }

.pe-color-trigger {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    /* Matches the height of a sibling .form-group input/select so the control
       sits on the same line as Division Name rather than a pixel off it. */
    min-height: 42px;
    padding: 0 10px;
    background: var(--pe-input-bg, var(--pe-card, #fff));
    border: 1px solid var(--pe-border, #e5e7eb);
    border-radius: 8px;
    color: var(--pe-text, #333);
    font: inherit;
    font-size: 14px;
    cursor: pointer;
    text-align: left;
}
.pe-color-trigger:hover { border-color: var(--pe-blue, #38bdf8); }
.pe-color-trigger:focus-visible { outline: 2px solid var(--pe-blue, #38bdf8); outline-offset: 1px; }

.pe-color-well {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    border-radius: 6px;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
}
/* Automatic has no colour to show, so it shows the ABSENCE of one — a quiet
   checkerboard, which is the convention for "nothing set" and does not get
   mistaken for a grey the league chose. */
.pe-color-well.is-auto {
    background:
        linear-gradient(45deg, var(--pe-border, #e5e7eb) 25%, transparent 25%, transparent 75%, var(--pe-border, #e5e7eb) 75%),
        linear-gradient(45deg, var(--pe-border, #e5e7eb) 25%, transparent 25%, transparent 75%, var(--pe-border, #e5e7eb) 75%),
        var(--pe-card, #fff);
    background-size: 10px 10px;
    background-position: 0 0, 5px 5px;
}

.pe-color-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.pe-color-caret { width: 14px; height: 14px; flex-shrink: 0; opacity: 0.6; }

.pe-color-panel {
    position: absolute;
    z-index: 60;
    top: calc(100% + 6px);
    right: 0;
    padding: 10px;
    background: var(--pe-card, #fff);
    border: 1px solid var(--pe-border, #e5e7eb);
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
}

.pe-swatches {
    display: grid;
    grid-template-columns: repeat(7, 30px);
    gap: 8px;
}

.pe-swatch {
    width: 30px;
    height: 30px;
    padding: 0;
    border: 0;
    border-radius: 8px;
    cursor: pointer;
    position: relative;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.14);
    display: flex;
    align-items: center;
    justify-content: center;
}
.pe-swatch svg { width: 15px; height: 15px; }
/* The selected ring sits OUTSIDE the chip, so selection does not resize it and
   nothing moves. Nothing lifts on hover either — house rule. */
.pe-swatch.is-on { box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.14), 0 0 0 2px var(--pe-card, #fff), 0 0 0 4px var(--pe-blue, #38bdf8); }
.pe-swatch:hover { box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.35); }
.pe-swatch.is-on:hover { box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.35), 0 0 0 2px var(--pe-card, #fff), 0 0 0 4px var(--pe-blue, #38bdf8); }
.pe-swatch:focus-visible { outline: 2px solid var(--pe-blue, #38bdf8); outline-offset: 3px; }

.pe-swatch-auto,
.pe-swatch-custom {
    background: var(--pe-bg, #f8f9fa);
    color: var(--pe-text-muted, #6b7280);
}
/* The native colour input is the click target but paints nothing — the chip
   around it is what you see. Opacity rather than display:none: a hidden input
   cannot open the OS picker in Safari. */
.pe-swatch-custom { overflow: hidden; }
.pe-swatch-custom .pe-swatch-input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    padding: 0;
    border: 0;
    opacity: 0;
    cursor: pointer;
}

@media (max-width: 599px) {
    /* 44px tap targets on a phone (mobile pass rule). Seven across still fits:
       7 x 44 + 6 x 6 = 344, inside a full-width bottom-sheet modal. */
    .pe-swatches { grid-template-columns: repeat(7, 44px); gap: 6px; }
    .pe-swatch { width: 44px; height: 44px; }
    .pe-color-trigger { font-size: 16px; min-height: 48px; }
    /* Right-aligned would hang off a narrow sheet; on a phone the field is
       full-width anyway, so the panel simply matches it. */
    .pe-color-panel { left: 0; right: auto; }
}

[data-theme="dark"] .pe-color-panel,
[data-theme="dark"] .pe-color-trigger { background: var(--pe-card, #1f2937); }
[data-theme="dark"] .pe-swatch { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18); }
[data-theme="dark"] .pe-swatch.is-on { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18), 0 0 0 2px var(--pe-card, #1f2937), 0 0 0 4px var(--pe-blue, #38bdf8); }

/* ── Buttons ──────────────────────────────────────────── */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 11px 24px;
    border: none;
    border-radius: 10px;
    font-family: inherit;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.2s ease;
    text-decoration: none;
    line-height: 1;
}

.btn-primary {
    width: 100%;
    background: linear-gradient(135deg, var(--pe-blue) 0%, var(--pe-blue-dark) 100%);
    color: white;
    padding: 13px 24px;
    font-size: 15px;
}

/* Hover feedback only on devices that can hover — on touch, a tap must
   not leave a stuck hover state (press feedback comes from :active). */
@media (hover: hover) {
    .btn-primary:hover {
        background: linear-gradient(135deg, var(--pe-blue-dark) 0%, #0284c7 100%);
        box-shadow: 0 4px 14px rgba(56, 189, 248, 0.35);
    }
}

.btn-primary:active {
    transform: translateY(0);
}

.btn-primary:disabled {
    background: #b0c4ce;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
}

.btn-secondary {
    background: var(--pe-white);
    color: var(--pe-text);
    border: 1.5px solid var(--pe-border-medium);
}

@media (hover: hover) {
    .btn-secondary:hover {
        background: var(--pe-gray-light);
        border-color: #c8cdd4;
        box-shadow: 0 1px 3px rgba(0,0,0,0.04);
    }
}

.btn-danger {
    background: #fef2f2;
    color: var(--pe-red);
    border: 1.5px solid #fecaca;
}

@media (hover: hover) {
    .btn-danger:hover {
        background: #fee2e2;
    }
}

.btn-sm {
    padding: 7px 14px;
    font-size: 13px;
}

/* ── Alerts / Messages ────────────────────────────────── */

.alert {
    padding: 12px 16px;
    border-radius: 10px;
    font-size: 13px;
    font-weight: 500;
    margin-bottom: 18px;
    display: none;
    line-height: 1.5;
}

.alert.show { display: block; }

/* Any alert with a type modifier (alert-error / alert-success / alert-warning
   / alert-info) is implicitly visible. This makes the showAlert(id, msg, type)
   helpers across pages work uniformly — they set className to "alert alert-X"
   without always adding ".show", and previously those silently stayed hidden.
   Pair-class selectors avoid showing a bare ".alert" (which is the default
   reset state). */
.alert.alert-error,
.alert.alert-success,
.alert.alert-warning,
.alert.alert-info { display: block; }

.alert-error {
    background: #fef2f2;
    color: #b91c1c;
    border: 1px solid #fecaca;
    border-left: 3px solid #ef4444;
}

.alert-success {
    background: #f0fdf4;
    color: #166534;
    border: 1px solid #bbf7d0;
    border-left: 3px solid #22c55e;
}

.alert-warning {
    background: #fffbeb;
    color: #92400e;
    border: 1px solid #fde68a;
    border-left: 3px solid #f59e0b;
}

.alert-info {
    background: var(--pe-blue-light);
    color: var(--pe-blue-dark);
    border: 1px solid #b8d7e5;
    border-left: 3px solid var(--pe-blue);
}

/* ── Text Links ───────────────────────────────────────── */

.auth-links {
    text-align: center;
    margin-top: 20px;
    font-size: 13px;
    color: var(--pe-gray);
}

.auth-links a {
    color: var(--pe-blue);
    text-decoration: none;
    font-weight: 600;
}

.auth-links a:hover {
    color: var(--pe-blue-dark);
    text-decoration: underline;
}

.text-link {
    color: var(--pe-blue);
    text-decoration: none;
    font-weight: 600;
    font-size: 13px;
    transition: color 0.2s ease;
}

.text-link:hover {
    color: var(--pe-blue-dark);
}

/* A venue that leads to driving directions. Used by the coach schedule and
   the parent Team Hub; the family calendar styles its own .fc-venue chip the
   same way. Colour and underline on hover — nothing lifts. */
.pe-venue-link {
    color: var(--pe-blue);
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

.pe-venue-link:hover {
    color: var(--pe-blue-dark);
    text-decoration: underline;
}

.pe-venue-link svg {
    width: 12px;
    height: 12px;
    flex: none;
}

/* Old header styles removed — replaced by app shell sidebar + bottom tabs */

/* ═══════════════════════════════════════════════════════════
   APP SHELL — Sidebar + Content Layout
   ═══════════════════════════════════════════════════════════ */

.app-shell {
    display: flex;
    min-height: 100vh;
    min-height: 100dvh;
}

/* ── Content Area ───────────────────────────────────────── */

.app-content {
    /* No margin-left offset any more — the sidebar is an in-flow flex sibling that
       occupies its own column, so content just takes the remaining width (flex:1). */
    flex: 1;
    min-width: 0;
    padding-top: var(--topbar-h);
    background: var(--pe-bg);
}

/* ── Sidebar ────────────────────────────────────────────── */

.app-sidebar {
    /* In normal document flow (NOT position:fixed) so that when the menu is taller
       than the viewport the WHOLE PAGE scrolls — no inner menu scrollbar. As a flex
       item in .app-shell it stretches to the shell height (>= 100vh, so the gradient
       always fills) and grows past the viewport when the menu needs it, making the
       window the single scroller. z-index keeps collapsed-mode flyouts above content. */
    position: relative;
    z-index: 20;
    width: var(--sidebar-w);
    flex-shrink: 0;
    background: linear-gradient(180deg, #1d1e33 0%, #17182a 100%);
    display: flex;
    flex-direction: column;
    transition: width var(--sidebar-ease);
    overflow-x: hidden; /* clip labels horizontally during collapse; vertical flows */
}

.app-shell.sidebar-collapsed .app-sidebar {
    width: var(--sidebar-w-collapsed);
}

[data-sidebar="collapsed"] .app-sidebar {
    width: var(--sidebar-w-collapsed);
}

/* ── Sidebar Logo ── */

.sidebar-logo {
    padding: 28px 16px 24px;
    border-bottom: 1px solid var(--sidebar-border);
    flex-shrink: 0;
}

.sidebar-logo a {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    text-decoration: none;
}

.sidebar-logo img {
    /* Trimmed "Leagues AI" wordmark (~4.17:1, padding cropped out). Fill the
       sidebar's ~208px content width so it reads as large as the old mark;
       height follows the aspect (~50px). */
    width: 100%;
    height: auto;
    transition: opacity var(--sidebar-ease);
}

.sidebar-logo-mark {
    width: 36px;
    height: 36px;
    background: linear-gradient(135deg, var(--pe-blue), var(--pe-blue-dark));
    border-radius: 8px;
    display: none;
    align-items: center;
    justify-content: center;
    color: white;
    font-weight: 700;
    font-size: 13px;
    font-family: var(--pe-font-display);
    letter-spacing: -0.5px;
    flex-shrink: 0;
}

.app-shell.sidebar-collapsed .sidebar-logo img,
[data-sidebar="collapsed"] .sidebar-logo img { display: none; }

.app-shell.sidebar-collapsed .sidebar-logo-mark,
[data-sidebar="collapsed"] .sidebar-logo-mark { display: flex; }

/* ── Sidebar Nav Items ── */

.sidebar-nav {
    /* flex:1 pushes the Collapse section to the bottom when the menu is short.
       overflow is visible (no inner scrollbar) — when the menu is taller than the
       viewport the whole page scrolls instead (sidebar is in normal flow). */
    flex: 1 0 auto;
    padding: 12px 8px;
    overflow: visible;
}

.sidebar-nav-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border-radius: 10px;
    color: var(--sidebar-text);
    text-decoration: none;
    font-family: inherit;
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.15s ease;
    white-space: nowrap;
    overflow: hidden;
    position: relative;   /* anchors the .active rounded accent nub */
    margin-bottom: 2px;
    border: none;
    background: none;
    width: 100%;
    text-align: left;
}

@media (hover: hover) {
    .sidebar-nav-item:hover {
        color: var(--sidebar-text-hover);
        background: var(--sidebar-bg-hover);
    }
}

.sidebar-nav-item.active {
    color: #eaf6ff;
    background: var(--sidebar-bg-active);
}
/* Rounded accent nub (P7) — a short floating pill instead of a full-height bar. */
.sidebar-nav-item.active::before {
    content: '';
    position: absolute;
    left: 5px;
    top: 50%;
    transform: translateY(-50%);
    width: 4px;
    height: 60%;
    border-radius: 4px;
    background: var(--pe-blue, #38bdf8);
}
.sidebar-nav-item.active .nav-icon svg {
    color: var(--pe-blue, #38bdf8);
}

.sidebar-nav-item.disabled {
    opacity: 0.35;
    pointer-events: none;
}

.sidebar-nav-item .nav-icon {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

.sidebar-nav-item .nav-icon svg {
    width: 18px;
    height: 18px;
}

.sidebar-nav-item .nav-label {
    opacity: 1;
    transition: opacity var(--sidebar-ease);
    overflow: hidden;
}

.sidebar-nav-item .nav-badge {
    margin-left: auto;
    font-size: 10px;
    font-weight: 600;
    padding: 2px 7px;
    border-radius: 10px;
    background: rgba(245,158,11,0.15);
    color: #f59e0b;
    white-space: nowrap;
    transition: opacity var(--sidebar-ease);
}

/* Collapsed sidebar: hide labels & badges */
.app-shell.sidebar-collapsed .sidebar-nav-item .nav-label,
.app-shell.sidebar-collapsed .sidebar-nav-item .nav-badge,
[data-sidebar="collapsed"] .sidebar-nav-item .nav-label,
[data-sidebar="collapsed"] .sidebar-nav-item .nav-badge {
    opacity: 0;
    width: 0;
    overflow: hidden;
}

.app-shell.sidebar-collapsed .sidebar-nav-item,
[data-sidebar="collapsed"] .sidebar-nav-item {
    justify-content: center;
    padding: 8px;
}

/* ── Sidebar Collapse Section ── */

.sidebar-collapse-section {
    padding: 8px;
    border-top: 1px solid var(--sidebar-border);
    flex-shrink: 0;
}

/* ── Sidebar Collapse Toggle ── */

.sidebar-toggle-btn {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 12px;
    border-radius: 8px;
    color: var(--sidebar-text);
    cursor: pointer;
    border: none;
    background: none;
    width: 100%;
    font-family: inherit;
    font-size: 13px;
    font-weight: 500;
    transition: all 0.15s ease;
}

.sidebar-toggle-btn:hover {
    color: var(--sidebar-text-hover);
    background: var(--sidebar-bg-hover);
}

.sidebar-toggle-btn .nav-icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

.sidebar-toggle-btn .nav-icon svg {
    width: 18px;
    height: 18px;
}

.sidebar-toggle-btn .nav-label {
    transition: opacity var(--sidebar-ease);
}

.app-shell.sidebar-collapsed .sidebar-toggle-btn,
[data-sidebar="collapsed"] .sidebar-toggle-btn {
    justify-content: center;
}

.app-shell.sidebar-collapsed .sidebar-toggle-btn .nav-label,
[data-sidebar="collapsed"] .sidebar-toggle-btn .nav-label {
    opacity: 0;
    width: 0;
    overflow: hidden;
}

/* ── Sidebar League Groups (league_admin) ─────────────────── */

.sidebar-league-divider {
    height: 1px;
    background: var(--sidebar-border);
    margin: 8px 12px;
}

.sidebar-league-group {
    margin-bottom: 2px;
}

.sidebar-league-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border-radius: 8px;
    color: var(--sidebar-text-hover);
    cursor: pointer;
    border: none;
    background: none;
    width: 100%;
    font-family: inherit;
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    transition: all 0.15s ease;
    white-space: nowrap;
    overflow: hidden;
    text-align: left;
}

.sidebar-league-header:hover {
    background: var(--sidebar-bg-hover);
}

.league-icon {
    width: 24px;
    height: 24px;
    border-radius: 6px;
    background: rgba(56, 189, 248, 0.15);
    color: var(--sidebar-text-active);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 700;
    flex-shrink: 0;
    font-family: var(--pe-font-display);
}
/* The Platform section reuses .league-icon but with an SVG (not an initial). */
.league-icon svg { width: 15px; height: 15px; }

.league-name {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
}

.league-chevron {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    transition: transform 0.2s ease;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-left: auto;
}

.league-chevron svg {
    width: 14px;
    height: 14px;
}

/* Chevron rotates when group is collapsed */
.sidebar-league-group.collapsed .league-chevron {
    transform: rotate(-90deg);
}

/* Sub-items container — animated expand/collapse */
.sidebar-league-items {
    overflow: hidden;
    /* Cap must clear a full league's sub-pages (13 items ≈ 585px and growing)
       or overflow:hidden clips the last ones (Reports/Campaigns) off the group.
       Kept finite so the collapse max-height transition still animates. */
    max-height: 900px;
    transition: max-height 0.25s ease, opacity 0.2s ease;
    opacity: 1;
}

.sidebar-league-group.collapsed .sidebar-league-items {
    max-height: 0;
    opacity: 0;
}

/* Sub-item indent */
.sidebar-sub-item {
    padding-left: 28px !important;
}

/* Grouped-nav section headers (sidebar + mobile League sheet). */
.sidebar-nav-group-label {
    padding: 12px 14px 5px 28px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .11em;
    text-transform: uppercase;
    color: #6d7789;
    user-select: none;
    pointer-events: none;
}
.app-shell.sidebar-collapsed .sidebar-nav-group-label { display: none; }
.league-sheet-group-label {
    padding: 13px 12px 4px;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--pe-text-secondary, #64748b);
    user-select: none;
}

/* ── Collapsed sidebar (68px): league groups ── */

.app-shell.sidebar-collapsed .sidebar-league-divider,
[data-sidebar="collapsed"] .sidebar-league-divider {
    margin: 8px 4px;
}

.app-shell.sidebar-collapsed .sidebar-league-header,
[data-sidebar="collapsed"] .sidebar-league-header {
    justify-content: center;
    padding: 10px;
}

.app-shell.sidebar-collapsed .league-name,
.app-shell.sidebar-collapsed .league-chevron,
[data-sidebar="collapsed"] .league-name,
[data-sidebar="collapsed"] .league-chevron {
    opacity: 0;
    width: 0;
    pointer-events: none;
    visibility: hidden;
}

.app-shell.sidebar-collapsed .sidebar-league-items,
[data-sidebar="collapsed"] .sidebar-league-items {
    max-height: 0;
    opacity: 0;
    overflow: hidden;
}

/* ═══════════════════════════════════════════════════════════
   BOTTOM TAB BAR — Mobile (<600px)
   ═══════════════════════════════════════════════════════════ */

.app-bottom-tabs {
    display: none;
}

.tab-item {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    text-decoration: none;
    color: var(--tab-text);
    font-size: 11px;
    font-weight: 500;
    transition: color 0.15s ease;
    -webkit-tap-highlight-color: transparent;
    padding: 6px 0;
}

.tab-item.active {
    color: var(--tab-text-active);
}

.tab-item .tab-icon {
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.tab-item .tab-icon svg {
    width: 24px;
    height: 24px;
}

.tab-item .tab-label {
    line-height: 1;
    /* A league admin who also coaches gets SIX tabs, and at ~400px "My
       Dashboard" and "Coach's Corner" wrapped to two lines — knocking those
       two items out of alignment with the rest of the bar. Labels are short
       forms (see SHORT_TAB_LABELS in nav.js) and never wrap. */
    white-space: nowrap;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Six tabs is the practical ceiling — tighten spacing so they still breathe. */
.app-bottom-tabs:has(> :nth-child(6)) .tab-item {
    font-size: 10px;
    gap: 2px;
}

/* ═══════════════════════════════════════════════════════════
   APP SHELL — Responsive Overrides
   ═══════════════════════════════════════════════════════════ */

/* Tablet (600–1023px) used to force an always-collapsed 68px icon rail with
   the expand toggle hidden and `.sidebar-league-items { max-height: 0 }` — so
   every label was invisible AND the league admin pages (Divisions/Seasons/
   Teams/…) were unreachable. Tablets now use the MOBILE NAV instead (sidebar
   hidden, bottom tab bar + League sheet) — see the <=1023 block below.
   Content layouts keep their own <=599 breakpoints so a tablet still gets
   real tables and multi-column grids. */

/* NAV — tablet + mobile (<=1023px): hide the sidebar, show the bottom tab bar.
   The bottom tabs carry labels and the League/Admin sheets expose the sub-pages
   (Divisions/Seasons/Teams/…), which the old tablet icon-rail could not reach.
   This is NAV ONLY — content layouts keep their own <=599 breakpoints, so a
   tablet still gets full-width tables and multi-column grids. */
@media (max-width: 1023px) {
    .app-sidebar { display: none; }
    /* (.topbar-logo is revealed in the Topbar Responsive section below — it has
       to come AFTER the base `.topbar-logo{display:none}` rule to win.) */
    .app-content {
        margin-left: 0 !important;
        /* Clear the bottom tab bar. The old +64px allowance was there because
           the Ask Phil launcher floated over this corner; it now lives in the
           topbar on mobile, so the last card no longer needs room beneath it. */
        padding-bottom: calc(var(--tab-h) + env(safe-area-inset-bottom, 0) + 16px);
    }

    .app-bottom-tabs {
        display: flex;
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        /* The inset is ADDED to the bar, not taken out of it. With the global
           box-sizing:border-box (line 159) a flat `height: var(--tab-h)` counts
           padding-bottom INSIDE the 64px, so on a phone with gesture navigation
           the safe-area inset ate the content box: 64 − 24 = 40px, or 64 − 48 =
           16px. A tab needs 38px (icon 24 + gap 3 + label 11, measured), and the
           label sits under the icon, so the label was the first thing clipped —
           icons present, text gone. Robb saw exactly that on a Pixel 10 while the
           emulator looked fine, because env(safe-area-inset-bottom) is 0 there
           (2026-09-14).
           .app-content above and line ~6211 already allow for
           tab-h PLUS the inset, so the rest of the layout already assumed the bar
           was this tall; the bar was the one place that did not. */
        height: calc(var(--tab-h) + env(safe-area-inset-bottom, 0px));
        background: var(--tab-bg);
        border-top: 1px solid var(--tab-border);
        box-shadow: 0 -2px 10px rgba(0,0,0,0.06);
        z-index: 150;
        padding-bottom: env(safe-area-inset-bottom, 0px);
        transition: transform 0.2s ease;
    }

    /* While the on-screen keyboard is up (a field is focused), slide the fixed
       tab bar out of the way so it never sits on top of the keyboard / the
       focused field. `kb-open` is toggled in nav.js. */
    body.kb-open .app-bottom-tabs {
        transform: translateY(100%);
        pointer-events: none;
    }
}

/* Mobile only (<600px) — page-level chrome, not core nav. */
@media (max-width: 599px) {
    /* /admin.html mobile: the bottom tabs ARE shown (Admin tab opens a
       bottom sheet listing the 5 admin sub-pages — same pattern as the
       League sheet for league_admin role). The top horizontal tab strip
       is hidden on mobile since the sheet handles sub-page navigation.
       Also hide the .page-title-bar wrapper outright — the topbar's
       title slot carries "Platform Admin · {sub-tab}" instead. */
    body.is-admin-page .profile-tabs {
        display: none !important;
    }
    body.is-admin-page .page-title-bar {
        display: none !important;
    }
}

/* ── League Bottom Sheet ─────────────────────────────────── */
.league-sheet-overlay {
    position: fixed;
    inset: 0;
    z-index: 200;
    background: rgba(0, 0, 0, 0);
    display: flex;
    align-items: flex-end;
    justify-content: center;
    transition: background 0.2s ease;
    pointer-events: none;
}
.league-sheet-overlay.show {
    background: rgba(0, 0, 0, 0.45);
    pointer-events: auto;
}
.league-sheet {
    width: 100%;
    max-width: 420px;
    background: var(--pe-card, #fff);
    border-radius: 16px 16px 0 0;
    padding: 0 0 env(safe-area-inset-bottom, 12px);
    box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.15);
    transform: translateY(100%);
    transition: transform 0.25s cubic-bezier(0.32, 0.72, 0, 1);
    /* The sheet is pinned to the bottom and grows UPWARD, so without a cap it
       runs off the top of a short screen — and since the item list didn't
       scroll, the header (close button included) and the first rows became
       unreachable. At 13 items it stood 766px tall against a 667px iPhone SE.
       Cap it, and let the list scroll instead. dvh first, vh as the fallback
       for browsers without it. */
    max-height: calc(100vh - 28px);
    max-height: calc(100dvh - 28px);
    display: flex;
    flex-direction: column;
}
.league-sheet-header { flex: 0 0 auto; }
.league-sheet-overlay.show .league-sheet {
    transform: translateY(0);
}
.league-sheet-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 18px 20px 14px;
    border-bottom: 1px solid var(--pe-border);
}
.league-sheet-title {
    font-size: 16px;
    font-weight: 700;
    color: var(--pe-text);
    margin: 0;
}
.league-sheet-close {
    width: 32px;
    height: 32px;
    border: none;
    background: var(--pe-hover, #f1f5f9);
    border-radius: 8px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--pe-text-muted);
    padding: 0;
    transition: background 0.15s;
}
.league-sheet-close svg {
    width: 18px;
    height: 18px;
}
.league-sheet-close:hover {
    background: var(--pe-border);
    color: var(--pe-text);
}
.league-sheet-items {
    padding: 8px 12px;
    /* min-height:0 or this flex child refuses to shrink below its content and
       the max-height above does nothing (the usual can't-shrink-below-content
       trap). overflow-y only — never overflow-x/overflow:auto here, which on
       iOS turns the whole sheet into a dead-scroll trap. */
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
}
.league-sheet-item {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 12px;
    border-radius: 10px;
    color: var(--pe-text);
    text-decoration: none;
    transition: background 0.15s;
    cursor: pointer;
    min-height: 48px;
}
.league-sheet-item:hover {
    background: var(--pe-hover, #f1f5f9);
}
.league-sheet-item.active {
    background: rgba(56, 189, 248, 0.08);
    color: var(--pe-blue);
}
.league-sheet-item.disabled {
    cursor: default;
    opacity: 0.45;
}
.league-sheet-item.disabled:hover {
    background: transparent;
}
.league-sheet-item-icon {
    width: 22px;
    height: 22px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}
.league-sheet-item-icon svg {
    width: 22px;
    height: 22px;
}
.league-sheet-item-label {
    font-size: 15px;
    font-weight: 500;
    flex: 1;
}
.league-sheet-item-badge {
    font-size: 11px;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: 6px;
    background: var(--pe-hover, #f1f5f9);
    color: var(--pe-text-muted);
    letter-spacing: 0.3px;
}
.league-sheet-divider {
    height: 1px;
    background: var(--pe-border);
    margin: 4px 12px;
}

/* ── League Sheet Dark Mode ─── */
[data-theme="dark"] .league-sheet {
    background: var(--pe-card, #1e293b);
    box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.4);
}
[data-theme="dark"] .league-sheet-header {
    border-color: var(--pe-border);
}
[data-theme="dark"] .league-sheet-close {
    background: rgba(255, 255, 255, 0.06);
}
[data-theme="dark"] .league-sheet-close:hover {
    background: rgba(255, 255, 255, 0.1);
}
[data-theme="dark"] .league-sheet-item:hover {
    background: rgba(255, 255, 255, 0.06);
}
[data-theme="dark"] .league-sheet-item.active {
    background: rgba(56, 189, 248, 0.12);
}
[data-theme="dark"] .league-sheet-item-badge {
    background: rgba(255, 255, 255, 0.08);
}
[data-theme="dark"] .league-sheet-divider {
    background: var(--pe-border);
}

/* ═══════════════════════════════════════════════════════════ */

.app-main {
    max-width: 1100px;
    margin: 0 auto;
    padding: 28px 24px 40px;
}
/* Data-heavy pages (tables, dashboards) opt into a wider canvas so the content
   uses the screen instead of leaving big side gutters. Forms/reading pages keep
   the 1100px default for comfortable line length. */
.app-main.app-main-wide { max-width: 1500px; }

@media (max-width: 599px) {
    .app-main { padding: 16px 16px 32px; }
}

/* ═══════════════════════════════════════════════════════════
   TOP BAR + USER DROPDOWN
   ═══════════════════════════════════════════════════════════ */

.app-topbar {
    position: fixed;
    top: 0;
    right: 0;
    left: var(--sidebar-w);
    height: var(--topbar-h);
    /* Solid at every width (Robb, 2026-07-21). This bar is position:fixed and
       page content scrolls UNDER it, so a transparent bar let content show
       through around the Phil button / user chip / logo. --pe-white is
       theme-aware, so dark mode gets its own surface. */
    background: var(--pe-white);
    border-bottom: 1px solid var(--pe-border);
    box-shadow: var(--pe-shadow-lg);
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 0 24px;
    z-index: 140;
    pointer-events: none;
    transition: left var(--sidebar-ease);
}

.app-shell.sidebar-collapsed .app-topbar,
[data-sidebar="collapsed"] .app-topbar {
    left: var(--sidebar-w-collapsed);
}

/* ── Topbar Left ──────────────────────────────────── */

.topbar-left {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

/* "Leagues by ProElite" wordmark in the topbar — used on tablet, where the
   sidebar that normally carries it is hidden. NOTE: logo.png is the
   WHITE/reverse mark built for the dark sidebar and would be invisible on the
   light topbar, so this uses logo-dark.png (dark ink, for light backgrounds),
   trimmed to its content box — the original has ~40% transparent padding that
   would otherwise render the wordmark a few pixels tall. */
.topbar-logo {
    display: none;
    align-items: center;
    flex-shrink: 0;
    text-decoration: none;
    /* .app-topbar is pointer-events:none so the page behind stays clickable;
       interactive children have to opt back in (as .topbar-right does). */
    pointer-events: auto;
}
.topbar-logo img {
    display: block;
    height: 26px;   /* trimmed mark (~4.17:1) ≈ 108px wide in the 56px topbar */
    width: auto;
}

.topbar-title {
    font-family: var(--pe-font-display);
    font-size: 20px;
    font-weight: 700;
    color: var(--pe-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin: 0;
}

/* ── Topbar Right — User Button ───────────────────── */

.topbar-right {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-shrink: 0;
    position: relative;
    pointer-events: auto;
}

.topbar-user-btn {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 5px 12px 5px 5px;
    border-radius: 12px;
    border: 1px solid var(--pe-border);
    /* Solid (not transparent): the topbar floats over the page, so a
       transparent button lets scrolling content bleed through its text —
       worst on smaller screens where a table sits right behind it. A solid
       surface + subtle shadow reads as a clean floating chip and hides
       whatever passes behind it. */
    background: var(--pe-white, #fff);
    box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);
    cursor: pointer;
    transition: all 0.15s ease;
    font-family: inherit;
}

@media (hover: hover) {
    .topbar-user-btn:hover {
        background: var(--pe-gray-light);
        border-color: var(--pe-border-medium);
    }
}

.topbar-user-btn .avatar {
    width: 34px;
    height: 34px;
    background: linear-gradient(135deg, var(--pe-blue), var(--pe-blue-dark));
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: white;
    font-weight: 600;
    font-size: 12px;
    flex-shrink: 0;
}

.topbar-user-name {
    font-size: 14px;
    font-weight: 500;
    color: var(--pe-text);
    max-width: 180px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: inline-block;
    vertical-align: middle;
}

.topbar-chevron {
    display: flex;
    color: var(--pe-text-tertiary);
    transition: transform 0.2s ease;
}

.topbar-chevron svg {
    width: 16px;
    height: 16px;
}

.topbar-user-btn.open .topbar-chevron {
    transform: rotate(180deg);
}

/* ── Dropdown Menu ────────────────────────────────── */

.topbar-dropdown {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    min-width: 220px;
    background: var(--pe-white);
    border: 1px solid var(--pe-border);
    border-radius: 12px;
    box-shadow: var(--pe-shadow-lg);
    padding: 6px;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px);
    transition: all 0.15s ease;
    z-index: 200;
}

.topbar-dropdown.open {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

.topbar-dropdown-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border-radius: 8px;
    color: var(--pe-text);
    text-decoration: none;
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.15s ease;
    border: none;
    background: none;
    width: 100%;
    font-family: inherit;
}

.topbar-dropdown-item:hover {
    background: var(--pe-gray-light);
}

.dropdown-icon {
    width: 18px;
    height: 18px;
    color: var(--pe-text-secondary);
    flex-shrink: 0;
    display: flex;
}

.dropdown-icon svg {
    width: 18px;
    height: 18px;
}

.dropdown-label {
    flex: 1;
    text-align: left;
}

.topbar-dropdown-divider {
    height: 1px;
    background: var(--pe-border);
    margin: 4px 0;
}

/* Sign Out — red on hover */
.topbar-dropdown-item.signout:hover {
    color: var(--pe-red);
    background: var(--pe-red-light);
}

.topbar-dropdown-item.signout:hover .dropdown-icon {
    color: var(--pe-red);
}

/* ── Dark Mode Toggle Row ─────────────────────────── */

.topbar-dropdown-toggle {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border-radius: 8px;
    font-size: 14px;
    font-weight: 500;
    color: var(--pe-text);
    cursor: pointer;
    transition: background 0.15s ease;
    width: 100%;
}

.topbar-dropdown-toggle:hover {
    background: var(--pe-gray-light);
}

/* CSS Toggle Switch */
.toggle-switch {
    position: relative;
    width: 40px;
    height: 22px;
    background: #d1d5db;
    border-radius: 11px;
    margin-left: auto;
    transition: background 0.2s ease;
    flex-shrink: 0;
}

.toggle-switch::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 18px;
    height: 18px;
    background: white;
    border-radius: 50%;
    transition: transform 0.2s ease;
    box-shadow: 0 1px 3px rgba(0,0,0,0.15);
}

.toggle-switch.active {
    background: var(--pe-blue);
}

.toggle-switch.active::after {
    transform: translateX(18px);
}

/* ── Topbar Responsive ────────────────────────────── */

@media (min-width: 600px) and (max-width: 1023px) {
    /* Sidebar is hidden on tablet — the topbar spans the full width, and
       space-between puts the brand mark on the LEFT with the Phil/user
       controls on the right.

       (The opaque surface now lives on the base .app-topbar rule — it applies
       at every width. Still NOT reusing the whole <=599 block: that one also
       hides .page-title-bar, which would take tablet's action buttons.) */
    .app-topbar {
        left: 0;
        justify-content: space-between;
    }
    /* Match mobile: the title/brand side opts back into pointer events
       (.app-topbar is pointer-events:none so the page stays clickable). */
    .topbar-left { pointer-events: auto; }
}

/* Tablet only: the sidebar (which carries the wordmark) is hidden here and
   there's room for the full logo. Deliberately NOT on phones — at 375px the
   ~119px wordmark would squeeze the page title, which shares this slot.
   Must sit AFTER the base `.topbar-logo{display:none}` above — same
   specificity, so source order decides. */
@media (min-width: 600px) and (max-width: 1023px) {
    .topbar-logo { display: flex; }
}

@media (max-width: 599px) {
    .app-topbar {
        left: 0;
        padding: 0 16px;
    }
    .topbar-user-name { display: none; }
}

/* ── Page Actions Bar (replaces header-zone buttons) ── */

/* ── Page Title Bar (injected by nav.js) ──────────── */

/* Mobile-only — on desktop the title bar already carries these actions.
   Turned on by the ≤599px block further down. */
.mobile-action-row { display: none; }

.page-title-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 20px;
    gap: 16px;
}

.page-title-bar-text {
    font-size: 26px;
    font-weight: 800;
    letter-spacing: -0.5px;
    color: var(--pe-text);
    margin: 0;
}

@media (max-width: 599px) {
    .page-title-bar-text {
        font-size: 22px;
    }
}

.page-actions-bar {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 16px;
}

/* ── Dashboard Cards ──────────────────────────────────── */

.page-title {
    font-size: 28px;
    font-weight: 800;
    margin-bottom: 6px;
    letter-spacing: -0.5px;
}

.page-subtitle {
    color: var(--pe-gray);
    font-size: 15px;
    margin-bottom: 24px;
}

.page-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}

.page-header .page-subtitle {
    margin-bottom: 0;
}

/* ── Stats Grid ─────────────────────────────────────── */

.stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 16px;
    margin-bottom: 28px;
    /* Every card the same height. Grid rows size independently, so a label
       that wraps ("Upcoming Events" takes three lines at ~400px) made its
       whole row 19px taller than the row above it. */
    grid-auto-rows: 1fr;
}

.stat-card {
    background: var(--pe-white);
    border-radius: 14px;
    padding: 22px 24px;
    box-shadow: var(--pe-shadow);
    border: 1px solid var(--pe-border);
    border-left: 4px solid var(--stat-accent, var(--pe-border));
    display: flex;
    align-items: flex-start;
    gap: 16px;
    transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.3s ease;
}
.stat-card[data-accent="blue"]   { --stat-accent: #38bdf8; }
.stat-card[data-accent="green"]  { --stat-accent: #22c55e; }
.stat-card[data-accent="orange"] { --stat-accent: #f59e0b; }
.stat-card[data-accent="red"]    { --stat-accent: #ef4444; }
.stat-card[data-accent="purple"] { --stat-accent: #8b5cf6; }
.stat-card[data-accent="teal"]   { --stat-accent: #14b8a6; }   /* My Registrations tile, 2026-10-19 */
.stat-icon-teal { background: linear-gradient(135deg, #ccfbf1 0%, #99f6e4 100%); color: #0f766e; }
[data-theme="dark"] .stat-icon-teal { background: rgba(20, 184, 166, 0.15); color: #5eead4; }
/* The parent dashboard's four tiles (Players · My Registrations · Team Hub ·
   Registration Near You) — "option 4", Robb's pick of four renders on
   2026-10-19: a stacked tile with the accent on TOP, icon above the label, the
   number large, and the sub-line allowed to WRAP. The sub-line carries the
   useful part (which kids, "2 need payment", the program that is closing) and
   this is the only layout where it reads in full instead of truncating. One
   row of four from 900px; the 2-up phone block further down already stacks
   each tile vertically, so it inherits this shape without a second rule. */
body.is-dashboard-page .stats-grid .stat-card {
    flex-direction: column; align-items: flex-start; gap: 16px;
    padding: 24px 26px 22px;
    border-left: 1px solid var(--pe-border);
    border-top: 4px solid var(--stat-accent, var(--pe-border));
}
body.is-dashboard-page .stats-grid .stat-card .stat-icon { width: 40px; height: 40px; border-radius: 12px; }
body.is-dashboard-page .stats-grid .stat-card .stat-icon svg { width: 20px; height: 20px; }
body.is-dashboard-page .stats-grid .stat-card .stat-label { margin-bottom: 6px; }
body.is-dashboard-page .stats-grid .stat-card .stat-value { font-size: 34px; line-height: 1.05; }
/* Air around the line under the number (Robb, 2026-10-19: "better white
   spacing… notice how crammed it looks"): it wraps, it breathes, the chips sit
   on a 6px grid capped at four + "+N more", and a second program on the
   Near You tile is a count on its own line rather than another bold name. */
body.is-dashboard-page .stats-grid .stat-card .stat-sub { white-space: normal; margin-top: 10px; line-height: 1.6; }
.stat-chips { display: flex; flex-wrap: wrap; gap: 6px 6px; }
.stat-chip-more { align-self: center; font-size: 12px; font-weight: 600; color: var(--pe-text-secondary, #64748b); padding-left: 2px; }
.stat-sub-more { display: block; margin-top: 4px; color: var(--pe-text-secondary, #64748b); }
@media (min-width: 900px) { body.is-dashboard-page .stats-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

@media (hover: hover) {
    .stat-card:hover {
        box-shadow: var(--pe-shadow-md);
    }
}

/* Dimmed "coming soon" stat cards */
.stat-card.stat-coming-soon {
    opacity: 0.5;
    border-left-color: var(--pe-border);
}
.stat-card.stat-coming-soon:hover {
    transform: none;
    box-shadow: var(--pe-shadow);
}

.stat-card .stat-icon {
    width: 48px;
    height: 48px;
    border-radius: 14px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.stat-card .stat-icon svg {
    width: 24px;
    height: 24px;
}

.stat-card .stat-body {
    flex: 1;
    min-width: 0;
}

.stat-card .stat-label {
    font-size: 12px;
    font-weight: 600;
    color: var(--pe-text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    margin-bottom: 4px;
}

.stat-card .stat-value {
    font-family: var(--pe-font-display);
    font-size: 30px;
    font-weight: 800;
    color: var(--pe-text);
    line-height: 1.1;
    letter-spacing: -0.5px;
}

.stat-icon-blue {
    background: linear-gradient(135deg, #e0f2fe 0%, #bae6fd 100%);
    color: var(--pe-blue-dark);
}
.stat-icon-green {
    background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%);
    color: #15803d;
}
.stat-icon-orange {
    background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
    color: #b45309;
}
.stat-icon-red {
    background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
    color: #b91c1c;
}
.stat-icon-purple {
    background: linear-gradient(135deg, #ede9fe 0%, #ddd6fe 100%);
    color: #6d28d9;
}

/* ── Dashboard Grid ─────────────────────────────────── */

.dashboard-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 20px;
}

/* Dashboard "My Registrations" grid — controls + status chips + mobile cards.
   Filter chips on the left, search + per-page on the right (Events layout). */
.reg-grid-controls { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 6px 0 16px; }
.reg-grid-filters { display: flex; gap: 6px; flex-wrap: wrap; }
.reg-grid-right { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.reg-chip { padding: 6px 12px; border-radius: 999px; border: 1px solid var(--pe-border); background: var(--pe-white); color: var(--pe-text-secondary); font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; transition: background .15s, border-color .15s, color .15s; }
.reg-chip:hover { border-color: #94a3b8; }
.reg-chip.active { background: #171a2b; border-color: #171a2b; color: #fff; }
[data-theme="dark"] .reg-chip { background: var(--pe-card-bg, var(--pe-white)); border-color: var(--pe-border); }
[data-theme="dark"] .reg-chip.active { background: var(--pe-blue); border-color: var(--pe-blue); color: #0c1017; }
.reg-m-card { padding: 14px 16px; border: 1px solid var(--pe-border); border-radius: 12px; margin-bottom: 10px; cursor: pointer; background: var(--pe-white); }
.reg-m-card:active { background: var(--pe-hover, #f8fafc); }
.reg-m-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.reg-m-name { font-weight: 700; }
.reg-m-prog { color: var(--pe-text-secondary); font-size: 13px; margin-top: 4px; }
/* League · Type under the program name on the stacked card — the two columns
   the table shows beside Program, which have nowhere else to go at phone width. */
/* text-secondary, NOT --pe-text-muted — that variable is never defined and
   falls back to initial black, which is what .reg-m-foot below still does. */
.reg-m-sub { color: var(--pe-text-secondary, #64748b); font-size: 12px; margin-top: 2px; }
.reg-m-foot { display: flex; justify-content: space-between; color: var(--pe-text-muted); font-size: 12px; margin-top: 10px; }
[data-theme="dark"] .reg-m-card { background: var(--pe-card-bg, var(--pe-white)); border-color: var(--pe-border); }
@media (max-width: 599px) { .reg-grid-right { width: 100%; } .reg-grid-right .pe-fb-search { flex: 1; } }
/* Money state in the Amount column (2026-09-15; shared since 2026-10-19 — the
   cell is rendered by reg-grid.js on BOTH the dashboard and my-registrations).
   The Payments card was removed for repeating this grid, so red here is the
   ONLY thing telling a family something is unpaid — Status shows the
   registration's state, not the money's. */
.reg-owed { font-weight: 700; color: #b91c1c; }
[data-theme="dark"] .reg-owed { color: #fca5a5; }
.reg-money { font-weight: 600; color: var(--pe-text); }
.reg-money-note { color: var(--pe-text-secondary, #64748b); font-size: 12.5px; }
/* A <button> in a cell written for text: state everything the UA supplies,
   or it arrives bevelled and in the wrong typeface (the .pay-btn lesson). */
.reg-pay-btn { appearance: none; -webkit-appearance: none; border: 0; cursor: pointer;
    margin-left: 8px; padding: 4px 10px; border-radius: 7px; background: var(--pe-blue, #38bdf8);
    color: #0b1220; font-family: inherit; font-size: 12px; font-weight: 800; line-height: 1.5; }
.reg-pay-btn:hover { background: var(--pe-blue-dark, #0284c7); color: #fff; }
.reg-pay-btn:disabled { opacity: .6; cursor: default; }
.reg-receipt { margin-left: 8px; font-size: 12px; font-weight: 600; color: var(--pe-blue-dark, #0284c7); text-decoration: none; }
.reg-receipt:hover { text-decoration: underline; }
[data-theme="dark"] .reg-receipt { color: var(--pe-blue, #38bdf8); }
@media (max-width: 599px) {
    /* On a card the money line is the last row — give the button a thumb.
       44px is the documented phone hit area (CLAUDE.md §mobile). */
    .reg-pay-btn { padding: 10px 16px; font-size: 13px; min-height: 44px; }
}
/* A plain action button in a filter bar (Export CSV on my-registrations) —
   the Group By button's look, without the menu. financials.html has its own
   page-local copy of this from before it existed (.fin-export-btn). */
.pe-fb-btn {
    display: inline-flex; align-items: center; gap: 6px;
    height: 38px; padding: 0 14px; border: 1px solid var(--pe-border);
    border-radius: 8px; background: var(--pe-card, #fff);
    color: var(--pe-text); font-size: 14px; font-weight: 500; font-family: inherit;
    cursor: pointer; white-space: nowrap; transition: border-color .15s, background .15s;
}
.pe-fb-btn:hover { border-color: var(--pe-blue, #38bdf8); }
.pe-fb-btn svg { width: 16px; height: 16px; }
@media (max-width: 599px) { .pe-fb-panel > .pe-fb-btn { height: 44px; justify-content: flex-start; padding: 0 12px; } }

.up-row { display: flex; align-items: center; gap: 14px; padding: 12px 4px; border-bottom: 1px solid var(--pe-border); text-decoration: none; color: inherit; }
.up-row:last-child { border-bottom: 0; }
.up-row:hover { background: var(--pe-hover, #f8fafc); border-radius: 10px; }
.up-date { flex: 0 0 96px; display: flex; flex-direction: column; }
.up-date-day { font-weight: 700; font-size: 13.5px; color: var(--pe-text); }
.up-date-time { font-size: 12px; color: var(--pe-text-muted); margin-top: 2px; }
.up-body { flex: 1; min-width: 0; }
.up-what { font-weight: 600; font-size: 14px; color: var(--pe-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.up-where { font-size: 12.5px; color: var(--pe-text-muted); margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.up-empty { color: var(--pe-text-muted); padding: 18px 4px; margin: 0; font-size: 14px; }
.tw-chip { --c: #38bdf8; display: inline-flex; align-items: center; gap: 6px; flex: none; font-size: 12px; font-weight: 700;
    color: color-mix(in srgb, var(--c) 70%, #111827); background: color-mix(in srgb, var(--c) 16%, var(--pe-white)); border-radius: 999px; padding: 3px 10px 3px 6px; max-width: 160px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tw-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--c); flex: none; }
/* ── GenProgress (2026-09-10): the "an image is being made" state every maker
   shares (assets/js/gen-progress.js) — a halftone field that fills in, a plain
   label, a percentage pill. No spinner, no clock. ── */
.gp { position: relative; display: block; width: 100%; min-height: 200px; box-sizing: border-box; padding: 34px 10px 14px; background: var(--pe-white, #fff); border-radius: 12px; overflow: hidden; }
.gp-label { position: absolute; left: 14px; top: 10px; font-size: 13.5px; font-weight: 600; color: var(--pe-text-secondary, #64748b); letter-spacing: .01em; }
.gp-canvas { display: block; width: 100%; height: 100%; min-height: 150px; }
.gp-pct { position: absolute; right: 12px; bottom: 10px; font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; color: #0369a1; background: var(--pe-white, #fff); border: 1px solid var(--pe-border, #e5e7eb); border-radius: 999px; padding: 3px 10px; box-shadow: 0 1px 2px rgba(17,22,36,.06); transition: background .2s, color .2s; }
.gp-pct.is-done { background: #16a34a; border-color: #16a34a; color: #fff; }
/* Inside a preview box the field takes the box's shape and background. */
.tn-im-prev.loading > .gp, .eb-mk-prev > .gp, .gp-host > .gp { height: 100%; min-height: 0; border-radius: inherit; }
.gp-host { display: block; width: 100%; aspect-ratio: 16 / 9; min-height: 200px; border: 1px solid var(--pe-border, #e5e7eb); border-radius: 12px; overflow: hidden; background: var(--pe-white, #fff); margin-bottom: 16px; }
/* A short, wide variant for a progress field that sits BESIDE the thing being
   worked on rather than in place of it — the AI Images refine box, where the
   image being redrawn stays on screen above it. The 16/9 host would add ~216px
   inside a panel that is already a label, a textarea, a hint and a button.
   .gp-canvas carries min-height:150px for the standalone case, so it has to be
   released here or a 76px host with overflow:hidden simply clips it. */
.gp-host--slim { aspect-ratio: auto; height: 76px; min-height: 0; margin: 10px 0 0; }
.gp-host--slim.gp { padding: 24px 10px 10px; }   /* GenProgress adds .gp to the HOST, not a child */
.gp-host--slim .gp-canvas { min-height: 0; }
[data-theme="dark"] .gp, [data-theme="dark"] .gp-host { background: var(--pe-card-bg, #1f2937); }
[data-theme="dark"] .gp-pct { background: var(--pe-card-bg, #1f2937); color: #7fd3ff; border-color: rgba(255,255,255,.12); }
[data-theme="dark"] .gp-label { color: #9fb2d8; }
/* The "Next up" tile: a short day+time in place of a count, with a one-line sub. */
.stat-card .stat-value-next { font-size: 20px; line-height: 1.2; }
.stat-card .stat-sub { font-size: 12.5px; color: var(--pe-text-secondary); margin-top: 4px; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The second line every tile now carries (2026-09-10). */
.stat-card .stat-sub b { color: var(--pe-text); font-weight: 600; }
.stat-sub-new { color: #0369a1; font-weight: 700; }
.stat-sub-act { color: var(--pe-blue); font-weight: 700; }
.stat-card .stat-chip { font-size: 11.5px; padding: 2px 8px 2px 6px; margin-right: 4px; max-width: none; vertical-align: middle; }
[data-theme="dark"] .stat-sub-new { color: #7fd3ff; }
[data-theme="dark"] .up-row:hover { background: var(--pe-bg); }
@media (max-width: 599px) {
    .up-date { flex-basis: 74px; }
}

/* ── Dashboard smart top (IF/THEN): In-Process nudge · today's agenda · onboard ── */
#smartTop { display: flex; flex-direction: column; gap: 12px; margin-bottom: 20px; }

/* In-Process nudge (amber) */
.st-nudge { display: flex; align-items: center; gap: 14px; padding: 13px 16px; border-radius: 14px; text-decoration: none;
    background: linear-gradient(120deg, #fef3c7, #fde4a8); border: 1px solid #f5cd74; color: #7a4e0a; transition: border-color .15s; }
.st-nudge:hover { border-color: #eab54a; }
.st-nudge-ic { width: 34px; height: 34px; border-radius: 10px; background: #f7c948; color: #6b4306; display: grid; place-items: center; flex: none; }
.st-nudge-ic svg { width: 18px; height: 18px; }
.st-nudge-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }
.st-nudge-lead b { font-weight: 700; }
.st-nudge-more { font-size: 11px; font-weight: 700; background: #f7c948; color: #6b4306; padding: 1px 7px; border-radius: 999px; margin-left: 8px; }
.st-nudge-sub { font-size: 12.5px; color: #9a6a1e; }
.st-nudge-cta { flex: none; font-weight: 700; font-size: 13px; white-space: nowrap; }
[data-theme="dark"] .st-nudge { background: linear-gradient(120deg, #382a0d, #2a2008); border-color: #574119; color: #f0c674; }
[data-theme="dark"] .st-nudge-more, [data-theme="dark"] .st-nudge-ic { background: #7a5a1c; color: #ffe9b0; }
[data-theme="dark"] .st-nudge-sub { color: #d3ad63; }

/* Get-started nudge (neutral) — the same slim shape as the amber in-process
   nudge, but calm: used for "add a player" / "browse programs", where nothing
   is actually pending. Amber stays reserved for a genuine unfinished action. */
.st-nudge.st-nudge-info { background: var(--pe-white); border-color: var(--pe-border); color: var(--pe-text); box-shadow: var(--pe-shadow); }
.st-nudge.st-nudge-info:hover { border-color: var(--pe-blue); }
.st-nudge.st-nudge-info .st-nudge-ic { background: #e6eef9; color: #2f5f86; }
.st-nudge.st-nudge-info .st-nudge-sub { color: var(--pe-text-muted); }
.st-nudge.st-nudge-info .st-nudge-cta { color: var(--pe-blue-dark, #0284c7); }
[data-theme="dark"] .st-nudge.st-nudge-info { background: #1b2333; border-color: #2b3752; color: #e8eef7; }
[data-theme="dark"] .st-nudge.st-nudge-info .st-nudge-ic { background: #24324a; color: #9cc4ec; }
[data-theme="dark"] .st-nudge.st-nudge-info .st-nudge-sub { color: #9fb2d8; }
[data-theme="dark"] .st-nudge.st-nudge-info .st-nudge-cta { color: #7fd3ff; }

/* Needs Attention — the league admin's action queue. Each row is a tappable
   link to where the item is resolved; tone (amber = a request awaiting a
   decision, blue = informational) is carried by the icon chip + count pill. */
.na-list { display: flex; flex-direction: column; }
/* The two setup items render as .st-nudge CARDS inside this list while every
   other item is a flush row with a divider (Robb, 2026-09-14 — he wanted the
   queue but missed the look). Cards need air between them and off the row that
   follows; the row's own border-bottom would otherwise sit tight against a
   card's rounded edge. First one loses its top margin so the list still starts
   level with the card header. */
.na-list > .st-nudge { margin: 10px 0 0; }
.na-list > .st-nudge:first-child { margin-top: 2px; }
.na-list > .st-nudge + .na-item { margin-top: 10px; border-top: 1px solid var(--pe-border); }
.na-item { position: relative; display: flex; align-items: center; gap: 14px; padding: 13px 4px; text-decoration: none; color: var(--pe-text); border-bottom: 1px solid var(--pe-border); transition: background .12s; }
.na-item:last-child { border-bottom: 0; }
.na-item:hover { background: var(--pe-bg, #f8fafc); }
.na-ic { width: 38px; height: 38px; border-radius: 10px; flex: none; display: grid; place-items: center; }
.na-ic svg { width: 19px; height: 19px; }
.na-ic.amber { background: #fef3c7; color: #b45309; }
.na-ic.blue  { background: #e0f2fe; color: #0369a1; }
.na-body { flex: 1; min-width: 0; }
.na-label { display: block; font-weight: 600; font-size: 14px; line-height: 1.3; }
.na-sub { display: block; font-size: 12.5px; color: var(--pe-text-muted); margin-top: 1px; }
/* The row's call to action. A text link, not a pill (Robb, 2026-09-14) — blue
   where the row does something, muted where it only reports a state. "Waiting"
   is the latter: styling a status as a link invites a click that goes nowhere.
   The row itself is the <a>, so these are labels, never separate targets.
   .na-count below is what they replaced; it is left defined because the league
   dashboard's own queue still uses it. */
.na-cta { flex: none; font-size: 13px; font-weight: 600; color: var(--pe-blue-dark, #0369a1); white-space: nowrap; }
.na-cta-wait { flex: none; font-size: 12.5px; color: var(--pe-text-secondary); white-space: nowrap; }
.na-count { flex: none; font-weight: 800; font-size: 13px; padding: 2px 11px; border-radius: 999px; font-variant-numeric: tabular-nums; }
.na-count.amber { background: #fde68a; color: #92400e; }
.na-count.blue  { background: #bae6fd; color: #075985; }
.na-arrow { flex: none; color: #94a3b8; display: grid; place-items: center; }
.na-arrow svg { width: 16px; height: 16px; }
/* A queue row carrying an Ask Phil button (league dashboard, 2026-09-16).
   The row is a DIV, not an <a>: a button cannot be nested inside a link. The
   body is the link instead and the chevron repeats it for the look, with
   aria-hidden + tabindex=-1 so only one of the two is in the tab order. */
/* The row used to be one <a class="na-item">, so its 26px of vertical padding
   was part of the tap target. Carrying an Ask Phil button forced the row to
   become a DIV (a button cannot nest inside a link) with the body as the link
   — and a flex item only stretches to the flex LINE, which is the 38px icon,
   not the padded row. Measured: 39px against a 66px row, and 39 on a phone
   where the chevron is hidden and that is the only target at all.
   So the link paints a stretched-link overlay over the whole row instead. The
   hit area is now the full row — slightly better than before, since the icon
   and the count are in it too — and the button sits above it on its own layer.
   Do NOT swap this for a min-height: the padding would still be dead. */
.na-item > .na-link {
    text-decoration: none; color: inherit;
    align-self: stretch; display: flex; flex-direction: column; justify-content: center;
}
.na-item > .na-link::after { content: ''; position: absolute; inset: 0; }
/* Above the overlay, or the button is unreachable. */
.na-item > .na-ask { position: relative; z-index: 1; }
.na-ask {
    flex: none; appearance: none; -webkit-appearance: none; cursor: pointer;
    font-family: inherit; font-size: 12.5px; font-weight: 600; line-height: 1;
    padding: 7px 11px; border-radius: 8px;
    border: 1px solid var(--pe-border); background: var(--pe-white); color: var(--pe-text);
}
.na-ask:hover { border-color: var(--pe-blue, #38bdf8); color: var(--pe-blue-dark, #0284c7); }
[data-theme="dark"] .na-ask { background: var(--pe-card-bg, #1f2937); }
[data-theme="dark"] .na-ask:hover { color: var(--pe-blue, #38bdf8); }
/* Phones: the button would squeeze the label to two words a line, and the
   count already carries the urgency. The row's own link still goes there. */
@media (max-width: 599px) { .na-ask { display: none !important; } }
.na-clear { display: flex; align-items: center; gap: 12px; padding: 10px 4px; color: var(--pe-text-muted); font-size: 13.5px; }
.na-clear-ic { width: 34px; height: 34px; border-radius: 10px; background: #dcfce7; color: #15803d; display: grid; place-items: center; flex: none; }
.na-clear-ic svg { width: 18px; height: 18px; }
[data-theme="dark"] .na-item:hover { background: rgba(255,255,255,.04); }
[data-theme="dark"] .na-ic.amber { background: #3a2c0d; color: #f5c56a; }
[data-theme="dark"] .na-ic.blue  { background: #0c2a3f; color: #7fd3ff; }
[data-theme="dark"] .na-count.amber { background: #5a4212; color: #fbd982; }
[data-theme="dark"] .na-count.blue  { background: #123844; color: #a9dcff; }
[data-theme="dark"] .na-clear-ic { background: #0f2e1c; color: #6ee7a8; }

/* Recent Activity feed — the most recent registrations as a chronological list.
   A status-coloured dot, the player + program, then status + relative time. */
.act-list { display: flex; flex-direction: column; }
.act-item { display: flex; align-items: flex-start; gap: 12px; padding: 11px 2px; border-bottom: 1px solid var(--pe-border); }
.act-item:last-child { border-bottom: 0; }
.act-dot { width: 8px; height: 8px; border-radius: 50%; margin-top: 6px; flex: none; }
.act-body { flex: 1; min-width: 0; }
.act-main { font-size: 13.5px; line-height: 1.35; }
.act-main b { font-weight: 700; }
.act-meta { font-size: 12px; color: var(--pe-text-muted); margin-top: 1px; }

/* This Week — league-wide games in the next 7 days. Date chip + matchup + place. */
.tw-list { display: flex; flex-direction: column; }
.tw-item { display: flex; align-items: center; gap: 14px; padding: 11px 2px; text-decoration: none; color: var(--pe-text); border-bottom: 1px solid var(--pe-border); transition: background .12s; }
.tw-item:last-child { border-bottom: 0; }
.tw-item:hover { background: var(--pe-bg, #f8fafc); }
.tw-date { flex: none; width: 46px; height: 46px; border-radius: 11px; background: #e0f2fe; color: #0369a1; display: flex; flex-direction: column; align-items: center; justify-content: center; line-height: 1; }
.tw-dow { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; font-weight: 700; }
.tw-day { font-size: 19px; font-weight: 800; font-family: var(--pe-font-display); margin-top: 1px; }
.tw-body { flex: 1; min-width: 0; }
.tw-match { font-size: 14px; font-weight: 600; line-height: 1.3; }
.tw-vs { color: var(--pe-text-muted); font-weight: 500; font-size: 12.5px; margin: 0 3px; }
.tw-meta { font-size: 12.5px; color: var(--pe-text-muted); margin-top: 2px; }
.tw-item.tw-cancelled .tw-match { text-decoration: line-through; color: var(--pe-text-muted); }
.tw-item.tw-cancelled .tw-date { background: #f1f5f9; color: #94a3b8; }
[data-theme="dark"] .tw-item:hover { background: rgba(255,255,255,.04); }
[data-theme="dark"] .tw-date { background: #0c2a3f; color: #7fd3ff; }
[data-theme="dark"] .tw-item.tw-cancelled .tw-date { background: #1e2633; color: #64748b; }

/* Registration detail drawer footer — Option A "full-width split": the action
   buttons fill the bar as equal columns (desktop and mobile alike), instead of
   floating right with dead space. A lone button simply fills the whole width. */
#regDetailActions .btn { flex: 1 1 0; min-width: 0; }

/* Drawer stays the standard full-height drawer (reverted). Fix the footer
   whitespace: the shared `.modal-drawer .modal-actions` uses margin:-32px to
   full-bleed when it sits INSIDE the 32px-padded body, but this footer is a
   direct child of .modal (padding 0), so that negative margin pulls the buttons
   flush to the modal edges — no left/right whitespace. Reset margin to 0 so the
   32px padding actually insets the buttons (aligned with the detail rows), and
   give the two buttons a comfortable gap. */
#regDetailModal .modal-actions { margin: 0; padding: 14px 32px; gap: 14px; }

/* Lead — navy smart bar / agenda */
.st-lead { display: block; text-decoration: none; border-radius: 16px; padding: 16px 18px; color: #fff;
    background: linear-gradient(120deg, #171a2b 0%, #242b46 100%); box-shadow: var(--pe-shadow-md); }
.st-lead-single { display: flex; align-items: center; gap: 16px; }
.st-cal { flex: none; width: 48px; height: 52px; border-radius: 11px; display: flex; flex-direction: column;
    align-items: center; justify-content: center; background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.18); }
.st-cal-mo { font-size: 10px; text-transform: uppercase; letter-spacing: .06em; color: #7fd3ff; font-weight: 700; }
.st-cal-dy { font-family: var(--pe-font-display); font-size: 20px; font-weight: 800; line-height: 1; }
.st-lead-body { flex: 1; min-width: 0; }
.st-lead-when { font-size: 12px; color: #9fb2d8; font-weight: 600; }
.st-lead-what { font-family: var(--pe-font-display); font-size: 16px; font-weight: 700; margin-top: 2px; }
.st-lead-where { font-size: 12.5px; color: rgba(255,255,255,.7); margin-top: 2px; }
/* The bar is a <div> holding its own links (2026-10-18), so the clickable body
   needs the layout the old single <a> had. */
.st-lead-main { display: flex; align-items: center; gap: 16px; flex: 1; min-width: 0; text-decoration: none; color: inherit; }
.st-lead-cta { flex: none; background: var(--pe-blue); color: #06263b; font-weight: 700; font-size: 13px; padding: 9px 15px; border-radius: 10px; white-space: nowrap; text-decoration: none; }
/* "Add to my calendar" — the family ICS feed, which lived in the This Week
   card's header until that card was removed. A quiet outline beside the solid
   CTA: it is the second thing you'd want here, not the first. */
.st-lead-actions { flex: none; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.st-lead-agenda .st-lead-actions { margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(255,255,255,.1); }
.st-lead-ics { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 700; white-space: nowrap;
    color: #cfe4ff; text-decoration: none; background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.18);
    border-radius: 10px; padding: 8px 13px; }
.st-lead-ics:hover { background: rgba(255,255,255,.16); border-color: rgba(255,255,255,.34); color: #fff; }
.st-lead-ics svg { width: 15px; height: 15px; }
.st-ag-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 4px; }
.st-ag-title { font-family: var(--pe-font-display); font-size: 15px; font-weight: 700; }
.st-ag-view { color: #7fd3ff; font-weight: 600; font-size: 12.5px; text-decoration: none; white-space: nowrap; }
.st-ag-row { display: flex; align-items: center; gap: 12px; padding: 9px 0; border-top: 1px solid rgba(255,255,255,.1); }
.st-ag-time { font-family: var(--pe-font-display); font-size: 13px; font-weight: 800; width: 64px; flex: none; color: #dbe6ff; }
.st-ag-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.st-ag-body { flex: 1; min-width: 0; }
.st-ag-who { font-weight: 600; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.st-ag-meta { font-size: 12px; color: rgba(255,255,255,.65); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.st-ag-more { display: inline-block; margin-top: 8px; color: #7fd3ff; font-weight: 600; font-size: 12.5px; text-decoration: none; }

/* Slim discovery link (demoted find-a-league for active users) */
.st-discover { align-self: flex-start; display: inline-flex; align-items: center; gap: 5px; color: var(--pe-blue-dark, #0284c7);
    font-weight: 600; font-size: 13.5px; text-decoration: none; padding: 2px; }
.st-discover svg { transition: transform .15s; }
.st-discover:hover svg { transform: translateX(2px); }
[data-theme="dark"] .st-discover { color: #7dd3fc; }

/* Secondary CTAs as quiet inline text links (Find a league · Become a coach · Start a league) */
.st-links { display: flex; flex-wrap: wrap; align-items: center; gap: 22px; padding: 2px; }
.st-link { display: inline-flex; align-items: center; gap: 7px; color: #475569; font-weight: 600; font-size: 13.5px;
    text-decoration: none; transition: color .15s; }
.st-link svg { width: 15px; height: 15px; flex: none; color: #8695ab; transition: color .15s; }
.st-link:hover, .st-link:hover svg { color: #0284c7; }
[data-theme="dark"] .st-link { color: #aeb9c9; }
[data-theme="dark"] .st-link svg { color: #7a8798; }
[data-theme="dark"] .st-link:hover, [data-theme="dark"] .st-link:hover svg { color: #7dd3fc; }
/* On a phone the three links are three equal TILES (Robb, 2026-09-13): as inline
   text they wrapped into an uneven two-line cluster with the third orphaned.
   Desktop keeps the inline row above. */
@media (max-width: 599px) {
    .st-links { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: stretch; gap: 8px; padding: 0; }
    .st-link { flex-direction: column; justify-content: center; text-align: center; gap: 6px; min-height: 72px;
        padding: 12px 6px; background: var(--pe-white); border: 1px solid var(--pe-border); border-radius: 12px;
        font-size: 12px; line-height: 1.3; color: var(--pe-text); }
    .st-link svg { width: 24px; height: 24px; color: #0284c7; }
    [data-theme="dark"] .st-link { color: var(--pe-text); }
    [data-theme="dark"] .st-link svg { color: #7fd3ff; }
}
/* CTAs on the greeting line (desktop) via #pageTitleBar; that bar hides on
   phones, so a copy rides at the top of the smart top there. */
.st-actions-m { display: none; }
@media (max-width: 599px) { .st-actions-m { display: block; } }
#dashTitleActions { flex: 0 1 auto; min-width: 0; }

@media (max-width: 599px) {
    .st-lead-single { flex-wrap: wrap; }
    /* The two actions share the width rather than stacking into a tall stub:
       the bar already carries a date chip, three lines of text and a CTA.
       44px because they are things you tap — the CTA had sat at 39px, which is
       the "close enough to seem fine, under the floor" case this pass exists
       to catch. These are buttons, so the height is real, not a pseudo-element. */
    .st-lead-actions { width: 100%; margin-top: 10px; gap: 8px; }
    .st-lead-cta, .st-lead-ics { flex: 1; display: inline-flex; align-items: center;
        justify-content: center; text-align: center; min-height: 44px; }
    .st-onboard { flex-wrap: wrap; }
    .st-onboard-cta { width: 100%; text-align: center; }
    .st-nudge-cta { display: none; }
    .st-ag-time { width: 54px; }
}

/* Reusable horizontal-scroll wrapper for wide tables on small screens.
   Exposes a scrollbar instead of clipping (body has overflow-x:hidden). */
.table-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

/* ─────────────────────────────────────────────────────────
   DataTable — reusable data grid (assets/js/data-table.js)
   Dense sortable table on desktop; collapses to a caller-supplied
   card list below 768px. Sort/pagination/selection live in the JS.
   ───────────────────────────────────────────────────────── */
.data-table-root { width: 100%; }

/* DataTable grouping — collapsible group header rows / card groups.

   A GROUP HEADING IS NOT A ROW OF DATA. It was rendering at 75px, the same
   height as a row carrying eight columns, because `.dt-group-row > td` is
   (0,1,1) and lost to `.data-table tbody td` at (0,1,2) — so `padding:0` never
   applied and the cell's 15px stacked on top of the toggle's own 11px. Eight
   teams inside a division meant 600px of headings before any player. The
   selector below carries the same weight as the rule it has to beat. */
.data-table tbody tr.dt-group-row > td { padding: 0; background: var(--pe-bg-subtle, #f8fafc); border-bottom: 1px solid var(--pe-border, #e2e8f0); }
.dt-group-toggle { display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 16px; background: none; border: none; cursor: pointer; font: inherit; text-align: left; color: var(--pe-text); font-weight: 700; }
.dt-group-toggle:hover { background: rgba(15, 23, 42, 0.03); }
.dt-group-chev { width: 16px; height: 16px; flex-shrink: 0; transition: transform 0.15s ease; }
.dt-group-row.collapsed .dt-group-chev, .dt-card-group.collapsed .dt-group-chev { transform: rotate(-90deg); }
.dt-group-label { font-size: 13.5px; }
.dt-group-count { font-size: 12px; font-weight: 700; color: var(--pe-text-secondary, #64748b); background: #e2e8f0; border-radius: 999px; padding: 1px 9px; }
[data-theme="dark"] .dt-group-count { background: rgba(255, 255, 255, 0.1); }
.dt-card-group { margin-bottom: 12px; }
.dt-card-group .dt-group-toggle { padding: 8px 4px; margin-bottom: 6px; }

/* ── Two-level grouping ──────────────────────────────────────────────────────
   With one level the header is just a divider and nothing more is needed. With
   two, the reader has to see CONTAINMENT, and three things were working against
   it: both levels looked alike (13.5/700 against 12.5/600, on backgrounds two
   shades apart), and the data rows were indented LESS than the team heading
   above them — so the deepest content sat furthest left and the nesting read
   backwards.

   Level 0 is now a section band: darker, letter-spaced, unmistakably a heading.
   Level 1 and the rows under it share one left rail, so a team and its players
   read as one block inside the division. */
.dt-nested .data-table tbody tr.dt-group-row.dt-group-lvl0 > td {
    background: var(--pe-bg, #eef2f7);
    border-top: 1px solid var(--pe-border, #e2e8f0);
}
.dt-nested .dt-group-row.dt-group-lvl0 .dt-group-label { font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; }
[data-theme="dark"] .dt-nested .data-table tbody tr.dt-group-row.dt-group-lvl0 > td { background: rgba(148, 163, 184, .16); }

.dt-nested .data-table tbody tr.dt-group-row.dt-group-lvl1 > td { background: var(--pe-surface, #fff); }
.dt-nested .dt-group-row.dt-group-lvl1 .dt-group-label { font-weight: 700; font-size: 13px; }
.dt-nested .dt-group-row.dt-group-lvl1 .dt-group-chev { width: 14px; height: 14px; }
/* The rail. It runs down the sub-heading and every row beneath it, which is the
   only thing on screen that says these players belong to that team. */
.dt-nested .dt-group-row.dt-group-lvl1 > td,
.dt-nested .dt-row.dt-row-d1 > td:first-child {
    box-shadow: inset 3px 0 0 -1px var(--pe-border, #e2e8f0);
}
/* Rows sit INSIDE their sub-heading, not left of it. */
.dt-nested .dt-row.dt-row-d1 > td:first-child { padding-left: 36px; }
.dt-card-group.dt-card-lvl1 { margin-bottom: 6px; }
.dt-card-group.dt-card-lvl1 .dt-group-label { font-size: 12.5px; }

/* DataTable row expansion (opts.rowDetail) — a caret column, and the detail
   row it opens. The caret rotates rather than swapping glyphs, so it matches
   the group chevron above instead of introducing a second idiom. */
.dt-x-col { width: 30px; padding-right: 0 !important; }
.dt-expand {
    display: flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; padding: 0; border: 0; border-radius: 6px;
    background: none; cursor: pointer; color: var(--pe-text-secondary, #94a3b8);
    transition: transform 0.15s ease, background 0.15s, color 0.15s;
}
.dt-expand svg { width: 14px; height: 14px; }
.dt-expand:hover { background: var(--pe-bg-subtle, #f1f5f9); color: var(--pe-text); }
[data-theme="dark"] .dt-expand:hover { background: rgba(255, 255, 255, 0.07); }
.dt-expand.open { transform: rotate(90deg); }
/* Indented to line up UNDER the row it belongs to, not out in the caret gutter.
   `.data-table tbody td` is (0,1,2) and beat the (0,1,1) this used to carry, so
   the detail inherited the plain 16px cell padding and sat 38px LEFT of the
   name above it — reading as a separate block rather than that row's detail.
   54px = the caret column (38) + the cell's own 16, which is exactly where the
   first data cell's text begins. */
.data-table tbody tr.dt-detail-row > td {
    padding: 2px 16px 16px 54px;
    background: var(--pe-bg-subtle, #f8fafc);
    border-bottom: 1px solid var(--pe-border, #e2e8f0);
}
[data-theme="dark"] .dt-detail-row > td { background: rgba(255, 255, 255, 0.02); }

/* Mobile: the detail hangs off the bottom of the card instead of a caret
   column, because a 22px target beside a card title is not a tap target. */
.dt-card-x { margin-bottom: 10px; }
/* The caller's card is the top of the stack now, so it gives up its bottom
   corners to the button beneath it — whatever element the caller used. */
.dt-card-x > :first-child { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.dt-card-xbtn {
    display: flex; align-items: center; justify-content: center; gap: 6px;
    width: 100%; padding: 9px; margin-top: -1px; border: 1px solid var(--pe-border, #e2e8f0);
    border-radius: 0 0 12px 12px; background: var(--pe-bg-subtle, #f8fafc); cursor: pointer;
    font: inherit; font-size: 12.5px; font-weight: 600; color: var(--pe-text-secondary, #64748b);
}
.dt-card-xbtn svg { width: 13px; height: 13px; transition: transform 0.15s ease; }
.dt-card-x.open .dt-card-xbtn svg { transform: rotate(180deg); }
[data-theme="dark"] .dt-card-xbtn { background: rgba(255, 255, 255, 0.03); }
.dt-card-detail { padding: 12px 14px; border: 1px solid var(--pe-border, #e2e8f0); border-top: 0; border-radius: 0 0 12px 12px; }
/* The card above owns the top corners; the button between them must not keep
   a rounded bottom once the detail is showing under it. */
.dt-card-x.open .dt-card-xbtn { border-radius: 0; }

/* Bulk-select toolbar (shown only when rows are selected). */
.dt-bulkbar {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 10px 14px;
    margin-bottom: 12px;
    background: var(--sidebar-bg-active, rgba(56,189,248,0.12));
    border: 1px solid var(--pe-blue, #38bdf8);
    border-radius: 10px;
}
.dt-selcount { font-size: 13px; font-weight: 600; color: var(--pe-blue, #0ea5e9); margin-right: 4px; }
.dt-bulk-btn, .dt-bulk-clear {
    height: 32px; padding: 0 14px;
    border: 1px solid var(--pe-border); border-radius: 8px;
    background: var(--pe-card, #fff); color: var(--pe-text);
    font-family: inherit; font-size: 13px; font-weight: 500; cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.dt-bulk-btn:hover { border-color: var(--pe-blue); color: var(--pe-blue); }
.dt-bulk-btn.danger { color: #b91c1c; }
.dt-bulk-btn.danger:hover { border-color: #f87171; background: #fef2f2; color: #b91c1c; }
.dt-bulk-clear { margin-left: auto; color: var(--pe-text-muted); }
.dt-bulk-clear:hover { color: var(--pe-text); }

.dt-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    border: 1px solid var(--pe-border);
    border-radius: 14px;
    background: var(--pe-white, #fff);
    box-shadow: 0 1px 2px rgba(16,24,40,0.04), 0 4px 12px rgba(16,24,40,0.05);
}
.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13.5px;
    min-width: 640px;
}
.data-table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: #334155;
    text-align: left;
    font-weight: 600;
    font-size: 11px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.74);
    padding: 13px 16px;
    white-space: nowrap;
    border-bottom: 1px solid #334155;
    user-select: none;
}
.data-table thead th .dt-check-all { accent-color: var(--pe-blue); }
.data-table th.dt-right, .data-table td.dt-right { text-align: right; }
.data-table th.dt-sortable { cursor: pointer; }
.data-table th.dt-sortable:hover { color: #fff; }
.dt-caret {
    display: inline-flex; vertical-align: middle; margin-left: 4px;
    opacity: 0; transition: opacity 0.15s, transform 0.15s;
}
.dt-caret svg { width: 13px; height: 13px; }
.data-table th.dt-sortable:hover .dt-caret { opacity: 0.4; }
.dt-caret.active { opacity: 1; color: var(--pe-blue, #38bdf8); }
.dt-caret.active.asc { transform: rotate(180deg); }
.dt-check-col { width: 40px; text-align: center; }
.dt-check-col input { cursor: pointer; accent-color: var(--pe-blue); }
.dt-cta-col { width: 96px; text-align: right; }

.data-table tbody td {
    padding: 15px 16px;
    border-top: 1px solid var(--pe-border);
    vertical-align: middle;
    color: var(--pe-text);
}
.data-table tbody tr:first-child td { border-top: none; }
.dt-row { transition: background 0.12s; }
.dt-row:hover { background: var(--pe-hover, #f8fafc); }
.dt-row.selected { background: var(--sidebar-bg-active, rgba(56,189,248,0.10)); }
.dt-row.dt-clickable { cursor: pointer; }
/* The row a drawer is currently editing (API.markEditing / data-editing).
   A LEFT BAR, not a background: .selected above already owns the blue wash
   and .dt-row:hover the grey one, and a third tint competing with those two
   reads as a bug. The bar survives being hovered or selected, which a
   background alone would not. Nothing MOVES - no transform, per the house
   rule - the bar is drawn inside the first cell's existing padding. */
.dt-row.pe-editing > td:first-child { box-shadow: inset 3px 0 0 0 var(--pe-blue, #38bdf8); }
.dt-row.pe-editing { background: var(--pe-hover, #f8fafc); }

/* The drawer subject line: WHICH item this drawer is editing, under the title.
   Robb, 2026-09-21 — "it is difficult to remember which item being edited".

   Shared because it is going on six drawers (season, event, tournament,
   facility, team, division) and six private copies is how the two season
   editors drifted apart in the first place.

   It reads as CONTENT, not chrome: full text colour, a size below the title.
   Clamped to two lines because a long name on a phone would otherwise push
   the form down the sheet. The right padding clears the absolutely
   positioned close and expand buttons; the header must stay at least 52px
   or those hang over the body, and this only ever makes it taller. */
.pe-drawer-subject {
    margin: 2px 0 0; font-size: 14.5px; font-weight: 650; line-height: 1.35;
    color: var(--pe-text); padding-right: 76px;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden; overflow-wrap: anywhere;
}
.pe-drawer-subject[hidden] { display: none; }
@media (max-width: 599px) { .pe-drawer-subject { padding-right: 44px; font-size: 14px; } }
[data-theme="dark"] .dt-row.pe-editing { background: rgba(255, 255, 255, 0.04); }
.dt-empty-row td { padding: 0 !important; border: none !important; }
.dt-empty { text-align: center; padding: 40px 20px; color: var(--pe-text-muted); }

/* ── Row action icons ─────────────────────────────────────────────
   The square icon buttons at the right of a grid row — edit, copy,
   activate/deactivate, remove. Shared, because the same four buttons
   belong on every list in the app and the first copy of this lived in
   facilities.html's page styles as `.ft-act-btn` (Robb, 2026-09-20:
   "let's make this our standard grid like the other pages").
   Text labels do not scale: four words per row is a wall, and at
   tablet width they wrapped. Each button keeps title + aria-label,
   and the mobile card layout still spells the actions out. */
.dt-acts { display: inline-flex; align-items: center; gap: 6px; justify-content: flex-end; }
.dt-act {
    display: inline-flex; align-items: center; justify-content: center;
    width: 32px; height: 32px; border-radius: 8px;
    border: 1px solid var(--pe-border); background: var(--pe-white, #fff);
    color: var(--pe-text-secondary, #64748b); cursor: pointer; padding: 0;
    transition: border-color .15s, color .15s, background .15s;
}
.dt-act:hover { border-color: var(--pe-blue, #38bdf8); color: var(--pe-blue, #38bdf8); }
.dt-act:focus-visible { outline: 2px solid var(--pe-blue); outline-offset: 2px; }
/* An action the row cannot take right now — still visible (so the column does not jump
   between rows) and still hoverable for its title, which explains why. */
.dt-act.is-disabled { opacity: .35; cursor: not-allowed; }
.dt-act.is-disabled:hover { border-color: var(--pe-border); color: inherit; }
.dt-act svg { width: 15px; height: 15px; pointer-events: none; }
.dt-act.danger:hover { border-color: var(--pe-danger, #dc2626); color: var(--pe-danger, #dc2626); }
.dt-act.is-on { color: #16a34a; border-color: rgba(34,197,94,.45); }
.dt-act.is-on:hover { color: var(--pe-text-secondary, #64748b); border-color: var(--pe-border); }
/* An action this row cannot take. Shown, not hidden, so the row keeps its shape
   and the title can say WHY — a button that vanishes teaches nothing. */
.dt-act.is-off, .dt-act:disabled { opacity: .4; cursor: not-allowed; }
.dt-act.is-off:hover, .dt-act:disabled:hover { border-color: var(--pe-border); color: var(--pe-text-secondary, #64748b); }
[data-theme="dark"] .dt-act { background: var(--pe-card-bg, #1f2937); }
/* A 32px control is under the 44px touch floor, so grow the hit area on a
   phone without growing the icon — the same trick the mobile pass uses for
   small text links at the end of this file. */
@media (max-width: 599px) {
    .dt-act { position: relative; }
    .dt-act::after { content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
                     width: 44px; height: 44px; }
}

.dt-cta {
    display: inline-flex; align-items: center; gap: 4px;
    height: 30px; padding: 0 10px 0 12px;
    border: 1px solid var(--pe-border); border-radius: 8px;
    background: var(--pe-card, #fff); color: var(--pe-text);
    font-family: inherit; font-size: 12.5px; font-weight: 500; cursor: pointer;
    white-space: nowrap; transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.dt-cta:hover { border-color: var(--pe-blue); color: var(--pe-blue); background: var(--pe-hover, #f8fafc); }
.dt-cta svg { width: 14px; height: 14px; }
/* Chevron-only variant — a quiet, always-visible affordance that brightens
   into a subtle circle on hover (professional SaaS row action). */
.dt-cta-icon {
    width: 30px; height: 30px; padding: 0;
    color: var(--pe-text-muted); border-color: transparent; background: transparent;
}
.dt-row:hover .dt-cta-icon { color: var(--pe-text); }
.dt-cta-icon:hover { border-color: var(--pe-border); background: var(--pe-hover, #f8fafc); color: var(--pe-blue); }
.dt-cta-icon svg { width: 16px; height: 16px; }

.dt-footer {
    display: flex; align-items: center; justify-content: space-between;
    gap: 12px; margin-top: 12px; font-size: 12.5px; color: var(--pe-text-muted);
}
/* Shared per-page (rows-per-page) selector for DataTable pages that build
   their controls in a toolbar (divisions, seasons). Pages with their own
   filter bar (teams, registrations, coaches, referees) style their own. */
.dt-perpage-select {
    height: 38px; padding: 0 12px;
    border: 1px solid var(--pe-border); border-radius: 8px;
    background: var(--pe-card, #fff); color: var(--pe-text);
    font-size: 13px; font-weight: 600; font-family: inherit;
    cursor: pointer; flex-shrink: 0;
}
[data-theme="dark"] .dt-perpage-select { background: var(--pe-card-bg, #1f2937); }

/* ══ Canonical page filter/controls bar (the "Events" header template) ══
   One shared toolbar so Events, Divisions, Seasons, Registrations, Teams and
   Facilities present an identical header: filter controls on the LEFT, then
   search + per-page pushed to the RIGHT. All controls are a uniform 38px tall.
   Elements keep their own #ids (JS/listeners unchanged) — only wrappers/classes
   move to these shared names. */
/* ── Schedule-area nav: shared by schedule.html + schedule-builder.html so the
   lens tabs + Division/Season scope look and behave identically on both. ── */
.sch-topbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.sch-scope { display: flex; gap: 8px; flex-wrap: wrap; }
.sch-scope .pe-fb-select { min-width: 150px; }
.sch-viewtabs { display: inline-flex; gap: 2px; padding: 3px; border-radius: 10px; background: var(--pe-hover, rgba(0,0,0,0.04)); }
.sch-viewtab { display: inline-flex; align-items: center; gap: 6px; appearance: none; border: 0; background: transparent; cursor: pointer; padding: 7px 15px; border-radius: 8px; font: inherit; font-size: 13.5px; font-weight: 600; color: var(--pe-text-secondary, #64748b); text-decoration: none; transition: background .15s, color .15s; white-space: nowrap; }
.sch-viewtab:hover { color: var(--pe-text); }
.sch-viewtab.active { background: var(--pe-white, #fff); color: var(--pe-text); box-shadow: 0 1px 2px rgba(0,0,0,.08); }
.sch-viewtab-build { color: var(--pe-blue, #2563eb); }
.sch-viewtab-build svg { width: 14px; height: 14px; }
[data-theme="dark"] .sch-viewtabs { background: rgba(255,255,255,0.06); }
[data-theme="dark"] .sch-viewtab.active { background: var(--pe-card-bg, #1f2937); }

/* ── Scope bar: a compact, collapsible context summary for a page's sub-nav.
   Reusable on any view via ScopeBar.mount() (assets/js/scope-bar.js). Collapsed
   shows only the last segment + a left chevron; expanded shows the whole
   "A · B · C" string. Right-anchored (grows leftward); auto-collapses on narrow
   rows, click toggles. ── */
.scope-bar { display: inline-flex; align-items: center; gap: 8px; min-width: 0; max-width: 100%;
    padding: 6px 12px; border: 1px solid var(--pe-border); border-radius: 8px;
    background: var(--pe-white); color: var(--pe-text); font: inherit; font-size: 13px;
    line-height: 1.4; cursor: pointer; overflow: hidden; transition: background .15s, border-color .15s; }
.scope-bar:hover { border-color: #94a3b8; background: var(--pe-hover, #f8fafc); }
[data-theme="dark"] .scope-bar { background: var(--pe-card-bg, var(--pe-white)); border-color: var(--pe-border); }
.scope-bar-chevron { flex: 0 0 auto; width: 15px; height: 15px; color: var(--pe-text-secondary, #64748b); transform: rotate(0deg); transition: transform .15s ease; }
.scope-bar:not(.collapsed) .scope-bar-chevron { transform: rotate(180deg); }
.scope-bar-segs { display: inline-flex; align-items: center; gap: 8px; min-width: 0; white-space: nowrap; }
.scope-seg { color: var(--pe-text); }
.scope-seg-last { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.scope-sep { color: var(--pe-text-tertiary, #94a3b8); }
.scope-bar.collapsed .scope-hideable { display: none; }
@media (max-width: 599px) { .sch-scope { width: 100%; } .sch-scope .pe-fb-select { flex: 1; min-width: 0; } }

.pe-filterbar { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; flex-wrap: wrap; }
/* On a phone every filter bar folds behind one "Filters" button (Robb, 2026-09-13):
   three selects, Group By and the page size stacked five rows deep above the
   list. nav.js tags each bar `pe-fb-mobile`, adds the toggle beside the search
   and MOVES the controls into a bordered panel (`.pe-fb-panel`: a "Filters"
   label with Clear, the selects and Group By at one height and alignment, the
   page size last as "Show N per page") that shows only while `is-open`. Above
   599px the controls are moved back and the toggle is display:none, so desktop
   never sees any of it. No page markup changes. */
.pe-fb-toggle, .pe-fb-panel { display: none; }
@media (max-width: 599px) {
    .pe-fb-mobile { gap: 10px; }
    .pe-fb-mobile > .pe-fb-right { order: -1; width: 100%; margin-left: 0; gap: 10px; }
    .pe-fb-mobile > .pe-fb-right > .pe-fb-search { flex: 1 1 auto; min-width: 0; }
    .pe-fb-mobile > .pe-fb-right > .pe-fb-search input { width: 100%; }
    .pe-fb-toggle { display: inline-flex; align-items: center; gap: 6px; height: 44px; padding: 0 14px; flex: none;
        border: 1px solid var(--pe-border); border-radius: 8px; background: var(--pe-card, #fff); color: var(--pe-text);
        font: inherit; font-size: 14px; font-weight: 600; cursor: pointer; }
    .pe-fb-toggle svg { width: 16px; height: 16px; }
    .pe-fb-toggle .pe-fb-count { display: none; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 999px;
        background: var(--pe-blue, #38bdf8); color: #06283d; font-size: 12px; font-weight: 800; line-height: 20px; text-align: center; }
    .pe-fb-toggle.has-active .pe-fb-count { display: inline-block; }
    .pe-fb-mobile.is-open .pe-fb-toggle { background: rgba(56, 189, 248, .16); border-color: rgba(56, 189, 248, .6); color: #0369a1; }
    [data-theme="dark"] .pe-fb-mobile.is-open .pe-fb-toggle { color: #7fd3ff; }
    .pe-fb-mobile.is-open > .pe-fb-panel { display: flex; flex-direction: column; gap: 10px; width: 100%; order: 0;
        padding: 12px; border: 1px solid var(--pe-border); border-radius: 12px; background: var(--pe-card, #fff); }
    .pe-fb-panel-head { display: flex; align-items: center; justify-content: space-between; min-height: 24px;
        font-size: 12px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; color: var(--pe-text-secondary, #64748b); }
    .pe-fb-clear { font: inherit; font-size: 13px; font-weight: 700; letter-spacing: 0; text-transform: none; color: var(--pe-blue, #0284c7);
        background: none; border: 0; padding: 8px 0 8px 12px; cursor: pointer; display: none; }
    .pe-fb-panel.has-active .pe-fb-clear { display: inline-block; }
    /* The date and number inputs belong in this list too. Naming only the
       select left them at their desktop 38px inside a 44px stack — visible on
       discover's "Max $" and on the schedule panel's From/To. */
    .pe-fb-panel > select.pe-fb-select, .pe-fb-panel > input.pe-fb-date, .pe-fb-panel > input.pe-fb-num,
    .pe-fb-panel > .groupby-wrap, .pe-fb-panel > .groupby-wrap > .pe-fb-gb { width: 100%; min-width: 0; }
    .pe-fb-panel > select.pe-fb-select, .pe-fb-panel > input.pe-fb-date, .pe-fb-panel > input.pe-fb-num,
    .pe-fb-panel > .groupby-wrap > .pe-fb-gb, .pe-fb-perpage select { height: 44px; }
    .pe-fb-panel > .groupby-wrap > .pe-fb-gb { justify-content: flex-start; padding: 0 12px; }
    .pe-fb-panel > .groupby-wrap > .pe-fb-gb .groupby-arrow { margin-left: auto; }
    .pe-fb-perpage { display: flex; align-items: center; gap: 8px; padding-top: 10px; border-top: 1px solid var(--pe-border);
        font-size: 14px; color: var(--pe-text-secondary, #64748b); }
    .pe-fb-perpage select { flex: 1 1 auto; min-width: 0; }
    /* Anything a page left loose in the bar (a button, a badge) hides while folded. */
    .pe-fb-mobile:not(.is-open) > :not(.pe-fb-right):not(.pe-fb-panel) { display: none !important; }
    .pe-fb-mobile.is-open > :not(.pe-fb-right):not(.pe-fb-panel) { width: 100%; }
}
/* Filter dropdown — the app standard select. Keyed on the class alone so it
   looks the same outside a .pe-filterbar (the Upcoming range picker, the
   schedule scope bar) as it does inside one. */
select.pe-fb-select {
    height: 38px; padding: 0 30px 0 12px;
    border: 1px solid var(--pe-border); border-radius: 8px;
    background: var(--pe-card, #fff); color: var(--pe-text);
    font-size: 14px; font-family: inherit; min-width: 150px; cursor: pointer;
}
/* Group By button (page keeps its own #id + dropdown JS; class = look only) */
.pe-fb-gb {
    display: inline-flex; align-items: center; gap: 6px;
    height: 38px; padding: 0 14px; border: 1px solid var(--pe-border);
    border-radius: 8px; background: var(--pe-card, #fff);
    color: var(--pe-text); font-size: 14px; font-weight: 500; font-family: inherit;
    cursor: pointer; white-space: nowrap; transition: border-color .15s, background .15s;
}
.pe-fb-gb:hover { border-color: var(--pe-blue, #38bdf8); }
.pe-fb-gb svg { width: 16px; height: 16px; }
.pe-fb-gb .groupby-arrow { width: 12px; height: 12px; color: var(--pe-text-muted); }
/* Right cluster: search + per-page, pushed to the far right */
.pe-fb-right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.pe-fb-search { position: relative; display: flex; align-items: center; }
.pe-fb-search svg { position: absolute; left: 11px; width: 15px; height: 15px; color: var(--pe-text-muted); pointer-events: none; }
.pe-fb-search input {
    height: 38px; padding: 0 12px 0 34px;
    border: 1px solid var(--pe-border); border-radius: 8px;
    background: var(--pe-card, #fff); color: var(--pe-text);
    font-size: 13px; font-family: inherit; width: 240px;
}
.pe-fb-search input::placeholder { color: var(--pe-text-muted); }
/* ══ Platform admin shell (Admin Tools + Leagues) ═════════════════════════════
   The platform pages are the oldest surfaces in the app and never got the
   vocabulary the league-facing pages use (Robb, 2026-09-12: "they are hideous").
   This is that vocabulary, not a new one: the tab strip mirrors .sch-viewtabs
   from the season page, the tiles mirror the season stat strip, and .pf-section
   replaces a .content-card that was doing four different jobs at one weight.

   Adopted a class at a time by the platform pages; nothing here restyles a
   shared component, so a page that has not been converted is untouched.

   The strip keeps .profile-tabs as well — admin.html hides `.profile-tabs` when
   it drills into one league, and six other pages style it — so .pf-tabs is
   ADDITIVE and only ever narrows. ── */
.pf-tabs {
    display: inline-flex; flex-wrap: wrap; gap: 2px; padding: 3px;
    border-radius: 10px; background: var(--pe-hover, rgba(0,0,0,0.04));
    border: 0;
}
.pf-tabs .profile-tab {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 7px 14px; border: 0; border-radius: 8px; background: transparent;
    font-size: 13px; font-weight: 600; color: var(--pe-text-secondary, #64748b);
    text-decoration: none; white-space: nowrap;
    transition: background .15s, color .15s;
}
.pf-tabs .profile-tab:hover { color: var(--pe-text); background: transparent; }
.pf-tabs .profile-tab.active {
    background: var(--pe-white, #fff); color: var(--pe-text);
    box-shadow: 0 1px 2px rgba(0,0,0,.08);
}
.pf-tabs .profile-tab svg { width: 15px; height: 15px; }
[data-theme="dark"] .pf-tabs { background: rgba(255,255,255,0.06); }
[data-theme="dark"] .pf-tabs .profile-tab.active { background: var(--pe-card-bg, #1f2937); }

/* One line that collapses every current problem, instead of a permanent red
   wall. Cron never ticks locally, so the loudest element on the page was always
   on — which is how a real alert gets trained out of you. */
.pf-attention {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    /* Robb, 2026-09-14: the message needed room. It was 10px/14px with a 10px
       gap, which put the text hard against the edges of a 41px strip wedged
       between the tabs and the stat cards — the shape of something you skim past.
       The 2px above separates it from the tab row it is not part of. */
    margin: 2px 0 20px; padding: 14px 18px; border-radius: 10px;
    background: rgba(217,119,6,.10); color: #b45309; font-size: 13px; line-height: 1.45;
}
.pf-attention svg { width: 16px; height: 16px; flex: 0 0 auto; }
/* The admin cron strip is the one .pf-attention that sits DIRECTLY under the
   platform tab row, and that row carries no bottom margin of its own — so the
   only thing between them was this rule's 2px, and it read as glued to the tabs
   (Robb, twice: 2026-09-14 and again on the 15th). Scoped rather than changed on
   the base, because the other user of the class (referees.html .games-noref) is
   a mid-page banner with content above it that already spaces itself.
   16px above matches the 20px the tab panel below puts between the tabs and the
   first card; 4px below keeps the total to the content at ~24px, since
   .tab-panel adds its own 20px padding-top underneath this. */
#cronHealthBanner .pf-attention { margin-top: 16px; margin-bottom: 4px; }
.pf-attention b { font-weight: 700; }
.pf-attention .pf-att-detail { color: #92400e; }
.pf-attention .pf-att-go {
    margin-left: auto; font-weight: 600; color: inherit; text-decoration: none;
    display: inline-flex; align-items: center; gap: 3px; white-space: nowrap;
}
.pf-attention .pf-att-go:hover { text-decoration: underline; }
.pf-attention-ok { background: rgba(22,163,74,.10); color: #15803d; }
[data-theme="dark"] .pf-attention { color: #fcd34d; }
[data-theme="dark"] .pf-attention .pf-att-detail { color: #fde68a; }
[data-theme="dark"] .pf-attention-ok { color: #86efac; }

/* Numbers. Neutral by default — colour here means a PROBLEM, never decoration.
   The old tiles cycled four accents with no meaning; Total users was red. */
.pf-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr)); gap: 10px; margin: 0 0 16px; }
.pf-tile { background: var(--pe-card, #fff); border: 1px solid var(--pe-border); border-radius: 10px; padding: 12px 14px; min-width: 0; }
[data-theme="dark"] .pf-tile { background: var(--pe-card-bg, #1f2937); }
/* Uppercase micro-cap, because that is the house tile label: email-usage,
   phil-admin, phil-report and revenue all use it. Step 1 shipped these tiles in
   sentence case, which made the new Overview the odd one out — cheaper and more
   honest to align the newcomer than to rewrite four working pages. */
.pf-tile-k { font-size: 11px; font-weight: 700; text-transform: uppercase;
    letter-spacing: .5px; color: var(--pe-text-muted); }
.pf-tile-v { font-size: 23px; font-weight: 700; color: var(--pe-text); line-height: 1.2; margin-top: 2px; }
.pf-tile-s { font-size: 11.5px; color: var(--pe-text-muted); margin-top: 2px; overflow-wrap: anywhere; }
.pf-tile-warn { border-color: rgba(220,38,38,.35); background: rgba(220,38,38,.05); }
.pf-tile-warn .pf-tile-v { color: #dc2626; }
/* Amber sibling of .pf-tile-warn: a number that needs a decision, not a number
   that is broken. Pending leagues and pending invites are work waiting, so they
   take the same amber as .pf-attention — red would file them next to a failure. */
.pf-tile-note { border-color: rgba(217,119,6,.35); background: rgba(217,119,6,.06); }
.pf-tile-note .pf-tile-v { color: #b45309; }
[data-theme="dark"] .pf-tile-note .pf-tile-v { color: #fcd34d; }
/* The tinted background needs its own dark rule: [data-theme="dark"] .pf-tile
   outranks a bare .pf-tile-warn/-note, so without these the tone shows only as a
   border and the tile reads plain. */
[data-theme="dark"] .pf-tile-warn { background: rgba(220,38,38,.12); }
[data-theme="dark"] .pf-tile-note { background: rgba(217,119,6,.12); }

/* ── Pro lock ──────────────────────────────────────────────────────────────
   A Pro-gated control that is SHOWN rather than hidden, so the feature stays
   discoverable to the only people who might buy it. Greyed, but deliberately
   STILL INTERACTIVE: pointer-events:none (what .ls-pro-locked does) or a
   [disabled] attribute would suppress the native title tooltip and swallow
   the click, and both are the point — hover explains, click offers upgrade.
   Sets no background or colour, so it greys an icon button and a .btn alike. */
.pe-pro-locked { opacity: .45; filter: grayscale(1); cursor: pointer; }
.pe-pro-locked:hover { opacity: .7; }

/* A titled block. Replaces .content-card where the card was a grab bag: this
   one carries a title, an optional action, and a body — nothing else. */
.pf-section { border: 1px solid var(--pe-border); border-radius: 12px; background: var(--pe-card, #fff); margin: 0 0 14px; }
[data-theme="dark"] .pf-section { background: var(--pe-card-bg, #1f2937); }
.pf-section-head { display: flex; align-items: center; gap: 12px; padding: 13px 16px; border-bottom: 1px solid var(--pe-border); }
.pf-section-head h3 { margin: 0; font-size: 14.5px; font-weight: 700; color: var(--pe-text); }
.pf-section-head .pf-section-note { font-size: 12.5px; color: var(--pe-text-secondary, #64748b); }
.pf-section-head .pf-section-act { margin-left: auto; }
.pf-section-body { padding: 14px 16px; }
.pf-section-body > p:first-child { margin-top: 0; }

/* In-tab sections, so reading the system and changing it stop competing for the
   same scroll (Payments was a health probe, a checklist, a pricing form and a
   settings panel, stacked at equal weight). */
.pf-subnav { display: flex; gap: 18px; flex-wrap: wrap; margin: 0 0 14px; border-bottom: 1px solid var(--pe-border); }
.pf-subnav a, .pf-subnav button {
    appearance: none; border: 0; background: transparent; font: inherit; cursor: pointer;
    padding: 0 0 8px; font-size: 13px; font-weight: 600;
    color: var(--pe-text-secondary, #64748b); text-decoration: none;
    box-shadow: inset 0 -2px 0 transparent; transition: color .15s, box-shadow .15s;
}
.pf-subnav a:hover, .pf-subnav button:hover { color: var(--pe-text); }
.pf-subnav .active { color: var(--pe-text); box-shadow: inset 0 -2px 0 var(--pe-blue, #38bdf8); }

/* A .pf-subnav section. [hidden] needs the !important because .pay-sec has no
   display of its own and a UA sheet can lose to a later block rule. */
.pay-sec[hidden] { display: none !important; }
.pay-sec > div:first-child > .content-card:first-child,
.pay-sec > div:first-child > .pf-section:first-child { margin-top: 0; }

/* A status checklist: label left, verdict right. */
.pf-rows { display: flex; flex-direction: column; }
.pf-row { display: flex; align-items: center; gap: 12px; padding: 9px 0; border-top: 1px solid var(--pe-border); font-size: 13.5px; color: var(--pe-text); }
.pf-row:first-child { border-top: 0; }
.pf-row-label { flex: 1; min-width: 0; }
.pf-row-val { flex: 0 0 auto; font-size: 12.5px; color: var(--pe-text-secondary, #64748b); }
.pf-yes { color: #16a34a; font-weight: 700; }
.pf-no  { color: #dc2626; font-weight: 700; }

@media (max-width: 599px) {
    .pf-tabs { display: flex; width: 100%; }
    .pf-section-head { flex-wrap: wrap; }
    .pf-section-head .pf-section-act { margin-left: 0; width: 100%; }
}

/* ── Spend ceilings card (admin.html, Payments Health tab) ──────────────────
   IMAGE_PLATFORM_MONTHLY_CAP sat at 0 — disabled — in every environment for
   months and nothing in the app said so. A limit you cannot see is one you
   cannot tell is missing, hence a card that states it plainly. admin.html has
   no <style> block of its own, so these live here. ── */
.sc-row { padding: 10px 0; border-top: 1px solid var(--pe-border); }
.sc-row:first-of-type { border-top: 0; }
.sc-l { font-size: 13.5px; color: var(--pe-text); }
.sc-v { font-size: 13px; color: var(--pe-text); margin-top: 2px; }
.sc-dim { color: var(--pe-text-muted); }
.sc-n { font-size: 12px; color: var(--pe-text-muted); margin-top: 3px; }
.sc-bar { height: 6px; border-radius: 999px; background: var(--pe-border); margin-top: 7px; overflow: hidden; }
/* display:block — an inline span ignores width/height. */
.sc-fill { display: block; height: 100%; border-radius: 999px; }
.sc-green { background: #16a34a; }
.sc-amber { background: #d97706; }
.sc-red   { background: #dc2626; }
.sc-badge { display: inline-block; font-size: 11px; font-weight: 700; padding: 2px 9px; border-radius: 999px; }
.sc-badge-off { background: rgba(220,38,38,.12); color: #dc2626; }
.sc-split { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
    gap: 12px 22px; margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--pe-border); font-size: 13px; }

/* ── Delinquent accounts (admin.html → Payments → Delinquent) ───────────────
   Lives here for the same reason the block above does: admin.html has no
   <style> of its own. The table itself is the shared DataTable; these are only
   the summary tiles and the status badge. ── */
.dq-stats {
    display: grid; gap: 10px; margin: 0 0 16px;
    /* minmax(0, …) — a nowrap tile inside a grid track grows the whole page on
       a phone; the 0 floor lets the track shrink instead. */
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
}
.dq-stat {
    min-width: 0;
    border: 1px solid var(--pe-border); border-radius: 10px;
    padding: 12px 14px; background: var(--pe-card, #fff);
}
.dq-n { display: block; font-size: 22px; font-weight: 700; color: var(--pe-text); line-height: 1.2; }
.dq-l { display: block; font-size: 12px; color: var(--pe-text-muted); margin-top: 2px; }
.dq-badge {
    display: inline-block; font-size: 11px; font-weight: 700;
    padding: 3px 9px; border-radius: 6px; white-space: nowrap;
}
.dq-past_due  { background: rgba(217,119,6,.14);  color: #b45309; }
.dq-suspended { background: rgba(220,38,38,.12);  color: #dc2626; }
.dq-cancelled { background: rgba(100,116,139,.16); color: #475569; }
[data-theme="dark"] .dq-past_due  { color: #fbbf24; }
[data-theme="dark"] .dq-suspended { color: #f87171; }
[data-theme="dark"] .dq-cancelled { color: #94a3b8; }

/* Date and number filters — the same control as .pe-fb-select, narrower because
   a date is a fixed width. min-width, not width: Chrome's date input will not
   shrink below its own picker chrome and a hard width clips the calendar button.
   .pe-fb-num is the numeric one ("Max $"), narrower again; it shares every
   declaration on purpose, so it cannot drift from the date box. */
input.pe-fb-date,
input.pe-fb-num {
    height: 38px; padding: 0 10px;
    border: 1px solid var(--pe-border); border-radius: 8px;
    background: var(--pe-card, #fff); color: var(--pe-text);
    font-size: 14px; font-family: inherit; min-width: 150px; cursor: pointer;
}
/* width, not min-width: the caveat above is about Chrome's DATE picker chrome,
   which a number box does not have. Left on min-width it sized to the number
   input's own ~176px default and read as the widest control in the row. */
input.pe-fb-num { width: 110px; min-width: 0; cursor: text; }
[data-theme="dark"] input.pe-fb-date,
[data-theme="dark"] input.pe-fb-num { background: var(--pe-card-bg, #1f2937); }
/* The picker glyph is near-black on a dark surface otherwise. */
[data-theme="dark"] input.pe-fb-date::-webkit-calendar-picker-indicator { filter: invert(1) opacity(.7); }
select.pe-fb-select:focus,
input.pe-fb-date:focus,
input.pe-fb-num:focus,
.pe-fb-gb:focus-visible,
.pe-fb-search input:focus,
.pe-filterbar .dt-perpage-select:focus {
    outline: none; border-color: var(--pe-blue, #38bdf8); box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.15);
}
[data-theme="dark"] select.pe-fb-select,
[data-theme="dark"] .pe-fb-gb,
[data-theme="dark"] .pe-fb-search input { background: var(--pe-card-bg, #1f2937); }
/* A primary action sitting in the bar matches the controls. Without this a
   .btn is ~43px and .btn-sm is 27px, so the row stepped up and down. */
.pe-filterbar .btn { height: 38px; padding: 0 18px; border-radius: 8px; }
/* Inline checkbox filter ("Pending only"), so it sits on the same baseline. */
.pe-fb-check {
    display: inline-flex; align-items: center; gap: 7px; height: 38px;
    font-size: 13.5px; color: var(--pe-text); white-space: nowrap; cursor: pointer;
}
.pe-fb-check input { margin: 0; cursor: pointer; }
/* Uniform per-page height inside the shared bar (matches the 38px controls) */
.pe-filterbar .dt-perpage-select { height: 38px; }
@media (max-width: 640px) {
    .pe-fb-right { margin-left: 0; width: 100%; }
    .pe-fb-search { flex: 1; }
    .pe-fb-search input { width: 100%; }
}

/* ══ Unified status badge system ═══════════════════════════════════════════
   One component + one color per status key, shared by divisions, seasons,
   teams and events (and everywhere status renders). Colors are driven by
   CSS variables so any hue is a one-line tweak with zero markup churn.
   Status meaning → color: green=live, blue=scheduled, amber/orange=transitional,
   violet=running, slate/gray=done or idle. */
:root {
    --st-draft-bg:#f1f5f9;       --st-draft-fg:#64748b;
    --st-upcoming-bg:#dbeafe;    --st-upcoming-fg:#1d4ed8;
    --st-open-bg:#dcfce7;        --st-open-fg:#15803d;
    --st-reg_closed-bg:#fef3c7;  --st-reg_closed-fg:#b45309;
    --st-in_progress-bg:#ede9fe; --st-in_progress-fg:#6d28d9;
    --st-completed-bg:#e2e8f0;   --st-completed-fg:#475569;
    --st-archived-bg:#cbd5e1;    --st-archived-fg:#334155;
    --st-forming-bg:#ffedd5;     --st-forming-fg:#c2410c;
    --st-full-bg:#cffafe;        --st-full-fg:#0e7490;
    --st-active-bg:#dcfce7;      --st-active-fg:#15803d;
    --st-inactive-bg:#f1f5f9;    --st-inactive-fg:#64748b;
}
[data-theme="dark"] {
    --st-draft-bg:#1e293b;       --st-draft-fg:#94a3b8;
    --st-upcoming-bg:#172554;    --st-upcoming-fg:#93c5fd;
    --st-open-bg:#14532d;        --st-open-fg:#4ade80;
    --st-reg_closed-bg:#451a03;  --st-reg_closed-fg:#fbbf24;
    --st-in_progress-bg:#2e1065; --st-in_progress-fg:#c4b5fd;
    --st-completed-bg:#1e293b;   --st-completed-fg:#cbd5e1;
    --st-archived-bg:#0f172a;    --st-archived-fg:#94a3b8;
    --st-forming-bg:#431407;     --st-forming-fg:#fdba74;
    --st-full-bg:#083344;        --st-full-fg:#67e8f9;
    --st-active-bg:#14532d;      --st-active-fg:#4ade80;
    --st-inactive-bg:#1e293b;    --st-inactive-fg:#94a3b8;
}
.pe-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 3px 10px; border-radius: 999px;
    font-size: 11px; font-weight: 700; letter-spacing: .02em;
    line-height: 1.5; white-space: nowrap; text-transform: capitalize;
}
.pe-badge .pe-badge-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
.pe-badge.draft       { background: var(--st-draft-bg);       color: var(--st-draft-fg); }
.pe-badge.upcoming    { background: var(--st-upcoming-bg);    color: var(--st-upcoming-fg); }
.pe-badge.open        { background: var(--st-open-bg);        color: var(--st-open-fg); }
.pe-badge.reg_closed  { background: var(--st-reg_closed-bg);  color: var(--st-reg_closed-fg); }
.pe-badge.in_progress { background: var(--st-in_progress-bg); color: var(--st-in_progress-fg); }
.pe-badge.completed   { background: var(--st-completed-bg);   color: var(--st-completed-fg); }
.pe-badge.archived    { background: var(--st-archived-bg);    color: var(--st-archived-fg); }
.pe-badge.forming     { background: var(--st-forming-bg);     color: var(--st-forming-fg); }
.pe-badge.full        { background: var(--st-full-bg);        color: var(--st-full-fg); }
.pe-badge.active      { background: var(--st-active-bg);      color: var(--st-active-fg); }
.pe-badge.inactive    { background: var(--st-inactive-bg);    color: var(--st-inactive-fg); }
/* Back-compat safety net: teams' ended state was renamed closed→completed.
   Any page still on the legacy .team-badge class lacks a .completed rule, so
   map it here to the unified color (page-local rules for other keys still win).
   Removed once the app-wide sweep migrates every page to .pe-badge. */
.team-badge.completed { background: var(--st-completed-bg); color: var(--st-completed-fg); }

.dt-pager { display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.dt-page-label { color: var(--pe-text-secondary); }
.dt-page-num {
    min-width: 30px; height: 30px; padding: 0 8px;
    border: 1px solid transparent; border-radius: 8px;
    background: transparent; color: var(--pe-text); cursor: pointer;
    font-family: inherit; font-size: 13px;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.dt-page-num:hover { border-color: var(--pe-border); background: var(--pe-hover, #f8fafc); }
.dt-page-num.active {
    background: var(--pe-blue, #38bdf8); border-color: var(--pe-blue, #38bdf8);
    color: #fff; font-weight: 600; cursor: default;
}
.dt-page-gap { min-width: 20px; text-align: center; color: var(--pe-text-muted); user-select: none; }
.dt-page-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; padding: 0;
    border: 1px solid var(--pe-border); border-radius: 8px;
    background: var(--pe-card, #fff); color: var(--pe-text); cursor: pointer;
    transition: border-color 0.15s, color 0.15s;
}
.dt-page-btn:hover:not([disabled]) { border-color: var(--pe-blue); color: var(--pe-blue); }
.dt-page-btn[disabled] { opacity: 0.4; cursor: default; }
.dt-page-btn svg { width: 15px; height: 15px; }

/* Card list is the mobile form; table is desktop. Toggle at 768px. */
.dt-cards { display: none; }
@media (max-width: 767px) {
    .data-table-root .dt-wrap { display: none; }
    .data-table-root .dt-cards { display: block; }
    .dt-footer { flex-direction: column; align-items: stretch; gap: 8px; text-align: center; }
    .dt-pager { justify-content: center; }
}

/* Dark mode */
[data-theme="dark"] .dt-wrap,
[data-theme="dark"] .dt-bulk-btn,
[data-theme="dark"] .dt-bulk-clear,
[data-theme="dark"] .dt-cta,
[data-theme="dark"] .dt-page-btn { background: var(--pe-card-bg, #1e293b); border-color: var(--pe-border); }
[data-theme="dark"] .dt-row:hover { background: rgba(255,255,255,0.04); }
[data-theme="dark"] .dt-bulk-btn.danger:hover { background: rgba(220,38,38,0.15); }

/* ─────────────────────────────────────────────────────────
   Action Center — Platform Admin overview
   "What needs you" panel. Each row is one operational item
   (stale invites, missing MFA, dormant league, etc.) with a
   severity-keyed colored left rail.
   ───────────────────────────────────────────────────────── */
.ac-item {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 18px;
    border-bottom: 1px solid var(--pe-border);
    border-left: 3px solid transparent;
    transition: background 0.15s;
}
.ac-item:last-child { border-bottom: 0; }
.ac-item:hover { background: rgba(56, 189, 248, 0.04); }

.ac-sev-urgent  { border-left-color: var(--pe-danger); }
.ac-sev-warning { border-left-color: #f59e0b; }
.ac-sev-info    { border-left-color: #38bdf8; }

.ac-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 10px;
    flex-shrink: 0;
}
.ac-icon svg { width: 18px; height: 18px; }
.ac-sev-urgent  .ac-icon { background: #fee2e2; color: #b91c1c; }
.ac-sev-warning .ac-icon { background: #fef3c7; color: #92400e; }
.ac-sev-info    .ac-icon { background: #dbeafe; color: #1d4ed8; }

.ac-body { flex: 1; min-width: 0; }
.ac-title {
    font-weight: 600;
    font-size: 14px;
    color: var(--pe-text);
    margin-bottom: 2px;
}
.ac-desc {
    font-size: 13px;
    color: var(--pe-text-muted);
    line-height: 1.4;
}

.ac-action {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 13px;
    font-weight: 600;
    color: var(--pe-blue, #38bdf8);
    text-decoration: none;
    padding: 6px 10px;
    border-radius: 6px;
    flex-shrink: 0;
    white-space: nowrap;
    transition: background 0.15s;
}
.ac-action:hover { background: rgba(56, 189, 248, 0.12); }
.ac-action svg { width: 14px; height: 14px; }

[data-theme="dark"] .ac-item:hover { background: rgba(56, 189, 248, 0.08); }
[data-theme="dark"] .ac-sev-urgent  .ac-icon { background: rgba(239, 68, 68, 0.2);  color: #fca5a5; }
[data-theme="dark"] .ac-sev-warning .ac-icon { background: rgba(245, 158, 11, 0.2); color: #fbbf24; }
[data-theme="dark"] .ac-sev-info    .ac-icon { background: rgba(56, 189, 248, 0.2); color: #93c5fd; }

@media (max-width: 599px) {
    .ac-item { flex-wrap: wrap; padding: 12px 14px; }
    .ac-body { flex-basis: calc(100% - 50px); }
    .ac-action { margin-left: auto; }
}

.content-card {
    background: var(--pe-white);
    border-radius: 14px;
    padding: 24px;
    box-shadow: var(--pe-shadow);
    border: 1px solid var(--pe-border);
    margin-bottom: 20px;
    transition: box-shadow 0.2s ease;
}

.content-card:hover {
    box-shadow: var(--pe-shadow-md);
}

.content-card .card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 18px;
}

.content-card .card-title {
    font-size: 17px;
    font-weight: 700;
}

/* Full-bleed card: a padded title header sitting directly on top of an
   edge-to-edge table (no big gap between the title and the table). Use on
   content-cards whose body is a full-width table instead of padded content. */
.content-card.card-flush { padding: 0; overflow: hidden; }
.content-card.card-flush .card-header { padding: 15px 18px; margin-bottom: 0; }

/* ── Quick Action Buttons ───────────────────────────── */

.quick-actions {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* Two-up inside ONE card — the league dashboard's merged settings card
   (2026-09-16). It was two cards in a .dashboard-grid until three of its four
   shortcuts turned out to be duplicates of something else on the same screen.
   1200px of VIEWPORT, not content: the sidebar takes ~264px, so this leaves
   ~870px here and two ~430px columns hold "Admins & emergency contact" on one
   line. Below that it stays the single column .quick-actions already is, which
   is also what the phone and tablet bands want. */
@media (min-width: 1200px) {
    .quick-actions-2col {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

.quick-action-btn {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 18px;
    background: var(--pe-white);
    border: 1.5px solid var(--pe-border);
    border-radius: 12px;
    font-family: inherit;
    font-size: 14px;
    font-weight: 500;
    color: var(--pe-text);
    cursor: pointer;
    text-decoration: none;
    transition: all 0.2s ease;
}

.quick-action-btn:hover {
    border-color: var(--pe-blue);
    background: var(--pe-blue-light);
    transform: translateX(4px);
}

.quick-action-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
}

.quick-action-btn:disabled:hover {
    border-color: var(--pe-border);
    background: var(--pe-white);
    transform: none;
}

.quick-action-icon {
    width: 40px;
    height: 40px;
    border-radius: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.quick-action-icon svg {
    width: 20px;
    height: 20px;
}

.quick-action-label {
    flex: 1;
    text-align: left;
}

.quick-action-arrow {
    color: var(--pe-text-tertiary);
    transition: transform 0.2s ease, color 0.2s ease;
}

.quick-action-btn:hover .quick-action-arrow {
    transform: translateX(3px);
    color: var(--pe-blue);
}

.quick-action-arrow svg {
    width: 16px;
    height: 16px;
}

/* ── Coming Soon Tiles (compact grid) ──────────────── */

.quick-actions-upcoming {
    margin-top: 4px;
}

.quick-actions-upcoming-label {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--pe-text-tertiary);
    margin-bottom: 8px;
    padding-left: 2px;
}

.quick-actions-upcoming-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
}

.qa-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 14px 8px;
    border: 1px dashed var(--pe-border-medium);
    border-radius: var(--pe-radius);
    background: var(--pe-surface);
    opacity: 0.55;
}

.qa-tile-icon {
    width: 32px;
    height: 32px;
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.qa-tile-icon svg {
    width: 16px;
    height: 16px;
}

.qa-tile-label {
    font-size: 12px;
    font-weight: 500;
    color: var(--pe-text-secondary);
    text-align: center;
    line-height: 1.2;
}

/* On mobile, stack the tiles back to rows */
@media (max-width: 599px) {
    .quick-actions-upcoming-grid {
        grid-template-columns: 1fr;
        gap: 6px;
    }

    .qa-tile {
        flex-direction: row;
        padding: 10px 14px;
        gap: 12px;
    }

    .qa-tile-label {
        text-align: left;
        font-size: 13px;
    }
}

/* Small avatar (reusable) */
.avatar-sm {
    width: 36px;
    height: 36px;
    min-width: 36px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--pe-blue), var(--pe-blue-dark));
    color: white;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 13px;
    font-weight: 700;
}

/* Dashboard player list rows */
.dashboard-player-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 16px;
    border-bottom: 1px solid var(--pe-border-light);
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    transition: background 0.15s ease;
}
.dashboard-player-row:hover {
    background: var(--pe-bg);
}
.dashboard-player-row:last-child {
    border-bottom: none;
}
.dashboard-player-info {
    flex: 1;
    min-width: 0;
}
.dashboard-player-name {
    font-size: 14px;
    font-weight: 600;
    color: var(--pe-text);
}
.dashboard-player-detail {
    font-size: 12px;
    color: var(--pe-text-tertiary);
    margin-top: 2px;
}
.dashboard-player-chevron {
    color: var(--pe-text-tertiary);
    flex-shrink: 0;
    transition: transform 0.15s ease, color 0.15s ease;
}
.dashboard-player-chevron svg {
    width: 16px;
    height: 16px;
}
.dashboard-player-row:hover .dashboard-player-chevron {
    color: var(--pe-blue);
    transform: translateX(2px);
}

/* ── Player Cards ─────────────────────────────────────── */

.player-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 16px;
}

.player-card {
    background: var(--pe-white);
    border-radius: 14px;
    padding: 0;
    box-shadow: var(--pe-shadow);
    border: 1px solid var(--pe-border);
    transition: all 0.25s ease;
    overflow: hidden;
}

.player-card:hover {
    box-shadow: var(--pe-shadow-md);
}

.player-card .player-header {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 20px 20px 16px;
}

.player-card .player-avatar {
    width: 52px;
    height: 52px;
    border-radius: 14px;
    background: linear-gradient(135deg, var(--pe-blue), var(--pe-blue-dark));
    color: white;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 17px;
    font-weight: 700;
    flex-shrink: 0;
    letter-spacing: 0.5px;
}

.player-card .player-name {
    font-size: 16px;
    font-weight: 700;
    color: var(--pe-text);
}

.player-card .player-age {
    font-size: 13px;
    color: var(--pe-text-secondary);
    margin-top: 2px;
}

.player-card .player-details {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px 12px;
    font-size: 13px;
    padding: 14px 20px;
    background: var(--pe-surface);
    border-top: 1px solid var(--pe-border-light);
}

.player-card .detail-label {
    color: var(--pe-text-secondary);
    font-weight: 500;
}

.player-card .detail-value {
    font-weight: 600;
    text-align: right;
    color: var(--pe-text);
}

.player-card .player-actions {
    display: flex;
    gap: 8px;
    padding: 14px 20px;
    border-top: 1px solid var(--pe-border-light);
}

/* ── Empty State ──────────────────────────────────────── */

.empty-state {
    text-align: center;
    padding: 60px 24px;
    color: var(--pe-gray);
}

/* `.empty-state-icon` is an alias: events / financials / registrations use that
   name, and it was styled nowhere — so those three rendered a raw oversized
   SVG while every other page got the circular badge. Aliased rather than
   renamed in the markup so both spellings are safe from here on. */
.empty-state .empty-icon,
.empty-state .empty-state-icon {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--pe-blue-light) 0%, #e0f2fe 100%);
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto 20px;
}

.empty-state .empty-icon svg,
.empty-state .empty-state-icon svg {
    width: 36px;
    height: 36px;
    color: var(--pe-blue-dark);
}

/* An empty state that is really a FAILURE gets a muted badge, so "nothing here
   yet" and "we couldn't load this" don't look identical (§3.5). */
.empty-state.is-error .empty-icon,
.empty-state.is-error .empty-state-icon {
    background: var(--pe-gray-light);
}

.empty-state.is-error .empty-icon svg,
.empty-state.is-error .empty-state-icon svg {
    color: var(--pe-text-secondary);
}

.empty-state h3 {
    color: var(--pe-text);
    margin-bottom: 8px;
    font-size: 18px;
}

.empty-state p {
    font-size: 14px;
    margin-bottom: 24px;
    color: var(--pe-text-secondary);
    max-width: 360px;
    margin-left: auto;
    margin-right: auto;
}

/* ── Modal ────────────────────────────────────────────── */

.modal-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.5);
    z-index: 200;
    align-items: center;
    justify-content: center;
    padding: 20px;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}

.modal-overlay.show {
    display: flex;
}

/* Scroll-lock: while any modal/drawer is open, freeze the page behind it so only
   the panel scrolls (its .modal / .modal-body keep their own overflow). Single
   app-wide rule via :has() — the per-page openModal() just toggles .show, and
   this file already relies on :has() elsewhere. */
html:has(.modal-overlay.show),
body:has(.modal-overlay.show) {
    overflow: hidden;
}

.modal {
    background: var(--pe-white);
    border-radius: 20px;
    padding: 0;
    width: 100%;
    /* Default form-modal width. Bumped 520→600 so add/edit forms aren't skinny on
       large screens (max-width is a cap; it never grows past this). Confirms stay
       narrow via .modal-sm; genuinely dense forms override wider (event 640,
       facility 680); recycle bins use .bulk-modal (640). Mobile: full-width sheet. */
    max-width: 600px;
    max-height: 90vh;
    max-height: 90dvh;
    overflow-y: auto;
    overflow-x: hidden;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15), 0 4px 16px rgba(0, 0, 0, 0.08);
    animation: modalIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Modals inherit the app-wide sleek floating scrollbar (defined at the top of
   this file): a subtle inset pill with a transparent border, so the thumb no
   longer clashes with the modal's 20px rounded corners. No bespoke override. */

@keyframes modalIn {
    from { opacity: 0; transform: scale(0.9) translateY(20px); }
    to   { opacity: 1; transform: scale(1) translateY(0); }
}

.modal-header-bar {
    padding: 24px 32px 0;
    position: relative;
}

.modal-close-x {
    position: absolute;
    top: 20px;
    right: 24px;
    width: 32px;
    height: 32px;
    border: none;
    background: var(--pe-hover, #f1f5f9);
    border-radius: 8px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--pe-text-muted);
    padding: 0;
    transition: background 0.15s, color 0.15s;
    z-index: 1;
}
.modal-close-x svg {
    width: 18px;
    height: 18px;
}
/* Drawer expand/shrink button is hidden by default; only shown on drawers at
   >=600px (below that the drawer is a full-width bottom sheet — nothing to
   expand). The >=600px rules live in the modal-drawer @media block below. */
.modal-expand-x { display: none; }
/* ── Stacked drawers: the way back ───────────────────────────────────────
   Shown only on a drawer that opened over another drawer (api.js adds
   `is-stacked` and injects the button). It closes THIS panel and leaves the
   one beneath — which the X does not say and, on a stack, does not look like
   it means. Not inside the >=600px drawer block on purpose: below that a
   drawer is a full-width bottom sheet, where a second sheet hides the first
   completely and the button is the ONLY thing distinguishing "go back" from
   "give up". The chevron points left, at the strip of the drawer underneath. */
.modal-back-x {
    display: none;
    align-items: center;
    gap: 6px;
    margin: 0 0 12px;
    padding: 7px 12px 7px 9px;
    border: 1px solid var(--pe-border);
    border-radius: 8px;
    background: var(--pe-hover, #f1f5f9);
    color: var(--pe-text-muted);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.modal-overlay.is-stacked .modal-back-x { display: inline-flex; }
/* A stacked drawer paints ABOVE the one it stacks on, whatever the DOM order.
   Every overlay is z-index 200, so until this the later sibling won:
   #rnDocPickDrawer is written before #rnDrawer in rentals.html and opened
   BEHIND it — "Add document" looked like it did nothing. Unconditional on
   purpose: the bottom-sheet form under 600px has the same order problem. */
.modal-overlay.is-stacked { z-index: 201; }
.modal-back-x svg { width: 15px; height: 15px; }
.modal-back-x:hover {
    background: var(--pe-blue, #38bdf8);
    border-color: var(--pe-blue, #38bdf8);
    color: #fff;
}
[data-theme="dark"] .modal-back-x { background: rgba(255, 255, 255, 0.08); }

/* The header title row (title + Publish toggle) must reserve space for the
   absolutely-positioned close (X); otherwise the toggle sits under it. */
.modal-header-bar .seas-modal-title-row {
    margin-right: 44px;
}
.modal-close-x:hover {
    background: var(--pe-border);
    color: var(--pe-text);
}

[data-theme="dark"] .modal-close-x {
    background: rgba(255, 255, 255, 0.06);
}
[data-theme="dark"] .modal-close-x:hover {
    background: rgba(255, 255, 255, 0.1);
    color: var(--pe-text);
}

.modal-title {
    font-size: 22px;
    font-weight: 800;
    margin-bottom: 4px;
    /* em, not px, so the tracking holds if the size changes. */
    letter-spacing: -0.022em;
}

.modal-subtitle {
    font-size: 14px;
    color: var(--pe-text-secondary);
    margin-bottom: 24px;
}

.modal-body {
    padding: 0 32px 32px;
}

/* Legacy support: modal without .modal-header-bar */
.modal > .modal-title:first-child,
.modal > h2.modal-title:first-child {
    padding: 24px 32px 0;
}

.modal > .modal-subtitle {
    padding: 0 32px;
}

.modal > form,
.modal > .text-muted {
    padding: 0 32px;
}

.modal > .modal-actions {
    padding: 0 32px 24px;
}

.modal-actions {
    display: flex;
    gap: 10px;
    justify-content: flex-end;
    margin-top: 24px;
    padding-top: 18px;
    border-top: 1px solid var(--pe-border);
}

.modal-sm { max-width: 400px; }
.modal-lg { max-width: 680px; }

/* ── Slide-in drawer variant ─────────────────────────────────────────────
   Add `modal-drawer` to a `.modal-overlay` to turn its centered popup into a
   full-height panel that slides in from the right — better for long forms like
   Edit Season. The header stays pinned at the top and the .modal-actions footer
   sticks to the bottom; only the form body scrolls (no inner-vs-page ambiguity).
   MOBILE IS DELIBERATELY UNTOUCHED: these rules are gated to >=600px, so below
   that the existing bottom-sheet rules (further down) keep the native sheet +
   drag handle. Reusable — any big edit form can opt in with this one class. */
@media (min-width: 600px) {
    /* Smooth (ease-in-out) slide, animating BOTH in and out. The panel is driven by
       CSS transitions off the `.show` class instead of a one-shot keyframe, so the
       per-page closeModal() (which just removes .show) slides it back out with no JS
       change. The overlay stays rendered while closed (visibility-hidden) so the exit
       can animate; its hide is delayed until the slide finishes. The dim moves to a
       ::before so it can fade with the panel. */
    .modal-overlay.modal-drawer {
        --drawer-dur: 0.42s;
        --drawer-ease: cubic-bezier(0.65, 0, 0.35, 1);   /* smooth ease-in-out */
        justify-content: flex-end;
        align-items: stretch;
        padding: 0;
        display: flex;
        visibility: hidden;
        pointer-events: none;
        background: transparent;
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
        transition: visibility 0s linear var(--drawer-dur);
    }
    .modal-overlay.modal-drawer.show {
        visibility: visible;
        pointer-events: auto;
        transition-delay: 0s;
    }
    /* The dim behind a drawer (2026-09-10): drawers open WIDE with the page
       blurred; shrunk to the narrow width the blur goes and the dim lightens, so
       the page and the drawer can be read side by side. Both transition. */
    .modal-overlay.modal-drawer::before {
        content: '';
        position: absolute;
        inset: 0;
        z-index: 0;
        background: rgba(15, 23, 42, 0.18);
        -webkit-backdrop-filter: blur(0px);
        backdrop-filter: blur(0px);
        opacity: 0;
        transition: opacity var(--drawer-dur) var(--drawer-ease),
                    background 0.32s var(--drawer-ease),
                    -webkit-backdrop-filter 0.32s var(--drawer-ease),
                    backdrop-filter 0.32s var(--drawer-ease);
    }
    /* THE BLUR WAITS FOR THE SLIDE TO FINISH (.is-settled, added by api.js a
       beat after the panel is shown, removed the moment it closes).

       This layer is the full viewport, and a backdrop-filter makes the compositor
       re-blur everything behind it on every frame it is composited. Run that
       through a 420ms slide and it is the most expensive thing in the animation
       by a wide margin — and it is GPU work, so it shows up in none of the
       main-thread measures: no long task, no blocking time in a long animation
       frame, no forced layout. It also scales with the screen, which is why a
       1440x860 pane could not reproduce what Robb saw on a large display, three
       times over ("the drawer slide is clunky", "still REALLY HORRIBLE").

       Deferring costs nothing in how it looks at rest: the dim still fades in
       with the panel, and the blur eases in behind it once the panel has stopped
       moving. Nothing about the open or shut state changes — only WHEN the
       expensive part happens. */
    .modal-overlay.modal-drawer.is-expanded::before {
        background: rgba(15, 23, 42, 0.5);
    }
    .modal-overlay.modal-drawer.is-expanded.is-settled::before {
        -webkit-backdrop-filter: blur(6px);
        backdrop-filter: blur(6px);
    }
    [data-theme="dark"] .modal-overlay.modal-drawer::before { background: rgba(0, 0, 0, 0.28); }
    [data-theme="dark"] .modal-overlay.modal-drawer.is-expanded::before { background: rgba(0, 0, 0, 0.6); }
    .modal-overlay.modal-drawer.show::before { opacity: 1; }
    /* A STACKED drawer does not dim again. The drawer underneath has already
       dimmed and blurred the page; a second pass at 0.5 + blur(6px) lands on the
       strip the stagger just revealed and turns it almost black — so the one
       thing the stagger exists to show is the one thing you cannot see. Killing
       the second dim leaves the lower panel at its own brightness, reading as a
       panel behind rather than as a dark edge, and the page further out stays
       dimmed by the bottom drawer exactly as before.
       Separation between the two white panels comes from a shadow down the
       stacked panel's left edge instead (below). */
    .modal-overlay.modal-drawer.is-stacked.show::before {
        opacity: 0;
        /* Belt and braces: opacity:0 already hides the layer, but a
           backdrop-filter on an invisible element still costs a GPU blur
           pass over the whole viewport on every frame of the slide, and the
           interaction between the two is the sort of thing engines disagree
           about. Turn it off rather than rely on being hidden. */
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
    }
    .modal-overlay.modal-drawer.is-stacked .modal {
        box-shadow: -16px 0 44px rgba(15, 23, 42, 0.26);
    }
    .modal-drawer .modal {
        position: relative;
        z-index: 1;                   /* above the ::before dim */
        width: 560px;
        max-width: 92vw;
        height: 100vh;
        height: 100dvh;
        max-height: none;
        border-radius: 0;
        display: flex;
        flex-direction: column;
        overflow: hidden;             /* the body scrolls, not the whole panel */
        animation: none;              /* override the base .modal `modalIn` pop — this panel slides */
        transform: translateX(100%);
        transition: transform var(--drawer-dur) var(--drawer-ease), width 0.32s var(--drawer-ease);
    }
    /* Expand/shrink: widen the drawer panel to use more of the screen. Toggled by
       the injected .modal-expand-x button (see api.js addExpandToggle). */
    .modal-overlay.modal-drawer.is-expanded .modal { width: min(1100px, 94vw); }
    /* A drawer owns its own width: 560px collapsed, min(1100px, 94vw) expanded.
       `.modal.modal-wide` also sets max-width:880px (meant for CENTERED modals).
       It ties with `.modal-drawer .modal` on specificity and sits LATER in this
       file, so it silently capped every modal-wide DRAWER at 880px while all the
       others expanded to 1100px — the Events / Teams / Sponsorship-publish drawers
       were visibly narrower than the rest. Re-assert the drawer's own cap here. */
    .modal-overlay.modal-drawer .modal.modal-wide { max-width: 92vw; }
    .modal-drawer .modal-expand-x {
        position: absolute;
        top: 20px;
        right: 64px;                  /* sits just left of the close (X) at right:24px */
        width: 32px;
        height: 32px;
        border: none;
        background: var(--pe-hover, #f1f5f9);
        border-radius: 8px;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        color: var(--pe-text-muted);
        padding: 0;
        transition: background 0.15s, color 0.15s;
        z-index: 1;
    }
    .modal-drawer .modal-expand-x svg { width: 17px; height: 17px; }
    .modal-drawer .modal-expand-x:hover { background: var(--pe-blue, #38bdf8); color: #fff; }
    [data-theme="dark"] .modal-drawer .modal-expand-x { background: rgba(255, 255, 255, 0.08); }
    /* Reserve header space so a long title never slides under the two buttons. */
    .modal-drawer .modal-title { padding-right: 76px; }
    /* Season/Event drawers carry a Publish toggle in the header title row.
       Reserve room on the right so it clears BOTH the expand and close buttons
       (which occupy ~right:24-96px); the row handles the spacing, so the title
       inside it doesn't need the extra padding. */
    .modal-drawer .seas-modal-title-row { margin-right: 104px; }
    .modal-drawer .seas-modal-title-row .modal-title { padding-right: 0; }
    .modal-drawer.show .modal {
        transform: translateX(0);
    }
    /* The stagger. The drawer underneath steps LEFT by a strip per level above
       it, so the stack reads as a stack instead of one panel replacing another
       in place. api.js writes --drawer-shift (see "Stacked drawers" there for
       why the lower panel moves rather than the upper one narrowing). This has
       to sit AFTER the `.show .modal` rule above — it overrides that rule's
       translateX(0), and carries more classes only because it has to win. */
    .modal-overlay.modal-drawer.show.has-stacked-child .modal {
        transform: translateX(calc(-1 * var(--drawer-shift, 0px)));
    }
    .modal-drawer .modal-header-bar {
        flex-shrink: 0;               /* header (title + Publish toggle) stays at top */
    }
    .modal-drawer .modal-body {
        flex: 1;
        min-height: 0;
        overflow-y: auto;            /* only the form scrolls */
        padding-bottom: 0;
    }
    /* Footer actions pinned to the panel bottom. They live inside the form inside
       .modal-body, so sticky pins them with no markup change; the negative side
       margins bleed the bar to the panel edges over the body's 32px padding. */
    .modal-drawer .modal-actions {
        position: sticky;
        bottom: 0;
        margin: 24px -32px 0;
        padding: 16px 32px;
        background: var(--pe-white);
        border-top: 1px solid var(--pe-border);
    }
    /* When impersonating, the fixed banner (z-index:9999) sits over the top 36px.
       The overlay is fixed to the viewport and ignores the shell's padding-top, so
       drop the panel below the banner and shorten it to match. */
    body.is-impersonating .modal-overlay.modal-drawer .modal {
        height: calc(100vh - 36px);
        height: calc(100dvh - 36px);
        margin-top: 36px;
    }
}

/* ── Responsive wide form modal ──────────────────────────────────────────
   Add `modal-wide` to a dense form .modal: it widens on desktop and lays the
   form's top-level fields in TWO columns, so a tall form uses the horizontal
   space and fits without a vertical scrollbar. Existing .form-row groups (which
   already pair/triple fields), textareas, alerts, and the actions bar span the
   full width; a .col-span-2 override forces any field full-width. Collapses to
   one column (full-width sheet) on mobile. Row spacing comes from each
   .form-group's own margin, so row-gap stays 0. */
.modal.modal-wide { max-width: 880px; }
.modal.modal-wide > .modal-body > form {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 18px;
    row-gap: 0;
    align-items: start;
}
.modal.modal-wide > .modal-body > form > .form-row,
.modal.modal-wide > .modal-body > form > .modal-actions,
.modal.modal-wide > .modal-body > form > .alert,
.modal.modal-wide > .modal-body > form > .col-span-2,
.modal.modal-wide > .modal-body > form > .form-group:has(textarea) {
    grid-column: 1 / -1;
}
@media (max-width: 640px) {
    .modal.modal-wide { max-width: 100%; }
    .modal.modal-wide > .modal-body > form { grid-template-columns: 1fr; }
}

/* ── Field help tip (ⓘ after a field label) ──────────────────────────────
   Hover/focus reveals a bubble above the field. Shared here because it was
   duplicated (and drifting: 250px vs 260px) in events.html + divisions.html.

   The bubble is anchored to the LABEL, not to the ⓘ icon, and that is the whole
   point. Labels are block boxes that span their form column, so `left:0` +
   `max-width:100%` makes it structurally impossible for the bubble to reach past
   the column — and therefore impossible for it to widen a scrolling ancestor.

   Anchored to the icon instead (the old `left:-6px`), a tip in a right-hand
   column threw its 250px bubble well past the panel edge. Because a bubble keeps
   its layout box while `visibility:hidden`, that produced a PERMANENT horizontal
   scrollbar on the drawer body — present without ever hovering, and the reason
   only the two pages with ⓘ tips ever showed one. Don't re-anchor to the icon. */
.field-tip {
    position: static;             /* the LABEL is the bubble's containing block */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 15px;
    height: 15px;
    margin-left: 6px;
    border-radius: 50%;
    background: var(--pe-text-muted, #94a3b8);
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    font-style: normal;
    cursor: help;
    vertical-align: middle;
    flex-shrink: 0;
}
.field-tip:focus { outline: 2px solid var(--pe-blue); outline-offset: 1px; }
label:has(.field-tip) { position: relative; }
.field-tip .field-tip-bubble {
    position: absolute;
    bottom: calc(100% + 8px);
    left: 0;
    right: auto;
    width: 250px;
    max-width: 100%;              /* never wider than the label/column */
    padding: 9px 11px;
    background: #1f2937;
    color: #f8fafc;
    font-size: 12px;
    font-weight: 400;
    line-height: 1.45;
    text-align: left;
    border-radius: 8px;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.15s ease;
    z-index: 10;
    pointer-events: none;
    white-space: normal;
}
/* No tail. The bubble is anchored to the label (see above), so it cannot know
   where along that label the ⓘ icon sits — a fixed tail pointed at empty space,
   up to ~200px away from its own icon on a long label. A tail-less bubble above
   the field reads fine and is always honest about what it belongs to. */
.field-tip:hover .field-tip-bubble,
.field-tip:focus .field-tip-bubble { opacity: 1; visibility: visible; }
.field-tip strong, .field-tip b { font-weight: 600; }

/* ── Modal → Bottom Sheet on Mobile ──────────────────── */

@media (max-width: 599px) {
    .modal-overlay {
        align-items: flex-end;
        padding: 0;
        /* Lift the bottom sheet above the on-screen keyboard. --kb is the iOS
           keyboard height (0 on Android, where the viewport already shrinks). */
        padding-bottom: var(--kb, 0px);
    }

    .modal {
        border-radius: 20px 20px 0 0;
        max-width: 100%;
        max-height: 90vh;
        max-height: calc(100dvh - var(--kb, 0px));
        animation: bottomSheetUp 0.35s cubic-bezier(0.32, 0.72, 0, 1);
    }

    .modal::before {
        content: '';
        display: block;
        width: 36px;
        height: 4px;
        background: #d1d5db;
        border-radius: 2px;
        margin: 10px auto 4px;
        flex-shrink: 0;
    }

    .modal-sm, .modal-lg {
        max-width: 100%;
    }

    .modal-header-bar {
        padding: 16px 24px 0;
    }

    .modal-close-x {
        top: 12px;
        right: 16px;
    }

    /* 44px tap target on a phone (see the mobile pass at the end of this
       file). The desktop button is ~31px tall, which is fine with a mouse
       and not fine with a thumb — and on a phone this is the ONLY way back
       to the sheet underneath, so it is the last control that should be hard
       to hit. Padding, not a pseudo-element: it sits in normal flow, so
       there is nothing for an invisible overlay to mis-cover. */
    .modal-back-x { padding: 13px 14px 13px 11px; }

    .modal-body {
        padding: 0 24px 24px;
    }
}

@keyframes bottomSheetUp {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
}

/* ── Card-header action cluster (sort buttons + Add on Lookup Data) ── */
.card-header-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* ── Platform admin lists: table on desktop, cards on a phone ─────────
   The Leagues and League Admins tables are seven and six columns of inline
   markup; they scrolled sideways on a phone (Robb, 2026-10-25: no horizontal
   scrolling anywhere). Below 768px the table is hidden and the same rows
   render as .pa-card blocks. Both are in the DOM; CSS picks. */
.pa-cards { display: none; }
.pa-card { padding: 14px 16px; border-top: 1px solid var(--pe-border); }
.pa-card:first-child { border-top: 0; }
.pa-card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.pa-card-name { font-weight: 700; font-size: 15px; color: var(--pe-text); text-decoration: none; min-width: 0; overflow-wrap: anywhere; }
.pa-card-sub { font-size: 13px; color: var(--pe-text-muted); margin-top: 2px; overflow-wrap: anywhere; }
.pa-card-pills { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; align-items: center; }
.pa-card-meta { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 8px; font-size: 12.5px; color: var(--pe-text-muted); }
.pa-card-actions { display: flex; gap: 4px; flex-shrink: 0; }
.pa-card-empty { padding: 28px 16px; text-align: center; color: var(--pe-text-muted); }
@media (max-width: 767px) {
    .pa-table { display: none; }
    .pa-cards { display: block; }
}

/* ── Lookup Chips (Admin Page) ───────────────────────── */

.lookup-list {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 16px 20px;
}

.lookup-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px 6px 12px;
    border: 1px solid var(--pe-border);
    border-radius: 8px;
    background: var(--pe-surface);
    font-size: 13px;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.lookup-chip:hover {
    border-color: var(--pe-blue);
    box-shadow: 0 1px 4px rgba(56,189,248,0.1);
}

.lookup-chip-label {
    font-weight: 500;
    color: var(--pe-charcoal);
    white-space: nowrap;
}

.lookup-chip-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex-shrink: 0;
}

.lookup-chip-dot-active   { background: var(--pe-green); }
.lookup-chip-dot-inactive { background: var(--pe-text-tertiary); }

.lookup-chip-order {
    font-size: 11px;
    color: var(--pe-text-tertiary);
    font-weight: 500;
    min-width: 14px;
    text-align: center;
}

.lookup-chip-actions {
    display: flex;
    gap: 2px;
    margin-left: 2px;
}

.lookup-chip-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--pe-text-tertiary);
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
    padding: 0;
}

.lookup-chip-btn svg {
    width: 13px;
    height: 13px;
}

.lookup-chip-btn:hover {
    background: var(--pe-blue-light);
    color: var(--pe-blue-dark);
}

.lookup-chip-btn-danger:hover {
    background: var(--pe-red-light);
    color: var(--pe-red);
}

/* On phones the content-width chips wrap to one-per-line at ragged widths
   (and the longest overflows the card). Stack them as full-width rows: dot +
   label on the left, order + actions pinned right, long labels wrap instead
   of overflowing. */
@media (max-width: 599px) {
    .lookup-list { flex-direction: column; }
    .lookup-chip { width: 100%; }
    .lookup-chip-label {
        flex: 1;
        min-width: 0;
        white-space: normal;
        overflow-wrap: anywhere;
    }
}

/* ── Loading Spinner ──────────────────────────────────── */

.spinner {
    display: inline-block;
    width: 18px;
    height: 18px;
    border: 2.5px solid rgba(255,255,255,0.3);
    border-top-color: white;
    border-radius: 50%;
    animation: spin 0.6s linear infinite;
}

.spinner-dark {
    border-color: rgba(90,143,168,0.2);
    border-top-color: var(--pe-blue);
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* ── Badge ────────────────────────────────────────────── */

.badge {
    display: inline-flex;
    align-items: center;
    padding: 3px 10px;
    border-radius: 20px;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.badge-verified {
    background: #f0fdf4;
    color: #166534;
}

.badge-pending {
    background: #fffbeb;
    color: #92400e;
}

.badge-active {
    background: var(--pe-blue-light);
    color: var(--pe-blue-dark);
}

.badge-inactive {
    background: #f1f5f9;
    color: #64748b;
}

.badge-suspended {
    background: #fef2f2;
    color: #dc2626;
}

.badge-accepted {
    background: #f0fdf4;
    color: #166534;
}

.badge-expired {
    background: #f1f5f9;
    color: #94a3b8;
}

.badge-revoked {
    background: #f1f5f9;
    color: #94a3b8;
}

/* ── Admin Rows (Leagues & Invitations) ──────────────── */

.admin-rows {
    padding: 0;
}

.admin-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 20px;
    border-bottom: 1px solid var(--pe-border);
    gap: 16px;
    transition: background 0.15s;
}

.admin-row:last-child {
    border-bottom: none;
}

.admin-row:hover {
    background: rgba(0, 0, 0, 0.02);
}

.admin-row-info {
    flex: 1;
    min-width: 0;
}

.admin-row-name {
    font-weight: 600;
    font-size: 14px;
    color: var(--pe-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.admin-row-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    margin-top: 3px;
    font-size: 12px;
    color: var(--pe-text-muted);
}

.meta-dot {
    width: 3px;
    height: 3px;
    border-radius: 50%;
    background: var(--pe-text-muted);
    opacity: 0.5;
    flex-shrink: 0;
}

.admin-row-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

/* Icon buttons for admin rows */
.btn-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 8px;
    border: none;
    background: transparent;
    cursor: pointer;
    color: var(--pe-text-muted);
    transition: all 0.15s;
}

.btn-icon svg {
    width: 16px;
    height: 16px;
}

.btn-icon:hover {
    background: var(--pe-gray-light);
    color: var(--pe-text);
}

.btn-icon-danger:hover {
    background: #fef2f2;
    color: #dc2626;
}

/* Responsive: stack rows on mobile */
@media (max-width: 599px) {
    .admin-row {
        flex-direction: column;
        align-items: flex-start;
        gap: 10px;
        padding: 12px 16px;
    }

    .admin-row-actions {
        align-self: flex-end;
    }
}

/* ── Card Search Bar ────────────────────────────────── */

.card-search-bar {
    position: relative;
    padding: 10px 20px;
    border-bottom: 1px solid var(--pe-border);
}

.card-search-bar input {
    width: 100%;
    padding: 8px 12px 8px 34px;
    border: 1px solid var(--pe-border);
    border-radius: 8px;
    font-size: 13px;
    font-family: inherit;
    background: var(--pe-bg);
    color: var(--pe-text);
    outline: none;
    transition: border-color 0.2s;
}

.card-search-bar input:focus {
    border-color: var(--pe-blue);
}

.card-search-bar input::placeholder {
    color: var(--pe-text-muted);
}

.card-search-bar .search-icon {
    position: absolute;
    left: 30px;
    top: 50%;
    transform: translateY(-50%);
    width: 15px;
    height: 15px;
    color: var(--pe-text-muted);
    pointer-events: none;
}

.card-count {
    font-weight: 400;
    color: var(--pe-text-muted);
    font-size: 14px;
}

/* Show more / pagination row */
.show-more-row {
    padding: 12px 20px;
    text-align: center;
    border-top: 1px solid var(--pe-border);
}

.show-more-btn {
    background: none;
    border: none;
    color: var(--pe-blue);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    padding: 4px 12px;
    font-family: inherit;
    border-radius: 6px;
    transition: background 0.15s;
}

.show-more-btn:hover {
    background: rgba(56, 189, 248, 0.08);
}

/* No results from search */
.no-results {
    padding: 30px 20px;
    text-align: center;
    color: var(--pe-text-muted);
    font-size: 14px;
}

@media (max-width: 599px) {
    .card-search-bar {
        padding: 8px 16px;
    }
    .card-search-bar .search-icon {
        left: 26px;
    }
}

/* ── Clickable Badge Button ──────────────────────────── */

.badge-btn {
    display: inline-flex;
    align-items: center;
    padding: 3px 10px;
    border-radius: 20px;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    border: none;
    cursor: pointer;
    transition: opacity 0.15s, box-shadow 0.15s;
}

.badge-btn:hover {
    opacity: 0.85;
    box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.08);
}

/* ── Status History Timeline ─────────────────────────── */

.status-history-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.status-history-item {
    padding: 10px 14px;
    background: var(--pe-gray-light, #f8fafc);
    border-radius: 10px;
    border-left: 3px solid var(--pe-border);
}

.status-history-badges {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 6px;
}

.status-history-reason {
    font-size: 13px;
    color: var(--pe-text);
    line-height: 1.4;
    margin-bottom: 4px;
}

.status-history-meta {
    font-size: 11px;
    color: var(--pe-text-muted);
}

/* ── MFA Step ─────────────────────────────────────────── */

.mfa-qr {
    text-align: center;
    margin: 20px 0;
}

.mfa-qr img {
    background: white;
    padding: 12px;
    border-radius: 12px;
    border: 1px solid #eee;
}

.recovery-codes {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
    margin: 12px 0;
}

.recovery-codes code {
    background: var(--pe-gray-light);
    padding: 6px 10px;
    border-radius: 5px;
    font-family: 'DM Mono', monospace;
    font-size: 13px;
    text-align: center;
    font-weight: 600;
    color: var(--pe-charcoal);
}

/* ── Password Strength ────────────────────────────────── */

.password-rules {
    font-size: 12px;
    color: var(--pe-gray);
    margin-top: 6px;
}

.password-rules .rule {
    display: flex;
    align-items: center;
    gap: 5px;
    margin-top: 3px;
    transition: color 0.2s;
}

.password-rules .rule.pass { color: var(--pe-green); }
.password-rules .rule.fail { color: #ccc; }

/* ── Utility Classes ────────────────────────────────────── */

.btn-auto { width: auto; }

.btn-row {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 16px;
}

.btn-row-center {
    display: flex;
    justify-content: center;
    gap: 8px;
}

.btn-action-list {
    justify-content: flex-start;
    text-decoration: none;
}

.text-muted {
    font-size: 13px;
    color: var(--pe-text-secondary);
    line-height: 1.6;
}

.text-muted-sm {
    font-size: 12px;
    color: var(--pe-text-tertiary);
    line-height: 1.5;
}

/* Section divider inside modals/forms */
.form-section-divider {
    margin: 20px 0 12px;
    padding-top: 16px;
    border-top: 1px solid var(--pe-border-light);
}

.form-section-divider h3 {
    font-size: 15px;
    font-weight: 700;
    margin-bottom: 4px;
}

.form-section-divider p {
    font-size: 12px;
    color: var(--pe-text-tertiary);
    margin-bottom: 12px;
}

/* Copy from existing player button */
.btn-copy-ec {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    font-size: 13px;
    font-weight: 500;
    color: var(--pe-blue);
    background: var(--pe-blue-light, rgba(56, 189, 248, 0.08));
    border: 1px dashed var(--pe-blue);
    border-radius: 8px;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
}
.btn-copy-ec:hover {
    background: rgba(56, 189, 248, 0.15);
    border-style: solid;
}

/* Warning/danger callout box */
.callout-danger {
    background: var(--pe-red-light);
    border: 1px solid #fca5a5;
    border-radius: 8px;
    padding: 12px 16px;
    margin-bottom: 14px;
}

.callout-danger .callout-title {
    font-size: 13px;
    font-weight: 600;
    color: #dc2626;
    margin-bottom: 4px;
}

.callout-danger .callout-text {
    font-size: 12px;
    color: var(--pe-text-secondary);
    margin: 0;
}

/* MFA code input */
.mfa-code-input {
    text-align: center;
    font-size: 20px;
    font-weight: 700;
    letter-spacing: 6px;
}

/* ── Responsive ───────────────────────────────────────── */

/* ── Responsive: Form + Auth ─────────────────────────── */

@media (max-width: 1023px) {
    .form-row { grid-template-columns: 1fr; }
    .form-row-address { flex-direction: column; }
    .form-row-address > .form-group { flex: none !important; }
}

@media (max-width: 640px) {
    .auth-page { min-height: 100dvh; overflow: visible; }
    .auth-page::before,
    .auth-page::after { display: none; }
    /* Phone view: card sizes to its content + centers in the viewport.
       The prior rule set flex:1 on container + card + body which forced
       the whole card to stretch to 100dvh, leaving a huge empty white
       slab below the Sign In button. Now the card is the same shape as
       desktop, just narrower and with tighter padding. */
    .auth-container {
        padding: 24px 16px;
        margin: auto;
    }
    .auth-card-header {
        padding: 20px 24px 16px;
    }
    .auth-card-body {
        padding: 24px 24px 28px;
    }
    .form-row { grid-template-columns: 1fr; }
    .stats-grid { grid-template-columns: 1fr 1fr; gap: 10px; }
    .stat-card { padding: 16px; gap: 12px; }
    .stat-card .stat-icon { width: 42px; height: 42px; border-radius: 12px; }
    .stat-card .stat-icon svg { width: 20px; height: 20px; }
    .stat-card .stat-value { font-size: 24px; }
}

/* Tablet content tier: keep the primary in-app content grids multi-column
   across the whole 600–1023 tablet range; only collapse to one column on
   true phones (<600), matching the shell's mobile/tablet split. Previously
   these collapsed at ≤640, leaving small tablets on a single column. */
@media (max-width: 599px) {
    .player-grid { grid-template-columns: 1fr; }
    .dashboard-grid { grid-template-columns: 1fr; }
}

/* ═══════════════════════════════════════════════════════════
   MOBILE CROSS-CUTTING (B1) — applies app-wide on phones ≤599px
   Single block of small fixes that hit every form, every auth page,
   every page with a password field. Replaces ~10 page-specific
   rules with one source-of-truth. Mobile-only; desktop + tablet
   untouched. Each rule is independently revertable.
   ═══════════════════════════════════════════════════════════ */
@media (max-width: 599px) {
    /* C1 — iOS Safari zooms into any input with font-size < 16px on
       focus. Every text/email/password/number input in the app uses
       .form-group input at 14px, so EVERY focus event auto-zoomed.
       Pinning to 16px on phones kills the zoom across all forms. */
    .form-group input,
    .form-group select,
    .form-group textarea {
        font-size: 16px;
    }
    /* Safety net: any focusable text control the class-scoped rule above misses
       still avoids the iOS focus-zoom. Page/template rules with higher
       specificity set their own 16px where they use smaller class-scoped sizes. */
    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]),
    textarea,
    select {
        font-size: 16px;
    }

    /* C2 — Password reveal-eye buttons were a 28×28 hit area (4px
       padding + 20px svg) — well below the 44×44 WCAG / iOS touch
       target. Bump padding + add a min size so the eye is a proper
       tap target. The matching input gets extra right padding so the
       eye doesn't overlap the typed password. */
    .password-toggle {
        padding: 10px;
        min-width: 44px;
        min-height: 44px;
    }
    .input-password-wrap input {
        padding-right: 48px;
    }

    /* C3 — 8 MFA recovery codes in a 2-column grid were unreadably
       narrow on phones at 320–360px. One column gives each code
       full width and proper readability. */
    .recovery-codes,
    .mfa-recovery-codes {
        grid-template-columns: 1fr;
    }

    /* C4 — Tiny "Forgot password?" / "Create one" links in auth-links
       were 13px font + no padding, giving a fingernail-sized tap area.
       inline-block + padding turns the whole link area into a target
       without affecting the surrounding layout. */
    .auth-links a {
        display: inline-block;
        padding: 8px 4px;
        font-size: 14px;
    }
}

/* ═══════════════════════════════════════════════════════════
   PARENT DASHBOARD STAT CARDS (mobile only, page-scoped)
   The 2×2 grid of Players / Active Registrations / Upcoming
   Events / Pending Payments stat cards on dashboard.html had two
   problems on phones:
     1. Labels of inconsistent length (1 line vs 2 lines) made
        right-column cards taller than left-column cards.
     2. The look was utilitarian — fine for an admin tool, but
        the parent landing page wants more visual confidence.
   These rules ONLY fire on body.is-dashboard-page at ≤599px.
   Other pages' stat cards (league-dashboard, admin Overview,
   registrations, etc.) are untouched.
   ═══════════════════════════════════════════════════════════ */

/* Cross-cutting mobile fixes — apply to ALL pages at <=599px. Lives
   outside the dashboard-specific block below so it isn't accidentally
   scoped. Source order before the media block keeps the cascade clean. */

/* Topbar title slot — hidden by default (desktop/tablet). The mobile
   media query below reveals it for every page. Placed BEFORE the
   @media block on purpose: the reveal rule and this one have the same
   specificity, so source order decides — keep this above so the
   mobile rule wins inside the media query. */
.topbar-page-title {
    display: none;
}

@media (max-width: 599px) {
    /* ═══════════════════════════════════════════════════════════
       L1 — Cross-cutting mobile fixes (apply to ALL pages)
       Catches systemic issues across the league admin pages without
       requiring per-file edits.
       ═══════════════════════════════════════════════════════════ */

    /* (1) Tap targets. Most .btn elements compute to ~36px on mobile
       (11px padding + 14px font). Apple's HIG and WCAG 2.5.5 both
       recommend 44px. Bump padding so .btn / modal action buttons
       are comfortable. .btn-sm / .btn-link / inline icon buttons
       opt out via existing class specificity. */
    .btn:not(.btn-sm):not(.btn-link):not(.btn-icon) {
        min-height: 44px;
        padding-top: 12px;
        padding-bottom: 12px;
    }
    .modal .btn:not(.btn-sm):not(.btn-link):not(.btn-icon),
    .modal-actions .btn:not(.btn-sm):not(.btn-link):not(.btn-icon) {
        min-height: 44px;
    }

    /* (2) Modal action rows should wrap when they don't fit + the
       buttons should stretch so they don't shrink to a tiny pill on
       narrow widths. Affects every .modal-actions on every page. */
    .modal-actions {
        flex-wrap: wrap;
        gap: 10px;
    }
    .modal-actions > .btn {
        flex: 1 1 auto;
    }

    /* (3) Stat cards on registrations / financials don't share the
       dashboard's `.stat-card` markup (no icons or body wrapper) so
       they can't inherit the gradient + halo treatment. Give them a
       parallel mobile look: drop the left-border accent, add a soft
       accent-tinted gradient, bigger value typography. Keeps the
       page visually consistent with the dashboard cards. */
    .reg-stats,
    .fin-stats {
        display: grid !important;
        grid-template-columns: 1fr 1fr;
        gap: 12px;
    }
    .reg-stat-card,
    .fin-stat-card {
        flex: none !important;
        min-width: 0 !important;
        border-radius: 16px !important;
        border: 0 !important;
        padding: 16px !important;
        text-align: left !important;
        background:
            linear-gradient(135deg,
                color-mix(in srgb, var(--stat-accent, #38bdf8) 12%, var(--pe-white, #fff)) 0%,
                var(--pe-white, #fff) 70%) !important;
        box-shadow:
            0 1px 2px rgba(15, 23, 42, 0.06),
            0 6px 18px -8px color-mix(in srgb, var(--stat-accent, #38bdf8) 35%, transparent);
    }
    /* Map each variant to the matching accent so the gradient picks
       it up via --stat-accent. */
    .reg-stat-card               { --stat-accent: #38bdf8; }
    .reg-stat-card.confirmed     { --stat-accent: #16a34a; }
    .reg-stat-card.pending       { --stat-accent: #f59e0b; }
    .reg-stat-card.waitlisted    { --stat-accent: #8b5cf6; }
    .reg-stat-card.revenue       { --stat-accent: #38bdf8; }
    .fin-stat-card               { --stat-accent: #38bdf8; }
    .fin-stat-card.net           { --stat-accent: #16a34a; }
    .fin-stat-card.fees          { --stat-accent: #f59e0b; }
    .fin-stat-card.refunded      { --stat-accent: #ef4444; }
    .fin-stat-card.pending       { --stat-accent: #8b5cf6; }
    .reg-stat-value,
    .fin-stat-value {
        font-size: 24px !important;
        font-weight: 800 !important;
        line-height: 1 !important;
        letter-spacing: -0.5px !important;
    }
    .reg-stat-label,
    .fin-stat-label {
        margin-top: 6px !important;
    }
    [data-theme="dark"] .reg-stat-card,
    [data-theme="dark"] .fin-stat-card {
        background:
            linear-gradient(135deg,
                color-mix(in srgb, var(--stat-accent, #38bdf8) 18%, var(--pe-card-bg, #1f2937)) 0%,
                var(--pe-card-bg, #1f2937) 70%) !important;
    }

    /* ═══════════════════════════════════════════════════════════
       Dashboard-specific mobile treatment continues below
       ═══════════════════════════════════════════════════════════ */

    /* A — Alignment + polish ─────────────────────────────── */

    /* Stretch every card to the tallest in its row so wrapping
       labels don't create height mismatches. */
    body.is-dashboard-page .stats-grid, body.is-admin-page .stats-grid {
        grid-template-columns: 1fr 1fr;
        align-items: stretch;
        gap: 12px;
    }
    body.is-dashboard-page .stats-grid .stat-card, body.is-admin-page .stats-grid .stat-card {
        display: flex;
        flex-direction: column;
        height: 100%;
        padding: 18px 16px;
        gap: 14px;
    }
    /* Reserve a 2-line slot for every label so 1-line labels
       ("PLAYERS") still occupy the same vertical space as the
       2-line ones ("ACTIVE REGISTRATIONS") below them. Avoids
       the bottom number jumping by an extra line of height. */
    body.is-dashboard-page .stats-grid .stat-label, body.is-admin-page .stats-grid .stat-label {
        min-height: 2.2em;
        line-height: 1.1;
        font-size: 11px;
        letter-spacing: 0.6px;
        text-transform: uppercase;
        color: var(--pe-text-muted);
        margin: 0;
    }
    /* Push the big number to the bottom of the card so multi-row
       cards still align their values on a shared baseline. */
    body.is-dashboard-page .stats-grid .stat-body, body.is-admin-page .stats-grid .stat-body {
        display: flex;
        flex-direction: column;
        flex: 1;
    }
    body.is-dashboard-page .stats-grid .stat-value, body.is-admin-page .stats-grid .stat-value {
        margin-top: auto;
        font-size: 30px;
        font-weight: 800;
        line-height: 1;
        letter-spacing: -0.5px;
    }
    body.is-dashboard-page .stats-grid .stat-meta, body.is-admin-page .stats-grid .stat-meta {
        margin-top: 4px;
        font-size: 11px;
        color: var(--pe-text-muted);
    }

    /* B — Modernize ──────────────────────────────────────── */

    /* Soft tinted background using the accent color at low opacity,
       plus a soft outer shadow + tighter radius. Drops the left-bar
       accent (utilitarian) in favor of a card-wide color wash and
       a stronger icon halo. */
    body.is-dashboard-page .stats-grid .stat-card, body.is-admin-page .stats-grid .stat-card {
        position: relative;
        border-radius: 16px;
        border: 0;
        background:
            linear-gradient(135deg,
                color-mix(in srgb, var(--stat-accent, #38bdf8) 12%, var(--pe-white, #fff)) 0%,
                var(--pe-white, #fff) 70%);
        box-shadow:
            0 1px 2px rgba(15, 23, 42, 0.06),
            0 6px 18px -8px color-mix(in srgb, var(--stat-accent, #38bdf8) 35%, transparent);
    }
    /* Remove the left-edge accent bar that the desktop card uses */
    body.is-dashboard-page .stats-grid .stat-card::before, body.is-admin-page .stats-grid .stat-card::before {
        display: none;
    }

    /* Icon: bigger, with a colored halo glow */
    body.is-dashboard-page .stats-grid .stat-card .stat-icon, body.is-admin-page .stats-grid .stat-card .stat-icon {
        width: 44px;
        height: 44px;
        border-radius: 14px;
        background: color-mix(in srgb, var(--stat-accent, #38bdf8) 18%, transparent);
        color: var(--stat-accent, #38bdf8);
        box-shadow:
            0 0 0 4px color-mix(in srgb, var(--stat-accent, #38bdf8) 8%, transparent),
            0 4px 10px -4px color-mix(in srgb, var(--stat-accent, #38bdf8) 35%, transparent);
    }
    body.is-dashboard-page .stats-grid .stat-card .stat-icon svg, body.is-admin-page .stats-grid .stat-card .stat-icon svg {
        width: 22px;
        height: 22px;
    }

    /* Value gets the accent color for the digit so the eye picks it
       out faster — but only when nonzero (zero stays neutral to avoid
       a wall of brightly-colored zeros on an empty account). */
    body.is-dashboard-page .stats-grid .stat-card[data-accent] .stat-value, body.is-admin-page .stats-grid .stat-card[data-accent] .stat-value {
        color: var(--pe-text);
    }

    /* Subtle tap feedback so it feels like an interactive surface
       even though most cards aren't tappable yet. */
    body.is-dashboard-page .stats-grid .stat-card:active, body.is-admin-page .stats-grid .stat-card:active {
        transform: translateY(1px);
        box-shadow:
            0 1px 1px rgba(15, 23, 42, 0.04),
            0 3px 10px -6px color-mix(in srgb, var(--stat-accent, #38bdf8) 30%, transparent);
    }

    /* Dark mode treatment: tint the card surface instead of mixing
       with white so the accent reads clearly on the dark background. */
    [data-theme="dark"] body.is-dashboard-page .stats-grid .stat-card, [data-theme="dark"] body.is-admin-page .stats-grid .stat-card {
        background:
            linear-gradient(135deg,
                color-mix(in srgb, var(--stat-accent, #38bdf8) 18%, var(--pe-card-bg, #1f2937)) 0%,
                var(--pe-card-bg, #1f2937) 70%);
    }

    /* C — Greeting moved into the topbar row (Robb's idea).
       Saves a row of vertical real estate by putting the greeting
       on the same line as the avatar/dropdown. The standalone
       .page-title-bar row goes away on this page on mobile only.
       Other pages (which use the same .page-title-bar pattern but
       not body.is-dashboard-page) keep the existing two-row layout.

       Topbar default is justify-content:flex-end (both halves hug
       the right). For the greeting to sit on the LEFT we either need
       space-between or push topbar-left to take remaining room. Using
       space-between is the simplest + keeps the right side stable. */
    /* ─── Mobile topbar: opaque + title-in-topbar pattern ───
       Originally dashboard-only ("Good evening, Robb!" in the topbar
       with the H1 page-title-bar hidden). Now applied to ALL pages on
       mobile — every page name lives in the topbar slot (set by
       nav.js buildTopbar from the page config; dashboard overrides
       with the time-of-day greeting via NAV.setTitle).
       Matches the dropdown menu look so the topbar reads as one piece. */
    .app-topbar {
        justify-content: space-between;
        background: var(--pe-white) !important;
        border-bottom: 1px solid var(--pe-border) !important;
        box-shadow: var(--pe-shadow-lg) !important;
    }
    .topbar-left {
        flex: 1;
        min-width: 0;
        /* Re-enable pointer events here — .app-topbar globally sets
           pointer-events:none so the right side has to opt back in
           (the user button does so today; the title area might gain
           tap targets later, so allow it now). */
        pointer-events: auto;
    }
    .topbar-page-title {
        display: block;
        margin: 0;
        font-size: 17px;
        font-weight: 700;
        line-height: 1.2;
        color: var(--pe-text);
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        /* Leave room for the user button on the right */
        max-width: calc(100vw - 100px);
    }
    /* Hide the big H1 page-title-bar on mobile — the title is now in
       the fixed topbar. Pages that inject tabs inside #pageTitleBar
       (profile.html, admin.html) still need those tabs visible, so
       leave page-title-bar's CHILDREN reachable: pages can opt back
       in by setting display on their tab strip explicitly. */
    .page-title-bar {
        display: none;
    }

    /* …which took every injected "+ New …" button with it. NAV.mobileActions()
       re-homes the primary action here, below the (hidden) title bar. */
    .mobile-action-row {
        display: flex;
        gap: 8px;
        margin: 0 0 14px;
    }
    .mobile-action-row .btn { flex: 1; min-height: 44px; }
    /* But: pages inject their tab strip INSIDE .page-title-bar. Bring
       it back as a standalone strip so tabs (Account/Security on
       profile, etc) keep working without each page needing edits. */
    .page-title-bar .profile-tabs {
        display: flex;
    }
    .page-title-bar:has(.profile-tabs) {
        display: block;
        margin-bottom: 16px;
        position: relative;   /* anchors the collapsed dropdown below */
    }

    /* ── Collapsible tab strip (NAV.pageTabs) ─────────────
       Up to 3 short tabs fit as equal columns. More than that wrapped into
       ragged rows, so nav.js marks the strip .pe-tabs-collapsible and adds a
       trigger: the strip becomes a dropdown anchored under it. */
    .page-title-bar .profile-tabs:not(.pe-tabs-collapsible) {
        width: 100%;
        gap: 2px;
        padding: 4px;
    }
    .page-title-bar .profile-tabs:not(.pe-tabs-collapsible) .profile-tab {
        flex: 1 1 0;
        min-width: 0;
        padding: 10px 4px;
        font-size: 12.5px;
        justify-content: center;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    /* Scoped to .page-title-bar so it outranks the base `display: none`
       regardless of source order — that base rule sits LATER in this file
       than this media block, so a bare `.pe-tabs-trigger` would lose. */
    /* .pe-subnav-host is the same dropdown for an IN-PAGE sub-menu strip
       (NAV.collapsibleSubnav) — Payments sections, Applications statuses. */
    .pe-subnav-host { position: relative; margin: 0 0 16px; }
    .pe-subnav-host .pe-tabs-collapsible.pf-subnav { border-bottom: 0; margin: 0; }
    .pe-subnav-host .pe-tabs-collapsible.la-tabs { width: 100%; overflow: visible; }
    .pe-subnav-host .pe-tabs-collapsible > button,
    .pe-subnav-host .pe-tabs-collapsible > a {
        display: flex; align-items: center; width: 100%; min-height: 46px;
        padding: 12px; border-radius: 9px; font-size: 14px; text-align: left;
        box-shadow: none; white-space: normal;
    }
    .pe-subnav-host .pe-tabs-collapsible > .active {
        background: rgba(56, 189, 248, 0.14); color: var(--pe-text); box-shadow: none;
    }
    .page-title-bar .pe-tabs-trigger,
    .pe-subnav-host .pe-tabs-trigger {
        display: flex;
        align-items: center;
        gap: 10px;
        width: 100%;
        min-height: 48px;
        padding: 12px 14px;
        background: var(--pe-white, #fff);
        border: 1px solid var(--pe-border, #e2e8f0);
        border-radius: 10px;
        font-family: inherit;
        font-size: 14px;
        font-weight: 600;
        color: var(--pe-text);
        text-align: left;
        cursor: pointer;
    }
    [data-theme="dark"] .pe-tabs-trigger { background: var(--pe-card-bg, #1f2937); }
    .pe-tabs-trigger-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .pe-tabs-caret {
        width: 15px; height: 15px; flex-shrink: 0;
        color: var(--pe-text-muted, #94a3b8);
        transition: transform var(--motion-micro, 150ms) ease;
    }
    .pe-tabs-trigger[aria-expanded="true"] .pe-tabs-caret { transform: rotate(180deg); }

    /* Stays display:flex and fades on opacity — display can't transition, so
       a display swap would pop. Deliberately does NOT animate `visibility`:
       children inherit it and mid-transition the inherited value resolves out
       of step with the parent. `inert` (toggled in nav.js) covers tab order
       and the a11y tree instead. */
    .page-title-bar .profile-tabs.pe-tabs-collapsible,
    .pe-subnav-host .pe-tabs-collapsible {
        display: flex;
        position: absolute;
        top: calc(100% + 6px);
        left: 0;
        right: 0;
        z-index: 60;
        /* An ABSOLUTELY POSITIONED panel must own an opaque surface.
           Without this it fell through to the base .profile-tabs fill,
           which is fine in light (#f5f6f8, opaque) and transparent in
           dark: [data-theme="dark"] .profile-tabs is rgba(255,255,255,.06),
           a 6% tint meant for a tab strip sitting ON the page. The menu
           rendered 94% see-through and the form underneath read straight
           through it. Matches .pe-tabs-trigger directly above.

           This panel only exists where nav.js collapses a strip, which
           is above TABS_COLLAPSE_ABOVE (3) tabs — today the League
           Settings sub-pages, at seven. The other pageTabs() callers
           (profiles + financials at 2, my-registrations at 3) render
           inline at every width and never built this panel. */
        background: var(--pe-white, #fff);
        flex-direction: column;
        gap: 2px;
        width: 100%;
        padding: 6px;
        border-radius: 12px;
        border: 1px solid var(--pe-border, #e2e8f0);
        box-shadow: 0 12px 34px rgba(15, 23, 42, 0.18);
        opacity: 0;
        transform: translateY(-6px);
        pointer-events: none;
        transition:
            opacity var(--motion-micro, 150ms) var(--ease-exit, ease),
            transform var(--motion-micro, 150ms) var(--ease-exit, ease);
    }
    .page-title-bar .profile-tabs.pe-tabs-collapsible.open,
    .pe-subnav-host .pe-tabs-collapsible.open {
        opacity: 1;
        transform: translateY(0);
        pointer-events: auto;
        transition:
            opacity var(--motion-micro, 150ms) var(--ease-enter, ease),
            transform var(--motion-micro, 150ms) var(--ease-enter, ease);
    }
    [data-theme="dark"] .page-title-bar .profile-tabs.pe-tabs-collapsible,
    [data-theme="dark"] .pe-subnav-host .pe-tabs-collapsible {
        background: var(--pe-card-bg, #1f2937);
    }

    /* A card header's title + button cluster wraps on a phone instead of
       squeezing the title to one word per line (Admin › Lookup Data). */
    .content-card .card-header { flex-wrap: wrap; gap: 10px; }
    .card-header-actions { width: 100%; }
    .card-header-actions .btn { flex: 1 1 auto; }
    .page-title-bar .profile-tabs.pe-tabs-collapsible .profile-tab {
        justify-content: flex-start;
        width: 100%;
        min-height: 46px;
        padding: 12px;
        border-radius: 9px;
        font-size: 14px;
        text-align: left;
    }
    .page-title-bar:has(.profile-tabs) .page-title-bar-text {
        display: none;
    }
}

/* ── Phone: the dashboard takes the profile hub's row grammar (Robb, 2026-10-23) ──
   "I like the way the profile looks vs the way the dashboards look." Below
   600px everything under the Find a league / Become a coach / Start a league
   row reads as the profile page's list: one white card per item, a 44px
   tinted icon on the left, a bold name over one muted summary line, and a
   chevron on the right. The four stat tiles stop being 2×2 number tiles and
   become rows — the count now leads the summary line instead of being a
   30px figure — and a "My Profile" row joins them, phone only (desktop keeps
   its four tiles: Robb, 2026-10-19, "Go back to four tiles"). The markup is
   untouched; the chevron is a ::after so dashboard.js keeps filling the same
   ids. Tablet and desktop are not affected by anything in this block. */
.stat-m-only, .stat-m-line { display: none; }
@media (max-width: 599px) {
    body.is-dashboard-page #smartTop { margin-bottom: 12px; }
    body.is-dashboard-page #needsAttentionCard { margin-top: 0 !important; margin-bottom: 0; }
    body.is-dashboard-page .stats-grid {
        grid-template-columns: minmax(0, 1fr);
        gap: 12px;
        margin-top: 12px !important;
    }
    body.is-dashboard-page .stats-grid .stat-card {
        flex-direction: row;
        align-items: center;
        gap: 16px;
        height: auto;
        padding: 16px 18px;
        border-radius: 14px;
        border: 1px solid var(--pe-border);
        background: var(--pe-white);
        box-shadow: var(--pe-shadow);
        min-width: 0;
    }
    [data-theme="dark"] body.is-dashboard-page .stats-grid .stat-card { background: var(--pe-card-bg, #1f2937); }
    body.is-dashboard-page .stats-grid .stat-card.stat-m-only { display: flex; }
    body.is-dashboard-page .stats-grid .stat-card:active {
        transform: none;
        background: var(--pe-gray-light, #f5f6f8);
        box-shadow: var(--pe-shadow);
    }
    [data-theme="dark"] body.is-dashboard-page .stats-grid .stat-card:active { background: var(--pe-hover, rgba(255,255,255,0.06)); }
    body.is-dashboard-page .stats-grid .stat-card .stat-icon {
        width: 44px; height: 44px; border-radius: 12px;
        background: color-mix(in srgb, var(--stat-accent, #38bdf8) 14%, var(--pe-white, #fff));
        color: var(--stat-accent, #38bdf8);
        box-shadow: none;
    }
    body.is-dashboard-page .stats-grid .stat-card .stat-icon svg { width: 22px; height: 22px; }
    body.is-dashboard-page .stats-grid .stat-body { display: block; flex: 1; min-width: 0; }
    /* No counts or detail on a phone (Robb, 2026-10-23: "I want it to be
       generic") — the row says what the page DOES, the page shows the numbers.
       .stat-m-line is in the markup and only shows here. */
    body.is-dashboard-page .stats-grid .stat-card .stat-value,
    body.is-dashboard-page .stats-grid .stat-card .stat-sub { display: none !important; }
    body.is-dashboard-page .stats-grid .stat-card .stat-m-line { display: block; font-size: 13px; line-height: 1.45; color: var(--pe-text-secondary); }
    body.is-dashboard-page .stats-grid .stat-label {
        min-height: 0; margin: 0 0 3px;
        font-size: 15px; font-weight: 700; line-height: 1.3;
        letter-spacing: 0; text-transform: none; color: var(--pe-text);
    }
    /* The count and the sub-line become ONE summary line, like the profile
       card's "Name, phone, address and emergency contact on file". */
    body.is-dashboard-page .stats-grid .stat-card .stat-value,
    body.is-dashboard-page .stats-grid .stat-card .stat-value-next {
        display: inline; margin: 0;
        font-size: 13px; font-weight: 700; line-height: 1.45; letter-spacing: 0;
        color: var(--pe-text-secondary);
    }
    body.is-dashboard-page .stats-grid .stat-card .stat-sub {
        display: inline; margin: 0; font-size: 13px; line-height: 1.45;
        color: var(--pe-text-secondary);
    }
    /* A number gets a dot before its detail ("1 · Tanner · 7th"); a text
       value ("Add your zip") runs straight into its line. A hidden sub-line
       (display:none, set by setStatSub) takes its ::before with it. */
    body.is-dashboard-page .stats-grid .stat-value:not(.stat-value-next) + .stat-sub::before { content: '· '; }
    body.is-dashboard-page .stats-grid .stat-chips { display: inline; }
    body.is-dashboard-page .stats-grid .stat-chips .tw-chip { vertical-align: middle; margin: 1px 2px 1px 0; }
    body.is-dashboard-page .stats-grid .stat-sub-more { display: inline; margin: 0 0 0 4px; }
    body.is-dashboard-page .stats-grid .stat-card::after {
        content: ''; flex: none; width: 9px; height: 9px; margin-right: 3px;
        border-right: 2px solid var(--pe-text-tertiary, #9ca3af);
        border-bottom: 2px solid var(--pe-text-tertiary, #9ca3af);
        transform: rotate(-45deg);
    }

    /* "Looking to register a player?" — the same row, built by stNudge(). Only
       the smart-top copy changes; the amber setup nudges inside Needs
       Attention keep their card look (Robb, 2026-09-14). */
    body.is-dashboard-page #smartTop .st-nudge.st-nudge-info { gap: 16px; padding: 16px 18px; }
    body.is-dashboard-page #smartTop .st-nudge.st-nudge-info .st-nudge-ic {
        width: 44px; height: 44px; border-radius: 12px;
        background: color-mix(in srgb, #2563eb 14%, var(--pe-white, #fff)); color: #2563eb;
    }
    [data-theme="dark"] body.is-dashboard-page #smartTop .st-nudge.st-nudge-info .st-nudge-ic { background: #24324a; color: #9cc4ec; }
    body.is-dashboard-page #smartTop .st-nudge.st-nudge-info .st-nudge-ic svg { width: 22px; height: 22px; }
    body.is-dashboard-page #smartTop .st-nudge.st-nudge-info .st-nudge-txt { gap: 3px; }
    body.is-dashboard-page #smartTop .st-nudge.st-nudge-info .st-nudge-lead { font-size: 15px; }
    body.is-dashboard-page #smartTop .st-nudge.st-nudge-info .st-nudge-sub { font-size: 13px; color: var(--pe-text-secondary); }
    body.is-dashboard-page #smartTop .st-nudge.st-nudge-info::after {
        content: ''; flex: none; width: 9px; height: 9px; margin-right: 3px;
        border-right: 2px solid var(--pe-text-tertiary, #9ca3af);
        border-bottom: 2px solid var(--pe-text-tertiary, #9ca3af);
        transform: rotate(-45deg);
    }
}

/* ═══════════════════════════════════════════════════════════
   PAGE HEADER ZONE (profile page patterned background)
   ═══════════════════════════════════════════════════════════ */

/* Page Header Zone — tabs only, no background bar */
.page-header-zone {
    background: transparent;
    margin: -28px -24px 0;
    padding: 12px 24px 12px;
    border-bottom: none;
}

.page-header-zone-inner {
    max-width: 1100px;
    margin: 0 auto;
}

@media (max-width: 599px) {
    .page-header-zone {
        margin: -16px -16px 0;
        padding: 8px 16px 8px;
    }
}

/* ═══════════════════════════════════════════════════════════
   PROFILE TABS (scoped to profile page only)
   ═══════════════════════════════════════════════════════════ */

/* Mobile-only control — the ≤599px block turns it on. Above that the tab
   strip renders inline and there is nothing to collapse. */
.pe-tabs-trigger { display: none; }

.profile-tabs {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 4px;
    margin: 0;
    background: var(--pe-gray-light);
    border-radius: 10px;
    padding: 4px;
}

.profile-tabs-wrap {
    text-align: center;
}

.profile-tab {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 10px 24px;
    font-size: 0.875rem;
    font-weight: 500;
    color: var(--pe-text-secondary);
    background: transparent;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    transition: all 0.2s;
    text-decoration: none; /* profile-tab is used as <a> too (platform strip) */
}

.profile-tab:hover {
    color: var(--pe-text);
    background: rgba(0,0,0,0.04);
}

.profile-tab.active {
    color: var(--pe-blue-dark);
    font-weight: 600;
    background: var(--pe-white);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

.profile-tab svg {
    /* Explicit size: icons from the ICONS map (Reports tabs) carry no
       width/height, so without this they expand to fill their box — fine in
       the narrow desktop strip, but 250px tall in the full-width mobile
       dropdown row. Fixed size keeps them 18px everywhere. */
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    opacity: 0.5;
}

.profile-tab.active svg {
    opacity: 1;
    color: var(--pe-blue);
}

.tab-panel {
    display: none;
    padding-top: 24px;
}

.tab-panel .content-card {
    border-left: 3px solid #38bdf8;
    transition: border-left-color 0.3s;
}

.tab-panel .content-card.unsaved {
    border-left-color: var(--pe-danger);
}

.tab-panel.active {
    display: block;
}

/* Desktop: limit tab width */
@media (min-width: 1024px) {
    .profile-tab {
        padding: 10px 28px;
    }
}

/* ═══════════════════════════════════════════════════════════
   PAGE TOOLBAR — title-bar actions + controls row + Group By
   ═══════════════════════════════════════════════════════════
   The action buttons at the right of a page title bar, and the
   Filters / Group By row beneath it.

   referees.html and coaches.html use this block outright.

   ⚠ teams.html deliberately keeps page-local overrides for the parts that are
   genuinely its own look: the Group By menu (right-anchored, 160px, fade-in),
   .filter-count-badge, .groupby-*, and its dark-mode .tb-btn set. Its button
   rules were consolidated here. Changing .groupby-* below will NOT move
   teams.html — check it too. events.html and seasons.html still carry their
   own .groupby-* variants (12px arrow, muted colour) and are untouched.     */

.title-toolbar {
    display: flex; align-items: center; gap: 6px; margin-left: auto;
}
.tb-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    height: 34px; padding: 0 14px; border: 1px solid var(--pe-border);
    border-radius: 8px; font-size: 13px; font-weight: 500;
    background: var(--pe-white); color: var(--pe-text);
    cursor: pointer; white-space: nowrap; transition: all .15s;
    position: relative; font-family: inherit;
    /* This class is used on <a> as well as <button> (a "View" that opens the
       public page, for instance). Without this the anchors render underlined
       while the buttons beside them don't, which reads as a broken row. .btn
       has always reset this; .tb-btn never did. */
    text-decoration: none;
}
.tb-btn:hover { border-color: #94a3b8; background: var(--pe-hover, #f8fafc); }
.tb-btn svg { width: 15px; height: 15px; flex-shrink: 0; color: var(--pe-text-secondary, #64748b); }
.tb-btn:hover svg { color: var(--pe-text); }
.tb-btn:disabled { opacity: .55; cursor: default; }
.tb-btn.has-filters { color: var(--pe-blue); border-color: var(--pe-blue); }
.tb-btn.has-filters svg { color: var(--pe-blue); }
.tb-btn.tb-primary {
    background: var(--pe-blue); color: #fff; border-color: var(--pe-blue); font-weight: 600;
}
.tb-btn.tb-primary:hover { background: #0ea5e9; border-color: #0ea5e9; }
.tb-btn.tb-primary svg { color: #fff; }
/* --pe-card-bg is not a real token (nothing defines it), so the bare
   var() was invalid at computed-value time and the background collapsed to
   transparent — toolbar buttons had no surface in dark mode. The fallback is
   --pe-white, which IS re-pointed at the dark card colour by [data-theme]. */
[data-theme="dark"] .tb-btn { background: var(--pe-card-bg, var(--pe-white)); border-color: var(--pe-border); color: var(--pe-text); }
[data-theme="dark"] .tb-btn:hover { background: var(--pe-bg); }

.filter-count-badge {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 18px; height: 18px; border-radius: 9px;
    background: var(--pe-blue); color: #fff; font-size: 11px; font-weight: 700;
    padding: 0 5px;
}

/* Sits OUTSIDE .page-title-bar, which is hidden entirely at <=599px — a
   Filters button inside the title bar is unreachable on a phone. */
.page-controls-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 16px; }
/* Teams keeps its collapsible Filters button, but align its controls-row
   buttons (Filters + Group By) to the canonical 38px toolbar height used by
   the shared .pe-filterbar controls on the other pages. */
.page-controls-row .tb-btn { height: 38px; font-size: 14px; }

.groupby-wrap { position: relative; }
.groupby-arrow { width: 13px !important; height: 13px !important; }
.groupby-menu {
    display: none; position: absolute; top: calc(100% + 6px); left: 0;
    min-width: 190px; max-width: calc(100vw - 24px); background: var(--pe-white, #fff);
    border: 1px solid var(--pe-border); border-radius: 10px;
    box-shadow: 0 10px 30px rgba(15,23,42,0.16); padding: 5px; z-index: 100;
}
[data-theme="dark"] .groupby-menu { background: var(--pe-card-bg, #1f2937); }
.groupby-menu.open { display: block; }
.groupby-item {
    display: flex; align-items: center; gap: 7px; padding: 9px 10px;
    border-radius: 7px; font-size: 13px; color: var(--pe-text); cursor: pointer;
}
.groupby-item:hover { background: var(--pe-bg, #f1f5f9); }
[data-theme="dark"] .groupby-item:hover { background: rgba(255,255,255,0.06); }
.groupby-check { visibility: hidden; color: var(--pe-blue, #0284c7); font-weight: 700; }
.groupby-item.active .groupby-check { visibility: visible; }
.groupby-item.active { color: var(--pe-blue, #0284c7); font-weight: 600; }
/* The "Group by (up to 3)" caption and the rule under None. Both have existed
   in the events + seasons page-local copies since that menu was built; the
   shared block never got them, so a page adopting THIS block rendered an
   unstyled caption and a divider that was not there at all. Additive — the two
   page-local copies define their own and are untouched. */
.groupby-hint {
    padding: 6px 10px 3px; font-size: 10px; font-weight: 600;
    color: var(--pe-text-muted, #94a3b8); text-transform: uppercase; letter-spacing: .05em;
}
.groupby-divider { height: 1px; background: var(--pe-border, #e2e8f0); margin: 4px 6px; }

@media (max-width: 599px) {
    /* Title-bar actions go icon-only — four labelled buttons don't fit a
       phone. The controls row keeps its labels; it has only two. */
    .title-toolbar .tb-btn .btn-label { display: none; }
}

/* ═══════════════════════════════════════════════════════════
   SUB-TABS (underline strip)
   ═══════════════════════════════════════════════════════════
   The in-page tab strip for switching views WITHIN a record —
   coaches.html, the parent Team Hub, the coach team page. Distinct from
   .profile-tabs above (the segmented pill used by profile/admin/reports):
   this one is a flat underline row, which reads as a sub-level of the page
   title rather than a control sitting on top of the content.

   Markup:
     <div class="subtabs" role="tablist">
       <button class="subtab active" data-tab="x" role="tab">
         <span class="tab-text-full">Long label</span>
         <span class="tab-text-short">Short</span>
         <span class="tab-count">5</span>
       </button>
     </div>
   .tab-count hides itself when empty, so a countless tab needs no special
   casing. Both label spans are optional — a tab with a plain text label
   just shows that label at every width.                                    */

.subtabs {
    display: flex;
    gap: 4px;
    margin-bottom: 16px;
    border-bottom: 1px solid var(--pe-border, #e2e8f0);
    overflow-x: auto;
}

.subtab {
    background: transparent;
    border: 0;
    border-bottom: 2px solid transparent;
    padding: 10px 16px;
    font-size: 14px;
    font-weight: 600;
    /* text-secondary, NOT --pe-text-muted: that variable is never defined,
       so it falls back to initial black — invisible on a dark card. */
    color: var(--pe-text-secondary, #64748b);
    cursor: pointer;
    white-space: nowrap;
    font-family: inherit;
    transition: color .15s ease, border-color .15s ease;
}

.subtab:hover { color: var(--pe-text); }

.subtab.active {
    color: var(--pe-blue, #0284c7);
    border-bottom-color: var(--pe-blue, #38bdf8);
}

.tab-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 6px;
    margin-left: 6px;
    border-radius: 9px;
    background: var(--pe-border, #e2e8f0);
    color: var(--pe-text-secondary, #64748b);
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
}

.tab-count:empty { display: none; }
.tab-count-alert { background: #fbbf24; color: #78350f; }
.subtab.active .tab-count { background: var(--pe-blue, #38bdf8); color: #fff; }

.tab-text-short { display: none; }

/* Phone: equal columns + short labels. Natural-width tabs overflowed and
   scrolled sideways, which left the last one permanently half-cut. */
@media (max-width: 599px) {
    .subtabs { gap: 0; overflow-x: visible; }
    .subtab {
        flex: 1 1 0;
        min-width: 0;
        padding: 10px 4px;
        font-size: 12.5px;
        text-align: center;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .tab-text-full { display: none; }
    .tab-text-short { display: inline; }
}

/* ═══════════════════════════════════════════════════════════
   SETTINGS COMPONENTS (iOS-style groups, rows, panels)
   ═══════════════════════════════════════════════════════════ */

/* Two-column layout */
.profile-layout {
    display: grid;
    /* minmax(0, 1fr), never a bare 1fr — `1fr` is minmax(AUTO, 1fr), so the
       column refuses to shrink below its content's min-content width. A long
       unbroken email address pushed this column to 449px inside a 375px
       phone and took the whole page with it. Same rule as the entity card
       grids. */
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
    padding-top: 24px;
}

@media (min-width: 1024px) {
    .profile-layout {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        gap: 24px;
    }
}

/* ── Settings Group (iOS-style card) ── */
.settings-group {
    background: var(--pe-white);
    border-radius: 14px;
    box-shadow: var(--pe-shadow);
    border: 1px solid var(--pe-border);
    margin-bottom: 20px;
    overflow: hidden;
    border-left: 3px solid var(--pe-blue);
    transition: border-left-color 0.3s, box-shadow 0.2s ease;
}

.settings-group:hover {
    box-shadow: var(--pe-shadow-md);
}

.settings-group.unsaved {
    border-left-color: var(--pe-danger);
}

.settings-group-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 20px 6px;
}

.settings-group-title {
    font-family: var(--pe-font-display);
    font-size: 11px;
    font-weight: 700;
    color: var(--pe-gray);
    text-transform: uppercase;
    letter-spacing: 0.8px;
}

.settings-group-subtitle {
    padding: 0 20px 8px;
    font-size: 12px;
    color: var(--pe-gray);
    line-height: 1.4;
}

.settings-group-action {
    font-size: 13px;
    font-weight: 600;
    color: var(--pe-blue);
    background: none;
    border: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 10px;
    border-radius: 6px;
    transition: all 0.2s;
}

.settings-group-action:hover {
    background: var(--pe-blue-light);
    color: var(--pe-blue-dark);
}

.settings-group-action svg {
    width: 14px;
    height: 14px;
}

/* ── Settings Row ── */
.settings-row {
    border-top: 1px solid var(--pe-border-light);
}

.settings-group-header + .settings-row,
.settings-group-subtitle + .settings-row {
    border-top: none;
}

.row-view {
    display: flex;
    align-items: center;
    padding: 13px 20px;
    min-height: 50px;
    gap: 12px;
    cursor: default;
}

.row-label {
    font-size: 14px;
    font-weight: 500;
    color: var(--pe-charcoal);
    min-width: 80px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 6px;
}

.row-value {
    flex: 1;
    /* Without min-width:0 a flex item won't shrink past its content, so the
       ellipsis above never fires — the value just widened the row instead. */
    min-width: 0;
    text-align: right;
    font-size: 14px;
    color: var(--pe-gray);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.row-value-masked {
    letter-spacing: 3px;
    color: var(--pe-text-placeholder);
}

.row-action {
    flex-shrink: 0;
    background: none;
    border: none;
    color: var(--pe-gray);
    cursor: pointer;
    padding: 6px;
    border-radius: 6px;
    transition: all 0.2s;
    display: flex;
    align-items: center;
    justify-content: center;
}

.row-action:hover {
    background: var(--pe-gray-light);
    color: var(--pe-blue);
}

.row-action svg {
    width: 15px;
    height: 15px;
}

.row-action-text {
    font-size: 13px;
    font-weight: 600;
    color: var(--pe-blue);
    padding: 4px 10px;
}

.row-action-text:hover {
    background: var(--pe-blue-light);
    color: var(--pe-blue-dark);
}

/* ── Row badges ── */
.row-badge {
    display: inline-flex;
    align-items: center;
    font-size: 10px;
    font-weight: 700;
    padding: 2px 7px;
    border-radius: 10px;
    letter-spacing: 0.2px;
    white-space: nowrap;
}

.row-badge-verified {
    background: var(--pe-green-light);
    color: #16a34a;
}

.row-badge-unverified {
    background: var(--pe-red-light);
    color: #dc2626;
}

/* ── Row edit area ── */
.row-edit {
    padding: 14px 20px 16px;
    background: var(--pe-surface);
    border-top: 1px solid var(--pe-border);
    animation: rowEditIn 0.2s ease;
}

@keyframes rowEditIn {
    from { opacity: 0; max-height: 0; padding-top: 0; padding-bottom: 0; }
    to { opacity: 1; max-height: 300px; }
}

.row-edit .form-group {
    margin-bottom: 14px;
}

.row-edit .form-group:last-of-type {
    margin-bottom: 0;
}

.row-edit-fields {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

@media (max-width: 599px) {
    .row-edit-fields {
        grid-template-columns: 1fr;
    }
}

.row-edit-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 14px;
}

/* ── Row notices ── */
.row-notice {
    padding: 8px 20px 12px;
    font-size: 13px;
}

.row-notice a {
    color: var(--pe-blue);
    text-decoration: none;
    font-weight: 500;
}

.row-notice a:hover {
    text-decoration: underline;
}

.pending-row {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 10px 20px;
    background: var(--pe-orange-light);
    border-top: 1px solid #fde68a;
    font-size: 13px;
    color: #92400e;
    flex-wrap: wrap;
}

.pending-row .pending-email-cancel {
    margin-left: auto;
    padding: 2px 10px;
    font-size: 12px;
    font-weight: 600;
    color: #991b1b;
    background: rgba(239,68,68,0.08);
    border: 1px solid rgba(239,68,68,0.2);
    border-radius: 6px;
    cursor: pointer;
    transition: all 0.2s;
}

.pending-row .pending-email-cancel:hover {
    background: rgba(239,68,68,0.15);
}

/* ── Row expand (email change) ── */
.row-expand {
    padding: 16px 20px 20px;
    background: var(--pe-surface);
    border-top: 1px solid var(--pe-border);
    animation: rowEditIn 0.2s ease;
}

.row-expand-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 6px;
}

.row-expand-header h4 {
    font-size: 14px;
    font-weight: 700;
    margin: 0;
}

.row-expand-close {
    background: none;
    border: none;
    font-size: 18px;
    color: var(--pe-gray);
    cursor: pointer;
    padding: 2px 6px;
    line-height: 1;
    border-radius: 4px;
}

.row-expand-close:hover {
    color: var(--pe-charcoal);
    background: var(--pe-gray-light);
}

.row-expand-desc {
    font-size: 13px;
    color: var(--pe-gray);
    margin-bottom: 14px;
    line-height: 1.5;
}

/* ── Settings summary ── */
.settings-summary {
    padding: 14px 20px 16px;
}

.summary-line {
    font-size: 14px;
    color: var(--pe-gray);
    line-height: 1.5;
}

.summary-detail {
    font-size: 13px;
    color: var(--pe-text-tertiary);
    margin-top: 2px;
}

.summary-empty {
    padding: 16px 20px;
    font-size: 14px;
    color: var(--pe-text-placeholder);
}

.summary-empty a {
    color: var(--pe-blue);
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
}

.summary-empty a:hover {
    text-decoration: underline;
}

/* ── Alerts inside groups ── */
.settings-group > .alert {
    margin: 0 20px;
    border-radius: 8px;
}

.settings-group > .alert.show {
    margin-bottom: 4px;
}

/* ── MFA section body ── */
.settings-group-body {
    padding: 4px 20px 20px;
}

/* ── Danger group ── */
.settings-group-danger {
    border-left-color: var(--pe-red);
}

.settings-group-danger .settings-group-title {
    color: var(--pe-red);
}

/* ═══════════════════════════════════════════════════════════
   SLIDE-IN PANELS
   ═══════════════════════════════════════════════════════════ */

.panel-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15,23,42,0.5);
    z-index: 200;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s ease, visibility 0s 0.3s;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}

.panel-backdrop.active {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.3s ease, visibility 0s;
}

/* Desktop: centered popup */
.slide-panel {
    position: fixed;
    top: 50%;
    left: 50%;
    right: auto;
    bottom: auto;
    width: 480px;
    max-width: calc(100vw - 40px);
    max-height: 85vh;
    background: var(--pe-white);
    z-index: 201;
    display: flex;
    flex-direction: column;
    box-shadow: 0 20px 60px rgba(0,0,0,0.15), 0 4px 16px rgba(0,0,0,0.08);
    border-radius: 20px;
    transform: translate(-50%, -50%) scale(0.9);
    opacity: 0;
    visibility: hidden;
    transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
                opacity 0.2s ease,
                visibility 0s 0.3s;
}

.slide-panel.active {
    transform: translate(-50%, -50%) scale(1);
    opacity: 1;
    visibility: visible;
    transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
                opacity 0.2s ease,
                visibility 0s;
}

.panel-header {
    display: flex;
    align-items: center;
    padding: 18px 24px;
    border-bottom: 1px solid var(--pe-border);
    flex-shrink: 0;
    gap: 12px;
}

.panel-title {
    font-family: var(--pe-font-display);
    font-size: 18px;
    font-weight: 700;
    flex: 1;
    margin: 0;
}

.panel-close {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--pe-gray);
    padding: 6px;
    border-radius: 6px;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.2s;
}

.panel-close:hover {
    background: var(--pe-gray-light);
    color: var(--pe-charcoal);
}

.panel-close svg {
    width: 20px;
    height: 20px;
}

.panel-body {
    padding: 24px;
    overflow-y: auto;
    flex: 1;
    -webkit-overflow-scrolling: touch;
}

.panel-body .form-group {
    margin-bottom: 16px;
}

.panel-body .form-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

.panel-body .form-row-address {
    display: flex;
    gap: 12px;
}

.panel-body .form-row-address > .form-group {
    min-width: 0;
}

.panel-footer {
    padding: 16px 24px;
    border-top: 1px solid var(--pe-border);
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    flex-shrink: 0;
    background: var(--pe-white);
}

/* Panel responsive — Mobile: bottom sheet */
@media (max-width: 599px) {
    .slide-panel {
        top: auto;
        left: 0;
        right: 0;
        bottom: 0;
        width: 100%;
        max-width: 100%;
        max-height: 90vh;
        max-height: calc(100dvh - var(--kb, 0px));
        border-radius: 20px 20px 0 0;
        transform: translateY(100%);
        opacity: 1;
        visibility: hidden;
        transition: transform 0.35s cubic-bezier(0.32, 0.72, 0, 1),
                    visibility 0s 0.35s;
    }

    /* Lift the panel sheet above the on-screen keyboard when a field inside it
       is focused (iOS; --kb is 0 on Android where the viewport already shrinks). */
    body.kb-open .slide-panel { bottom: var(--kb, 0px); }

    .slide-panel.active {
        transform: translateY(0);
        visibility: visible;
        transition: transform 0.35s cubic-bezier(0.32, 0.72, 0, 1),
                    visibility 0s;
    }

    .slide-panel::before {
        content: '';
        display: block;
        width: 36px;
        height: 4px;
        background: #d1d5db;
        border-radius: 2px;
        margin: 10px auto 0;
        flex-shrink: 0;
    }

    .panel-header {
        padding: 12px 20px;
    }

    .panel-body {
        padding: 20px;
    }

    .panel-body .form-row {
        grid-template-columns: 1fr;
    }

    .panel-body .form-row-address {
        flex-direction: column;
    }

    .panel-body .form-row-address > .form-group {
        flex: none !important;
    }
}

/* Body scroll lock when panel open */
body.panel-open {
    overflow: hidden;
}

/* ═══════════════════════════════════════════════════════════
   MFA SECTION
   ═══════════════════════════════════════════════════════════ */

.mfa-status {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 12px 16px;
    background: var(--pe-bg-card, #f8fafc);
    border: 1px solid var(--pe-border);
    border-radius: 8px;
}

.mfa-status svg {
    margin-top: 2px;
    color: var(--pe-blue);
}

.mfa-status-label {
    font-size: 0.9375rem;
    margin-bottom: 2px;
}

.mfa-status-desc {
    font-size: 0.8125rem;
    color: var(--pe-gray);
    margin: 0;
    line-height: 1.4;
}

.mfa-manual-entry {
    background: var(--pe-bg-card, #f8fafc);
    border: 1px solid var(--pe-border);
    border-radius: 8px;
    padding: 12px 16px;
}

.mfa-secret-key {
    display: flex;
    align-items: center;
    gap: 8px;
}

.mfa-secret-key code {
    font-family: 'SF Mono', 'Fira Code', monospace;
    font-size: 0.875rem;
    font-weight: 600;
    letter-spacing: 2px;
    color: var(--pe-text);
    word-break: break-all;
}

.mfa-copy-btn {
    background: none;
    border: none;
    cursor: pointer;
    padding: 4px;
    color: var(--pe-gray);
    border-radius: 4px;
    flex-shrink: 0;
}

.mfa-copy-btn:hover {
    color: var(--pe-blue);
    background: var(--pe-blue-light, #e0f2fe);
}

.mfa-recovery-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
}

.mfa-recovery-code {
    font-family: 'SF Mono', 'Fira Code', monospace;
    font-size: 0.8125rem;
    font-weight: 600;
    text-align: center;
    padding: 8px 4px;
    background: var(--pe-bg-card, #f8fafc);
    border: 1px solid var(--pe-border);
    border-radius: 6px;
    letter-spacing: 1px;
    color: var(--pe-text);
}

@media (max-width: 599px) {
    .mfa-recovery-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

/* ═══════════════════════════════════════════════════════════
   EMAIL CHANGE UI
   ═══════════════════════════════════════════════════════════ */

.email-field-wrap {
    display: flex;
    gap: 8px;
    align-items: stretch;
}

.email-field-wrap input[readonly] {
    flex: 1;
    background: var(--pe-bg);
    color: var(--pe-gray);
    cursor: default;
}

.email-status {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 6px;
    font-size: 13px;
    font-weight: 600;
}

.email-status.email-verified {
    color: #16a34a;
}

.email-status.email-unverified {
    color: #dc2626;
}

.email-status.email-unverified a {
    font-weight: 500;
}

.btn-change-email {
    padding: 0 16px;
    font-size: 13px;
    font-weight: 600;
    color: #38bdf8;
    background: rgba(56,189,248,0.08);
    border: 1px solid rgba(56,189,248,0.25);
    border-radius: var(--pe-radius);
    cursor: pointer;
    white-space: nowrap;
    transition: all 0.2s;
}

.btn-change-email:hover {
    background: rgba(56,189,248,0.15);
    border-color: rgba(56,189,248,0.4);
}

.pending-email-notice {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 6px;
    padding: 8px 12px;
    background: #fffbeb;
    border: 1px solid #fde68a;
    border-radius: 8px;
    font-size: 13px;
    color: #92400e;
}

.pending-email-icon {
    flex-shrink: 0;
}

.pending-email-cancel {
    margin-left: auto;
    padding: 2px 10px;
    font-size: 12px;
    font-weight: 600;
    color: #991b1b;
    background: rgba(239,68,68,0.08);
    border: 1px solid rgba(239,68,68,0.2);
    border-radius: 6px;
    cursor: pointer;
    transition: all 0.2s;
}

.pending-email-cancel:hover {
    background: rgba(239,68,68,0.15);
}

.email-change-modal {
    margin-top: 16px;
    padding: 20px;
    background: var(--pe-bg);
    border: 1px solid var(--pe-border);
    border-radius: var(--pe-radius);
    border-left: 3px solid #38bdf8;
}

.email-change-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
}

.email-change-header h4 {
    font-size: 15px;
    font-weight: 700;
    margin: 0;
}

.email-change-close {
    background: none;
    border: none;
    font-size: 18px;
    color: var(--pe-gray);
    cursor: pointer;
    padding: 4px 8px;
    line-height: 1;
}

.email-change-close:hover {
    color: var(--pe-charcoal);
}

.email-change-desc {
    font-size: 13px;
    color: var(--pe-gray);
    margin-bottom: 16px;
    line-height: 1.5;
}

/* ═══════════════════════════════════════════════════════════
   PULL TO REFRESH (§3.9)
   Custom, because an installed iOS PWA has no native pull-to-
   refresh at all and Chrome-Android's reloads the whole page.
   Transform-only so the pull stays on the compositor.
   ═══════════════════════════════════════════════════════════ */

.pe-ptr {
    position: fixed;
    top: 0;
    left: 50%;
    z-index: 900;
    width: 34px;
    height: 34px;
    margin-left: -17px;
    border-radius: 50%;
    background: var(--pe-white);
    border: 1px solid var(--pe-border);
    box-shadow: 0 2px 10px rgba(0,0,0,0.12);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--pe-blue);
    pointer-events: none;
    /* Parked offscreen until a pull moves it. */
    transform: translate3d(0, -48px, 0);
    opacity: 0;
}

/* Only animate on release — during the drag it tracks the finger 1:1. */
.pe-ptr.is-settling {
    transition: transform .28s cubic-bezier(0.32, 0.72, 0, 1), opacity .2s ease;
}

.pe-ptr svg { width: 18px; height: 18px; }

.pe-ptr.is-spinning svg {
    animation: pe-ptr-spin .7s linear infinite;
}

@keyframes pe-ptr-spin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .pe-ptr.is-spinning svg { animation-duration: 1.4s; }
}

/* ═══════════════════════════════════════════════════════════
   LEAGUE ACCESS DENIED
   Full-page takeover when the server refuses a league (401/403).
   Deliberately covers everything: a half-rendered page behind it
   reads as "this league is empty" rather than "not yours".
   ═══════════════════════════════════════════════════════════ */

.pe-access-denied {
    position: fixed;
    inset: 0;
    z-index: 9998;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: var(--pe-bg);
}

.pe-access-denied-card {
    max-width: 420px;
    text-align: center;
    background: var(--pe-white);
    border: 1px solid var(--pe-border);
    border-radius: 14px;
    padding: 36px 28px;
    box-shadow: 0 6px 24px rgba(0,0,0,0.08);
}

.pe-access-denied-icon {
    width: 52px;
    height: 52px;
    margin: 0 auto 18px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--pe-gray-light);
    color: var(--pe-text-secondary);
}

.pe-access-denied-icon svg { width: 26px; height: 26px; }

.pe-access-denied-card h2 {
    font-size: 20px;
    margin: 0 0 10px;
    color: var(--pe-text);
}

.pe-access-denied-card p {
    font-size: 15px;
    line-height: 1.6;
    color: var(--pe-text-secondary);
    margin: 0 0 24px;
}

/* ═══════════════════════════════════════════════════════════
   TOAST NOTIFICATIONS
   ═══════════════════════════════════════════════════════════ */

.toast-container {
    position: fixed;
    bottom: 24px;
    right: 24px;
    z-index: 9999;
    display: flex;
    flex-direction: column-reverse;
    gap: 10px;
    pointer-events: none;
}

.toast {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 20px;
    border-radius: 10px;
    font-size: 14px;
    font-weight: 500;
    box-shadow: 0 6px 20px rgba(0,0,0,0.15);
    pointer-events: auto;
    transform: translateX(calc(100% + 24px));
    opacity: 0;
    transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1),
                opacity 0.35s ease;
}

.toast.show {
    transform: translateX(0);
    opacity: 1;
}

.toast-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    font-size: 12px;
    font-weight: 700;
    flex-shrink: 0;
}

.toast-msg {
    line-height: 1.3;
}

.toast-success {
    background: #f0fdf4;
    color: #166534;
    border: 1px solid #bbf7d0;
}

.toast-success .toast-icon {
    background: #22c55e;
    color: #ffffff;
}

.toast-error {
    background: #fef2f2;
    color: #991b1b;
    border: 1px solid #fecaca;
}

.toast-error .toast-icon {
    background: #ef4444;
    color: #ffffff;
}

/* Undo toast — dark snackbar (Gmail pattern). The delete already happened
   (soft, recycle-binned); the button reverses it within the toast's window. */
.toast-undo {
    background: #1a1a2e;
    color: #f1f5f9;
    border: 1px solid rgba(255, 255, 255, 0.08);
}

.toast-action {
    background: none;
    border: 0;
    color: var(--pe-blue);
    font-family: inherit;
    font-size: 14px;
    font-weight: 700;
    line-height: 1;
    padding: 12px;
    margin: -12px -14px -12px 0;
    border-radius: 10px;
    cursor: pointer;
    flex-shrink: 0;
}

.toast-action:hover { background: rgba(56, 189, 248, 0.12); }
.toast-action:active { background: rgba(56, 189, 248, 0.2); }
.toast-action:disabled { opacity: 0.5; cursor: default; }

@media (max-width: 599px) {
    .toast-container {
        bottom: calc(var(--tab-h) + env(safe-area-inset-bottom, 0) + 16px);
        right: 16px;
        left: 16px;
    }
    /* When the keyboard is up the tab bar is hidden, so anchor toasts above the
       keyboard instead (the Undo snackbar must stay tappable). */
    body.kb-open .toast-container {
        bottom: calc(var(--kb, 0px) + 16px);
    }
}

/* ═══════════════════════════════════════════════════════════
   DARK MODE
   Redefine CSS variables + targeted overrides for components
   that can't rely on variable swaps alone.
   ═══════════════════════════════════════════════════════════ */

[data-theme="dark"] {
    color-scheme: dark;
    /* Core palette */
    --pe-bg:             #0f1117;
    --pe-white:          #1a1d27;
    /* --pe-card-bg is deliberately DARK-ONLY. It is referenced 191 times with
       contradictory fallbacks — 138 dark (#1f2937/#1e293b/#111827) and 30 light
       (#fff / --pe-white) — because the dark rules name it and the base rules
       name it too. Defining it globally would force one of those groups to the
       wrong colour. Every one of its 23 fallback-less uses is inside a
       [data-theme="dark"] selector, so scoping the definition here fixes them
       and leaves light mode resolving to each rule's own fallback, untouched.
       #1f2937 is what 123 of the fallbacks already name. */
    --pe-card-bg:        #1f2937;
    /* 35 of the 38 dark fallbacks for --pe-card name #1e293b, so that is the
       value rather than reusing --pe-white's #1a1d27 or --pe-card-bg's #1f2937.
       Three slightly different darks is what this palette already had; matching
       the existing fallbacks keeps 35 rules pixel-identical. */
    --pe-card:           #1e293b;
    /* Danger goes LIGHTER in dark mode, not darker. #dc2626 is chosen in :root
       because it clears AA on white (4.83:1, where #ef4444 managed 3.76:1), but
       the same darkening on a dark surface drops it to 3.03:1 — worse than what
       it replaced. #f87171 is the same red ramp one stop up and clears AA on
       every dark surface here (5.29 on the card, 6.82 on the page). */
    --pe-danger:         #f87171;
    --pe-charcoal:       #e4e6eb;
    --pe-gray:           #9ca3af;
    --pe-gray-light:     #252830;

    /* Borders & surfaces */
    --pe-border:         #2d3039;
    --pe-border-light:   #23262f;
    --pe-border-medium:  #3a3f4a;
    --pe-surface:        #1e2028;
    --pe-bg-subtle:      #262a33;

    /* Text */
    --pe-text:           #e4e6eb;
    --pe-text-secondary: #9ca3af;
    --pe-text-tertiary:  #6b7280;
    --pe-text-placeholder: #4b5563;

    /* Brand adjustments */
    --pe-blue-light:     rgba(56, 189, 248, 0.12);

    /* Shadows (stronger for dark backgrounds) */
    --pe-shadow:         0 1px 2px rgba(0,0,0,0.4), 0 16px 34px -16px rgba(0,0,0,0.6);
    --pe-shadow-md:      0 8px 24px -8px rgba(0,0,0,0.55);
    --pe-shadow-lg:      0 28px 56px -20px rgba(0,0,0,0.7);

    /* Alert backgrounds (dark variants) */
    --pe-red-light:      rgba(239, 68, 68, 0.1);
    --pe-green-light:    rgba(34, 197, 94, 0.1);
    --pe-orange-light:   rgba(245, 158, 11, 0.1);

    /* Tab bar */
    --tab-bg:            #1a1d27;
    --tab-border:        #2d3039;
    --tab-text:          #6b7280;
}

/* ── Form inputs ── */
[data-theme="dark"] .form-group input,
[data-theme="dark"] .form-group select,
[data-theme="dark"] .form-group textarea,
[data-theme="dark"] .pe-input {
    background: var(--pe-gray-light);
    color: var(--pe-text);
    border-color: transparent;
}

[data-theme="dark"] .form-group input:focus,
[data-theme="dark"] .form-group select:focus,
[data-theme="dark"] .form-group textarea:focus,
[data-theme="dark"] .pe-input:focus {
    background: var(--pe-surface);
    border-color: var(--pe-blue);
    box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.15);
}

[data-theme="dark"] .form-group input::placeholder,
[data-theme="dark"] .form-group textarea::placeholder {
    color: var(--pe-text-placeholder);
}

/* ── Buttons ── */
[data-theme="dark"] .btn-secondary {
    background: var(--pe-gray-light);
    color: var(--pe-text);
    border-color: var(--pe-border-medium);
}

@media (hover: hover) {
    [data-theme="dark"] .btn-secondary:hover {
        background: #2d3039;
        border-color: #4b5563;
    }
}

[data-theme="dark"] .btn-danger {
    background: rgba(239, 68, 68, 0.12);
    color: #fca5a5;
    border-color: rgba(239, 68, 68, 0.25);
}

@media (hover: hover) {
    [data-theme="dark"] .btn-danger:hover {
        background: rgba(239, 68, 68, 0.2);
    }
}

/* ── Alerts ── */
[data-theme="dark"] .alert-error {
    background: var(--pe-red-light);
    color: #fca5a5;
    border-color: rgba(239, 68, 68, 0.25);
}

[data-theme="dark"] .alert-success {
    background: var(--pe-green-light);
    color: #86efac;
    border-color: rgba(34, 197, 94, 0.25);
}

[data-theme="dark"] .alert-warning {
    background: var(--pe-orange-light);
    color: #fcd34d;
    border-color: rgba(245, 158, 11, 0.25);
}

[data-theme="dark"] .alert-info {
    background: rgba(56, 189, 248, 0.08);
    color: #7dd3fc;
    border-color: rgba(56, 189, 248, 0.2);
}

/* ── Toasts ── */
[data-theme="dark"] .toast-success {
    background: #0d2818;
    color: #86efac;
    border-color: rgba(34, 197, 94, 0.3);
}

[data-theme="dark"] .toast-success .toast-icon {
    background: #16a34a;
}

[data-theme="dark"] .toast-error {
    background: #2d1618;
    color: #fca5a5;
    border-color: rgba(239, 68, 68, 0.3);
}

[data-theme="dark"] .toast-error .toast-icon {
    background: #dc2626;
}

[data-theme="dark"] .toast-undo {
    background: var(--pe-surface);
    color: var(--pe-text);
    border-color: var(--pe-border-medium);
}

/* ── Badges ── */
[data-theme="dark"] .badge-verified,
[data-theme="dark"] .row-badge-verified {
    background: rgba(34, 197, 94, 0.12);
    color: #86efac;
}

[data-theme="dark"] .badge-pending {
    background: rgba(245, 158, 11, 0.12);
    color: #fcd34d;
}

[data-theme="dark"] .row-badge-unverified {
    background: rgba(239, 68, 68, 0.12);
    color: #fca5a5;
}

[data-theme="dark"] .badge-active {
    background: rgba(56, 189, 248, 0.12);
    color: #7dd3fc;
}

[data-theme="dark"] .badge-inactive {
    background: rgba(148, 163, 184, 0.12);
    color: #94a3b8;
}

[data-theme="dark"] .badge-suspended {
    background: rgba(239, 68, 68, 0.12);
    color: #fca5a5;
}

[data-theme="dark"] .badge-accepted {
    background: rgba(34, 197, 94, 0.12);
    color: #86efac;
}

[data-theme="dark"] .badge-expired,
[data-theme="dark"] .badge-revoked {
    background: rgba(148, 163, 184, 0.08);
    color: #64748b;
}

/* ── Admin rows ── */
[data-theme="dark"] .admin-row:hover {
    background: rgba(255, 255, 255, 0.04);
}

[data-theme="dark"] .btn-icon:hover {
    background: rgba(255, 255, 255, 0.08);
    color: var(--pe-text);
}

[data-theme="dark"] .btn-icon-danger:hover {
    background: rgba(239, 68, 68, 0.12);
    color: #fca5a5;
}

/* ── Card search bar ── */
[data-theme="dark"] .card-search-bar input {
    background: rgba(255, 255, 255, 0.06);
    border-color: rgba(255, 255, 255, 0.1);
}

[data-theme="dark"] .card-search-bar input:focus {
    border-color: var(--pe-blue);
}

[data-theme="dark"] .show-more-btn:hover {
    background: rgba(56, 189, 248, 0.12);
}

/* ── Clickable badge + status history ── */
[data-theme="dark"] .badge-btn:hover {
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}

[data-theme="dark"] .status-history-item {
    background: rgba(255, 255, 255, 0.04);
    border-left-color: rgba(255, 255, 255, 0.1);
}

/* ── Stat icon backgrounds ── */
[data-theme="dark"] .stat-icon-blue   { background: rgba(56, 189, 248, 0.15); color: #7dd3fc; }
[data-theme="dark"] .stat-icon-green  { background: rgba(34, 197, 94, 0.15); color: #86efac; }
[data-theme="dark"] .stat-icon-orange { background: rgba(245, 158, 11, 0.15); color: #fcd34d; }
[data-theme="dark"] .stat-icon-red    { background: rgba(239, 68, 68, 0.15); color: #fca5a5; }
[data-theme="dark"] .stat-icon-purple { background: rgba(139, 92, 246, 0.15); color: #c4b5fd; }

/* ── Dashboard player rows ── */
[data-theme="dark"] .dashboard-player-row:hover {
    background: rgba(255, 255, 255, 0.04);
}

/* ── Quick action buttons ── */
[data-theme="dark"] .quick-action-btn {
    background: var(--pe-white);
    border-color: var(--pe-border);
    color: var(--pe-text);
}

[data-theme="dark"] .quick-action-btn:hover {
    background: rgba(56, 189, 248, 0.08);
    border-color: var(--pe-blue);
}

[data-theme="dark"] .quick-action-btn:disabled:hover {
    background: var(--pe-white);
    border-color: var(--pe-border);
}

[data-theme="dark"] .qa-tile {
    background: rgba(255,255,255,0.03);
    border-color: var(--pe-border);
}

[data-theme="dark"] .qa-tile-label {
    color: var(--pe-text-secondary);
}

/* ── Lookup chips ── */
[data-theme="dark"] .lookup-chip {
    background: rgba(255,255,255,0.04);
    border-color: var(--pe-border);
}

[data-theme="dark"] .lookup-chip:hover {
    border-color: var(--pe-blue);
    box-shadow: 0 1px 4px rgba(56,189,248,0.08);
}

[data-theme="dark"] .lookup-chip-label {
    color: var(--pe-text);
}

[data-theme="dark"] .lookup-chip-btn:hover {
    background: rgba(56,189,248,0.12);
    color: var(--pe-blue);
}

[data-theme="dark"] .lookup-chip-btn-danger:hover {
    background: rgba(233,69,96,0.12);
    color: #f87171;
}

/* ── Empty state icon ── */
[data-theme="dark"] .empty-state .empty-icon {
    background: rgba(56, 189, 248, 0.12);
}

[data-theme="dark"] .empty-state .empty-icon svg {
    color: #7dd3fc;
}

/* ── Modals & Panels ── */
[data-theme="dark"] .modal-overlay {
    background: rgba(0, 0, 0, 0.6);
}

[data-theme="dark"] .panel-backdrop {
    background: rgba(0, 0, 0, 0.6);
}

[data-theme="dark"] .slide-panel {
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
}

[data-theme="dark"] .slide-panel::before {
    background: #4a4f5a;
}

[data-theme="dark"] .modal::before {
    background: #4a4f5a;
}

/* ── Player cards ── */
[data-theme="dark"] .player-card:hover {
    box-shadow: var(--pe-shadow-md);
}

/* ── Settings rows ── */
[data-theme="dark"] .row-edit,
[data-theme="dark"] .row-expand {
    background: var(--pe-surface);
    border-top-color: var(--pe-border);
}

[data-theme="dark"] .row-value-masked {
    color: var(--pe-text-placeholder);
}

/* ── Pending row (email change) ── */
[data-theme="dark"] .pending-row {
    background: rgba(245, 158, 11, 0.08);
    border-top-color: rgba(245, 158, 11, 0.2);
    color: #fcd34d;
}

[data-theme="dark"] .pending-row .pending-email-cancel {
    color: #fca5a5;
    background: rgba(239, 68, 68, 0.1);
    border-color: rgba(239, 68, 68, 0.2);
}

[data-theme="dark"] .pending-email-notice {
    background: rgba(245, 158, 11, 0.08);
    border-color: rgba(245, 158, 11, 0.2);
    color: #fcd34d;
}

/* ── Callout danger ── */
[data-theme="dark"] .callout-danger {
    background: rgba(239, 68, 68, 0.08);
    border-color: rgba(239, 68, 68, 0.2);
}

[data-theme="dark"] .callout-danger .callout-title {
    color: #fca5a5;
}

[data-theme="dark"] .callout-danger .callout-text {
    color: var(--pe-text-secondary);
}

/* ── Password strength ── */
[data-theme="dark"] .password-rules .rule.fail {
    color: #4b5563;
}

/* ── Email status ── */
[data-theme="dark"] .email-status.email-verified {
    color: #86efac;
}

[data-theme="dark"] .email-status.email-unverified {
    color: #fca5a5;
}

/* ── Change email button ── */
[data-theme="dark"] .btn-change-email {
    color: var(--pe-blue);
    background: rgba(56, 189, 248, 0.08);
    border-color: rgba(56, 189, 248, 0.2);
}

[data-theme="dark"] .btn-change-email:hover {
    background: rgba(56, 189, 248, 0.15);
    border-color: rgba(56, 189, 248, 0.35);
}

/* ── MFA components ── */
[data-theme="dark"] .mfa-status {
    background: var(--pe-surface);
}

[data-theme="dark"] .mfa-manual-entry {
    background: var(--pe-surface);
}

[data-theme="dark"] .mfa-recovery-code {
    background: var(--pe-surface);
}

/* ── Topbar + dropdown ── */
/* No dark-mode override needed: the base .app-topbar uses var(--pe-white),
   which is already theme-aware (a dark surface in dark mode). Forcing
   transparent here would undo the solid bar. */

[data-theme="dark"] .topbar-user-btn {
    border-color: var(--pe-border);
    /* --pe-white is already theme-aware (a dark surface in dark mode), so the
       base rule's background carries over; only deepen the shadow here. */
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}

@media (hover: hover) {
    [data-theme="dark"] .topbar-user-btn:hover {
        background: rgba(255,255,255,0.05);
        border-color: rgba(255,255,255,0.12);
    }
}

[data-theme="dark"] .topbar-dropdown {
    background: #1e2130;
    border-color: var(--pe-border);
    box-shadow: 0 8px 24px rgba(0,0,0,0.4);
}

[data-theme="dark"] .topbar-dropdown-item:hover,
[data-theme="dark"] .topbar-dropdown-toggle:hover {
    background: rgba(255,255,255,0.06);
}

[data-theme="dark"] .topbar-dropdown-divider {
    border-color: var(--pe-border);
}

/* ── Page header zone (tabs bar) ── */
/* Inherits transparent background from base; no dark override needed */

/* ── Profile tabs dark mode ── */
[data-theme="dark"] .profile-tabs {
    background: rgba(255,255,255,0.06);
}

[data-theme="dark"] .profile-tab {
    color: rgba(255,255,255,0.5);
}

[data-theme="dark"] .profile-tab:hover {
    color: rgba(255,255,255,0.8);
    background: rgba(255,255,255,0.05);
}

[data-theme="dark"] .profile-tab.active {
    color: #e4e6eb;
    background: rgba(56, 189, 248, 0.12);
    box-shadow: none;
}

/* ── Settings group unsaved ── */
[data-theme="dark"] .settings-group.unsaved {
    border-left-color: var(--pe-danger);
}

[data-theme="dark"] .tab-panel .content-card {
    border-left-color: var(--pe-blue);
}

[data-theme="dark"] .tab-panel .content-card.unsaved {
    border-left-color: var(--pe-danger);
}

/* ── Email change modal ── */
[data-theme="dark"] .email-change-modal {
    background: var(--pe-surface);
    border-color: var(--pe-border);
    border-left-color: var(--pe-blue);
}

/* ── Spinner dark variant in dark mode ── */
[data-theme="dark"] .spinner-dark {
    border-color: rgba(56, 189, 248, 0.15);
    border-top-color: var(--pe-blue);
}

/* ═══════════════════════════════════════════════════════════
   IMPERSONATION — Banner + User Search Modal
   ═══════════════════════════════════════════════════════════ */

/* ── Impersonation Banner ──────────────────────────────── */
.impersonation-banner {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    background: linear-gradient(135deg, #f59e0b, #d97706);
    color: #fff;
    padding: 0;
    width: 100%;
    z-index: 9999;
    flex-shrink: 0;
}

/* Push everything down when impersonation banner is visible */
body.is-impersonating .app-shell {
    padding-top: 36px;
}

.impersonation-banner-content {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 8px 16px;
    font-size: 13px;
    font-weight: 500;
}

.impersonation-banner-icon {
    display: flex;
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}

.impersonation-banner-icon svg {
    width: 18px;
    height: 18px;
}

.impersonation-banner-text strong {
    font-weight: 700;
}

.impersonation-role-badge {
    display: inline-block;
    background: rgba(255,255,255,0.2);
    padding: 1px 8px;
    border-radius: 10px;
    font-size: 11px;
    font-weight: 600;
    text-transform: capitalize;
    margin-left: 4px;
}

.impersonation-stop-btn {
    background: rgba(255,255,255,0.2);
    color: #fff;
    border: 1px solid rgba(255,255,255,0.3);
    padding: 4px 16px;
    border-radius: 6px;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.15s;
    margin-left: 8px;
}

.impersonation-stop-btn:hover {
    background: #fff;
    color: #d97706;
    border-color: #fff;
}

/* When impersonation is active, offset all fixed elements below the banner */
body.is-impersonating .app-topbar {
    top: 36px;
}
/* The sidebar is in normal flow now (position:relative), so the shell's
   padding-top:36px already drops it below the banner — an extra top offset here
   would double it up and leave a 36px gap. Keep it at 0. */
body.is-impersonating .app-sidebar {
    top: 0;
}

/* ── Impersonate Modal Overlay ────────────────────────── */
.impersonate-modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    backdrop-filter: blur(4px);
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding-top: 10vh;
    z-index: 10000;
    animation: fadeIn 0.15s ease;
}

@keyframes fadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}

.impersonate-modal {
    background: #fff;
    border-radius: 14px;
    width: 480px;
    max-width: 92vw;
    max-height: 70vh;
    display: flex;
    flex-direction: column;
    box-shadow: 0 20px 60px rgba(0,0,0,0.2);
    animation: slideUp 0.2s ease;
    overflow: hidden;
}

@keyframes slideUp {
    from { transform: translateY(20px); opacity: 0; }
    to   { transform: translateY(0); opacity: 1; }
}

.impersonate-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 18px 20px 14px;
    border-bottom: 1px solid #e5e7eb;
}

.impersonate-modal-header h2 {
    font-family: var(--pe-font-display);
    font-size: 17px;
    font-weight: 600;
    color: #111827;
    margin: 0;
}

.impersonate-modal-close {
    background: none;
    border: none;
    cursor: pointer;
    color: #9ca3af;
    padding: 4px;
    border-radius: 6px;
    display: flex;
    transition: all 0.15s;
}

.impersonate-modal-close:hover {
    background: #f3f4f6;
    color: #374151;
}

.impersonate-modal-close svg {
    width: 20px;
    height: 20px;
}

/* ── Search Input ──────────────────────────────────────── */
.impersonate-search-wrap {
    position: relative;
    padding: 12px 16px;
    border-bottom: 1px solid #e5e7eb;
}

.impersonate-search-icon {
    position: absolute;
    left: 28px;
    top: 50%;
    transform: translateY(-50%);
    color: #9ca3af;
    display: flex;
    pointer-events: none;
}

.impersonate-search-icon svg {
    width: 16px;
    height: 16px;
}

.impersonate-search-input {
    width: 100%;
    padding: 10px 12px 10px 36px;
    border: 1px solid #d1d5db;
    border-radius: 8px;
    font-size: 14px;
    color: #111827;
    background: #f9fafb;
    outline: none;
    transition: all 0.15s;
}

.impersonate-search-input:focus {
    border-color: var(--pe-blue);
    box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.12);
    background: #fff;
}

/* ── User List ─────────────────────────────────────────── */
.impersonate-user-list {
    flex: 1;
    overflow-y: auto;
    padding: 8px;
    min-height: 120px;
}

.impersonate-loading,
.impersonate-empty,
.impersonate-error {
    padding: 32px 16px;
    text-align: center;
    color: #9ca3af;
    font-size: 14px;
}

.impersonate-error {
    color: var(--pe-danger);
}

.impersonate-results-hint {
    padding: 10px 16px;
    text-align: center;
    font-size: 12px;
    color: #9ca3af;
    border-top: 1px solid var(--pe-border);
}

.impersonate-user-row {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 10px 12px;
    border: none;
    background: none;
    border-radius: 8px;
    cursor: pointer;
    text-align: left;
    transition: background 0.12s;
    font-family: inherit;
}

.impersonate-user-row:hover {
    background: #f3f4f6;
}

.impersonate-user-row:active {
    background: #e5e7eb;
}

.impersonate-user-row:disabled {
    cursor: wait;
}

.impersonate-user-avatar {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 13px;
    font-weight: 600;
    flex-shrink: 0;
}

.impersonate-user-info {
    flex: 1;
    min-width: 0;
}

.impersonate-user-name {
    font-size: 14px;
    font-weight: 500;
    color: #111827;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.impersonate-user-email {
    font-size: 12px;
    color: #6b7280;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.impersonate-role-tag {
    font-size: 11px;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: 10px;
    text-transform: capitalize;
    flex-shrink: 0;
    white-space: nowrap;
}

/* ── Dark Mode ─────────────────────────────────────────── */
[data-theme="dark"] .impersonate-modal {
    background: var(--pe-surface, #1e1e2e);
}

[data-theme="dark"] .impersonate-modal-header {
    border-color: var(--pe-border, #2d2d3f);
}

[data-theme="dark"] .impersonate-modal-header h2 {
    color: #f1f5f9;
}

[data-theme="dark"] .impersonate-modal-close:hover {
    background: rgba(255,255,255,0.06);
    color: #e2e8f0;
}

[data-theme="dark"] .impersonate-search-wrap {
    border-color: var(--pe-border, #2d2d3f);
}

[data-theme="dark"] .impersonate-search-input {
    background: rgba(255,255,255,0.04);
    border-color: var(--pe-border, #2d2d3f);
    color: #f1f5f9;
}

[data-theme="dark"] .impersonate-search-input:focus {
    background: rgba(255,255,255,0.06);
    border-color: var(--pe-blue);
}

[data-theme="dark"] .impersonate-user-row:hover {
    background: rgba(255,255,255,0.05);
}

[data-theme="dark"] .impersonate-user-row:active {
    background: rgba(255,255,255,0.08);
}

[data-theme="dark"] .impersonate-user-name {
    color: #f1f5f9;
}

[data-theme="dark"] .impersonate-user-email {
    color: #94a3b8;
}

/* ── Inline Confirmation View (replaces user list) ─────── */
.impersonate-confirm-view {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 40px 24px 32px;
    text-align: center;
    gap: 8px;
}

.impersonate-confirm-icon {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    background: linear-gradient(135deg, #f59e0b22, #d9770622);
    color: #d97706;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 8px;
}

.impersonate-confirm-icon svg {
    width: 24px;
    height: 24px;
}

.impersonate-confirm-title {
    font-size: 18px;
    font-weight: 600;
    color: #111827;
    margin: 0;
}

.impersonate-confirm-text {
    font-size: 14px;
    color: #6b7280;
    margin: 4px 0 16px;
    line-height: 1.5;
    max-width: 320px;
}

.impersonate-confirm-text strong {
    color: #111827;
    font-weight: 600;
}

.impersonate-confirm-actions {
    display: flex;
    gap: 10px;
    width: 100%;
    max-width: 280px;
}

.impersonate-confirm-cancel {
    flex: 1;
    padding: 10px 16px;
    border: 1px solid #d1d5db;
    background: #fff;
    color: #374151;
    font-size: 14px;
    font-weight: 500;
    border-radius: 8px;
    cursor: pointer;
    transition: all 0.12s;
    font-family: inherit;
}

.impersonate-confirm-cancel:hover {
    background: #f9fafb;
    border-color: #9ca3af;
}

.impersonate-confirm-btn {
    flex: 1;
    padding: 10px 16px;
    border: none;
    background: linear-gradient(135deg, #f59e0b, #d97706);
    color: #fff;
    font-size: 14px;
    font-weight: 600;
    border-radius: 8px;
    cursor: pointer;
    transition: all 0.12s;
    font-family: inherit;
}

.impersonate-confirm-btn:hover {
    filter: brightness(1.05);
    box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3);
}

.impersonate-confirm-btn:disabled,
.impersonate-confirm-cancel:disabled {
    opacity: 0.6;
    cursor: wait;
}

/* Dark mode */
[data-theme="dark"] .impersonate-confirm-title {
    color: #f1f5f9;
}

[data-theme="dark"] .impersonate-confirm-text {
    color: #94a3b8;
}

[data-theme="dark"] .impersonate-confirm-text strong {
    color: #f1f5f9;
}

[data-theme="dark"] .impersonate-confirm-cancel {
    background: rgba(255,255,255,0.06);
    border-color: var(--pe-border, #2d2d3f);
    color: #e2e8f0;
}

[data-theme="dark"] .impersonate-confirm-cancel:hover {
    background: rgba(255,255,255,0.1);
}

/* ── Mobile: Bottom Sheet Style Modal ──────────────────── */
@media (max-width: 599px) {
    .impersonate-modal-overlay {
        align-items: flex-end;
        padding-top: 0;
    }

    .impersonate-modal {
        width: 100%;
        max-width: 100%;
        border-radius: 16px 16px 0 0;
        max-height: 80vh;
        animation: slideUpMobile 0.25s ease;
    }

    @keyframes slideUpMobile {
        from { transform: translateY(100%); }
        to   { transform: translateY(0); }
    }

    .impersonation-banner-content {
        font-size: 12px;
        gap: 6px;
        padding: 6px 12px;
    }

    .impersonation-banner-icon {
        display: none;
    }

    /* On mobile the banner is shorter (~30px) */
    body.is-impersonating .app-shell {
        padding-top: 30px;
    }
    body.is-impersonating .app-topbar {
        top: 30px;
    }
}

/* ══════════════════════════════════════════════════════════
   ── Shared: Trash / Recycle Bin ──────────────────────────
   Used by: teams.html, divisions.html, seasons.html
   ══════════════════════════════════════════════════════════ */

/* Topbar trash icon button */
.topbar-trash-btn {
    position: relative; display: flex; align-items: center; justify-content: center;
    width: 36px; height: 36px; border: none; border-radius: 8px;
    background: transparent; cursor: pointer; color: var(--pe-text-muted); transition: all 0.15s;
}
.topbar-trash-btn:hover { background: var(--pe-hover, #f1f5f9); color: var(--pe-text); }
.topbar-trash-btn svg { width: 18px; height: 18px; }

/* Trash badge (red pill on trash icon) */
.trash-badge {
    position: absolute; top: -6px; right: -6px;
    background: var(--pe-danger, #dc2626); color: #fff;
    font-size: 10px; font-weight: 700;
    min-width: 18px; height: 18px; border-radius: 9px;
    display: flex; align-items: center; justify-content: center;
    padding: 0 5px; line-height: 1;
}

/* Trash days remaining */
.trash-days { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 8px; white-space: nowrap; }
.trash-days.urgent { background: rgba(239, 68, 68, 0.1); color: var(--pe-danger, #dc2626); }
.trash-days.normal { background: rgba(100, 116, 139, 0.1); color: var(--pe-text-muted); }
.trash-empty { text-align: center; padding: 32px 16px; color: var(--pe-text-muted); font-size: 14px; }
.trash-actions-right { display: flex; gap: 10px; }

/* Danger outline button */
.btn-danger-outline {
    background: transparent; color: var(--pe-danger, #dc2626);
    border: 1.5px solid var(--pe-danger, #dc2626); font-weight: 600;
}
.btn-danger-outline:hover { background: rgba(239, 68, 68, 0.08); }

/* Shared bulk/trash list components */
.bulk-modal { max-width: 640px; }
.bulk-select-bar {
    display: flex; align-items: center; justify-content: space-between;
    padding: 8px 12px; background: var(--pe-gray-light, #f8fafc);
    border-radius: 8px; margin-bottom: 8px; font-size: 13px;
}
.bulk-select-all { display: flex; align-items: center; gap: 8px; cursor: pointer; font-weight: 500; }
.bulk-select-all input { accent-color: var(--pe-blue); }
.bulk-selected-count { font-weight: 600; color: var(--pe-blue); }
.bulk-team-list {
    max-height: 220px; overflow-y: auto;
    border: 1px solid var(--pe-border); border-radius: 8px;
    margin-bottom: 16px;
}
.bulk-team-row {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 12px; border-bottom: 1px solid var(--pe-border);
    cursor: pointer; transition: background 0.1s;
}
.bulk-team-row:last-child { border-bottom: none; }
.bulk-team-row:hover { background: var(--pe-hover, rgba(0,0,0,0.02)); }
.bulk-team-row.selected { background: rgba(56, 189, 248, 0.06); }
.bulk-team-row input[type="checkbox"] { width: 16px; height: 16px; flex-shrink: 0; accent-color: var(--pe-blue); }
.bulk-team-swatch { width: 20px; height: 20px; border-radius: 5px; flex-shrink: 0; border: 1px solid rgba(0,0,0,0.1); }
.bulk-team-name { flex: 1; font-size: 13px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bulk-team-meta { font-size: 11px; color: var(--pe-text-muted); white-space: nowrap; }
.bulk-empty { text-align: center; padding: 24px 16px; color: var(--pe-text-muted); font-size: 13px; }


/* Dark mode */
[data-theme="dark"] .topbar-trash-btn:hover { background: rgba(255,255,255,0.08); color: var(--pe-text); }
[data-theme="dark"] .bulk-select-bar { background: rgba(255,255,255,0.04); }
[data-theme="dark"] .bulk-team-list { border-color: var(--pe-border); }
[data-theme="dark"] .bulk-team-row:hover { background: rgba(255,255,255,0.04); }
[data-theme="dark"] .bulk-team-row.selected { background: rgba(56,189,248,0.1); }
[data-theme="dark"] .trash-days.normal { background: rgba(255,255,255,0.06); }

/* ──────────────────────────────────────────────────────────
   Publish toggle (used in season/event modal headers).
   The `seas-` prefix is kept for parity with seasons.html where
   it was introduced; events.html reuses the same classes.
   ────────────────────────────────────────────────────────── */
.seas-publish-toggle {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    user-select: none;
}
.seas-publish-toggle input { position: absolute; opacity: 0; pointer-events: none; }
.seas-publish-track {
    width: 36px;
    height: 20px;
    background: #cbd5e1;
    border-radius: 999px;
    position: relative;
    transition: background 0.18s;
    flex-shrink: 0;
}
.seas-publish-thumb {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 16px;
    height: 16px;
    background: #fff;
    border-radius: 50%;
    box-shadow: 0 1px 2px rgba(0,0,0,0.2);
    transition: transform 0.18s;
}
.seas-publish-toggle input:checked ~ .seas-publish-track { background: var(--pe-blue, #38bdf8); }
.seas-publish-toggle input:checked ~ .seas-publish-track .seas-publish-thumb { transform: translateX(16px); }
.seas-publish-label {
    font-size: 13px;
    font-weight: 600;
    color: var(--pe-text);
}
[data-theme="dark"] .seas-publish-track { background: #475569; }

/* ═══════════════════════════════════════════════════════════
   TERMS ACCEPTANCE (mig 258) — the checkbox on every signup
   surface. Shared so registration, the league application and
   checkout cannot drift into three different-looking consents.
   The box is never pre-checked; see the HTML.
   ═══════════════════════════════════════════════════════════ */
.form-consent {
    margin: 18px 0 4px;
}
.consent-row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    cursor: pointer;
    /* 44px tappable target without making the text bigger — the row is the
       hit area, so the label counts as part of the control. */
    min-height: 44px;
    padding: 6px 0;
    font-size: 14px;
    line-height: 1.45;
    color: var(--pe-text-muted, #64748b);
}
.consent-row input[type="checkbox"] {
    /* 18px box, nudged onto the first line of text rather than centred on a
       two-line label. flex-shrink so a long label can never squash it. */
    width: 18px;
    height: 18px;
    margin: 1px 0 0;
    flex-shrink: 0;
    accent-color: var(--pe-blue, #38bdf8);
    cursor: pointer;
}
.consent-row a {
    color: var(--pe-blue, #38bdf8);
    text-decoration: underline;
    text-underline-offset: 2px;
}
.consent-row a:hover { opacity: .85; }

/* The browser's own "please tick this" bubble points at the box, which is
   enough — but an invalid box should also read as unfinished, not decorative. */
.consent-row input[type="checkbox"]:invalid,
.consent-row input[type="checkbox"]:user-invalid {
    outline: 2px solid var(--pe-danger, #ef4444);
    outline-offset: 2px;
}

/* ── Record profile shell (.rp-*) ────────────────────────────────────────────
   The page chrome a RECORD gets when it has a home of its own: breadcrumb,
   header card with avatar/name/tags/counts, tab row, panels, empty states.

   Lived inside season-profile.html until 2026-09-21, when the event page needed
   the same shell. Copying it would have repeated a documented bug: that file's
   own comment records 046cf307, where a shared rule lost a body and only the
   page WITHOUT a private copy showed the breakage. Two copies is the drift the
   extraction was for — so it was extracted rather than duplicated.

   Season-specific chrome (.tm-*, .sf-tools) deliberately stayed on that page:
   this is the shell, not everything that happens to sit inside it. */
.rp-crumb { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--pe-text-secondary, #64748b); margin-bottom: 16px; flex-wrap: wrap; }
.rp-crumb a { color: var(--pe-blue, #0284c7); text-decoration: none; font-weight: 600; }
.rp-crumb a:hover { text-decoration: underline; }
.rp-crumb svg { width: 14px; height: 14px; opacity: .6; }
.rp-crumb .rp-here { color: var(--pe-text); font-weight: 600; }
.rp-crumb .rp-back { margin-left: auto; display: inline-flex; align-items: center; gap: 4px; }
.rp-crumb .rp-back svg { width: 13px; height: 13px; opacity: 1; }
@media (max-width: 599px) { .rp-crumb .rp-back { margin-left: 0; } }
.rp-header { background: var(--pe-surface, #fff); border: 1px solid var(--pe-border, #e2e8f0); border-radius: 14px; padding: 20px; box-shadow: 0 1px 2px rgba(15,23,42,.04); margin-bottom: 18px; }
.rp-head-top { display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
.rp-av { width: 56px; height: 56px; border-radius: 14px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; background: var(--pe-blue-light, #e0f2fe); color: var(--pe-blue, #0284c7); }
.rp-av svg { width: 28px; height: 28px; }
.rp-id { min-width: 0; flex: 1; }
.rp-name { font-size: 22px; font-weight: 700; color: var(--pe-text); line-height: 1.2; }
.rp-sub { font-size: 13px; color: var(--pe-text-secondary, #64748b); margin-top: 4px; }
.rp-tags { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.rp-counts { display: flex; gap: 22px; margin-top: 14px; }
.rp-count b { display: block; font-size: 20px; font-weight: 800; color: var(--pe-text); }
.rp-count span { font-size: 12px; color: var(--pe-text-secondary, #64748b); }
.rp-actions { display: flex; gap: 8px; flex-shrink: 0; flex-wrap: wrap; }
/* On a phone the action button and the stats cannot share a row. `.rp-actions`
   does not shrink, so on a 375px screen `.rp-id` was down to 121px and each
   stat cell to 31px — "Class times" broke across two lines and dropped the
   second word under the next column's label. Giving the actions the full basis
   wraps them to their own line and hands the stats the width back. */
@media (max-width: 599px) {
  .rp-head-top > .rp-actions { flex-basis: 100%; }
  .rp-counts { flex-wrap: wrap; gap: 14px 20px; }
  .rp-count { flex: 0 0 auto; }
}
.rp-card { background: var(--pe-surface, #fff); border: 1px solid var(--pe-border, #e2e8f0); border-radius: 12px; overflow: hidden; margin-bottom: 16px; }
.rp-card-title { padding: 12px 16px; font-weight: 700; font-size: 13px; text-transform: uppercase; letter-spacing: .03em; color: var(--pe-text-secondary, #64748b); background: var(--pe-bg-subtle, #f8fafc); border-bottom: 1px solid var(--pe-border, #e2e8f0); display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.rp-card-title a { font-size: 12.5px; font-weight: 600; color: var(--pe-blue, #0284c7); text-decoration: none; text-transform: none; letter-spacing: 0; }
.rp-panel-head { display: flex; align-items: center; justify-content: flex-end; min-height: 34px; margin: 0 0 12px; }
.rp-empty { font-size: 13px; color: var(--pe-text-secondary, #64748b); padding: 22px 16px; text-align: center; }
.rp-state { text-align: center; padding: 60px 20px; color: var(--pe-text-secondary, #64748b); }
.rp-tabs { display: flex; align-items: center; gap: 12px; border-bottom: 1px solid var(--pe-border, #e2e8f0); margin-bottom: 18px; }
.rp-tabs-left { display: flex; flex-wrap: wrap; gap: 0 2px; }
@media (max-width: 599px) { .rp-tabs { overflow-x: auto; } .rp-tabs-left { flex-wrap: nowrap; } }
.rp-tab { padding: 10px 13px; font-size: 14px; font-weight: 600; color: var(--pe-text-secondary, #64748b); background: none; border: none; border-bottom: 2px solid transparent; cursor: pointer; white-space: nowrap; }
.rp-tab:hover { color: var(--pe-text); }
.rp-tab.active { color: var(--pe-blue, #0284c7); border-bottom-color: var(--pe-blue, #0284c7); }
.rp-tab .rp-tab-count { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 5px; margin-left: 5px; border-radius: 9px; font-size: 11px; font-weight: 700; background: #eef2f6; color: #475569; }
.rp-tab .rp-tab-count:empty { display: none; }
.rp-tab .rp-tab-count.rp-tab-alert { background: #fbbf24; color: #78350f; }
.rp-panel { display: none; }
.rp-panel.active { display: block; }
.rp-empty-state { text-align: center; padding: 44px 16px; color: var(--pe-text-secondary, #64748b); }
.rp-empty-state svg { width: 34px; height: 34px; color: var(--pe-blue, #38bdf8); opacity: .6; }
.rp-empty-state p { margin: 10px 0 0; font-size: 14px; }
.rp-empty-state p.rp-empty-line { font-size: 13px; max-width: 560px; margin-left: auto; margin-right: auto; }

/* ═══════════════════════════════════════════════════════════
   MOBILE MECHANICAL COMPLIANCE — Tier 1 of
   docs/mobile-design-standards.md. Native-feel + platform-standard
   fixes; nothing here changes how any screen looks at rest.
   ═══════════════════════════════════════════════════════════ */

/* Stop iOS from inflating text on rotation to landscape. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

/* Installed app: no body rubber-band past the edge, no accidental
   Chrome-Android pull-to-refresh reloading a page mid-wizard. */
@media (display-mode: standalone) {
    html, body { overscroll-behavior: none; }
}

/* Inner scrollers keep their scroll to themselves — a bottom sheet's
   scroll never chains to the page behind it, and a table's horizontal
   pan never triggers browser back-swipe. */
.modal, .modal-body, .league-sheet, .table-wrap,
.phil-messages, .phil-activity-list {
    overscroll-behavior: contain;
}

/* Touch responsiveness: no double-tap-zoom delay on taps, no gray
   iOS tap flash (we provide our own hover/active feedback). */
a, button, select, label,
.btn, .chip, .tab-item, .sidebar-nav-item, .stat-card, .topbar-user-btn {
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
}

/* App chrome is not selectable text — long-pressing a tab label must
   not start iOS text selection. Content (names, addresses, schedules)
   stays selectable; parents copy addresses. */
.app-bottom-tabs, .app-sidebar, .app-topbar, .tab-label, button, .chip {
    -webkit-user-select: none;
    user-select: none;
}

/* Notched iPhones (active once nav.js adds viewport-fit=cover): grow
   the topbar by the status-bar inset so its content sits below the
   Dynamic Island; --topbar-h also drives the page's padding-top, so
   the content offset tracks automatically. Zero effect in a normal
   browser (inset = 0). */
@media (max-width: 599px) {
    :root { --topbar-h: calc(56px + env(safe-area-inset-top, 0px)); }
    .app-topbar { padding-top: env(safe-area-inset-top, 0px); }
}

/* Accessibility kill-switch: the OS "reduce motion" setting wins over
   every animation/transition in the app (sheets, toasts, celebrations). */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* ═══════════════════════════════════════════════════════════
   MOBILE FEEL — Tier 2 of docs/mobile-design-standards.md.
   Press states, motion tokens, page transitions, skeletons.
   ═══════════════════════════════════════════════════════════ */

/* Motion tokens (M3 duration bands; sheets keep the iOS curve that the
   modal system already uses). Adopt these for new animation work. */
:root {
    --motion-micro: 150ms;                             /* state/selection */
    --motion-comp:  300ms;                             /* card, chip, toast */
    --motion-sheet: 350ms;                             /* sheets, page-level */
    --ease-enter: cubic-bezier(0.05, 0.7, 0.1, 1);
    --ease-exit:  cubic-bezier(0.3, 0, 0.8, 0.15);
    --ease-sheet: cubic-bezier(0.32, 0.72, 0, 1);
}

/* Press states — feedback the instant the finger lands (0s in), animate
   only on release. Buttons/chips/cards compress; nav/list rows tint. */
.btn:active, .btn-primary:active, .btn-secondary:active, .btn-danger:active,
.chip:active, .stat-card:active {
    transform: scale(0.97);
    transition-duration: 0s;
}
.sidebar-nav-item:active {
    color: var(--sidebar-text-hover);
    background: var(--sidebar-bg-hover);
    transition-duration: 0s;
}
.tab-item:active { opacity: 0.65; transition-duration: 0s; }
.topbar-user-btn:active { background: var(--pe-gray-light); transition-duration: 0s; }

/* NO cross-document view transitions on app pages (removed 2026-09-09).
   A 220ms crossfade lived here from 2026-07-18. It held a frozen snapshot
   of the old page — input ignored — until the new document was paint-ready,
   which after every deploy (re-versioned 270KB CSS + 117KB nav.js, cold
   opcache) or under a running test gate was long enough to read as lag; and
   the app shell paints "Loading…" placeholders first, so the fade landed on
   an empty shell and content popped in afterwards. The public site zeroed
   its own copy for the same complaint (league-page.css), and
   email-campaigns.html had opted out for the same "ghost content" flash.
   Robb: "Remove the rule." A slow load now looks like a slow load — the
   page appears and fills in, and stays clickable. */

/* ── Registration questions (mig 256) ─────────────────────────────────────
   The block of checkboxes that asks a league admin which extra questions a
   program's registration should ask. It appears in THREE drawers — the season
   drawer, the event drawer and the tournament create drawer — and the markup is
   copied between them verbatim, which is only safe while the styling lives here
   rather than in three page-level <style> blocks.

   The row is a <label> wrapping the checkbox, so the text is part of the hit
   area and no `for=` has to stay in step with an id. Rows are driven by
   `data-rq="jersey|skill|coach"`, not per-page ids, so the same markup can be
   pasted into all three files and API.rqRead/rqFill reads any of them. */
.rq-group { border: 1px solid var(--pe-border); border-radius: 12px; padding: 14px 16px; background: var(--pe-bg-subtle); }
/* Same trap as label.rq-row below: the legend is a <label> inside a
   .form-group, so `.form-group label` (0,1,1) was winning and rendering this
   section heading at 12.5px in secondary text — a field label, not the heading
   it declares itself to be. Element+class ties and wins on order. */
.rq-legend,
label.rq-legend { display: block; font-size: 13px; font-weight: 700; letter-spacing: 0; color: var(--pe-text); margin: 0; }
/* 6px, not 10px: the first row adds 12px of its own padding under this, so
   the gap the eye sees was 22px — twice the space between the rows themselves. */
.rq-hint { font-size: 12.5px; color: var(--pe-text-muted); margin: 4px 0 6px; line-height: 1.45; }
/* `label.rq-row`, not just `.rq-row`: the row IS a <label>, and the block sits
   inside a `.form-group` in all three drawers, so `.form-group label`
   (0,1,1) — `display: block` — was beating `.rq-row` (0,1,0) and stacking the
   checkbox ABOVE its own title. That is what the rows have looked like since
   they shipped. Element+class ties on specificity and wins on order, because
   this block is far below `.form-group label` in this file. */
.rq-row,
label.rq-row {
    display: flex; align-items: flex-start; gap: 11px;
    /* 12px of vertical padding puts the row over the 44px touch floor without
       a min-height that would fight a two-line subtitle.

       NO HORIZONTAL PADDING (Robb, 2026-09-22: "clean up the white spacing
       around the check boxes"). It was 4px, which is nothing on its own and
       is exactly the problem: the group already insets its contents by 16px,
       so the 4px pushed every row title 4px right of the heading and the hint
       above it, and pulled every checkbox 4px in from the edge those two
       align to. Three things that should share one left edge sat on two, and
       the separator lines stopped short at both ends. At 0 the row spans the
       group's own content width and everything lines up. */
    padding: 12px 0; margin: 0; cursor: pointer; border-radius: 8px;
    border-top: 1px solid var(--pe-border);
}
.rq-row:first-of-type { border-top: 0; }
.rq-row:hover { background: var(--pe-card, #fff); }
[data-theme="dark"] .rq-row:hover { background: rgba(255,255,255,.04); }
/* The box sits at the FAR RIGHT, on the title's line (Robb, 2026-09-19).
   Done with `order`, not by moving the input in the markup: this block is
   copied verbatim into seasons.html, events.html and tournament.html, and the
   <input> has to stay the label's first child for the three of them to keep
   reading as one thing. margin-top 0 centres an 18px box on the 18.2px title
   line box (14px x 1.3). */
.rq-row > input[type="checkbox"] { order: 2; flex: none; width: 18px; height: 18px; margin: 0 0 0 auto; accent-color: var(--pe-blue); cursor: pointer; }
.rq-text { order: 1; flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rq-title { font-size: 14px; font-weight: 600; color: var(--pe-text); line-height: 1.3; }
.rq-sub { font-size: 12.5px; color: var(--pe-text-muted); line-height: 1.45; }
/* A row the program cannot offer — a tournament has no season, so it has
   nowhere to store a coach opt-in. Shown greyed with the reason rather than
   hidden, so the three drawers still read as the same three questions. */
.rq-row.rq-off { cursor: default; opacity: .55; }
.rq-row.rq-off:hover { background: none; }
.rq-row.rq-off > input[type="checkbox"] { cursor: default; }
@media (max-width: 1023px) {
    /* The house touch size for a checkbox (see the tablet/phone block at the
       end of this file, which lists selectors one by one). A 22px box is
       taller than the title's line box, so lift it to stay centred on it. */
    .rq-row > input[type="checkbox"] { width: 22px; height: 22px; margin-top: -2px; }
}

/* Skeleton loading blocks — perceived-speed for list/dashboard loads.
   Usage: <div class="skeleton skeleton-line w-60"></div> placeholders,
   replaced by real content when data lands. Shimmer dies under the
   reduced-motion kill-switch above (static gray blocks remain). */
.skeleton {
    position: relative;
    overflow: hidden;
    background: var(--pe-gray-light, #e2e8f0);
    border-radius: 8px;
}
.skeleton::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.55), transparent);
    transform: translateX(-100%);
    animation: pe-shimmer 1s infinite;
}
[data-theme="dark"] .skeleton { background: rgba(255, 255, 255, 0.08); }
[data-theme="dark"] .skeleton::after {
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.07), transparent);
}
@keyframes pe-shimmer { to { transform: translateX(100%); } }
.skeleton-line { height: 12px; }
.skeleton-line + .skeleton-line { margin-top: 8px; }
.skeleton-line.w-80 { width: 80%; }
.skeleton-line.w-60 { width: 60%; }
.skeleton-line.w-40 { width: 40%; }
.skeleton-circle { border-radius: 50%; }

/* The 300ms rule: never show a loading affordance for fast responses —
   add this class to spinners/skeleton wrappers so sub-300ms loads render
   nothing at all (a flashing spinner makes fast feel slow). */
.loading-delayed { opacity: 0; transition: opacity 0.15s ease 0.3s; }
.loading-delayed.show, .is-loading .loading-delayed { opacity: 1; }

/* Page load progress — the honest version of a spinner (Robb, 2026-09-16:
   "do something else when it's loading or having a difficult time loading,
   like a percentage or a loading effect").

   A dashboard fans several requests out at once and paints each answer as it
   lands. Until the slow one arrives the page sits on dashes and shimmer, which
   is indistinguishable from a page that is simply broken — and with a 12s
   per-request cap it can sit there a long time. This bar counts how many
   requests have LANDED, so the wait reads as progress and says out loud when
   it is going badly.

   Markup (the page drives it; see dashboard.html):
     <div class="pe-loadbar" id="x"><div class="pe-loadbar-track">
        <div class="pe-loadbar-fill"></div></div>
        <span class="pe-loadbar-text"></span></div>
   .show reveals it (hold it back 300ms — a fast load must never flash one),
   .is-slow greys it once the wait stops being normal — it de-emphasises rather than
   warns, because a slow load is not a fault and amber told the user it was
   (Robb, 2026-09-19). */
.pe-loadbar {
    display: none; align-items: center; gap: 12px;
    margin-bottom: 14px;
}
.pe-loadbar.show { display: flex; }
.pe-loadbar-track {
    flex: 1 1 auto; min-width: 0; height: 5px; border-radius: 999px;
    background: var(--pe-border, #e6e8ee); overflow: hidden;
}
.pe-loadbar-fill {
    height: 100%; width: 0; border-radius: 999px;
    background: var(--pe-blue, #38bdf8);
    transition: width .3s cubic-bezier(.4,0,.2,1), background-color .3s ease;
}
.pe-loadbar-text {
    flex: none; font-size: 12.5px; font-weight: 600;
    color: var(--pe-text-muted); font-variant-numeric: tabular-nums;
}
.pe-loadbar.is-slow .pe-loadbar-fill { background: var(--pe-text-secondary, #64748b); }
.pe-loadbar.is-slow .pe-loadbar-text { color: var(--pe-text-secondary, #64748b); }
/* A value still waiting for its request. Sized in ch so the block is roughly
   the width of the number that will replace it, instead of jumping. */
.pe-val-skeleton {
    display: inline-block; width: 2.4em; height: .85em; vertical-align: -.05em;
    border-radius: 6px;
}

/* ══════════════════════════════════════════════════════════════════════
   DESIGN REFRESH — Pass 2: app-shell component polish (2026-08-05)
   Scoped to .app-main so ONLY the internal app pages get the confident
   treatment; auth, marketing, and public-league pages keep their own brand
   styling. Additive + self-contained — rollback = delete this whole block, or
   restore docs/_design-backup/app.css.bak / tag pre-design-refresh-20260805.
   ══════════════════════════════════════════════════════════════════════ */
/* Primary action: solid, confident dark instead of the washed-out sky gradient
   (keeps the base .btn width/padding — only the fill changes). In dark mode the
   primary becomes the brand sky for contrast on the dark ground. */
.app-main .btn-primary {
    background: #171a2b;
    box-shadow: 0 1px 2px rgba(17,22,36,0.18);
}
.app-main .btn-primary:hover { background: #252a41; }
[data-theme="dark"] .app-main .btn-primary { background: var(--pe-blue); color: #0c1017; }
[data-theme="dark"] .app-main .btn-primary:hover { background: #7dd3fc; }
/* Secondary: clean outline button. */
.app-main .btn-secondary {
    background: var(--pe-white);
    color: var(--pe-text);
    border: 1px solid var(--pe-border-medium);
}
.app-main .btn-secondary:hover { border-color: var(--pe-text-tertiary); background: var(--pe-surface); }
/* Form fields: a touch more border definition + a soft brand focus ring.
   Only sets border + focus — no sizes/padding, so no layout shift. */
.app-main input[type=text], .app-main input[type=email], .app-main input[type=search],
.app-main input[type=number], .app-main input[type=tel], .app-main input[type=url],
.app-main input[type=date], .app-main input[type=password], .app-main input[type=time],
.app-main select, .app-main textarea {
    border: 1px solid var(--pe-border-medium);
}
.app-main input[type=text]:focus, .app-main input[type=email]:focus, .app-main input[type=search]:focus,
.app-main input[type=number]:focus, .app-main input[type=tel]:focus, .app-main input[type=url]:focus,
.app-main input[type=date]:focus, .app-main input[type=password]:focus, .app-main input[type=time]:focus,
.app-main select:focus, .app-main textarea:focus {
    outline: none;
    border-color: var(--pe-blue-dark);
    box-shadow: 0 0 0 3.5px rgba(56,189,248,0.16);
}
/* Pass 3: same treatment inside pop-up modals (they render outside .app-main,
   so the app-shell rules above don't reach them) — keeps buttons/inputs
   consistent everywhere. Rollback with the rest of this refresh block. */
.modal .btn-primary { background: #171a2b; box-shadow: 0 1px 2px rgba(17,22,36,0.18); }
.modal .btn-primary:hover { background: #252a41; }
[data-theme="dark"] .modal .btn-primary { background: var(--pe-blue); color: #0c1017; }
[data-theme="dark"] .modal .btn-primary:hover { background: #7dd3fc; }
.modal .btn-secondary { background: var(--pe-white); color: var(--pe-text); border: 1px solid var(--pe-border-medium); }
.modal .btn-secondary:hover { border-color: var(--pe-text-tertiary); background: var(--pe-surface); }
.modal input[type=text], .modal input[type=email], .modal input[type=search],
.modal input[type=number], .modal input[type=tel], .modal input[type=url],
.modal input[type=date], .modal input[type=password], .modal input[type=time],
.modal select, .modal textarea { border: 1px solid var(--pe-border-medium); }
.modal input[type=text]:focus, .modal input[type=email]:focus, .modal input[type=search]:focus,
.modal input[type=number]:focus, .modal input[type=tel]:focus, .modal input[type=url]:focus,
.modal input[type=date]:focus, .modal input[type=password]:focus, .modal input[type=time]:focus,
.modal select:focus, .modal textarea:focus {
    outline: none;
    border-color: var(--pe-blue-dark);
    box-shadow: 0 0 0 3.5px rgba(56,189,248,0.16);
}


/* ══ Registration wizard shell ═══════════════════════════════════════════
   The chrome every multi-step registration flow shares: the centred
   container, the white card, the numbered stepper, panels, the field and
   info-box primitives, and the Back / Save / Continue action row.

   Lifted verbatim out of register-player.html, which was the only page
   that had it. tournament-signup.html therefore had nothing to reuse and
   grew its own look instead — which is the drift this section exists to
   stop. A new flow gets the house wizard by using these class names.

   Page-specific pieces (player tiles, waivers, payment, review) stay with
   the page that owns them.
   ══════════════════════════════════════════════════════════════════════ */
.rw-container { max-width: 720px; margin: 0 auto; padding: 24px 0; }
.rw-card {
    background: var(--pe-white, #fff);
    border: 1px solid var(--pe-border, #e2e8f0);
    border-radius: 12px;
    padding: 28px 28px 24px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
    margin-bottom: 24px;
}
[data-theme="dark"] .rw-card {
    background: var(--pe-card-bg, #1f2937);
    border-color: var(--pe-border, #374151);
}
@media (max-width: 599px) {
    .rw-card { padding: 20px 16px 18px; }
}
.rw-stepper {
    display: flex; align-items: center;
    margin: 0 0 28px;
    padding: 0 4px 20px;
    border-bottom: 1px solid var(--pe-border, #e2e8f0);
}
[data-theme="dark"] .rw-stepper {
    border-bottom-color: var(--pe-border, #374151);
}
.rw-step { display: flex; align-items: center; gap: 8px; flex: 1; }
.rw-step-num {
    width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
    font-size: 14px; font-weight: 600; flex-shrink: 0;
    background: var(--pe-border, #e2e8f0); color: var(--pe-text-secondary, #64748b);
    transition: background 0.2s, color 0.2s;
}
.rw-step.active .rw-step-num { background: var(--pe-blue, #38bdf8); color: #fff; }
.rw-step.done .rw-step-num { background: #22c55e; color: #fff; }
.rw-step-label { font-size: 13px; color: var(--pe-text-secondary, #64748b); white-space: nowrap; }
.rw-step.active .rw-step-label { color: var(--pe-text, #1e293b); font-weight: 600; }
.rw-step.done .rw-step-label { color: var(--pe-text, #1e293b); }
.rw-step-line { flex: 1; height: 2px; background: var(--pe-border, #e2e8f0); margin: 0 8px; min-width: 20px; }
.rw-step-line.done { background: #22c55e; }
.rw-panel { display: none; }
.rw-panel.active { display: block; }
.rw-panel-title { font-size: 20px; font-weight: 700; margin-bottom: 6px; color: var(--pe-text); }
.rw-panel-sub { font-size: 14px; color: var(--pe-text-secondary); margin-bottom: 20px; }
.rw-info-box {
    background: var(--pe-bg, #f8fafc);
    border: 1px solid var(--pe-border, #e2e8f0);
    border-radius: 10px; padding: 16px 20px; margin-bottom: 20px;
}
[data-theme="dark"] .rw-info-box {
    background: rgba(255, 255, 255, 0.04);
    border-color: var(--pe-border, #374151);
}
.rw-info-label { font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--pe-text-secondary); margin-bottom: 4px; }
.rw-info-value { font-size: 16px; font-weight: 600; color: var(--pe-text); }
.rw-info-row { display: flex; gap: 24px; flex-wrap: wrap; }
.rw-info-item { flex: 1; min-width: 120px; }
.rw-field { margin-bottom: 16px; }
.rw-field label { display: block; font-size: 13px; font-weight: 600; color: var(--pe-text); margin-bottom: 6px; }
.rw-field input, .rw-field textarea, .rw-field select {
    width: 100%; padding: 10px 14px; border: 1px solid var(--pe-border, #e2e8f0); border-radius: 8px;
    font-size: 14px; background: var(--pe-white, #fff); color: var(--pe-text); font-family: inherit;
}
.rw-field textarea { resize: vertical; min-height: 80px; }
.rw-field input:focus, .rw-field textarea:focus, .rw-field select:focus { outline: none; border-color: var(--pe-blue, #38bdf8); box-shadow: 0 0 0 3px rgba(56,189,248,0.15); }
.rw-field-hint { font-size: 12px; color: var(--pe-text-secondary); margin-top: 4px; }
.rw-field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
/* A fork the registrant answers before the form — "I'm coaching a team" /
   "I'm registering a player". Lifted out of tournament-signup.html so the
   season wizard draws the same choice with the same classes. */
.rw-choice { display: grid; gap: 12px; }
.rw-opt {
    display: flex; align-items: flex-start; gap: 14px; text-align: left; width: 100%;
    font: inherit; color: inherit; background: var(--pe-bg, #f8fafc);
    border: 2px solid var(--pe-border, #e2e8f0); border-radius: 10px; padding: 16px 18px;
    cursor: pointer; transition: border-color .15s, background-color .15s, box-shadow .15s;
}
.rw-opt:hover { background: var(--pe-white, #fff); border-color: var(--pe-blue, #38bdf8); }
[data-theme="dark"] .rw-opt { background: rgba(255,255,255,.03); }
[data-theme="dark"] .rw-opt:hover { background: var(--pe-card-bg); }
.rw-opt-ico {
    width: 42px; height: 42px; border-radius: 11px; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    background: rgba(56,189,248,.12); color: var(--pe-blue);
}
.rw-opt-ico svg { width: 21px; height: 21px; }
.rw-opt-t { font-size: 15px; font-weight: 700; margin-bottom: 2px; display: block; }
.rw-opt-d { font-size: 12.5px; color: var(--pe-text-secondary, #64748b); line-height: 1.45; display: block; }
/* The team-code strip: the code a coach reads out, typed in. Letter-spaced
   monospace for the code itself; the placeholder is an EXAMPLE ("e.g. K7P2QM")
   and reads as prose, so it keeps normal spacing and case. The left padding is
   wider than a normal field's so the first letter doesn't sit on the border. */
.rw-code-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.rw-code-input {
    flex: 1; min-width: 160px; padding: 10px 14px 10px 18px;
    border: 1px solid var(--pe-border, #e2e8f0); border-radius: 8px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 15px; letter-spacing: 3px;
    text-transform: uppercase; background: var(--pe-white, #fff); color: var(--pe-text);
}
.rw-code-input::placeholder { letter-spacing: 1px; text-transform: none; color: var(--pe-text-secondary, #94a3b8); opacity: 1; }
.rw-code-input:focus { outline: none; border-color: var(--pe-blue, #38bdf8); box-shadow: 0 0 0 3px rgba(56,189,248,0.15); }
.rw-code-change {
    font: inherit; font-size: 12px; font-weight: 600; color: var(--pe-blue, #0ea5e9);
    background: none; border: 0; padding: 0 4px; cursor: pointer; vertical-align: middle;
}
.rw-code-change:hover { text-decoration: underline; }
/* The code handed BACK to a coach once their team is in — big, spaced, easy
   to read out over the phone. Same look as the tournament's .tsu-code. */
.rw-code-display {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 26px; font-weight: 800;
    letter-spacing: 5px; text-align: center; padding: 12px; border-radius: 10px;
    background: var(--pe-bg, #f8fafc); border: 1px dashed var(--pe-border);
}
[data-theme="dark"] .rw-code-display { background: rgba(148,163,184,.12); }
/* Availability note under the team-name field. */
.rw-name-note { font-size: 12.5px; margin-top: 6px; min-height: 16px; }
.rw-name-note.ok { color: #16a34a; }
.rw-name-note.bad { color: #b91c1c; }
.rw-nav { display: flex; justify-content: space-between; align-items: center; margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--pe-border); }
.rw-nav-pay { flex-wrap: wrap; }
@media (max-width: 599px) {
    .rw-nav {
        position: sticky;
        bottom: var(--kb, 0px);
        z-index: 5;
        background: var(--pe-white, #fff);
        margin-left: -16px; margin-right: -16px;
        padding-left: 16px; padding-right: 16px;
        padding-bottom: max(12px, env(safe-area-inset-bottom, 0px));
    }
    [data-theme="dark"] .rw-nav { background: var(--pe-card-bg, #1f2937); }
}
.rw-btn-back {
    display: inline-flex; align-items: center; gap: 6px; padding: 10px 20px; border-radius: 8px;
    font-size: 14px; font-weight: 600; cursor: pointer; background: none; border: 1px solid var(--pe-border);
    color: var(--pe-text);
}
.rw-btn-back:hover { background: var(--pe-gray-light, #f8fafc); }
.rw-btn-save {
    display: inline-flex; align-items: center; gap: 6px; padding: 10px 20px; border-radius: 8px;
    font-size: 14px; font-weight: 600; cursor: pointer; background: none;
    border: 1px solid var(--pe-border); color: var(--pe-text);
    margin-left: auto; margin-right: 8px;   /* sits with Continue, not with Back/Cancel */
}
.rw-btn-save:hover:not(:disabled) { background: var(--pe-gray-light, #f8fafc); }
.rw-btn-save:disabled { opacity: .5; cursor: not-allowed; }
.rw-btn-next {
    display: inline-flex; align-items: center; gap: 6px; padding: 10px 24px; border-radius: 8px;
    font-size: 14px; font-weight: 600; cursor: pointer; background: var(--pe-blue, #38bdf8); border: none;
    color: #fff;
}
.rw-btn-next:hover { opacity: 0.9; }
.rw-btn-next:disabled { opacity: 0.5; cursor: not-allowed; }
.rw-loading { text-align: center; padding: 40px 0; color: var(--pe-text-secondary); font-size: 14px; }
@media (max-width: 599px) {
    .rw-container { padding: 16px 0; }
    .rw-stepper { margin-bottom: 24px; }
    .rw-step-label { display: none; }
    .rw-step-num { width: 28px; height: 28px; font-size: 12px; }
    .rw-field-row { grid-template-columns: 1fr; }
    .rw-info-row { flex-direction: column; gap: 12px; }
}

/* ── Marketing drawers (shared) ──────────────────────────────────────────────
   The tournament promote card is the standard for marketing (Robb, 2026-08-26),
   so seasons and events open the SAME drawers: "Choose who gets the email",
   "Build the email" with its Build | Schedule tabs, and the send confirmation.

   These rules lived only in tournament.html's inline <style>, which is why the
   first attempt at sharing invented a parallel set of classes instead of reusing
   these. They belong here, where every page that opens the drawer can see them.
   tournament.html still defines its own identical copies; those are redundant
   now, not conflicting, and can be deleted once this has run on dev. */
.tn-field { display:flex; flex-direction:column; gap:5px; min-width:0; }
.tn-field.full { grid-column:1 / -1; }
.tn-field > label { font-size:12px; font-weight:600; color:var(--pe-text); }
.hint { font-size:11px; color:var(--pe-text-secondary,#64748b); line-height:1.45; }
.tn-inp { width:100%; padding:9px 11px; border:1px solid var(--pe-border); border-radius:8px;
    font:inherit; font-size:13.5px; background:var(--pe-white); color:var(--pe-text); box-sizing:border-box; }
[data-theme="dark"] .tn-inp { background:var(--pe-bg); }
.tn-inp:focus { outline:none; border-color:var(--pe-blue); box-shadow:0 0 0 3px rgba(56,189,248,.15); }
.tn-empty { color:var(--pe-text-secondary,#64748b); font-size:13.5px; padding:20px 0; text-align:center; }
.tn-opt { font-weight:500; font-size:11px; color:var(--pe-text-secondary,#64748b); }
/* The second send and its audience are one decision — the box says so. */
.tn-sched-group { border:1px solid var(--pe-border,#e2e8f0); border-radius:10px;
    padding:14px; margin-top:6px; background:var(--pe-bg,#f8fafc); }
[data-theme="dark"] .tn-sched-group { background:rgba(255,255,255,.03); }
.tn-sched-group .tn-field + .tn-field { margin-top:12px; }
/* Audience picker */
.tn-sd-who { font-size:12px; font-weight:600; color:var(--pe-text); margin:0 0 7px; }
/* The radio rows these replaced are gone; AudiencePicker renders .mk-aud-*.
   Monospace on the paste box so a long column of addresses lines up and a typo
   is easier to spot than in a proportional face. */
.mk-aud-paste textarea { resize:vertical; min-height:96px; font-size:12.5px; line-height:1.5;
    font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
/* Send confirmation / blockers */
.tn-ps-block { border:1px solid var(--pe-border,#e2e8f0); border-left:3px solid #f59e0b;
    border-radius:8px; padding:12px 14px; background:var(--pe-bg,#f8fafc); }
.tn-ps-block strong { display:block; font-size:13px; margin-bottom:6px; }
.tn-ps-block ul { margin:0; padding-left:18px; font-size:13px; line-height:1.6; }
.tn-ps-block p { margin:8px 0 0; font-size:12.5px; color:var(--pe-text-secondary,#64748b); }
/* The drawer body is a flex COLUMN so the builder can fill the height instead
   of sitting at its own 600px default with dead space beneath it — which is what
   made the events drawer visibly shorter than the tournament's. Ported from
   tournament.html, where this already existed and was the reason its builder
   looked right. */
#mkBuildModal .modal-body, #emailBuilderModal .modal-body { display:flex; flex-direction:column; }
/* Breathing room under "Build the email" (Robb, 2026-08-27). On #ebTabs, not
   .profile-tabs — the profile page and others share that class and a global
   margin would move rows nobody complained about. */
#ebTabs { flex:0 0 auto; align-self:flex-start; margin:18px 0 24px; }
/* Build is the pane that stretches; Audience and Schedule are short forms and
   sit at their natural height. Hiding is handled centrally by [hidden]. */
#ebPaneBuild { flex:1 1 auto; min-height:0; display:flex; flex-direction:column; }
#ebPaneAudience, #ebPaneSchedule { flex:0 0 auto; display:block; }
#ebMount { margin:12px 0; flex:1 1 auto; min-height:340px; display:flex; }
/* Override the builder's own height:min(68vh,600px) — in a drawer it should take
   the room it is given, not a fixed slice of the viewport. */
#ebMount .eb-embed { height:auto; flex:1 1 auto; min-height:0; }
@media (min-width: 701px) {
    #ebMount .eb-settings { flex: 0 0 clamp(280px, 26%, 420px); width: auto; }
}
/* THE MARKETING DRAWER IS WIDER THAN THE STANDARD 1100 — and it has to be.

   The builder's three panes are fixed: palette 150, settings 280, and a canvas
   that is 600px because that is how wide an email is. Add the wrap and body
   padding and the panel needs ~1145px before the preview stops being squeezed —
   at the shared 1100 it renders around 556px. Past that width nothing grows:
   extra pixels become grey gutter either side of a 600px email, which is why a
   percentage alone (70vw and the like) is the wrong rule. It starves the preview
   on a laptop and pads it out with nothing on a large monitor. 94vw still
   governs small screens.

   70% of the screen once there is 70% worth of screen, never below 1160 (where
   the preview first gets its full 600px), capped at 1600 so a 27" monitor does
   not hand over the whole page.

   THE IMAGE MAKER JOINS THEM (Robb: "match what the other code is doing for
   Edit Email drawer"). It opens already expanded and its whole job is judging a
   16:9 banner or card, which is the same argument as the email preview: at the
   shared 1100 the artwork is squeezed, and on a wide monitor the difference is
   plain — 1100px against the builder's 1330px on a 1900px window. A drawer you
   evaluate a picture in wants the builder's width, not a form's.

   Every other drawer in the app keeps min(1100px, 94vw); this is scoped to the
   three that hold something worth looking at full size. */
@media (min-width: 600px) {
    #mkBuildModal.modal-drawer.is-expanded .modal,
    #emailBuilderModal.modal-drawer.is-expanded .modal,
    #imgMakeModal.modal-drawer.is-expanded .modal {
        width: min(94vw, clamp(1160px, 70vw, 1600px));
    }
}

/* ── The image maker (shared) ────────────────────────────────────────────────
   Ported verbatim from tournament.html so the maker opened from an Image
   block in a season or event email looks exactly like the tournament's
   "Create the email banner". Same caveat as the drawer styles above:
   tournament.html still has its own identical copies, redundant not
   conflicting, and they can go once this has run on dev. */
.tn-im-prev { border:1px solid var(--pe-border); border-radius:10px; overflow:hidden;
    aspect-ratio:16/9; display:flex; align-items:center; justify-content:center;
    background:var(--pe-bg,#f1f5f9); margin:0 auto 10px; width:100%; }
/* Portrait art was pinned left at 230px, stranding half the drawer empty.
   Centred and larger — you cannot judge a flyer at thumbnail size. */
.tn-im-prev.por { aspect-ratio:4/5; max-width:300px; }
/* The email banner is 1600x600. It must never wear .por — pushed into a
   4:5 box it letterboxes to a sliver and you cannot judge the layout. */
.tn-im-prev.wide { aspect-ratio:8/3; max-width:none; }

.tn-im-prev img { width:100%; height:100%; object-fit:contain; display:block; }
.tn-im-load { display:flex; flex-direction:column; align-items:center; justify-content:center;
    gap:11px; padding:18px; text-align:center; font-size:12.5px;
    color:var(--pe-text-secondary,#64748b); line-height:1.5; }
.tn-im-load strong { font-size:13px; color:var(--pe-text,#0f172a); }
.tn-im-elapsed { font-variant-numeric:tabular-nums; font-weight:700; font-size:12px;
    color:var(--pe-text-secondary,#64748b); }
.tn-spin { width:32px; height:32px; border-radius:50%; flex-shrink:0;
    border:3px solid var(--pe-border); border-top-color:var(--pe-blue,#38bdf8);
    animation:tn-spin .8s linear infinite; }
@keyframes tn-spin { to { transform:rotate(360deg); } }
/* Someone who prefers less motion still needs to know it is working, so
   the ring stays and pulses instead of spinning. */
@media (prefers-reduced-motion:reduce) {
    .tn-spin { animation:tn-pulse 1.4s ease-in-out infinite; }
    @keyframes tn-pulse { 50% { opacity:.35; } }
}
.tn-im-prev.fail { border-color:color-mix(in srgb,#dc2626 40%,transparent); }
/* A disabled primary button inside a modal renders identically to an
   enabled one: .modal .btn-primary sets its own dark background and beats
   .btn-primary:disabled on source order. These two buttons genuinely gate
   on state — Use this image is off until there IS an image, Send now until
   an audience resolves — so a button that looks pressable while refusing to
   work is the worst of both. An id selector settles it outright. */
#imUse:disabled, #sdSend:disabled {
    background:#b0c4ce; color:#f8fafc; cursor:not-allowed;
    box-shadow:none; transform:none; opacity:1;
}
[data-theme="dark"] #imUse:disabled, [data-theme="dark"] #sdSend:disabled {
    background:rgba(148,163,184,.28); color:rgba(248,250,252,.65);
}
.tn-im-cap { font-size:11px; color:var(--pe-text-secondary,#94a3b8);
    line-height:1.45; margin:0 0 14px; display:block; text-align:center; }
#imDiag { margin:10px 0 0; padding:10px 12px; border-radius:8px; max-height:230px;
    overflow:auto; background:var(--pe-bg,#0f172a0d); border:1px solid var(--pe-border);
    font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace; white-space:pre-wrap;
    word-break:break-word; color:var(--pe-text-secondary,#475569); }
/* The two ways to fill the box. Peers, not a main option and an
   afterthought — each states its own cost and carries its own button. */
.tn-im-opts { border-top:1px solid var(--pe-border); margin-top:16px; padding-top:16px;
    display:flex; flex-direction:column; gap:18px; }
.tn-im-opt { display:flex; flex-direction:column; gap:7px; }
.tn-im-opt + .tn-im-opt { border-top:1px solid var(--pe-border); padding-top:16px; }
.tn-im-opt-h { display:flex; align-items:center; gap:10px; }
.tn-im-opt-h strong { font-size:12px; font-weight:600; color:var(--pe-text); }
.tn-im-cost { margin-left:auto; font-size:11px; font-weight:700; padding:3px 9px;
    border-radius:999px; background:var(--pe-bg,#f1f5f9); color:var(--pe-text-secondary,#64748b); }
/* Free is the reassuring word in this drawer — it should not read as the
   same grey chip as a price. */
.tn-im-cost.free { background:color-mix(in srgb,#16a34a 12%,transparent); color:#15803d; }
[data-theme="dark"] .tn-im-cost.free { color:#4ade80; }
.tn-im-opt textarea { resize:vertical; min-height:64px; font:inherit; }
.tn-im-opt .tb-btn { align-self:flex-start; }
/* The resting state of the preview box. */
.tn-im-ph { font-size:12.5px; color:var(--pe-text-secondary,#94a3b8); }

/* ── The AI Studio image picker (shared) ─────────────────────────────────────
   marketing.js's "Choose an existing image" grid. These lived only in
   tournament.html's inline <style>, so the picker was written against class
   names that resolved to nothing and every image rendered at its natural
   size — one full-width banner per row. .tn-pick img is what makes them
   thumbnails. */
.tn-pick-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:10px; margin-top:10px; }
.tn-pick { display:flex; flex-direction:column; gap:4px; padding:0; border:1px solid var(--pe-border); border-radius:10px;
    overflow:hidden; background:var(--pe-white); cursor:pointer; font:inherit; text-align:left; }
[data-theme="dark"] .tn-pick { background:var(--pe-card-bg,#1f2937); }
.tn-pick:hover { border-color:var(--pe-blue); }
.tn-pick img { width:100%; aspect-ratio:16/10; object-fit:cover; display:block; }
.tn-pick-m { font-size:10px; font-weight:800; text-transform:uppercase; letter-spacing:.04em;
    color:var(--pe-text-secondary,#94a3b8); padding:0 8px; }
.tn-pick-t { font-size:11.5px; padding:0 8px 8px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }


/* ── Audience picker: cards + a live tally ────────────────────────────────────
   Layout C (Robb, 2026-08-26). Replaced a single column of radio buttons, which
   left most of an 1160px drawer empty and could only ever express one choice.

   The right-hand rail is the point of the layout: picking two audiences that
   overlap is now normal, so the number that matters is the deduplicated total,
   and it needs somewhere permanent to live. */
/* @container, not @media, for anything in here that reacts to width: the drawer
   is 560px collapsed and 1100+ wide on the very same window, so the window says
   nothing about the room these cards have. Same trap as .tn-promo-wrap. */
#sdBody { container-type: inline-size; }
.mk-aud-wrap { display: block; }
.mk-aud-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
    gap: 10px;
}
.mk-aud {
    display: block;
    width: 100%;
    text-align: left;
    background: #fff;
    border: 1px solid var(--border, #e2e8f0);
    border-radius: 12px;
    padding: 11px 13px;
    cursor: pointer;
    font: inherit;
    color: inherit;
    transition: border-color .12s, background .12s;
}
.mk-aud:hover:not(:disabled) { border-color: #94a3b8; }
.mk-aud:disabled { cursor: default; opacity: .65; }
/* Selected state is a 2px border rather than a fill: these cards hold a
   dropdown, and a tinted background behind a <select> reads as disabled. */
.mk-aud.on { border: 2px solid var(--pe-blue, #38bdf8); padding: 10px 12px; }
.mk-aud-h { display: flex; align-items: center; gap: 7px; }
.mk-aud-i { width: 16px; height: 16px; flex: 0 0 16px; color: #94a3b8; }
.mk-aud.on .mk-aud-i { color: var(--pe-blue, #38bdf8); }
.mk-aud-l { font-size: 13px; font-weight: 600; flex: 1 1 auto; min-width: 0; }
.mk-aud-tick {
    width: 16px; height: 16px; flex: 0 0 16px;
    border: 1.5px solid #cbd5e1; border-radius: 50%;
}
.mk-aud.on .mk-aud-tick {
    border-color: var(--pe-blue, #38bdf8);
    background: var(--pe-blue, #38bdf8) no-repeat center;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8.5l3 3 6-6'/%3E%3C/svg%3E");
    background-size: 12px 12px;
}
.mk-aud-n { font-size: 21px; font-weight: 600; line-height: 1.25; margin-top: 3px; }
.mk-aud-n.zero { color: #94a3b8; }
.mk-aud-sel { margin-top: 8px; width: 100%; font-size: 12.5px; }
.mk-aud-paste { margin-top: 12px; }
.mk-aud-paste textarea { width: 100%; box-sizing: border-box; }

/* The tally, under the paste hint rather than beside the cards (Robb,
   2026-08-26). Full width lets it read as one line — 57 people, and what that is
   made of — instead of the tall rail a narrow column forced it into. */
.mk-aud-sum {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px 20px;
    background: #f8fafc;
    border: 1px solid var(--border, #e2e8f0);
    border-radius: 12px;
    padding: 12px 16px;
    margin-top: 16px;
}
.mk-sum-lead { display: flex; align-items: baseline; gap: 8px; }
.mk-sum-k {
    font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;
    color: #94a3b8; white-space: nowrap;
}
.mk-sum-n { font-size: 26px; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
.mk-sum-n.muted { color: #cbd5e1; }
.mk-sum-s { font-size: 12.5px; color: #64748b; }
/* The breakdown sits after a rule, so the total reads as the answer and the
   parts as the working behind it. */
.mk-sum-b {
    display: flex; flex-wrap: wrap; gap: 4px 16px;
    border-left: 1px solid var(--border, #e2e8f0);
    padding-left: 20px;
}
.mk-sum-r { font-size: 12.5px; color: #475569; font-variant-numeric: tabular-nums; }
.mk-sum-r b { font-weight: 600; color: #94a3b8; margin-right: 6px; }
.mk-sum-r.dim { color: #94a3b8; }

/* Nothing sits side by side any more, so the only narrow concern is the
   breakdown: once it drops below the total, its left rule points at nothing. */
@container (max-width: 620px) {
    .mk-sum-b { border-left: 0; padding-left: 0; flex-basis: 100%; }
}

/* The reason the Send button is disabled, beside the button rather than behind
   a click. This replaced a whole drawer whose only job was to say it. */
.mk-send-note { font-size: 12.5px; color: #64748b; flex: 1 1 160px; min-width: 0; }


/* hidden means hidden — once, for everything.

   The UA rule is [hidden]{display:none} at specificity 0,1,0, which any class
   setting display either ties (and then beats on source order) or outranks
   outright. That has now shipped three separate visible bugs: .btn left a dark
   unlabelled pill under "Not ready to send"; #sdCustom — an id, so not even
   close — left the paste box open with no audience picked; .tn-field showed the
   waiver textarea and the PDF picker at the same time. Every one of them looked
   like a JavaScript fault and none of them was.

   !important because the entire point is that nothing outranks it. Checked
   first that no rule here deliberately re-shows a [hidden] element — the only
   three that mention it were all fighting to make it win, which is what this
   now does centrally. If something ever needs to be visible while carrying the
   attribute, it wants a class, not the attribute. */
[hidden] { display: none !important; }

/* "Today" beside the date, not under it. The field keeps the width it had; the
   button is only as wide as its word. */
.tn-send-row { display: flex; align-items: stretch; gap: 8px; }
.tn-send-row .tn-inp { flex: 1 1 auto; min-width: 0; }
.tn-send-row .tb-btn { flex: 0 0 auto; white-space: nowrap; height: auto; }

/* ── Tournament board ─────────────────────────────────────────────────────
   Brackets, pools, standings and the score/schedule chrome around them.
   Shared by tournament.html (every division) and tournament-division.html
   (one of them) — it moved out of the page's own <style> when the second
   page appeared, because two copies of a bracket is two brackets to fix.
   The JS that renders these classes is assets/js/tournament-board.js. */
.tn-card { background:var(--pe-white); border:1px solid var(--pe-border); border-radius:12px;
    padding:16px 18px; margin-bottom:16px; box-shadow:var(--pe-shadow); }
[data-theme="dark"] .tn-card { background:var(--pe-card-bg,#1f2937); }
.tn-card h2 { font-size:15px; margin:0 0 4px; display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.tn-card .tn-sub { font-size:12.5px; color:var(--pe-text-secondary,#64748b); margin:0 0 14px; }
/* Muted body text swallows a default link, so cross-page links inside it
   need to actually look clickable. */
.tn-card .tn-sub a, .tn-fac-warn a { color:var(--pe-blue); font-weight:700; text-decoration:none; }
.tn-card .tn-sub a:hover, .tn-fac-warn a:hover { text-decoration:underline; }
.tn-badge { font-size:10.5px; font-weight:800; text-transform:uppercase; letter-spacing:.04em;
    padding:2px 9px; border-radius:999px; background:var(--pe-bg,#f1f5f9); color:var(--pe-text-secondary,#64748b); }
.tn-badge.champ { background:#fef3c7; color:#92400e; }

/* Bracket — rounds as horizontal columns of match cards */
.tn-bracket-scroll { overflow-x:auto; padding-bottom:6px; }
/* STRETCH, not flex-start. Every round column is now the height of the tallest,
   which is what lets `.tn-round-inner`'s space-around actually distribute — with
   flex-start each column was only as tall as its own content, the inner's flex:1
   had no spare height, and the matches simply stacked from the top. That is why
   a Semifinal sat level with the FIRST Quarterfinal instead of centred between
   the two feeding it (Robb's sketch: each box halfway between its two sources).
   The wider gap gives the elbow connectors room to read as elbows. */
.tn-bracket { display:flex; gap:40px; align-items:stretch; min-width:min-content; }
.tn-round { display:flex; flex-direction:column; gap:14px; min-width:200px; }
.tn-round-label { font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.05em;
    color:var(--pe-text-secondary,#64748b); padding-left:2px; }
.tn-round-inner { display:flex; flex-direction:column; gap:14px; justify-content:space-around; flex:1; }
.tn-match { border:1px solid var(--pe-border); border-radius:10px; overflow:hidden; background:var(--pe-white);
    font-size:13px; transition:border-color .15s, box-shadow .15s; }
[data-theme="dark"] .tn-match { background:var(--pe-bg); }
.tn-match.scorable { cursor:pointer; }
.tn-match.scorable:hover { border-color:var(--pe-blue); box-shadow:0 0 0 2px rgba(56,189,248,.18); }
.tn-match.done { opacity:.92; }
.tn-side { display:flex; justify-content:space-between; align-items:center; gap:8px; padding:7px 10px; }
.tn-side + .tn-side { border-top:1px solid var(--pe-border); }
.tn-side .nm { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.tn-side.win { font-weight:800; }
.tn-side.win .nm::after { content:" ✓"; color:#16a34a; }
.tn-side.tbd .nm { color:var(--pe-text-secondary,#94a3b8); font-style:italic; }
.tn-side .sc { font-variant-numeric:tabular-nums; font-weight:700; color:var(--pe-text-secondary,#64748b); min-width:18px; text-align:right; }
.tn-match-label { font-size:9.5px; font-weight:800; text-transform:uppercase; letter-spacing:.04em;
    color:var(--pe-text-secondary,#94a3b8); padding:3px 10px 0; }
.tn-match.bye { opacity:.6; }
.tn-match-foot { display:flex; align-items:center; justify-content:space-between; gap:6px; padding:5px 8px; border-top:1px solid var(--pe-border); }
.tn-sched-when { font-size:10.5px; color:var(--pe-text-secondary,#64748b); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.tn-sched-when.unset { font-style:italic; opacity:.7; }
.tn-sched-btn { flex:0 0 auto; font:inherit; font-size:10.5px; font-weight:700; padding:3px 8px; border-radius:8px;
    border:1px solid var(--pe-border); background:transparent; color:var(--pe-blue); cursor:pointer; }
.tn-sched-btn:hover { background:rgba(56,189,248,.1); }
.tn-bracket-title { font-size:12.5px; font-weight:800; margin:2px 0 12px; }
.tn-champ-banner { display:flex; align-items:center; gap:10px; padding:12px 14px; border-radius:10px;
    background:#fffbeb; border:1px solid #fde68a; color:#92400e; font-weight:700; font-size:14px; margin-bottom:16px; }
[data-theme="dark"] .tn-champ-banner { background:rgba(251,191,36,.12); }

/* Pools */
.tn-pools { display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:16px; }
.tn-pool h3 { font-size:13.5px; margin:0 0 8px; }
table.tn-standings { width:100%; border-collapse:collapse; font-size:12.5px; }
table.tn-standings th, table.tn-standings td { padding:6px 8px; text-align:center; border-bottom:1px solid var(--pe-border); }
table.tn-standings th { font-size:10.5px; text-transform:uppercase; letter-spacing:.03em; color:var(--pe-text-secondary,#64748b); }
/* The team column takes whatever the numbers do not, and ellipsizes only when
   the name genuinely will not fit. `max-width:0` alone — the usual trick to get
   text-overflow working inside a table — collapsed the column to its minimum and
   kept it there, so "Ammon Aces" read "Amm…" with 250px of empty table beside
   it. Full screen made that plain: the whole point of the view is seeing the
   content, and the names were the one thing still cut off.
   width:100% + max-width:0 is the pair that means "take the rest, but shrink
   rather than push"; the numeric columns hold themselves to their content. */
table.tn-standings td.tm { text-align:left; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; width:100%; max-width:0; }
table.tn-standings th:not(:first-child),
table.tn-standings td:not(.tm) { width:1%; white-space:nowrap; }
table.tn-standings tr.adv td { background:rgba(56,189,248,.08); }
table.tn-standings tr.adv td.tm::before { content:"▲ "; color:var(--pe-blue); }
.tn-poolgames { margin-top:12px; }
/* A pool card is only ~300px wide, so the teams get a line to themselves and
   when/schedule/score share the second — one line could not hold both without
   truncating every team name. */
.tn-pg { display:flex; flex-direction:column; gap:4px; padding:7px 0; border-top:1px solid var(--pe-border); font-size:12.5px; }
.tn-pg .pgn { min-width:0; display:flex; justify-content:space-between; gap:8px; }
.tn-pg .pgn span { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.tn-pg .pgm { display:flex; align-items:center; gap:6px; }
.tn-pg-when { flex:1; min-width:0; font-size:10.5px; color:var(--pe-text-secondary,#64748b);
              white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.tn-pg-when.unset { font-style:italic; opacity:.7; }

/* Blackout dates inside the tournament Add-facility drawer — the same idea
   Facilities uses, sized for a drawer rather than a full page. */
.tn-blackout-add { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.tn-blackout-add .tn-inp { flex:1 1 140px; min-width:0; }
.tn-blackout-row { display:flex; align-items:center; gap:10px; padding:6px 0;
                   border-bottom:1px solid var(--pe-border); font-size:12.5px; }
.tn-blackout-row:last-child { border-bottom:0; }
.tn-blackout-row .tn-blackout-why { flex:1; min-width:0; color:var(--pe-text-secondary,#64748b);
                                    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.tn-blackout-x { border:0; background:none; cursor:pointer; font-size:16px; line-height:1;
                 color:var(--pe-text-secondary,#64748b); padding:0 4px; }
.tn-blackout-x:hover { color:#b91c1c; }

/* Officials across the whole tournament (tournament.html, Officials step).
   The gap line is the point of the screen: a game nobody is on shows up as
   an absence, which is the hardest thing to notice by eye. */
.tn-off-people { display:flex; flex-wrap:wrap; gap:8px; margin:12px 0 4px; }
.tn-off-person { border:1px solid var(--pe-border); border-radius:10px; padding:8px 12px;
                 display:flex; flex-direction:column; gap:2px; min-width:150px; }
.tn-off-person .n { font-weight:600; font-size:13px; }
.tn-off-person .m { font-size:11.5px; color:var(--pe-text-secondary,#64748b); }
.tn-off-gap { margin:12px 0 4px; padding:9px 12px; border-radius:10px; font-size:12.5px; font-weight:600;
              background:rgba(245,158,11,.14); color:#b45309; }
.tn-off-ok  { margin:12px 0 4px; padding:9px 12px; border-radius:10px; font-size:12.5px; font-weight:600;
              background:rgba(16,185,129,.14); color:#059669; }
.tn-off-none { font-size:11.5px; font-weight:700; color:#b45309; }
.tn-pg input { width:42px; padding:4px; text-align:center; border:1px solid var(--pe-border); border-radius:6px; font:inherit; }
.tn-pg-done { color:#16a34a; font-weight:700; }

.tn-actions { display:flex; gap:8px; flex-wrap:wrap; margin-top:8px; }
.tn-actions .tb-btn { font-size:12.5px; }

.tn-score-row { display:flex; align-items:center; gap:12px; margin:14px 0; }
.tn-score-row .tn-score-name { flex:1; font-weight:600; font-size:14px; }
.tn-score-row input { width:64px; padding:9px; text-align:center; font-size:16px; border:1px solid var(--pe-border); border-radius:8px; font:inherit; }

.tn-modal-title { margin:0; font-size:15px; font-weight:800; line-height:1.35;
    min-height:32px; display:flex; align-items:center; padding-right:64px; }
/* Above 600px the expand button appears beside the close, so reserve both. */
@media (min-width:600px) { .tn-modal-title { padding-right:104px; } }

.tn-seed { display:inline-flex; align-items:center; justify-content:center; min-width:17px; height:17px; padding:0 4px; border-radius:5px;
    background:var(--pe-bg,#eef2f7); color:var(--pe-text-secondary,#64748b); font-size:9.5px; font-weight:800; margin-right:7px; flex-shrink:0; font-variant-numeric:tabular-nums; }
[data-theme="dark"] .tn-seed { background:rgba(148,163,184,.16); }
.tn-side.win .tn-seed { background:color-mix(in srgb,#16a34a 18%,transparent); color:#16a34a; }
.tn-round-label { display:inline-block; background:var(--pe-bg,#f1f5f9); padding:3px 10px; border-radius:999px; }
[data-theme="dark"] .tn-round-label { background:rgba(148,163,184,.12); }
/* Connectors are drawn as an SVG layer (drawBracketLinks) rather than
   pseudo-elements: byes are filtered out of the columns, so the columns
   are NOT a clean 2:1 pairing and geometry has to come from the real
   winner_to_match_id links, not from position. */
.tn-bracket { position:relative; }
.tn-match { position:relative; z-index:1; }
.tn-bracket-links { position:absolute; inset:0; width:100%; height:100%; pointer-events:none; z-index:0; overflow:visible; }
.tn-bracket-links path { fill:none; stroke:var(--pe-border); stroke-width:2; }
.tn-bracket-links path.done { stroke:var(--pe-blue); }
.tn-bracket-links polygon { fill:var(--pe-border); }
.tn-bracket-links polygon.done { fill:var(--pe-blue); }


/* ── Bracket full screen + print (assets/js/tournament-board.js) ─────────────
   A bracket is the one board that does not fit a page: eight entrants is three
   columns, sixteen with a losers bracket is far wider than any card. On the page
   it lives in a horizontal scroller, so you can never see the SHAPE of it —
   which is the whole point of a bracket. Full screen hands it the viewport and
   scales it down until the widest section fits. */
body.tb-fs-open { overflow: hidden; }
.tn-card.tb-fs {
    position: fixed;
    inset: 0;
    z-index: 1200;              /* over the app shell; under the modal layer */
    margin: 0;
    border: 0;
    border-radius: 0;
    max-width: none;
    overflow: auto;
    padding: 0 24px 24px;
    background: var(--pe-white, #fff);
    box-shadow: none;
}
[data-theme="dark"] .tn-card.tb-fs { background: var(--pe-card-bg, #1f2937); }
/* The toolbar rides at the top of the scroll area, so it stays reachable on a
   tall double-elimination board without covering the first round. */
.tb-fs-bar {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin: 0 -24px 14px;
    padding: 12px 24px;
    background: var(--pe-white, #fff);
    border-bottom: 1px solid var(--pe-border, #e2e8f0);
}
[data-theme="dark"] .tb-fs-bar { background: var(--pe-card-bg, #1f2937); }
.tb-fs-title { font-size: 15px; font-weight: 800; margin-right: auto; }
.tb-fs-zoom {
    font-size: 11px; font-weight: 800; font-variant-numeric: tabular-nums;
    padding: 2px 9px; border-radius: 999px;
    background: var(--pe-bg, #f1f5f9); color: var(--pe-text-secondary, #64748b);
}
[data-theme="dark"] .tb-fs-zoom { background: rgba(148, 163, 184, 0.14); }
/* The card's own heading is redundant once the toolbar names the stage, and its
   buttons (Full screen / Delete stage) do not belong on a presentation view. */
.tn-card.tb-fs > h2 { display: none; }
/* Wider pool columns in full screen. A pool game row is two team names, two
   score boxes and a Save button; at the 300px the page grid uses, the names get
   about 45px each and read "Firth Coug… v. Ririe Bull…". Given the whole screen
   there is no reason for that — 420px still fits two pools side by side on a
   laptop and three on a monitor. */
.tn-card.tb-fs .tn-pools { grid-template-columns: repeat(auto-fill, minmax(420px, 1fr)); }

/* ── Print ───────────────────────────────────────────────────────────────────
   Printing the full-screen view gives a clean bracket; the browser's own dialog
   is also where "Save as PDF" lives, so there is no library and the reader picks
   their own paper.

   Hidden by VISIBILITY, not display, and the board is then lifted to the page
   origin. The obvious version — hide every sibling of the card — assumes the
   card is a child of .app-main; it is not. On the division page it sits inside
   #panel-bracket, so hiding that panel's siblings hid the card's own ancestor
   and printed a blank sheet. This recipe does not care where in the tree the
   board lives, which is the point: two pages render it and neither should have
   to arrange its DOM for the printer.

   Landscape comes from a <style> the full-screen view injects while it is open
   (tournament-board.js), not from here — a global @page rule would turn every
   other page in the app landscape too. */
@media print {
    body.tb-fs-open * { visibility: hidden; }
    body.tb-fs-open .tn-card.tb-fs,
    body.tb-fs-open .tn-card.tb-fs * { visibility: visible; }
    /* Chrome, not content — and every one of these prints as an empty box.
       The pool score INPUTS stay: an <input> prints its value, and the value is
       the result. It is the Save button beside it that means nothing on paper. */
    body.tb-fs-open .tb-fs-bar,
    body.tb-fs-open .tb-fs-bar *,
    body.tb-fs-open .tn-actions,
    body.tb-fs-open .tn-actions *,
    body.tb-fs-open .pg-save,
    body.tb-fs-open .tn-sched-btn { visibility: hidden !important; }
    /* A pool is a unit — its table split over a page break is unreadable. */
    .tn-card.tb-fs .tn-pool { break-inside: avoid; }
    .tn-card.tb-fs .tn-standings { break-inside: avoid; }
    body.tb-fs-open { overflow: visible; }
    .tn-card.tb-fs {
        position: absolute;
        left: 0;
        top: 0;
        width: 100%;
        padding: 0;
        overflow: visible;
        background: #fff;
    }
    /* Let the whole board through: on paper there is no scrolling to fall back
       on, so a clipped scroller would silently print half a bracket. */
    .tn-card.tb-fs .tn-bracket-scroll { overflow: visible !important; height: auto !important; }
    .tn-card.tb-fs .tn-bracket { break-inside: avoid; }
    .tn-bracket-title { break-after: avoid; }
    /* Colour matters here — a completed match and its connector are the only
       thing distinguishing a played round from an empty one. */
    * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}

/* ── Choice cards: a "who / how" pre-step before a form ──────────────────────
   Born on players.html (PLYR-005: Myself / My child / An adult in my care).
   Shared since 2026-09-10, when the tournament drawer began asking
   "From scratch / From a season" the same way. Page CSS must not redefine
   these — edit them here. */
.pt-lead { margin: 0 0 14px; font-size: 14px; color: var(--pe-text-secondary, #64748b); }
.pt-choices { display: grid; gap: 10px; }
.pt-choice {
    display: block; width: 100%; min-width: 0;   /* never let a long line widen the drawer */
    text-align: left; padding: 14px 16px;
    border: 1px solid var(--pe-border, #e5e7eb); border-radius: 10px;
    background: var(--pe-white, #fff); cursor: pointer;
    transition: border-color .15s, background .15s;
}
.pt-choice:hover, .pt-choice:focus-visible { border-color: var(--pe-blue, #38bdf8); background: var(--pe-hover, #f8fafc); }
[data-theme="dark"] .pt-choice { background: var(--pe-card-bg, #1f2937); }
.pt-choice-title { display: block; font-size: 15px; font-weight: 700; color: var(--pe-text); }
.pt-choice-sub { display: block; margin-top: 3px; font-size: 12.5px; line-height: 1.45; color: var(--pe-text-secondary, #64748b); overflow-wrap: anywhere; }
.pt-chosen {
    display: flex; align-items: center; gap: 10px;
    margin-bottom: 16px; padding-bottom: 12px;
    border-bottom: 1px solid var(--pe-border, #e5e7eb);
    font-size: 13px; font-weight: 600;
}
.pt-chosen .link-btn, .pt-chosen .tn-linkish { margin-left: auto; font-weight: 500; font-size: 12.5px; }

/* ── Scoped facilities: picker, editor drawer, grid (assets/js/scoped-facilities.js, 2026-09-11) ──
   Moved here from season-profile.html so the same module mounts on any page. ── */
.sf-add { display:flex; gap:8px; align-items:center; justify-content:flex-end; flex-wrap:wrap; padding:12px 16px; border-bottom:1px solid var(--pe-border,#e5e7eb); }
/* The facility picker (2026-09-10): a native select dressed as a control —
   pin icon, custom chevron, 40px, focus ring — that stops at a sensible
   width instead of spanning the card. Disabled when nothing can be added. */
.sf-pick { position:relative; display:flex; align-items:center; flex:0 1 380px; min-width:220px; }
.sf-pick-btn > svg { position:absolute; width:16px; height:16px; color:var(--pe-text-secondary,#64748b); pointer-events:none; }
.sf-pick .sf-pick-ic { left:12px; }
.sf-pick .sf-pick-chev { right:11px; width:14px; height:14px; transition:transform .15s; }
.sf-pick-btn { display:flex; align-items:center; width:100%; height:40px; padding:0 34px 0 36px; border:1px solid var(--pe-border,#e5e7eb); border-radius:10px; text-align:left;
    background:var(--pe-white,#fff); color:var(--pe-text); font:inherit; font-size:14px; font-weight:500; cursor:pointer; box-shadow:0 1px 2px rgba(17,22,36,.04); transition:border-color .15s, box-shadow .15s; }
.sf-pick-btn:hover { border-color:#bfd9ea; }
.sf-pick-btn:focus-visible, .sf-pick.open .sf-pick-btn { outline:none; border-color:var(--pe-blue,#38bdf8); box-shadow:0 0 0 3px rgba(56,189,248,.15); }
.sf-pick.open .sf-pick-chev { transform:rotate(180deg); }
.sf-pick-btn:disabled { cursor:default; color:var(--pe-text-secondary,#64748b); background:var(--pe-gray-50,#f5f6f8); box-shadow:none; }
.sf-pick-btn:disabled ~ .sf-pick-chev { opacity:.4; }
.sf-pick-label { flex:1; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.sf-pick-label .sf-pick-n { color:var(--pe-blue,#0284c7); font-weight:700; }
/* The checklist: venues as group rows (tick one = every area under it), areas indented. */
.sf-pick-pop { position:absolute; left:0; right:0; top:calc(100% + 6px); z-index:40; max-height:340px; overflow-y:auto; padding:6px; border:1px solid var(--pe-border,#e5e7eb); border-radius:12px;
    background:var(--pe-white,#fff); box-shadow:0 12px 32px -12px rgba(17,22,36,.28), 0 2px 6px rgba(17,22,36,.08); }
.sf-pick-row { display:flex; align-items:center; gap:10px; padding:7px 10px; border-radius:8px; cursor:pointer; font-size:13.5px; color:var(--pe-text); }
.sf-pick-row:hover { background:var(--pe-gray-50,#f5f6f8); }
.sf-pick-row input { width:16px; height:16px; margin:0; accent-color:var(--pe-blue,#0284c7); flex:none; cursor:pointer; }
.sf-pick-row.venue { font-weight:700; margin-top:2px; }
.sf-pick-row.venue small { font-weight:500; color:var(--pe-text-secondary,#64748b); margin-left:auto; font-size:12px; }
.sf-pick-row.area { padding-left:34px; font-weight:500; }
.sf-pick-row.area .sf-pick-type { color:var(--pe-text-secondary,#64748b); font-weight:400; }
.sf-pick-foot { display:flex; align-items:center; justify-content:space-between; gap:8px; padding:8px 10px 4px; border-top:1px solid var(--pe-border,#e5e7eb); margin-top:4px; font-size:12.5px; color:var(--pe-text-secondary,#64748b); }
.sf-pick-foot button { border:0; background:none; padding:0; font:inherit; font-size:12.5px; font-weight:700; color:var(--pe-blue,#0284c7); cursor:pointer; }
.sf-add .btn { height:40px; }
.sf-add .btn:disabled { opacity:.45; cursor:default; }
[data-theme="dark"] .sf-pick-btn, [data-theme="dark"] .sf-pick-pop { background:var(--pe-card-bg,#1f2937); }
[data-theme="dark"] .sf-pick-btn:disabled { background:rgba(255,255,255,.04); }
[data-theme="dark"] .sf-pick-row:hover { background:rgba(255,255,255,.06); }
@media (max-width:599px) { .sf-pick { flex:1 1 100%; } }
.sf-block { border-top:1px solid var(--pe-border,#e5e7eb); padding:14px 16px 16px; }
.sf-block:first-of-type { border-top:0; }
.sf-head { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:10px; }
.sf-head .sf-name { font-size:14px; font-weight:700; color:var(--pe-text); }
.sf-head .sf-type { font-size:11px; font-weight:600; padding:2px 8px; border-radius:999px; background:rgba(56,189,248,.14); color:#0369a1; }
.sf-head .sf-note { flex:1 1 200px; min-width:0; font-size:12.5px; padding:6px 9px; border:1px solid var(--pe-border,#e5e7eb); border-radius:6px; background:var(--pe-white,#fff); color:var(--pe-text); }
.sf-head .sf-x { margin-left:auto; }
.sf-cols { display:grid; grid-template-columns:minmax(0,1.3fr) minmax(0,1fr); gap:18px; }
@media (max-width:760px) { .sf-cols { grid-template-columns:1fr; } }
.sf-h { font-size:12px; font-weight:600; color:var(--pe-text); margin:0 0 6px; }
/* the day-switch hours editor (2026-09-11, Robb's pick "B") */
.sfx-presets { display:flex; flex-wrap:wrap; gap:6px; margin-bottom:10px; }
.sfx-chip { font:inherit; font-size:12px; font-weight:600; padding:6px 11px; border-radius:999px; border:1px solid var(--pe-border,#e6e8ee); background:var(--pe-white,#fff); color:var(--pe-text); cursor:pointer; }
.sfx-chip:hover { border-color:var(--pe-blue); color:var(--pe-blue-dark,#0284c7); }
.sfx-chip.ghost { color:var(--pe-text-secondary,#64748b); border-style:dashed; }
.sf-r.sfx-day { position:relative; grid-template-columns:40px 92px 1fr 28px; gap:10px; margin:0; padding:7px 0; border-bottom:1px solid var(--pe-gray-light,#f1f3f6); }
.sf-r.sfx-day:last-child { border-bottom:0; }
.sfx-sw { position:relative; display:flex; align-items:center; cursor:pointer; margin:0; }
.sfx-sw input { position:absolute; opacity:0; width:0; height:0; margin:0; }
.sfx-sw .toggle-switch { margin-left:0; }
.sfx-sw input:checked + .toggle-switch { background:var(--pe-blue); }
.sfx-sw input:checked + .toggle-switch::after { transform:translateX(18px); }
.sfx-sw input:focus-visible + .toggle-switch { box-shadow:0 0 0 3px rgba(56,189,248,.35); }
.sfx-dn { font-size:13px; font-weight:600; color:var(--pe-text); }
.sfx-time { display:flex; align-items:center; gap:6px; }
.sf-r.sfx-day input[type=time] { font-size:13px; font-weight:600; padding:7px 10px; border:1.5px solid var(--pe-border,#e6e8ee); border-radius:8px; background:var(--pe-gray-light,#f5f6f8); color:var(--pe-text); width:auto; min-width:0; }
.sf-r.sfx-day input[type=time]:focus { outline:none; border-color:var(--pe-blue); background:var(--pe-white,#fff); }
.sfx-any { font-size:13px; color:var(--pe-text-secondary,#94a3b8); }
.sf-r.sfx-day.off .sfx-time { display:none; }
.sf-r.sfx-day:not(.off) .sfx-any { display:none; }
.sfx-copy { width:26px; height:26px; padding:5px; border-radius:6px; border:1px solid var(--pe-border,#e6e8ee); background:var(--pe-white,#fff); color:var(--pe-text-secondary,#94a3b8); cursor:pointer; display:flex; align-items:center; justify-content:center; }
.sfx-copy svg { width:14px; height:14px; }
.sfx-copy:hover { color:var(--pe-blue-dark,#0284c7); border-color:var(--pe-blue); }
.sf-r.sfx-day.off .sfx-copy { visibility:hidden; }
.sfx-copy-pop { position:absolute; right:0; top:calc(100% - 4px); z-index:5; width:260px; padding:12px; border-radius:12px; background:var(--pe-white,#fff); border:1px solid var(--pe-border,#e6e8ee); box-shadow:0 12px 30px rgba(15,23,42,.14); }
.sfx-copy-h { font-size:12.5px; font-weight:600; margin-bottom:8px; }
.sfx-copy-days { display:grid; grid-template-columns:1fr 1fr 1fr; gap:6px 8px; margin-bottom:10px; }
.sfx-copy-days label { display:flex; align-items:center; gap:6px; font-size:12.5px; cursor:pointer; }
.sfx-copy-days input { width:auto; margin:0; }
.sfx-copy-a { display:flex; justify-content:flex-end; gap:6px; }
@media (max-width:480px) { .sf-r.sfx-day { grid-template-columns:40px 1fr 28px; } .sf-r.sfx-day .sfx-time, .sf-r.sfx-day .sfx-any { grid-column:2 / span 2; } .sf-r.sfx-day .sfx-copy { grid-row:1; grid-column:3; } }
/* under the two columns: Used for (under the days) | secondary contact (under the closed dates) */
.sfx-details { display:grid; grid-template-columns:minmax(0,1.3fr) minmax(0,1fr); gap:14px 18px; margin-top:16px; padding-top:16px; border-top:1px solid var(--pe-border,#e6e8ee); }
.sfx-details .form-group { margin-bottom:0; }
@media (max-width:760px) { .sfx-details { grid-template-columns:1fr; } }
.sfx-contact { display:grid; gap:8px; margin-bottom:0; }
.sfx-contact-2 { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
.sfx-contact + .sf-hint { padding:6px 0 0; }
/* Set every day at once — Robb: "a feature for the user to update all dates/times at once". */
.sf-bulk { display:grid; grid-template-columns:auto 1fr 12px 1fr; gap:8px; align-items:center; padding:8px 10px; margin-bottom:8px;
    border:1px dashed var(--pe-border,#cbd5e1); border-radius:8px; background:var(--pe-bg,#f8fafc); font-size:12px; }
.sf-bulk .sf-bulk-l { font-weight:600; white-space:nowrap; }
.sf-bulk .sf-bulk-a { grid-column:1 / -1; display:flex; gap:6px; flex-wrap:wrap; }
.sf-bulk input[type=time] { min-width:0; font-size:12.5px; padding:5px 7px; border:1px solid var(--pe-border,#e5e7eb); border-radius:6px; background:var(--pe-white,#fff); color:var(--pe-text); }
.sf-r { display:grid; grid-template-columns:104px 1fr 12px 1fr; gap:8px; align-items:center; margin-bottom:5px; }
.sf-r.off input[type=time] { opacity:.45; }
.sf-d { display:flex; align-items:center; gap:7px; font-size:12.5px; cursor:pointer; }
.sf-d input { width:auto; margin:0; }
.sf-r input[type=time] { min-width:0; font-size:12.5px; padding:5px 7px; border:1px solid var(--pe-border,#e5e7eb); border-radius:6px; background:var(--pe-white,#fff); color:var(--pe-text); }
.sf-sep { text-align:center; color:var(--pe-text-secondary,#94a3b8); font-size:12px; }
@media (max-width:559px) { .sf-r, .sf-bulk { grid-template-columns:1fr 1fr; } .sf-r .sf-d, .sf-bulk .sf-bulk-l { grid-column:1 / -1; } .sf-sep { display:none; } }
.sf-blk { display:flex; align-items:center; gap:8px; padding:5px 0; font-size:12.5px; border-bottom:1px dashed var(--pe-border,#e5e7eb); }
.sf-blk .why { color:var(--pe-text-secondary,#64748b); flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.sf-blk .x { background:none; border:0; color:var(--pe-text-secondary,#94a3b8); font-size:16px; cursor:pointer; padding:0 4px; line-height:1; }
.sf-blk-add { display:grid; grid-template-columns:auto 1fr auto; gap:6px; margin-top:8px; }
.sf-blk-add input { min-width:0; font-size:12.5px; padding:5px 7px; border:1px solid var(--pe-border,#e5e7eb); border-radius:6px; background:var(--pe-white,#fff); color:var(--pe-text); }
.sf-blk-yearly { display:flex; align-items:center; gap:6px; margin-top:6px; font-size:12.5px; color:var(--pe-text-secondary,#64748b); cursor:pointer; }
.sf-blk-yearly input { margin:0; }
.sf-foot { display:flex; align-items:center; gap:10px; margin-top:12px; }
.sf-foot .sf-status { font-size:12px; color:var(--pe-text-secondary,#64748b); }
.sf-foot .sf-status.dirty { color:#b45309; }
.sf-hint { font-size:12.5px; color:var(--pe-text-secondary,#64748b); line-height:1.5; padding:10px 16px 0; }
/* ── End-of-season tournament card (mig 237/238): what this season fed ── */
.eos-card { margin-bottom: 18px; }
.eos-row { display:flex; align-items:center; gap:14px; flex-wrap:wrap; padding:12px 16px; border-top:1px solid var(--pe-border,#e5e7eb); }
.eos-row:first-of-type { border-top:0; }
.eos-main { flex:1 1 260px; min-width:0; display:flex; flex-direction:column; gap:3px; }
.eos-name { font-size:14px; font-weight:700; color:var(--pe-text); text-decoration:none; }
.eos-name:hover { color:var(--pe-blue,#0284c7); }
.eos-meta { font-size:12.5px; color:var(--pe-text-secondary,#64748b); line-height:1.45; }
.eos-actions { display:flex; gap:8px; align-items:center; }
.eos-empty { padding:12px 16px; font-size:12.5px; color:var(--pe-text-secondary,#64748b); display:flex; align-items:center; gap:12px; flex-wrap:wrap; }
.eos-empty a { margin-left:auto; }
[data-theme="dark"] .sf-bulk { background:rgba(255,255,255,.03); }
[data-theme="dark"] .sf-head .sf-note, [data-theme="dark"] .sf-r input[type=time], [data-theme="dark"] .sf-bulk input[type=time], [data-theme="dark"] .sf-blk-add input { background:rgba(255,255,255,.04); }
.sfx .sfx-addrow { padding: 12px 0 14px; border-bottom: 0; }
.sfx .sfx-controls { margin: 0 0 12px; }
.sfx .pe-fb-right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.sfx .sfx-filters { margin-bottom: 12px; }
.sfx-name { display: flex; align-items: center; gap: 8px; min-width: 0; }
.sfx-name-t { font-weight: 700; color: var(--pe-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sfx-open { font-size: 13px; color: var(--pe-text); }
.sfx-muted { color: var(--pe-text-secondary, #64748b); }
.sfx-pill { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; border-radius: 999px; padding: 3px 10px; white-space: nowrap; }
.sfx-pill.active { background: #dcfce7; color: #15803d; } .sfx-pill.inactive { background: var(--pe-gray-100, #eef0f3); color: var(--pe-text-secondary, #64748b); }
.sfx-pill-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.sfx-row-inactive .sfx-name-t, .sfx-row-inactive .sfx-open { color: var(--pe-text-secondary, #64748b); }
.sfx-actions { display: inline-flex; align-items: center; gap: 4px; justify-content: flex-end; }
.sfx-act { width: 32px; height: 32px; border: 0; border-radius: 8px; background: transparent; color: var(--pe-text-secondary, #64748b); display: inline-grid; place-items: center; cursor: pointer; transition: background .12s, color .12s; }
.sfx-act svg { width: 17px; height: 17px; }
.sfx-act:hover { background: var(--pe-hover, #f1f5f9); color: var(--pe-text); }
.sfx-act.is-on { color: #15803d; } .sfx-act.danger:hover { background: #fee2e2; color: #b91c1c; }
.sfx-card { border: 1px solid var(--pe-border); border-radius: 12px; padding: 12px 14px; background: var(--pe-white, #fff); margin-bottom: 10px; }
.sfx-card-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 4px; }
.sfx-card-sub { font-size: 12.5px; color: var(--pe-text-secondary, #64748b); }
.sfx-card-foot { display: flex; justify-content: flex-end; margin-top: 8px; }
#sfxDrawer .modal-title .sf-type { vertical-align: middle; margin-left: 8px; }
#sfxDrawer .modal-actions .sf-status { margin-right: auto; }
[data-theme="dark"] .sfx-pill.active { background: #14351f; color: #86efac; }
/* Remind Me (remind-list.js) rides this grid's chrome and adds two states of its own. */
.sfx-pill.waiting { background: #fef3c7; color: #92400e; } .sfx-pill.emailed { background: var(--pe-blue-light, #e0f2fe); color: var(--pe-blue-dark, #0369a1); }
[data-theme="dark"] .sfx-pill.waiting { background: #3a2a0c; color: #fcd34d; }
[data-theme="dark"] .sfx-pill.emailed { background: #0c2a3e; color: #7dd3fc; }
.rml-email { min-width: 0; overflow-wrap: anywhere; }
[data-theme="dark"] .sfx-act:hover { background: rgba(255,255,255,.06); }
@media (max-width: 599px) { .sfx .pe-fb-right { margin-left: 0; width: 100%; } .sfx .pe-fb-search input { width: 100%; } }

/* ══ Rentals — the listings page + the listing drawer (2026-09-17, Phase 1b) ══
   The grid reuses the Season Facilities chrome (.sfx-*), the drawer is the house
   drawer with sections top to bottom (Robb chose this over tabs from a mock). */
.rn-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 14px; }
.rn-lead { margin: 4px 0 0; color: var(--pe-text-muted); font-size: 13.5px; max-width: 720px; }
.rn-count { font-weight: 600; color: var(--pe-text-muted); font-size: 15px; margin-left: 4px; }
.rn-empty { text-align: center; padding: 56px 20px; color: var(--pe-text-muted); }
.rn-empty h2, .rn-empty h3 { color: var(--pe-text); margin: 0 0 8px; font-size: 20px; }
.rn-empty p { max-width: 560px; margin: 0 auto 18px; font-size: 14px; line-height: 1.55; }
.rn-empty-grid { padding: 36px 20px; }
.rn-pill { display: inline-block; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; padding: 2px 8px; border-radius: 999px; margin-left: 8px; vertical-align: middle; }
.rn-pill-facility { background: rgba(56,189,248,.14); color: #0369a1; }
.rn-pill-equipment { background: rgba(245,158,11,.16); color: #92400e; }
.rn-pill-service { background: rgba(139,92,246,.14); color: #5b21b6; }
.rn-from { font-weight: 700; color: var(--pe-text); }
.rn-hint { font-size: 12.5px; color: var(--pe-text-muted); margin-top: 6px; }
.rn-hint a { color: var(--pe-blue); font-weight: 600; text-decoration: none; }
.rn-kind { display: flex; gap: 8px; margin: 4px 0 16px; }
.rn-kind button { flex: 1; min-width: 0; padding: 12px 10px; border: 1.5px solid var(--pe-border); background: var(--pe-card); border-radius: 10px; cursor: pointer; text-align: left; color: var(--pe-text); font-family: inherit; }
.rn-kind button strong { display: block; font-size: 14px; }
.rn-kind button span { font-size: 12px; color: var(--pe-text-muted); line-height: 1.35; }
.rn-kind button.on { border-color: var(--pe-blue); background: rgba(56,189,248,.08); box-shadow: 0 0 0 1px var(--pe-blue) inset; }
.rn-kind button:disabled { cursor: default; opacity: .7; }
.rn-kind button:disabled.on { opacity: 1; }
.rn-kind-lock { font-size: 12.5px; color: var(--pe-text-muted); margin: -8px 0 14px; }
.rn-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 16px; }
.rn-sec { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--pe-border); }
.rn-sec-h { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 10px; flex-wrap: wrap; }
.rn-sec-h h3 { margin: 0; font-size: 13px; letter-spacing: .04em; text-transform: uppercase; color: var(--pe-text-muted); font-weight: 800; }
.rn-sec-h .hint { font-size: 12.5px; color: var(--pe-text-muted); }
.rn-rate-wrap { overflow-x: auto; }
.rn-rate { width: 100%; border-collapse: collapse; font-size: 13.5px; min-width: 800px; }
.rn-rate th { text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em; color: var(--pe-text-muted); padding: 6px 6px; border-bottom: 1px solid var(--pe-border); font-weight: 700; }
.rn-rate td { padding: 6px 6px; border-bottom: 1px solid var(--pe-border); vertical-align: middle; }
.rn-rate input, .rn-rate select { width: 100%; min-width: 0; padding: 7px 8px; border: 1px solid var(--pe-border); border-radius: 8px; font-size: 13.5px; background: var(--pe-card); color: var(--pe-text); font-family: inherit; }
/* .t holds two 12-hour time inputs ("06:30 PM" needs ~112px each in Chrome)
   plus the dash: 190px clipped both to "06:3" / "00 PM". */
.rn-rate .n { width: 96px; } .rn-rate .u { width: 130px; } .rn-rate .d { width: 112px; } .rn-rate .t { width: 262px; } .rn-rate .q { width: 140px; }
.rn-rate .rn-sub input[type="time"] { width: 116px; flex: 0 0 116px; }
.rn-rate .rn-block { margin-top: 4px; }
.rn-rate .x { background: none; border: 0; color: var(--pe-text-muted); cursor: pointer; font-size: 20px; line-height: 1; padding: 4px 6px; }
.rn-rate .x:hover { color: var(--pe-danger); }
.rn-add { margin-top: 10px; }
.rn-sub { display: flex; gap: 6px; align-items: center; font-size: 12.5px; color: var(--pe-text-muted); }
.rn-sub input[type="time"], .rn-sub input[type="number"] { width: 84px; }
.rn-addons { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.rn-addon { display: flex; gap: 10px; align-items: flex-start; border: 1px solid var(--pe-border); border-radius: 10px; padding: 10px 12px; cursor: pointer; background: var(--pe-card); }
.rn-addon input { margin-top: 3px; flex: none; }
.rn-addon strong { display: block; font-size: 13.5px; color: var(--pe-text); }
.rn-addon span { font-size: 12.5px; color: var(--pe-text-muted); line-height: 1.4; }
.rn-addon.on { border-color: var(--pe-blue); background: rgba(56,189,248,.06); }
.rn-photos { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
.rn-photo { position: relative; width: 84px; height: 64px; border-radius: 8px; overflow: hidden; border: 1px solid var(--pe-border); background: var(--pe-bg); }
.rn-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.rn-photo .x { position: absolute; top: 2px; right: 2px; width: 20px; height: 20px; border-radius: 50%; border: 0; background: rgba(17,17,36,.75); color: #fff; font-size: 14px; line-height: 1; cursor: pointer; }
.rn-photo .tag { position: absolute; left: 0; bottom: 0; right: 0; font-size: 10px; font-weight: 800; text-transform: uppercase; background: rgba(56,189,248,.9); color: #04263a; text-align: center; padding: 1px 0; }
/* "Set cover" on every other tile: a quiet strip that reads as a button on hover */
.rn-photo .tag.set { border: 0; cursor: pointer; font: inherit; font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .02em; background: rgba(17,17,36,.62); color: #fff; padding: 2px 0; }
.rn-photo .tag.set:hover, .rn-photo .tag.set:focus-visible { background: rgba(56,189,248,.95); color: #04263a; outline: none; }
.rn-switch { display: inline-flex; align-items: center; gap: 8px; font-size: 13.5px; cursor: pointer; margin-top: 6px; }
.rn-status { margin-right: auto; font-size: 12.5px; color: var(--pe-text-muted); }
.rn-status.dirty { color: #b45309; font-weight: 600; }
@media (max-width: 760px) {
    .rn-kind { flex-direction: column; }
    .rn-grid2, .rn-addons { grid-template-columns: 1fr; }
}

/* ══ Merge-field token in a rich editor (Robb, 2026-09-21) ═════════════════
   "I also want to add in data from the database for the season data this
   belongs to." The editor shows this tag; the DB stores {{season.price}};
   the public page resolves it. It is an embed, so it deletes as one unit and
   cannot be half-edited into a broken brace.

   8px, not a pill — see bin/test-no-pill-buttons.php. Nothing lifts on
   hover either; the cursor and a slightly firmer border do the work. */
.ql-editor .pe-mf {
    display: inline-block;
    padding: 1px 8px;
    border-radius: 8px;
    border: 1px solid rgba(56, 189, 248, 0.4);
    background: rgba(56, 189, 248, 0.14);
    color: #0369a1;
    font-size: 0.92em;
    font-weight: 600;
    white-space: nowrap;
    cursor: default;
    user-select: none;
}
.ql-editor .pe-mf:hover { border-color: rgba(56, 189, 248, 0.85); }
[data-theme="dark"] .ql-editor .pe-mf {
    border-color: rgba(56, 189, 248, 0.45);
    background: rgba(56, 189, 248, 0.18);
    color: #7dd3fc;
}

/* The "Insert field" TOOLBAR BUTTON and its menu.

   Robb, 2026-09-21: "Can we move the Insert Field feature to the end of the
   gray bar?" Quill renders a custom toolbar item as a 24px empty icon slot, so
   it is widened here to hold words. 8px corners, and nothing moves on hover. */
.ql-toolbar button.ql-mergeField {
    width: auto;
    padding: 0 10px;
    margin-left: 2px;
    border-radius: 8px;
    font: inherit;
    font-size: 12.5px;
    font-weight: 600;
    line-height: 22px;
    color: var(--pe-text-secondary, #64748b);
    white-space: nowrap;
}
.ql-toolbar button.ql-mergeField:hover { background: var(--pe-bg-subtle, #f1f5f9); color: var(--pe-text); }
.ql-toolbar button.ql-mergeField:disabled { opacity: .45; cursor: not-allowed; background: none; }

/* FIXED, not absolute: it is opened from inside a drawer, and an absolutely
   positioned menu is clipped by the drawer's own scroll container. JS measures
   it off the button. */
.pe-mf-menu {
    position: fixed; z-index: 11000;
    width: 290px; max-height: 340px; overflow-y: auto;
    padding: 8px; border-radius: 10px;
    background: var(--pe-white, #fff);
    border: 1px solid var(--pe-border, #e5e7eb);
    box-shadow: 0 12px 28px rgba(17, 22, 36, 0.16);
}
.pe-mf-menu[hidden] { display: none; }
.pe-mf-group {
    padding: 8px 8px 4px; font-size: 11px; font-weight: 800;
    letter-spacing: 0.05em; text-transform: uppercase;
    color: var(--pe-text-muted, #94a3b8);
}
.pe-mf-item {
    display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
    width: 100%; padding: 7px 8px; border: 0; border-radius: 8px;
    background: transparent; font: inherit; font-size: 13.5px; text-align: left;
    color: var(--pe-text); cursor: pointer;
}
.pe-mf-item:hover { background: var(--pe-bg-subtle, #f1f5f9); }
.pe-mf-item:disabled { opacity: 0.45; cursor: not-allowed; }
.pe-mf-item:disabled:hover { background: transparent; }
.pe-mf-prev {
    flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; font-size: 12px; color: var(--pe-text-muted, #94a3b8);
    font-variant-numeric: tabular-nums;
}
.pe-mf-note { margin: 6px 8px 2px; font-size: 12px; color: var(--pe-text-muted, #94a3b8); }

/* ══ The link dialog (Robb, 2026-09-21) ═══════════════════════════════════
   "can we make the link tool better… and the ability to target new or same",
   plus a dropdown of the league's own pages. Replaces Quill's one-line
   tooltip, which is hidden just below so there is only one way to make a
   link. Sits above the drawers (z-index 12000) because it is opened FROM
   one. 8px corners, nothing lifts on hover. */
.ql-tooltip { display: none !important; }

.pe-lnk-back {
    position: fixed; inset: 0; z-index: 12000;
    display: flex; align-items: center; justify-content: center;
    padding: 16px; background: rgba(15, 23, 42, 0.45);
}
.pe-lnk-back[hidden] { display: none; }
.pe-lnk {
    width: 100%; max-width: 440px; padding: 20px;
    border-radius: 12px; background: var(--pe-white, #fff);
    box-shadow: 0 24px 60px rgba(15, 23, 42, 0.28);
}
.pe-lnk-title { margin: 0 0 14px; font-size: 17px; font-weight: 700; color: var(--pe-text); }
.pe-lnk-lab {
    display: block; margin: 12px 0 5px; font-size: 12px; font-weight: 700;
    letter-spacing: 0.03em; text-transform: uppercase;
    color: var(--pe-text-muted, #94a3b8);
}
.pe-lnk-lab:first-of-type { margin-top: 0; }
.pe-lnk-in {
    width: 100%; padding: 9px 11px; font: inherit; font-size: 14px;
    border: 1px solid var(--pe-border, #e5e7eb); border-radius: 8px;
    background: var(--pe-white, #fff); color: var(--pe-text);
}
.pe-lnk-in:focus { outline: 2px solid var(--pe-blue, #38bdf8); outline-offset: -1px; }
/* Read-only because a page was picked — say so, do not just ignore typing. */
.pe-lnk-in.is-locked { background: var(--pe-bg-subtle, #f1f5f9); color: var(--pe-text-muted, #94a3b8); }
.pe-lnk-check {
    display: flex; align-items: center; gap: 8px; margin: 14px 0 0;
    font-size: 14px; color: var(--pe-text); cursor: pointer;
}
.pe-lnk-check input { width: 18px; height: 18px; margin: 0; cursor: pointer; }
.pe-lnk-err { margin: 12px 0 0; font-size: 13px; color: #b91c1c; }
.pe-lnk-err[hidden] { display: none; }
.pe-lnk-actions { display: flex; align-items: center; gap: 8px; margin-top: 18px; }
.pe-lnk-spacer { flex: 1 1 auto; }
.pe-lnk-actions .btn[hidden] { display: none; }
[data-theme="dark"] .pe-lnk { background: var(--pe-surface, #1e293b); }


/* ══ Images inside the editor ═════════════════════════════════════════════
   The same four presets the public page uses, so what the author arranges is
   what a parent sees. Selected state is an outline, not a lift. */
.ql-editor img { max-width: 100%; height: auto; border-radius: 8px; display: block; }
.ql-editor img.pe-img-full  { width: 100%; margin: 1em 0; }
.ql-editor img.pe-img-half  { width: 50%;  margin: 1em 0; }
.ql-editor img.pe-img-left  { width: 45%; float: left;  margin: 0.3em 1.2em 0.8em 0; }
.ql-editor img.pe-img-right { width: 45%; float: right; margin: 0.3em 0 0.8em 1.2em; }
.ql-editor::after { content: ""; display: table; clear: both; }
.ql-editor img.ql-selected { outline: 2px solid var(--pe-blue, #38bdf8); outline-offset: 2px; }

/* The picker: Upload / AI Library / a web address. */
.pe-imgp-back {
    position: fixed; inset: 0; z-index: 12000;
    display: flex; align-items: center; justify-content: center;
    padding: 16px; background: rgba(15, 23, 42, 0.45);
}
.pe-imgp-back[hidden] { display: none; }
.pe-imgp {
    width: 100%; max-width: 640px; max-height: 86vh; display: flex; flex-direction: column;
    padding: 20px; border-radius: 12px; background: var(--pe-white, #fff);
    box-shadow: 0 24px 60px rgba(15, 23, 42, 0.28);
}
.pe-imgp-title { margin: 0 0 14px; font-size: 17px; font-weight: 700; color: var(--pe-text); }
.pe-imgp-tabs { display: flex; gap: 4px; margin-bottom: 14px; border-bottom: 1px solid var(--pe-border, #e5e7eb); }
.pe-imgp-tab {
    padding: 8px 14px; border: 0; border-bottom: 2px solid transparent; border-radius: 8px 8px 0 0;
    background: transparent; font: inherit; font-size: 13.5px; font-weight: 600;
    color: var(--pe-text-muted, #94a3b8); cursor: pointer;
}
.pe-imgp-tab:hover { color: var(--pe-text); background: var(--pe-bg-subtle, #f1f5f9); }
.pe-imgp-tab.is-on { color: var(--pe-blue, #0284c7); border-bottom-color: var(--pe-blue, #38bdf8); }
.pe-imgp-body { flex: 1 1 auto; min-height: 150px; overflow-y: auto; }
.pe-imgp-pane[hidden] { display: none; }
.pe-imgp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; }
.pe-imgp-pick {
    padding: 0; border: 2px solid transparent; border-radius: 10px; background: none;
    cursor: pointer; overflow: hidden; line-height: 0;
}
.pe-imgp-pick img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block; border-radius: 8px; }
.pe-imgp-pick:hover { border-color: var(--pe-border, #e5e7eb); }
.pe-imgp-pick.is-on { border-color: var(--pe-blue, #38bdf8); }
.pe-imgp-empty { padding: 28px 8px; text-align: center; font-size: 13.5px; color: var(--pe-text-muted, #94a3b8); }
.pe-imgp-lab {
    display: block; margin: 12px 0 5px; font-size: 12px; font-weight: 700;
    letter-spacing: 0.03em; text-transform: uppercase; color: var(--pe-text-muted, #94a3b8);
}
.pe-imgp-in {
    width: 100%; padding: 9px 11px; font: inherit; font-size: 14px;
    border: 1px solid var(--pe-border, #e5e7eb); border-radius: 8px;
    background: var(--pe-white, #fff); color: var(--pe-text);
}
.pe-imgp-hint { margin: 6px 0 0; font-size: 12px; color: var(--pe-text-muted, #94a3b8); }
.pe-imgp-warn { margin: 6px 0 0; font-size: 12px; color: #b45309; }
.pe-imgp-err { margin: 10px 0 0; font-size: 13px; color: #b91c1c; }
.pe-imgp-err[hidden] { display: none; }
.pe-imgp-foot { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--pe-border, #e5e7eb); }
.pe-imgp-sizes { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px; }
.pe-imgp-size {
    padding: 7px 12px; border: 1px solid var(--pe-border, #e5e7eb); border-radius: 8px;
    background: var(--pe-white, #fff); font: inherit; font-size: 13px; font-weight: 600;
    color: var(--pe-text-secondary, #64748b); cursor: pointer;
}
.pe-imgp-size:hover { background: var(--pe-bg-subtle, #f1f5f9); }
.pe-imgp-size.is-on { border-color: var(--pe-blue, #38bdf8); background: rgba(56,189,248,.12); color: #0369a1; }
.pe-imgp-check { display: flex; align-items: center; gap: 8px; margin: 10px 0 0; font-size: 13.5px; cursor: pointer; }
.pe-imgp-check input { width: 18px; height: 18px; margin: 0; cursor: pointer; }
.pe-imgp-actions { display: flex; align-items: center; gap: 8px; margin-top: 14px; }
.pe-imgp-spacer { flex: 1 1 auto; }
[data-theme="dark"] .pe-imgp { background: var(--pe-surface, #1e293b); }


/* ══ Tables in the editor, and the generator that starts them ═════════════
   Since 2026-09-23 a table is Quill's own (`modules/table`): the author
   clicks a cell and types, formats, drops an image in. The generator
   dialog only starts one (rows × columns, first-row heading, pre-filled
   text). A heading row's cells carry a `data-row` id beginning `hdr-` —
   that is the whole flag, and it is what this styles. */
.ql-editor table {
    width: 100%; margin: 1em 0; border-collapse: collapse; font-size: 0.95em;
    table-layout: auto;
}
.ql-editor td {
    padding: 7px 10px; text-align: left; vertical-align: top;
    border: 1px solid var(--pe-border, #e5e7eb);
    min-width: 3em;
}
.ql-editor td[data-row^="hdr-"] { font-weight: 700; background: var(--pe-bg-subtle, #f1f5f9); }
[data-theme="dark"] .ql-editor td[data-row^="hdr-"] { background: rgba(255, 255, 255, 0.06); }
.ql-editor td img { display: block; max-width: 100%; }
.ql-editor td img.pe-img-half { width: 100%; }
.ql-editor td img.pe-img-left, .ql-editor td img.pe-img-right { width: 100%; float: none; margin: 0.3em 0; }
/* Text size: the same three steps the public block draws (gen-richtext-css.php),
   overriding Quill's .75/1.5/2.5em so the editor shows what will publish. */
/* .ql-container in front: quill.snow.css loads AFTER app.css on the admin pages
   and its own .ql-editor .ql-size-* (2.5em) would win on order alone. */
/* A highlight is a light tint; the words over it are near-black whatever the
   theme, or a dark-mode editor shows light text on lemon. Inline colour still
   wins, which is the author's own palette colour. */
.ql-container .ql-editor span[style*="background-color"] { color: #1a1a2e; padding: 0 0.15em; border-radius: 3px; }
/* A video is a 16:9 box the width of the column; inert in the editor so a
   click selects it rather than playing it. */
.ql-container .ql-editor iframe.ql-video {
    display: block; width: 100%; aspect-ratio: 16 / 9; height: auto;
    border: 0; border-radius: 8px; margin: 1em 0; pointer-events: none; background: #000;
}
.pe-vid { max-width: 520px; }
.pe-vid-url {
    width: 100%; padding: 9px 11px; font: inherit; font-size: 14px;
    border: 1px solid var(--pe-border, #e5e7eb); border-radius: 8px; margin-bottom: 8px;
}
.pe-tbl-err { margin: 8px 0 0; font-size: 13px; color: #b91c1c; }
.pe-tbl-err[hidden] { display: none; }
.ql-container .ql-editor .ql-size-small { font-size: 0.85em; }
.ql-container .ql-editor .ql-size-large { font-size: 1.25em; }
.ql-container .ql-editor .ql-size-huge  { font-size: 1.6em; line-height: 1.25; }
.ql-editor blockquote {
    margin: 1em 0; padding: 0.2em 0 0.2em 1em;
    border-left: 4px solid var(--pe-blue, #38bdf8); color: var(--pe-text-muted, #475569);
}
.ql-editor hr {
    border: 0; border-top: 1px solid var(--pe-border, #cbd5e1); margin: 1.2em 0;
}
[data-theme="dark"] .ql-editor hr { border-top-color: var(--pe-border, #334155); }

/* The row/column strip. Appears under the toolbar while the caret is in a
   table, and is the only way to change a table's shape — there is no
   dialog to reopen any more. */
.pe-tbl-tools {
    display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px;
    padding: 6px 10px;
    border: 1px solid var(--pe-border, #e2e8f0); border-top: none;
    background: var(--pe-bg-subtle, #f1f5f9); font-size: 12.5px;
}
.pe-tbl-tools[hidden] { display: none; }
.pe-tbl-tools-lab { font-weight: 700; margin-right: 4px; color: var(--pe-text-muted, #64748b); }
.pe-tbl-tools-hint { margin-left: auto; color: var(--pe-text-muted, #94a3b8); font-size: 12px; }
.pe-tbl-tool {
    font: inherit; font-size: 12.5px; padding: 4px 9px; cursor: pointer;
    border: 1px solid var(--pe-border, #e2e8f0); border-radius: 6px;
    background: var(--pe-white, #fff); color: var(--pe-text, #1e293b);
}
.pe-tbl-tool:hover { border-color: var(--pe-blue, #38bdf8); }
.pe-tbl-tool.is-toggle[aria-pressed="true"] { background: var(--pe-blue, #38bdf8); border-color: var(--pe-blue, #38bdf8); color: #0b1320; }
.pe-tbl-tool.is-danger { color: #b91c1c; }
.pe-tbl-tool.is-danger:hover { border-color: #b91c1c; }
[data-theme="dark"] .pe-tbl-tools { background: var(--pe-card, #1e293b); border-color: var(--pe-border, #334155); }
[data-theme="dark"] .pe-tbl-tool { background: var(--pe-surface, #0f172a); border-color: var(--pe-border, #334155); color: var(--pe-text, #e2e8f0); }
[data-theme="dark"] .pe-tbl-tool.is-danger { color: #fca5a5; }
/* Headings and lists are not formats a cell can hold (Quill would replace
   the cell); while the caret is in a table those controls read as off. */
.ql-toolbar.pe-in-table .ql-header,
.ql-toolbar.pe-in-table .ql-list,
.ql-toolbar.pe-in-table .ql-blockquote,
.ql-toolbar.pe-in-table .ql-video,
.ql-toolbar.pe-in-table .ql-divider { opacity: 0.35; pointer-events: none; }

/* The text-colour picker's "back to default" item is the swatch with no
   value. Quill paints it black, which reads as a tenth colour; drawn here
   as a struck-through white box so it reads as "none". */
.ql-snow .ql-color-picker.ql-color .ql-picker-item:not([data-value]),
.ql-snow .ql-color-picker.ql-background .ql-picker-item:not([data-value]) {
    background: #fff;
    background-image: linear-gradient(to top right, transparent 45%, #b91c1c 45%, #b91c1c 55%, transparent 55%);
    border-color: var(--pe-border, #cbd5e1);
}
.ql-snow .ql-color-picker .ql-picker-item { border-radius: 3px; }
[data-theme="dark"] .ql-snow .ql-picker-options { background: var(--pe-card, #1e293b); }

.pe-tbl-back {
    position: fixed; inset: 0; z-index: 12000;
    display: flex; align-items: center; justify-content: center;
    padding: 16px; background: rgba(15, 23, 42, 0.45);
}
.pe-tbl-back[hidden] { display: none; }
.pe-tbl {
    width: 100%; max-width: 760px; max-height: 88vh; display: flex; flex-direction: column;
    padding: 20px; border-radius: 12px; background: var(--pe-white, #fff);
    box-shadow: 0 24px 60px rgba(15, 23, 42, 0.28);
}
.pe-tbl-title { margin: 0 0 14px; font-size: 17px; font-weight: 700; color: var(--pe-text); }
.pe-tbl-bar { display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap; margin-bottom: 12px; }
.pe-tbl-lab {
    display: block; margin-bottom: 5px; font-size: 12px; font-weight: 700;
    letter-spacing: 0.03em; text-transform: uppercase; color: var(--pe-text-muted, #94a3b8);
}
.pe-tbl-num {
    width: 76px; padding: 7px 9px; font: inherit; font-size: 14px;
    border: 1px solid var(--pe-border, #e5e7eb); border-radius: 8px;
}
.pe-tbl-check { display: flex; align-items: center; gap: 8px; font-size: 13.5px; cursor: pointer; }
.pe-tbl-check input { width: 18px; height: 18px; margin: 0; cursor: pointer; }
.pe-tbl-help { margin: 0 0 10px; font-size: 13px; color: var(--pe-text-muted, #64748b); }
/* The grid scrolls, not the dialog: a 6-column table must not push the
   buttons off the bottom of the screen. */
.pe-tbl-gridwrap {
    flex: 1 1 auto; min-height: 90px; overflow: auto;
    border: 1px solid var(--pe-border, #e5e7eb); border-radius: 10px; padding: 8px;
    overscroll-behavior: contain;
}
.pe-tbl-grid { border-collapse: separate; border-spacing: 6px; }
.pe-tbl-cell {
    min-width: 120px; padding: 7px 9px; font: inherit; font-size: 13.5px;
    border: 1px solid var(--pe-border, #e5e7eb); border-radius: 8px;
}
.pe-tbl-grid tr:first-child .pe-tbl-cell.is-head { font-weight: 700; background: var(--pe-bg-subtle, #f1f5f9); }
.pe-tbl-actions { display: flex; align-items: center; gap: 8px; margin-top: 14px; }
.pe-tbl-spacer { flex: 1 1 auto; }
[data-theme="dark"] .pe-tbl { background: var(--pe-surface, #1e293b); }


/* ══ The rich editor's own chrome (2026-09-21) ════════════════════════════
   The box the editor lives in: border, radius and placeholder, matched to
   the form fields beside it so a rich field does not read as a foreign
   control in the middle of a form.

   It lives HERE rather than in a `<style>` block because four pages had
   identical private copies and phase 2 was about to make it nine. The
   remaining per-page copies are the same rules and simply win by source
   order, so they can be deleted whenever somebody is in those files; a new
   page needs to load app.css and nothing else.

   `.ql-toolbar.ql-snow` / `.ql-container.ql-snow` are Quill's own classes
   and are styled unscoped on purpose — every editor in the admin should
   look the same, and scoping them to `.pe-rich-editor` would miss the ones
   Quill builds as siblings rather than children. */
.pe-rich-editor {
    border-radius: 0 0 8px 8px;
}
.pe-rich-editor .ql-editor {
    min-height: 220px;
    font-size: 14px;
    font-family: var(--pe-font);
}
.pe-rich-editor .ql-editor.ql-blank::before {
    font-style: italic;
    color: var(--pe-text-muted, #94a3b8);
}
.pe-rich-editor + .ql-toolbar,
.ql-toolbar.ql-snow {
    border: 1px solid var(--pe-border, #e2e8f0);
    border-radius: 8px 8px 0 0;
    background: var(--pe-bg, #f8fafc);
}
.ql-container.ql-snow {
    border: 1px solid var(--pe-border, #e2e8f0);
    border-top: none;
    border-radius: 0 0 8px 8px;
}
[data-theme="dark"] .ql-toolbar.ql-snow {
    background: var(--pe-card, #1e293b);
    border-color: var(--pe-border, #334155);
}
[data-theme="dark"] .ql-container.ql-snow {
    border-color: var(--pe-border, #334155);
    background: var(--pe-card, #1e293b);
    color: var(--pe-text, #e2e8f0);
}

/* A SHORT field — a card blurb, a slide body — does not want the 220px of
   a page-length writeup. `data-rt="short"` on the container asks for less. */
.pe-rich-editor[data-rt="short"] .ql-editor { min-height: 110px; }
/* ── The "Public" cell on the Seasons / Events / Tournaments grids ──────────
   (public-pill.js, 2026-09-23). The .pe-badge shape; the ON state is a LINK
   to the public page, so it gets a hover the badge does not — colour only,
   nothing lifts. */
.pe-pubpill {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 3px 9px; border-radius: 999px;
    font-size: 11px; font-weight: 700; letter-spacing: .02em; line-height: 1.5;
    white-space: nowrap; text-decoration: none;
}
.pe-pubpill svg { flex-shrink: 0; }
.pe-pubpill.is-on  { background: #dcfce7; color: #166534; }
.pe-pubpill.is-on:hover { background: #bbf7d0; color: #14532d; text-decoration: none; }
.pe-pubpill.is-off { background: #f3f4f6; color: #6b7280; }
[data-theme="dark"] .pe-pubpill.is-on  { background: rgba(34, 197, 94, .18); color: #86efac; }
[data-theme="dark"] .pe-pubpill.is-on:hover { background: rgba(34, 197, 94, .28); color: #bbf7d0; }
[data-theme="dark"] .pe-pubpill.is-off { background: rgba(255, 255, 255, .08); color: #9ca3af; }

/* ══ Social sign-in buttons (docs/social-signin-spec.md §5.1, §7) ══════════
   Provider branding is NOT a design decision. Google's guidelines fix the
   wording ("Continue with Google"), the mark, the colours and the clear space
   around them, so the only things chosen here are height and radius — both
   inside what the guidelines allow.

   Deliberate deviation, recorded so it is not mistaken for an oversight:
   Google asks for Roboto. This app loads Outfit and DM Sans, and pulling a
   third family for one button costs a render-blocking font request on the
   page with the strictest first paint in the product. The mark, the wording
   and the palette — the parts that make it recognisably Google — are exact.

   Sits ABOVE the form: it is the faster path, and burying it under the form
   the user was trying to avoid defeats the point. */
.sso-group {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 20px;
}

.btn-social {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    width: 100%;
    min-height: 48px;
    padding: 0 16px;
    /* 4px, not a pill. Google permits either; this house does not do pills. */
    border-radius: 6px;
    font-family: inherit;
    font-size: 15px;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    text-decoration: none;
    transition: background-color .15s ease, border-color .15s ease;
}

.btn-social:disabled { opacity: .6; cursor: not-allowed; }

.btn-social .sso-mark {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

/* Google, light. Exact values from the brand guidelines. */
.btn-social-google {
    background: #ffffff;
    border: 1px solid #747775;
    color: #1f1f1f;
}

/* Hover changes colour only — nothing on this page lifts. */
@media (hover: hover) {
    .btn-social-google:hover {
        background: #f2f2f2;
        border-color: #5f6368;
        text-decoration: none;
    }
}

.btn-social-google:active { background: #e8e8e8; }

.btn-social:focus-visible {
    outline: 2px solid var(--pe-blue);
    outline-offset: 2px;
}

/* Google, dark. Also from the guidelines — NOT the light button dimmed. */
[data-theme="dark"] .btn-social-google {
    background: #131314;
    border-color: #8e918f;
    color: #e3e3e3;
}
@media (hover: hover) {
    [data-theme="dark"] .btn-social-google:hover {
        background: #1e1f20;
        border-color: #a8abaa;
    }
}
[data-theme="dark"] .btn-social-google:active { background: #282a2c; }

/* The "or" rule between the social buttons and the email form. */
.sso-divider {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 0 0 20px;
    color: var(--pe-gray);
    font-size: 13px;
    text-transform: lowercase;
}
.sso-divider::before,
.sso-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--pe-border);
}

/* Moved back above the mobile pass (2026-09-28). These groups were appended
   AFTER it, which quietly cost the pass the ordering its precedence depends
   on. Nothing in the pass names either selector and its only element-level
   rule targets input/select/textarea, so no computed style changes — probed
   at 1024px and 375px either side of the move to confirm it. */
/* "Use the default name: 26-Spring-Boys-Basketball" — offered under the season
   name field when one was typed by hand and a default exists to go back to
   (2026-09-20). A link, not a button-shaped thing: it changes a field, it does
   not submit. */
.seas-defname {
    display: inline-block; margin-top: 6px; padding: 4px 0;
    background: none; border: 0; font: inherit; font-size: 12.5px; font-weight: 600;
    color: var(--pe-blue); cursor: pointer; text-align: left;
}
.seas-defname:hover { text-decoration: underline; }

/* What "Same as division" resolves to, under the season Gender select (mig 275).
   Shared: BOTH season drawers show it — the Seasons page's and the season
   profile's — and a page-local copy is why `.form-row-3` silently did nothing
   on the second one. Empty = an explicit choice was made and needs no gloss. */
.seas-gender-hint { display: block; margin-top: 5px; font-size: 12px; color: var(--pe-text-muted, #94a3b8); }
.seas-gender-hint:empty { display: none; }


/* ── Tournament auto-schedule: the court checklist ──
   Lives here rather than in tournament-board.js because that module injects
   markup only; every other drawer it renders is styled from app.css too. */
.tb-auto-courts { display: flex; flex-direction: column; gap: 2px; max-height: 190px; overflow-y: auto; }
.tb-auto-courts .tb-auto-court {
    display: flex; align-items: center; gap: 9px; padding: 7px 5px; border-radius: 8px;
    cursor: pointer; font-size: 14px; color: var(--pe-text); min-height: 32px; font-weight: 500;
}
.tb-auto-courts .tb-auto-court:hover { background: rgba(56,189,248,0.08); }
.tb-auto-courts .tb-auto-court input { width: 16px; height: 16px; margin: 0; flex-shrink: 0; accent-color: var(--pe-blue, #38bdf8); }

/* ── Updated-terms prompt (legal-gate.js, ToS 14.2) ─────────────────────────
   Deliberately plain and deliberately not dismissible: there is nothing useful
   behind it for a blocked account. Lives here rather than in the script so it
   themes with the rest of the app. ── */
.lg-overlay {
    position: fixed; inset: 0; z-index: 9000;
    background: rgba(15, 18, 28, .62);
    display: flex; align-items: center; justify-content: center;
    padding: 20px;
}
.lg-card {
    background: var(--pe-card, #fff); color: var(--pe-text);
    border-radius: 14px; padding: 26px 28px;
    width: 100%; max-width: 480px;
    box-shadow: 0 18px 50px rgba(0, 0, 0, .3);
    max-height: calc(100vh - 40px); overflow-y: auto;
}
.lg-card h2 {
    font-family: 'Outfit', sans-serif; font-size: 20px;
    margin: 0 0 8px; color: var(--pe-text);
}
.lg-lead { font-size: 14.5px; line-height: 1.6; color: var(--pe-text-muted); margin: 0 0 14px; }
.lg-docs { margin: 0 0 16px; padding-left: 20px; font-size: 14.5px; }
.lg-docs li { margin: 0 0 5px; }
.lg-docs a { color: var(--pe-blue, #0b7fb4); }
.lg-consent {
    display: flex; gap: 10px; align-items: flex-start;
    font-size: 14px; line-height: 1.55; cursor: pointer;
    padding: 12px; border: 1px solid var(--pe-border); border-radius: 10px;
    margin: 0 0 14px;
}
/* 16px floor and a real hit area — the phone rules at the end of this file
   apply to the checkbox itself, and the row is the tap target. */
.lg-consent input { width: 20px; height: 20px; flex: 0 0 auto; margin-top: 1px; }
.lg-err {
    background: rgba(220, 38, 38, .1); color: #b91c1c;
    border-radius: 8px; padding: 10px 12px; font-size: 13.5px; margin: 0 0 12px;
}
.lg-actions { display: flex; justify-content: flex-end; }
.lg-btn {
    background: var(--pe-blue, #0b7fb4); color: #fff; border: 0;
    font: 600 15px 'DM Sans', sans-serif; cursor: pointer;
    padding: 12px 22px; border-radius: 9px; min-height: 44px;
    transition: background .14s, opacity .14s;
}
.lg-btn:hover:not(:disabled) { background: var(--pe-navy, #1a1a2e); }
.lg-btn:disabled { opacity: .5; cursor: not-allowed; }
.lg-foot { font-size: 12.5px; color: var(--pe-text-muted); margin: 14px 0 0; }
@media (max-width: 599px) {
    .lg-card { padding: 20px 18px; }
    .lg-actions { display: block; }
    .lg-btn { width: 100%; }
}
/* ══ Card action icon row (.pe-iconrow) ═══════════════════════════════════
   A row of small icon buttons at the foot of a card, each with a tooltip on
   a pointer and a permanent label on a phone. Shared because it is on two
   pages — the AI Images working session and the Image Library — and the ⓘ
   field tip is the standing lesson about what a second copy becomes.

   THE ROW GOES LAST IN THE CARD, and the bubble opens DOWNWARD. The first
   cut opened upward and landed squarely on the dark "Save to library"
   button underneath: a dark bubble on a dark button, unreadable (Robb,
   "this looks horrible"). Below the last row there is nothing to cover.
   That is a layout contract, not a preference — put this row above anything
   else and the collision comes back. */
.pe-iconrow {
    display: flex; justify-content: space-between; align-items: flex-start;
    gap: 6px; margin-top: 10px; position: relative;
}
.pe-ib-wrap { display: inline-flex; flex-direction: column; align-items: center; gap: 3px; min-width: 0; }
.pe-ib {
    position: relative; width: 32px; height: 32px; padding: 0; flex-shrink: 0;
    border: 1px solid var(--pe-border); border-radius: 8px;
    background: transparent; color: var(--pe-text-secondary, #64748b);
    display: inline-flex; align-items: center; justify-content: center;
    cursor: pointer; transition: background .12s, border-color .12s, color .12s;
}
.pe-ib svg { width: 16px; height: 16px; }
/* NOTHING lifts on hover — background, border and colour only. */
.pe-ib:hover { background: var(--pe-hover, #f1f5f9); border-color: var(--pe-blue, #38bdf8); color: var(--pe-text); }
.pe-ib.is-danger:hover { background: #fee2e2; border-color: #fca5a5; color: #b91c1c; }
[data-theme="dark"] .pe-ib:hover { background: rgba(255,255,255,.06); }
.pe-ib:focus-visible { outline: none; border-color: var(--pe-blue, #38bdf8); box-shadow: 0 0 0 3px rgba(56,189,248,.2); }

/* display:none, NOT opacity:0. An absolutely-positioned descendant counts
   toward its scroll container's overflow even while invisible, and these
   cards sit inside .modal-drawer .modal-body, where overflow-y:auto makes
   overflow-x auto too — exactly what grew the permanent phantom scrollbar
   off the ⓘ field tips. Nothing to fade, nothing to overflow. */
.pe-ib-tip {
    display: none; position: absolute; top: calc(100% + 7px); left: 50%;
    transform: translateX(-50%); z-index: 20;
    background: #1b2235; color: #fff; font-size: 11.5px; line-height: 1;
    font-weight: 600; padding: 6px 8px; border-radius: 6px; white-space: nowrap;
    pointer-events: none;
}
[data-theme="dark"] .pe-ib-tip { background: #0b0f1a; }
.pe-ib:hover .pe-ib-tip, .pe-ib:focus-visible .pe-ib-tip { display: block; }
.pe-ib-tip::after {
    content: ""; position: absolute; bottom: 100%; left: 50%;
    transform: translateX(-50%); border: 4px solid transparent; border-bottom-color: #1b2235;
}
[data-theme="dark"] .pe-ib-tip::after { border-bottom-color: #0b0f1a; }
/* The end bubbles are pinned inside the row rather than centred, tails moved
   to stay on their icon: centred, a ~110px bubble on a 32px button at the
   card edge reaches ~28px past it. */
.pe-iconrow > .pe-ib-wrap:first-child .pe-ib-tip { left: 0; transform: none; }
.pe-iconrow > .pe-ib-wrap:first-child .pe-ib-tip::after { left: 16px; transform: none; }
.pe-iconrow > .pe-ib-wrap:last-child .pe-ib-tip { left: auto; right: 0; transform: none; }
.pe-iconrow > .pe-ib-wrap:last-child .pe-ib-tip::after { left: auto; right: 12px; transform: none; }

.pe-ib-lbl { display: none; }
@media (max-width: 599px) {
    /* No hover on a phone, so the bubble can never be reached: it goes, and a
       permanent label sits under each icon instead. */
    .pe-ib { width: 44px; height: 44px; }       /* 44px tap target */
    .pe-ib .pe-ib-tip { display: none !important; }
    .pe-ib-lbl { display: block; font-size: 12px; color: var(--pe-text-muted); line-height: 1.15; text-align: center; }
}

/* ══ Mobile & tablet pass (2026-09-13 audit) ══════════════════════════════
   Everything here was MEASURED in the harness at 375×812 and 768×1024 with a
   fresh load per page (bin/test-mobile-css.php guards the rules; the numbers
   are in WORKLOG). Appended last on purpose: order is what makes it win over
   the page-level rules above without a specificity war. */
@media (max-width: 599px) {
    /* 1. The parent dashboard laid out 758px wide on a phone. `.stat-sub` is
       nowrap, and a nowrap line is the grid track's min-content, so `1fr 1fr`
       grew to fit it (cards 427px wide from x=331) and the fixed topbar and
       bottom tabs stretched off the right edge. Zero-minimum tracks, a zero
       minimum on the card, and the sub-line may wrap on a phone. */
    .stats-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
    .stats-grid > .stat-card { min-width: 0; }
    .stat-card .stat-sub { white-space: normal; }

    /* 3. iOS zooms the page on focus of any control under 16px and leaves it
       zoomed. Page-size selects were 13px, filters 14px, the team builder's
       13.5px. !important because the page-level rules are id-specific. */
    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]),
    select, textarea { font-size: 16px !important; }

    /* 4. Text links and icon buttons measured 15–22px tall (view-all links,
       "needs a coach", the setup-item dismiss, the seasons stat links at 5px
       wide). A 44px hit area from a pseudo-element, so nothing moves. */
    .text-link, .st-link, .team-needs-coach, .tn-linkish, .ss-statlink, .setup-item-x,
    .link-btn, .foot-email, .pe-venue-link, a.fc-venue { position: relative; }
    .text-link::after, .st-link::after, .team-needs-coach::after, .tn-linkish::after,
    .ss-statlink::after, .setup-item-x::after, .link-btn::after,
    .pe-venue-link::after, a.fc-venue::after {
        content: ""; position: absolute; inset: -11px -12px;
    }

    /* 5. Meta labels and chips at 9.5–10.5px: 12px is the floor on a phone. */
    .badge, .badge-btn, .row-badge, .trash-badge, .field-tip,
    .tw-dow, .st-cal-mo, .tn-pick-m, .tn-badge, .tn-match-label, .tn-sched-when,
    .tn-sched-btn, .league-sheet-group-label,
    .sidebar-nav-group-label { font-size: 12px; }

    /* 7. Needs Attention rows were built for the desktop card: icon, text, CTA
       and chevron on ONE flex line. Inside a .content-card (24px padding each
       side) on a 375px phone that leaves the text column ~120px, and the sub
       wrapped to five lines — worse than the standalone cards these rows
       replaced (dcf2f63). Stack instead: the sub gets the full width, the CTA
       drops under it, and the chevron goes — the CTA text already says where
       the row leads. Desktop is untouched; this is the hybrid Robb asked for
       (2026-09-14).
       NOTE the row is no longer always an <a>: on the league dashboard it is a
       DIV carrying an Ask Phil button (a button cannot nest inside a link), so
       .na-link is the tap target there — and with the chevron hidden here it is
       the only one. It covers the whole row anyway, via the stretched-link
       overlay on .na-link (see the base rule), which is why nothing in this
       block needs to prop up its height (2026-09-16).
       min-width:0 on the body because a grid track will not shrink below its
       content otherwise — the same trap as .stats-grid in 1 above. */
    .na-item {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        grid-template-areas: "ic body" ". cta";
        gap: 2px 12px;
        align-items: start;
        padding: 14px 2px;
    }
    .na-item > .na-ic { grid-area: ic; }
    .na-item > .na-body { grid-area: body; min-width: 0; }
    .na-item > .na-cta, .na-item > .na-cta-wait { grid-area: cta; justify-self: start; margin-top: 4px; }
    .na-item > .na-arrow { display: none; }
    .na-sub { white-space: normal; }
}
/* 4. Checkboxes in tables and cards measured 13×13 (tablet table view) and
   18×18 (phone cards). Bigger on any touch width, and the whole check cell
   toggles the row (data-table.js) so the tap zone is the 44px cell. */
@media (max-width: 1023px) {
    .dt-check-row, .dt-check-all,
    .team-card input[type="checkbox"], .tn-card input[type="checkbox"] { width: 22px; height: 22px; }
    .dt-check-col { width: 48px; padding: 11px 8px; cursor: pointer; }
}
/* 6. The tablet band was the phone layout with wider cards: two-up wherever
   an auto-fit grid dropped to one column between 600 and 700px. */
@media (min-width: 600px) and (max-width: 1023px) {
    .stats-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

