:root {
    --brand-primary: #0ea5b7;
    --brand-primary-dark: #0a6f7d;
    --brand-primary-light: #12b6c8;
    --brand-accent: #ff5e7e;
    --brand-accent-warm: #ff7e5f;
    --brand-danger: #e3342f;

    /* The nav's "Me today" pill. Deliberately outside the coral/teal brand palette: the pill sits next to the
       red LIVE badge and must not be mistaken for another tournament state, so it takes a hue used nowhere
       else in the app. */
    --myday-from: #7c3aed;
    --myday-to: #4f46e5;

    /* Shared brand button gradients — referenced by .btn-primary/.btn-secondary and the SplitButton parts
       so the fills stay in lock-step. */
    --gradient-primary: linear-gradient(135deg, var(--brand-primary), var(--brand-primary-dark));
    --gradient-primary-hover: linear-gradient(135deg, var(--brand-primary-light), var(--brand-primary-dark));
    --gradient-secondary: linear-gradient(135deg, #ffb37a 0%, var(--brand-accent-warm) 60%, color-mix(in oklab, var(--brand-accent-warm) 75%, black) 100%);
    --gradient-secondary-hover: linear-gradient(135deg, #ffc096 0%, #ff9270 55%, var(--brand-accent-warm) 100%);

    /* Brighter success palette shared by Bootstrap alert-success and .banner-success */
    --bs-success-bg-subtle: #f0fdf4;
    --bs-success-border-subtle: #bbf7d0;
    --bs-success-text-emphasis: #15803d;

    /* Neutral slate for the Neutral / NeutralOutline button + split-button style — a plain greyscale
       alternative to the brand fills (e.g. the schedule category filter pills). */
    --btn-neutral: #95a1ab;
    --btn-neutral-dark: #7d8b97;
    --gradient-neutral: linear-gradient(135deg, var(--btn-neutral), var(--btn-neutral-dark));

    /* Neutral FILLED split-button (the selected schedule category pill): a light, flat tonal grey
       with dark slate ink — not the dark --gradient-neutral fill. The unselected pill stays the white
       NeutralOutline. Shared so SplitButton (fill/border) and SplitButtonPart (ink/divider) match. */
    --split-neutral-fill: #eceff2;
    --split-neutral-border: #dadfe4;
    --split-neutral-ink: #4a5760;
    --split-neutral-divider: #d6dce2;

    /* Podium medal palette — gold (1st-place championship final) and bronze (3rd-place play-off)
       accents used by the live timeline's trophy treatment. */
    --gold-light: #fde68a;
    --gold: #e3b341;
    --gold-deep: #9a6b00;
    --bronze-light: #e6c39a;
    --bronze: #c07b3a;
    --bronze-deep: #7c4a1e;

    /* Frosted dark-grey surface: a translucent slate slab that floats over page content and lets it show
       through, blurred. Shared so every floating overlay reads as the same material — the list pages'
       search footer (.page-footer) and the notification stack (NotificationCenter) both build on it. */
    --surface-frost-dark: rgba(55, 65, 81, 0.85);
    --surface-frost-dark-border: rgba(255, 255, 255, 0.08);
    --surface-frost-dark-ink: #f3f4f6;
    --surface-frost-blur: blur(8px);

    /* Constant height of a viewport-locked page's bottom StatusBar (it only grows when the user expands
       it). Declared here rather than in the component so overlays that must clear the bar — see
       --notif-bottom below — can key off the same number instead of repeating it. */
    --status-bar-height: 58px;

    /* ── Data-visualisation palette (Bevoman.Web.Client/Charts) ─────────────────────────────
       Categorical slots are assigned in this fixed order and never cycled — a series keeps its hue when
       another is filtered out. The set was validated (not eyeballed) against the white card surface for
       colour-vision separation and 3:1 contrast: worst adjacent pair scores ΔE 14.7 simulated / 28.3
       normal-vision, comfortably over the 8 / 15 floors.

       Slot 1 is NOT --brand-primary (#0ea5b7): the brand teal only reaches 2.96:1 on white, so the chart
       series takes the same hue one step darker. Use the brand token for chrome, this one for marks. */
    --chart-series-1: #0e93a3;
    --chart-series-2: #eb6834;
    --chart-series-3: #4a3aa7;
    --chart-series-4: #c98500;

    /* Ordered (not categorical) scale: one hue, light→dark, for genuinely ranked bands such as lifecycle
       stages or recency buckets. Never use it to shade nominal categories by size. */
    --chart-ordinal-1: #6cbfcd;
    --chart-ordinal-2: #2ba5b8;
    --chart-ordinal-3: #0b7a88;
    --chart-ordinal-4: #08535c;

    /* Reserved for state, never for a series: a status colour must always mean good/warning/bad. */
    --chart-good: #0ca30c;
    --chart-warning: #fab219;
    --chart-critical: #d03b3b;

    /* Chart chrome. The surface is the card a chart sits on — mark rings and stacked-segment gaps are
       drawn in it, so it must match the real card background. */
    --chart-surface: #ffffff;
    --chart-ink-muted: #898781;
    --chart-axis: #c3c2b7;
}

/* The notification stack sits 18px off the bottom edge, or 10px above the status bar on the pages that
   mount one, so it never covers the live counters. The extra 1px is the bar's top border, which sits
   outside its height token. Read by NotificationCenter's scoped CSS. */
body:has(.status-bar) {
    --notif-bottom: calc(var(--status-bar-height) + 1px + 10px);
}

html, body {
    font-family: 'Manrope', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    background: #fafbfc;
}

a, .btn-link {
    color: #006bb7;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50350;
}

.validation-message {
    color: #e50350;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

.muted {
    color: #5b7280;
    margin: 0;
}

/* ── Heading scale ──────────────────────────────────────────────────────
   The single source of truth for h1–h6; it replaces Bootstrap's 40/32/28/24/20/16px
   defaults, which ran far larger than the rest of the theme.

   h1–h3 are titles, h4–h6 are uppercase tracked labels from the same family as
   .eyebrow — which is how the app actually uses small headings (roster headers,
   group labels, panel captions). Never re-declare a size for these in a scoped
   .razor.css; use the level that fits, or override only the one property a
   deliberately compact surface needs (see .side-panel-title). */
h1, h2, h3, h4, h5, h6 {
    color: #0f2a36;
    font-weight: 700;
    margin: 0 0 0.5em;
}

h1 {
    font-size: 30px;
    line-height: 1.12;
    letter-spacing: -0.028em;
}

h2 {
    font-size: 22px;
    line-height: 1.22;
    letter-spacing: -0.018em;
    margin-top: 1.45em;
}

h3 {
    font-size: 18px;
    line-height: 1.3;
    margin-top: 1.3em;
}

h4 {
    font-size: 14px;
    font-weight: 800;
    line-height: 1.35;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--brand-primary-dark);
    margin-top: 1.25em;
}

h5 {
    font-size: 12px;
    font-weight: 800;
    line-height: 1.4;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: #5b7280;
}

h6 {
    font-size: 11px;
    font-weight: 800;
    line-height: 1.4;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: #5b7280;
}

/* ── Hero typography & CTAs (used inside PageHeader) ───────────────────── */
.eyebrow {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    opacity: 0.85;
}

/* Size/weight/tracking come from the global h1 rule; the hero only owns its
   rhythm and inherits the header's white ink. */
.hero-title {
    color: inherit;
    margin: 10px 0 14px;
}

.hero-sub {
    font-size: 17px;
    opacity: 0.92;
    max-width: 560px;
    margin: 0 0 24px;
}

.hero-cta {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
}

.page-actions {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    margin: 0 0 20px;
}

/* ── Charts (shared chrome for Bevoman.Web.Client/Charts) ────────────────
   Defined once here rather than per chart component: every chart draws the same recessive axis, the same
   empty state and the same hover readout, and a scoped copy would silently override this one. */
.chart-axis {
    stroke: var(--chart-axis);
    stroke-width: 1;
}

.chart-empty {
    font-size: 13px;
    color: #5b7280;
    margin: 0;
    padding: 18px 0;
}

/* The hovered value, riding beside the mark it belongs to. Shared by every chart that has one, so the
   chip reads identically across them; each chart supplies only its own position and series colour inline.
   Absolutely positioned by its host, so showing and hiding it never reflows the chart. It sets its own
   line-height because a chart frame zeroes that out to keep its height equal to the plot's.

   NOTE the white ink sits on the series colour by design. Contrast against the lighter slots is 3.07–3.67
   (slot 4 amber .. slot 1 teal), under the 4.5 AA wants for text this size. It is a transient chip whose
   value is also on the axis and in the mark's accessible name, so nothing is gated behind reading it. */
.chart-tip {
    position: absolute;
    z-index: 3;
    padding: 4px 9px;
    border-radius: 7px;
    font-size: 12px;
    font-weight: 700;
    line-height: 1.35;
    color: #fff;
    white-space: nowrap;
    pointer-events: none;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
}

/* The value the pointer is over. Held at a fixed height so hovering never reflows the card around it. */
.chart-readout {
    display: block;
    min-height: 18px;
    margin-top: 6px;
    font-size: 12px;
    font-weight: 600;
    color: #5b7280;
}

/* ── Stat tiles ──────────────────────────────────────────────────────────
   A row of headline figures. Each tile is one subject: the primary number, an optional secondary detail
   drawn from the same subject, and an optional signed delta. */
.stat-strip {
    display: grid;
    /* Column count is the strip's own <StatStrip Columns="N"> knob; the steps below take over on narrow
       viewports whatever N is, so a strip never has to opt out of the responsive behaviour. */
    grid-template-columns: repeat(var(--stat-strip-columns, 4), minmax(0, 1fr));
    gap: 0;
    background: #fff;
    border: 1px solid rgba(11, 11, 11, 0.1);
    border-radius: 14px;
    overflow: hidden;
    box-shadow: 0 2px 16px rgba(0, 0, 0, 0.045);
}

@media (max-width: 900px) {
    .stat-strip {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 520px) {
    .stat-strip {
        grid-template-columns: minmax(0, 1fr);
    }
}

.stat-tile {
    padding: 15px 18px 14px;
    border-right: 1px solid #f0f1f3;
    min-width: 0;
}

.stat-tile:last-child {
    border-right: 0;
}

.stat-label {
    font-size: 12px;
    font-weight: 700;
    color: #52514e;
}

/* Proportional figures on purpose: tabular-nums pads every digit to the width of a 0, which reads loose
   at this size. Reserve tabular figures for columns that must align vertically. */
.stat-value {
    font-size: 29px;
    font-weight: 800;
    letter-spacing: -0.03em;
    line-height: 1;
    margin: 5px 0 3px;
}

.stat-detail {
    font-size: 12px;
    font-weight: 700;
    color: #898781;
}

.stat-delta {
    font-size: 12px;
    font-weight: 700;
    color: #898781;
}

.stat-delta.is-up {
    color: #006300;
}

.stat-delta.is-down {
    color: var(--chart-critical);
}

.stat-spark {
    margin-top: 9px;
}

/* Live indicator on a tile's detail line. Colour and the adjacent count already carry the state, so the
   pulse is decoration only — hence the reduced-motion guard below removes it outright. */
.dot-live {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--chart-critical);
    margin-right: 5px;
    vertical-align: middle;
    animation: dot-live-pulse 1.6s ease-in-out infinite;
}

@keyframes dot-live-pulse {
    0%, 100% {
        opacity: 1;
    }

    50% {
        opacity: 0.35;
    }
}

@media (prefers-reduced-motion: reduce) {
    .dot-live {
        animation: none;
    }
}

/* ── Page content column ─────────────────────────────────────────────────
   The centred reading column a dashboard-style page holds its panels in, on top of the shell's own gutters.
   Defined once here so every page that opts in lands on the same measure — a scoped copy would win over this
   rule (Blazor's b-xxx attribute selector outranks a plain class) and that page would silently drift.
   Vertical rhythm stays with the page: this owns the measure and the gutters only. Full-bleed content (a wide
   data grid) is placed OUTSIDE the column so it keeps the shell's whole width. */
.page-column {
    max-width: 1320px;
    margin: 0 auto;
}

@media (max-width: 760px) {
    .page-column {
        padding: 0 18px;
    }
}

/* ── Dashboard panels ────────────────────────────────────────────────────
   Shared by every admin-dashboard tab component. Defined once here rather than per tab: a scoped copy in
   any one tab would win over this rule (Blazor's b-xxx attribute selector outranks a plain class) and that
   tab would silently drift from the others. */
.tab-lede {
    font-size: 14px;
    line-height: 1.55;
    color: #52514e;
    margin: 0 0 18px;
    max-width: 680px;
}

.dash-panel {
    background: #fff;
    border: 1px solid rgba(11, 11, 11, 0.1);
    border-radius: 14px;
    padding: 20px 22px;
    box-shadow: 0 2px 16px rgba(0, 0, 0, 0.045);
    min-width: 0;
}

.dash-grid-2 {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 16px;
}

@media (max-width: 860px) {
    .dash-grid-2 {
        grid-template-columns: minmax(0, 1fr);
    }
}

.dash-subtitle {
    font-size: 11.5px;
    font-weight: 800;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    color: #898781;
    margin: 0 0 13px;
}

.dash-rows {
    display: grid;
}

.dash-row {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 10px 0;
    border-bottom: 1px solid #f0f1f3;
    font-size: 13px;
}

.dash-row:last-child {
    border-bottom: 0;
}

.dash-row-key {
    color: #52514e;
    font-weight: 600;
}

/* Tabular figures here on purpose — these values stack into a column that must align vertically. */
.dash-row-value {
    margin-left: auto;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

.dash-row-value.is-warning {
    color: #a8760a;
}

.dash-row-value.is-critical {
    color: var(--chart-critical);
}

/* Placeholder body for a tab that is planned but not built yet. */
.dash-placeholder {
    text-align: center;
    padding: 46px 20px;
    color: #5b7280;
}

.dash-placeholder i {
    font-size: 30px;
    color: #c3c2b7;
    display: block;
    margin-bottom: 12px;
}

.dash-placeholder h3 {
    margin: 0 0 6px;
    font-size: 15px;
    font-weight: 800;
    letter-spacing: -0.01em;
    text-transform: none;
    color: #0b0b0b;
}

.dash-placeholder p {
    margin: 0;
    font-size: 13px;
}

.btn-fill,
.btn-out,
.btn-ghost {
    display: inline-block;
    padding: 12px 22px;
    border-radius: 999px;
    font-weight: 700;
    font-size: 14px;
    cursor: pointer;
    border: none;
    text-decoration: none;
    line-height: 1;
    transition: background-color 0.12s, color 0.12s, border-color 0.12s, transform 0.08s;
}

a.btn-fill,
a.btn-out,
a.btn-ghost {
    text-align: center;
}

/* .btn-fill / .btn-out retired — superseded by the shared <AppButton> (.app-btn) primitive. */

.btn-ghost {
    background: transparent;
    color: var(--brand-primary-dark);
    border: 1px solid transparent;
    padding: 6px 14px;
    font-size: 13px;
}

.btn-ghost:hover:not(:disabled) {
    background: rgba(10, 111, 125, 0.08);
    border-color: rgba(10, 111, 125, 0.5);
    color: #08515c;
}

.btn-ghost:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ── Bootstrap btn-primary / btn-secondary brand overrides ─────────────── */
.btn.btn-primary,
.btn.btn-secondary {
    --bs-btn-border-width: 1px;
    border: 1px solid transparent;
    border-radius: 999px;
    padding: 11px 22px;
    font-weight: 700;
    font-size: 14px;
    line-height: 1;
    transition: transform 0.08s, box-shadow 0.12s, background 0.12s, color 0.12s, border-color 0.12s;
}

.btn.btn-primary {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--brand-primary);
    --bs-btn-border-color: transparent;
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--brand-primary-dark);
    --bs-btn-hover-border-color: var(--brand-primary-dark);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--brand-primary-dark);
    --bs-btn-active-border-color: transparent;
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--brand-primary);
    --bs-btn-disabled-border-color: transparent;
    background: var(--gradient-primary);
    color: #fff;
    box-shadow: 0 8px 18px rgba(10, 111, 125, 0.3);
}

.btn.btn-primary:hover:not(:disabled),
.btn.btn-primary:focus-visible:not(:disabled) {
    background: var(--gradient-primary-hover);
    color: #fff;
    border-color: var(--brand-primary-dark);
    box-shadow: 0 10px 24px rgba(10, 111, 125, 0.4);
}

.btn.btn-primary:disabled,
.btn.btn-primary.disabled {
    background: var(--gradient-primary);
    opacity: 0.6;
    box-shadow: 0 8px 18px rgba(10, 111, 125, 0.2);
}

.btn.btn-secondary {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--brand-accent-warm);
    --bs-btn-border-color: transparent;
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--brand-accent-warm);
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--brand-accent-warm);
    --bs-btn-active-border-color: transparent;
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--brand-accent-warm);
    --bs-btn-disabled-border-color: transparent;
    background: var(--gradient-secondary);
    color: #fff;
    border: 1px solid transparent;
    box-shadow: 0 8px 18px rgba(255, 126, 95, 0.3);
}

.btn.btn-secondary:hover:not(:disabled),
.btn.btn-secondary:focus-visible:not(:disabled) {
    background: var(--gradient-secondary-hover);
    color: #fff;
    border-color: transparent;
    box-shadow: 0 10px 24px rgba(255, 126, 95, 0.4);
}

.btn.btn-secondary:disabled,
.btn.btn-secondary.disabled {
    opacity: 0.6;
}

.btn.btn-outline-secondary {
    --bs-btn-border-width: 1px;
    border: 1px solid var(--brand-accent-warm);
    border-radius: 999px;
    padding: 11px 22px;
    font-weight: 700;
    font-size: 14px;
    line-height: 1;
    background: transparent;
    color: var(--brand-accent-warm);
    box-shadow: none;
    transition: transform 0.08s, box-shadow 0.12s, background 0.12s, color 0.12s, border-color 0.12s;
}

.btn.btn-outline-secondary:hover:not(:disabled),
.btn.btn-outline-secondary:focus-visible:not(:disabled) {
    background: var(--brand-accent-warm);
    color: #fff;
    border-color: var(--brand-accent-warm);
    box-shadow: 0 8px 18px rgba(255, 126, 95, 0.3);
}

.btn.btn-outline-secondary:disabled,
.btn.btn-outline-secondary.disabled {
    opacity: 0.6;
}

/* .btn.btn-ghost-neutral retired — superseded by AppButton's .app-btn--ghost-neutral variant. */

/* Space between a button icon and its adjacent text.
   Flex + gap spaces icon-from-text without adding space to icon-only buttons. */
.btn:has(> .bi) {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5em;
}

/* ── AppButton — the single button primitive ──────────────────────────────────
   One coherent pill on every surface (forms, dialogs, toolbars, lists). The look
   is chosen by ButtonStyleKind on the <AppButton> component and lives here only —
   never spelled as CSS classes at the call site. Reuses the shared brand gradient
   tokens so fills stay in lock-step with the SplitButton parts. */
.app-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5em;
    padding: 11px 22px;
    border-radius: 999px;
    border: 1px solid transparent;
    font-weight: 700;
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    text-decoration: none;
    text-align: center;
    transition: transform 0.08s, box-shadow 0.12s, background 0.12s, color 0.12s, border-color 0.12s;
}

.app-btn:hover:not(:disabled) {
    transform: translateY(-1px);
}

.app-btn:disabled,
.app-btn.disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.app-btn:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px rgba(10, 111, 125, 0.35);
}

/* Primary — teal gradient solid (main CTA / submit / confirm) */
.app-btn--primary {
    background: var(--gradient-primary);
    color: #fff;
    box-shadow: 0 8px 18px rgba(10, 111, 125, 0.3);
}

.app-btn--primary:hover:not(:disabled) {
    background: var(--gradient-primary-hover);
    box-shadow: 0 10px 24px rgba(10, 111, 125, 0.4);
}

/* Secondary — coral gradient solid ("add new" / affirmative-not-primary) */
.app-btn--secondary {
    background: var(--gradient-secondary);
    color: #fff;
    box-shadow: 0 8px 18px rgba(255, 126, 95, 0.3);
}

.app-btn--secondary:hover:not(:disabled) {
    background: var(--gradient-secondary-hover);
    box-shadow: 0 10px 24px rgba(255, 126, 95, 0.4);
}

/* Primary outline — teal border on transparent */
.app-btn--primary-outline {
    background: transparent;
    color: var(--brand-primary-dark);
    border-color: var(--brand-primary);
}

.app-btn--primary-outline:hover:not(:disabled) {
    background: var(--brand-primary);
    color: #fff;
    border-color: var(--brand-primary);
}

/* Secondary outline — coral border on transparent */
.app-btn--secondary-outline {
    background: transparent;
    color: var(--brand-accent-warm);
    border-color: var(--brand-accent-warm);
}

.app-btn--secondary-outline:hover:not(:disabled) {
    background: var(--brand-accent-warm);
    color: #fff;
    border-color: var(--brand-accent-warm);
}

/* Neutral — slate filled */
.app-btn--neutral {
    background: var(--gradient-neutral);
    color: #fff;
    box-shadow: 0 8px 18px rgba(125, 139, 151, 0.3);
}

.app-btn--neutral:hover:not(:disabled) {
    filter: brightness(1.05);
}

/* Neutral outline — calm grey; the unified Cancel / dismiss treatment */
.app-btn--neutral-outline {
    background: #fff;
    color: #5b7280;
    border-color: #d6dce3;
}

.app-btn--neutral-outline:hover:not(:disabled) {
    background: #f5f7fa;
    color: #0f2a36;
    border-color: #c4ccd6;
}

/* Danger — solid red; destructive confirmations */
.app-btn--danger {
    background: #dc3545;
    color: #fff;
    box-shadow: 0 8px 18px rgba(220, 53, 69, 0.3);
}

.app-btn--danger:hover:not(:disabled) {
    background: #b8222e;
}

.app-btn--danger:focus-visible {
    box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.35);
}

/* Ghost-neutral — transparent, borderless grey; a quiet tertiary action (e.g. a toolbar "back"). */
.app-btn--ghost-neutral {
    background: transparent;
    color: #5a6570;
    border-color: transparent;
    box-shadow: none;
}

.app-btn--ghost-neutral:hover:not(:disabled) {
    background: rgba(90, 101, 112, 0.1);
    color: #39424a;
    border-color: transparent;
}

/* Icon-only — an icon with no label renders as a compact circular teal-ghost affordance
   (close / remove / add). The colour variant is dropped for these; tone comes from context
   (e.g. .tournament-child turns the hover red to read as "remove"). */
.app-btn--icon {
    /* Fixed circular size that never grows or shrinks: as a flex item the default
       flex-shrink would let a crowded row squeeze it below 32px, so pin it with
       flex:none + matching min/max sizing so the glyph stays perfectly round. */
    flex: none;
    width: 32px;
    height: 32px;
    min-width: 32px;
    min-height: 32px;
    padding: 0;
    border-radius: 50%;
    background: rgba(10, 111, 125, 0.08);
    color: var(--brand-primary-dark);
    box-shadow: none;
    font-size: 16px;
}

.app-btn--icon:hover:not(:disabled) {
    background: rgba(10, 111, 125, 0.18);
    border-color: rgba(10, 111, 125, 0.5);
    box-shadow: none;
    transform: none;
}

.app-btn--icon:active:not(:disabled) {
    transform: scale(0.96);
}

/* ── Dropdown action menu ─────────────────────────────────────────────── */
/* A small popover of actions anchored to a trigger button (e.g. the live board's settings gear).
   Wrap the trigger + panel in .app-menu-anchor; render .app-menu-backdrop alongside the open panel
   so any outside click dismisses it. */
.app-menu-anchor {
    position: relative;
}

/* Full-viewport click-away layer under the open panel. */
.app-menu-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1050;
    background: transparent;
    border: 0;
    padding: 0;
    margin: 0;
}

.app-menu {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 1051;
    min-width: 230px;
    padding: 6px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    background: #fff;
    border: 1px solid var(--border-color, #e2e6ea);
    border-radius: 12px;
    box-shadow: 0 12px 32px rgba(15, 42, 54, 0.18);
}

.app-menu-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 9px 12px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: #0f2a36;
    font-size: 14px;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}

.app-menu-item:hover:not(:disabled) {
    background: rgba(10, 111, 125, 0.08);
}

.app-menu-item .bi {
    font-size: 15px;
    color: var(--brand-primary-dark);
}

/* Destructive entries read red across glyph, label and hover tint. */
.app-menu-item--danger,
.app-menu-item--danger .bi {
    color: #dc2626;
}

.app-menu-item--danger:hover:not(:disabled) {
    background: rgba(220, 38, 38, 0.1);
}

@media (max-width: 760px) {
    /* Collapses non-essential inline elements (e.g. button label text next to
       an icon) on narrow screens so toolbars stay on one line. */
    .collapse-narrow {
        display: none;
    }
}

/* ── Form panel & layout primitives ───────────────────────────────────── */
.form-panel {
    max-width: 1024px;
    margin-left: auto;
    margin-right: auto;
}

.form-section {
    margin-top: 24px;
    padding-top: 20px;
    border-top: 1px solid rgba(255, 255, 255, 0.5);
}

/* ── Grouped form cards (Identity, Visibility, Schedule, …) ───────────── */
.form-group-card {
    background: #fff;
    border: 1px solid rgba(10, 111, 125, 0.12);
    border-radius: 16px;
    padding: 16px 20px 14px;
    box-shadow: 0 4px 22px rgba(10, 111, 125, 0.05);
    margin-bottom: 14px;
}

.form-group-card.tint-warm {
    background: linear-gradient(180deg, rgba(255, 126, 95, 0.06), #fff 70%);
    border-color: rgba(255, 126, 95, 0.28);
}

.form-group-card.tint-sea {
    background: linear-gradient(180deg, rgba(14, 165, 183, 0.06), #fff 70%);
    border-color: rgba(14, 165, 183, 0.28);
}

.form-group-card.tint-accent {
    background: linear-gradient(180deg, rgba(255, 94, 126, 0.06), #fff 70%);
    border-color: rgba(255, 94, 126, 0.28);
}

.form-group-head {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin-bottom: 12px;
}

.form-group-icon {
    flex: none;
    width: 38px;
    height: 38px;
    border-radius: 11px;
    background: var(--gradient-primary);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 18px;
    line-height: 1;
}

.form-group-icon > .bi {
    font-size: 18px;
    line-height: 1;
}

.form-group-card.tint-warm .form-group-icon {
    background: linear-gradient(135deg, var(--brand-accent-warm), #e0573a);
}

.form-group-card.tint-accent .form-group-icon {
    background: linear-gradient(135deg, var(--brand-accent), #d63862);
}

.form-group-meta {
    flex: 1;
    min-width: 0;
}

.form-group-title {
    margin: 0;
}

.form-group-desc {
    margin: 4px 0 0;
    font-size: 13px;
    color: #5b7280;
    line-height: 1.45;
}

.form-group-hint {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 14px;
    padding: 10px 14px;
    background: rgba(14, 165, 183, 0.08);
    color: var(--brand-primary-dark);
    border-radius: 10px;
    font-size: 12.5px;
}

.form-group-hint .dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #0a8a4a;
    flex: none;
}

.form-group-hint.warn {
    background: rgba(255, 126, 95, 0.12);
    color: #b1452a;
}

.form-group-hint.warn .dot {
    background: var(--brand-accent-warm);
}

@media (max-width: 720px) {
    .form-group-card {
        padding: 14px 14px 12px;
    }
}

/* Organiser chips (additional organisers list) */
.organiser-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 14px 0 4px;
}

.organiser-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 6px 6px 12px;
    background: #fff;
    border: 1px solid rgba(10, 111, 125, 0.18);
    border-radius: 999px;
    font-size: 14px;
    font-weight: 500;
    color: #0f2a36;
    box-shadow: 0 1px 2px rgba(10, 111, 125, 0.06);
}

.organiser-chip-name {
    line-height: 1;
}

.organiser-chip-remove {
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: rgba(15, 42, 54, 0.06);
    color: #5b7280;
    font-size: 12px;
    line-height: 1;
    cursor: pointer;
    transition: background 0.12s, color 0.12s, transform 0.08s;
}

.organiser-chip-remove:hover {
    background: rgba(220, 38, 38, 0.14);
    color: #dc2626;
}

.organiser-chip-remove:active {
    transform: scale(0.92);
}

.organiser-chips-empty {
    margin: 12px 0 4px;
    font-size: 13px;
    color: #5b7280;
    font-style: italic;
}

.organiser-search-name {
    font-weight: 500;
    color: #0f2a36;
}

.organiser-search-email {
    margin-left: 8px;
    font-size: 12px;
    color: #5b7280;
}

.form-section-header {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 12px;
}

/* The circular icon-button base now lives in the shared .app-btn--icon rule (see AppButton).
   These context rules push the close control to the row edge and tint its hover red inside a
   removable card. */
.tournament-child .app-btn--icon {
    margin-left: auto;
}

.tournament-child .app-btn--icon:hover:not(:disabled) {
    background: rgba(220, 38, 38, 0.12);
    color: #dc2626;
    border-color: rgba(220, 38, 38, 0.5);
}

.form-section-header label {
    font-size: 11px;
    font-weight: 700;
    color: var(--brand-primary-dark);
    text-transform: uppercase;
    letter-spacing: 0.12em;
}

.tournament-child {
    background: rgba(255, 255, 255, 0.45);
    border-radius: 16px;
    padding: 18px 18px 6px;
    margin-bottom: 14px;
    box-shadow: inset 0 0 0 1px rgba(10, 111, 125, 0.08);
}

.tournament-child:last-child {
    margin-bottom: 0;
}

.category-courts {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 8px;
    margin-bottom: 12px;
}

.category-courts-label {
    font-size: 11px;
    font-weight: 700;
    color: var(--brand-primary-dark);
    text-transform: uppercase;
    letter-spacing: 0.12em;
}

.category-courts-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.court-chip {
    border: 1px solid rgba(10, 111, 125, 0.35);
    background: rgba(255, 255, 255, 0.7);
    color: var(--brand-primary-dark);
    border-radius: 999px;
    padding: 4px 12px;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}

.court-chip:hover {
    background: rgba(10, 111, 125, 0.12);
}

.court-chip.is-selected {
    background: var(--brand-primary-dark);
    border-color: var(--brand-primary-dark);
    color: #ffffff;
}

.court-chip.is-selected:hover {
    background: #0a5e6a;
}

.organiser-row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin-bottom: 10px;
}

.organiser-row > :first-child {
    flex: 1;
}

.organiser-row .app-btn--icon {
    margin-top: 6px;
}

.child-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}

.child-header .text-input-display,
.child-header .text-input-textarea {
    font-size: 17px;
    font-weight: 700;
    color: #0f2a36;
    flex: 1;
    min-width: 0;
}

.child-header .text-input-placeholder {
    color: #94a3b8;
    font-weight: 500;
}

.child-title {
    margin: 0;
    flex: 1;
    min-width: 0;
}

.form-actions {
    display: flex;
    gap: 12px;
    margin-top: 28px;
    padding-top: 20px;
    border-top: 1px solid rgba(10, 111, 125, 0.12);
}

/* Button styling now comes from the shared <AppButton> (.app-btn) primitive. */

.toast-container {
    position: fixed;
    top: 1.5rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 9999;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    min-width: 280px;
    max-width: 480px;
    pointer-events: none;
    /* Reset the UA [popover] box (centred inset:0, border, padding) — the container
       is a top-layer popover but must keep its top-centre, chrome-free layout.
       Only clear right/bottom (the UA inset:0) so top/left above stay in effect. */
    right: auto;
    bottom: auto;
    margin: 0;
    border: 0;
    padding: 0;
    overflow: visible;
    background: transparent;
}

/* A live popover defaults to display:block; restore the flex column we rely on. */
.toast-container:popover-open {
    display: flex;
}

.toast-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.9rem 1.25rem;
    border-radius: 8px;
    font-size: 1.05rem;
    color: #fff;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
    animation: toast-in 0.2s ease;
    pointer-events: auto;
}

.toast-success { background-color: rgba(38, 176, 80, 0.92); }
.toast-error   { background-color: rgba(220, 53, 69, 0.92); }
.toast-info    { background-color: rgba(27, 110, 194, 0.92); }

.toast-close {
    background: none;
    border: none;
    color: #fff;
    font-size: 1.6rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
    opacity: 0.8;
    flex-shrink: 0;
}

.toast-close:hover {
    opacity: 1;
}

@keyframes toast-in {
    from { opacity: 0; transform: translateY(-8px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* Wraps a wide data table so it scrolls horizontally within its own bounds instead of
   pushing the page past the viewport. The app shell (.page) is capped at the viewport
   width (see MainLayout.razor.css), so every list-page table lives inside this wrapper. */
.table-scroll {
    overflow-x: auto;
    max-width: 100%;
    -webkit-overflow-scrolling: touch;
}

/* QuickGrid styling for any grid that opts in via class="data-table" */
.data-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    background: #fff;
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 4px 18px rgba(15, 42, 54, 0.06);
}

    .data-table thead th {
        text-align: left;
        font-size: 12px;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        color: #46606f;
        /* QuickGrid grids move this onto .col-title (see below); it stays here so a plain
           table borrowing .data-table still gets padded header cells. */
        padding: 14px 18px;
        background: #e7edf0;
        border-bottom: 1px solid #d3dce1;
    }

/* ── QuickGrid header cells ───────────────────────────────────────────────────
   QuickGrid renders a sortable column title as a <button class="col-title"> and a
   non-sortable one as a <div class="col-title">. Left alone, the button falls back to
   the browser's system font (Arial 13.33px, mixed case) while the div inherits the th's
   Manrope 12px uppercase — so the two kinds of header never match. Everything below
   makes the two render identically and replaces the theme's filled-triangle sort arrow
   and grey-box hover. It applies to every grid carrying .data-table; no opt-in class.

   The '.quickgrid[theme=default]' qualifier is load-bearing, not decoration: the
   package's own theme rules are specificity (0,2,x) and outrank a plain '.data-table th'
   selector. That includes 'thead > tr > th { font-weight: normal }', which is why the
   weight has to be restated here for .col-title to inherit anything but 400. */
.data-table.quickgrid[theme=default] > thead > tr > th {
    font-weight: 700;
    /* Padding lives on .col-title instead (below) so the sortable button fills the whole
       cell — its hover/focus then covers the full header cell rather than a ragged box
       around the text. */
    padding: 0;
}

.data-table.quickgrid[theme=default] .col-header-content {
    /* The theme reserves --col-gap here for the (unused) column-options button. */
    padding-right: 0;
}

.data-table.quickgrid[theme=default] .col-title {
    font: inherit;
    color: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    font-weight: inherit;
    align-items: center;
    gap: 8px;
    padding: 13px 18px;
    width: 100%;
    transition: background-color 0.12s, color 0.12s;
}

/* Right-aligned numeric columns put the arrow on the inside, left of the label. */
.data-table .col-num .col-title {
    flex-direction: row-reverse;
}

/* The sort arrow: a 6px chevron drawn from two borders, so it takes currentColor and
   stays crisp at any zoom. Replaces the theme's 1rem filled-triangle background image. */
.data-table.quickgrid[theme=default] thead .sort-indicator {
    box-sizing: border-box;
    width: 6px;
    height: 6px;
    flex: 0 0 auto;
    background-image: none;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    border-radius: 1px;
    /* Reserved by .col-title's gap, so showing/hiding it never shifts the header. */
    opacity: 0;
    transform: rotate(45deg) translate(-1px, -1px);
    transition: opacity 0.12s, color 0.12s;
}

.data-table.quickgrid[theme=default] thead .col-sort-asc .sort-indicator {
    transform: rotate(-135deg) translate(-1px, -1px);
}

/* Hover tints the whole cell and darkens the label; the arrow ghosts in to preview which
   way a click would sort. Overrides the theme's rgba(128,128,128,.2) box. */
.data-table.quickgrid[theme=default] button.col-title:hover,
.data-table.quickgrid[theme=default] button.col-title:active {
    background-color: #d8e1e6;
    color: #0f2a36;
}

/* Only on a column that ISN'T the active sort — otherwise this outranks the sorted rule
   below (it carries an extra :hover class) and the live arrow fades to 40% under the
   pointer, exactly where the user is looking. */
.data-table.quickgrid[theme=default] thead th:not(.col-sort-asc):not(.col-sort-desc) button.col-title:hover .sort-indicator {
    opacity: 0.4;
}

/* The column actually sorted: ink-black label, full-strength arrow in the brand teal. */
.data-table.quickgrid[theme=default] thead .col-sort-asc .col-title,
.data-table.quickgrid[theme=default] thead .col-sort-desc .col-title {
    color: #0f2a36;
}

.data-table.quickgrid[theme=default] thead .col-sort-asc .sort-indicator,
.data-table.quickgrid[theme=default] thead .col-sort-desc .sort-indicator {
    opacity: 1;
    color: var(--brand-primary-dark);
}

.data-table.quickgrid[theme=default] thead th .col-title:focus {
    outline: none;
}

.data-table.quickgrid[theme=default] thead th .col-title:focus-visible {
    outline: 2px solid var(--brand-primary);
    outline-offset: -2px;
    border-radius: 6px;
}

@media (prefers-reduced-motion: reduce) {
    .data-table.quickgrid[theme=default] .col-title,
    .data-table.quickgrid[theme=default] thead .sort-indicator {
        transition: none;
    }
}

/* Outweigh QuickGrid's built-in '.quickgrid[theme=default] > tbody > tr > td' rule (specificity 0,2,4) */
.data-table.quickgrid[theme=default] > tbody > tr > td {
    padding: 14px 18px;
    font-size: 14px;
    color: #0f2a36;
    border-bottom: 1px solid #eef0f3;
}

/* All three of these use child combinators on purpose: a grid can now hold a nested table inside an expanded
   row's detail panel (see .data-table-expandable below), and as descendant selectors they would reach into it —
   hiding its rows outright, since none of them carries an aria-rowindex. */
.data-table > tbody > tr:last-child > td {
    border-bottom: none;
}

/* QuickGrid pads to Pagination.ItemsPerPage; padded rows lack aria-rowindex */
.data-table > tbody > tr:not([aria-rowindex]) {
    display: none;
}

.data-table > tbody > tr:hover > td {
    background: #fafbfc;
}

.data-table .row-actions {
    text-align: left;
    white-space: nowrap;
    width: 1%;
}

/* Compact numeric stat columns: right-aligned, shrink to content. */
.data-table .col-num {
    text-align: right;
    white-space: nowrap;
    width: 1%;
}

    .data-table thead th.col-num {
        text-align: right;
    }

/* The 'text-align: right' above never reached the body cells: QuickGrid tags every column
   with col-justify-start and its own scoped 'td.col-justify-start { text-align: left }'
   outranks '.data-table .col-num'. The numbers rendered left-aligned while the header
   label sat right, so the two never lined up. Restate it at a specificity that wins. */
.data-table.quickgrid[theme=default] > tbody > tr > td.col-num {
    text-align: right;
}

/* ── Expandable QuickGrid rows ────────────────────────────────────────────────
   QuickGrid renders exactly one <td> per column per row and exposes no row template, so a
   detail panel cannot be a second <tr>. Laying the table out as a CSS grid with
   display:contents rows makes every cell a grid item, which lets one designated cell span
   all the columns on its own line — that spanning cell IS the expanded panel.

   To opt a grid in: add .data-table-expandable next to .data-table, declare the column
   tracks on the grid itself (only the page knows its own columns), give the grid a
   RowClass that returns 'is-expanded' for the open row, and make the LAST column a
   TemplateColumn with Class="col-detail" holding the panel. */
.data-table.data-table-expandable {
    display: grid;
}

    .data-table.data-table-expandable > thead,
    .data-table.data-table-expandable > tbody,
    .data-table.data-table-expandable > thead > tr,
    .data-table.data-table-expandable > tbody > tr {
        display: contents;
    }

    /* QuickGrid pads the page out to Pagination.ItemsPerPage and the filler rows carry no
       aria-rowindex. The global rule that hides them is a plain '.data-table tbody tr', which
       the display:contents rule above now outranks — so it has to be restated here, or every
       filler row comes back as a run of blank grid cells. */
    .data-table.data-table-expandable > tbody > tr:not([aria-rowindex]) {
        display: none;
    }

    /* Table-layout shrink-to-fit ('width: 1%' on .col-num and .row-actions above) means something else
       entirely to a grid item: it sizes to 1% of its track instead of filling it, so those columns collapse
       to a sliver. The tracks do the sizing here, so the cells just fill what they are given. */
    .data-table.data-table-expandable > thead > tr > th,
    .data-table.data-table-expandable > tbody > tr > td {
        width: auto;
    }

    /* The chevron column holds a 26px control and no text, so the grid's standard 18px cell padding would
       make it nearly three times wider than its content. Tighten it on both the header and the body cells —
       the track is sized from them — so the disclosure sits close to the table edge instead of floating in
       its own column. Qualified with .quickgrid[theme=default] to outrank the package's own cell padding. */
    .data-table.data-table-expandable.quickgrid[theme=default] > tbody > tr > td.col-expand {
        padding-left: 8px;
        padding-right: 6px;
    }

    .data-table.data-table-expandable.quickgrid[theme=default] > thead > tr > th.col-expand .col-title {
        padding-left: 8px;
        padding-right: 6px;
    }

    /* Collapsed is the resting state of the detail column, header cell included — it carries no
       label of its own and must never occupy a track. */
    .data-table.data-table-expandable .col-detail {
        display: none;
    }

    /* '1 / -1' spans the explicit tracks the page declared, so the panel never creates a column
       of its own; auto-placement drops it onto its own line directly under its row. No tint either: a change
       of surface halfway down reads as a second, nested table, and the summary and its panel are one object.
       The panel KEEPS the grid's bottom rule — being the last line of the open row, that rule is what closes
       the whole unit off from the next tournament. */
    .data-table.data-table-expandable > tbody > tr.is-expanded > td.col-detail {
        display: block;
        grid-column: 1 / -1;
        padding: 0 18px 18px;
    }

    /* Only the summary cells give up their rule, so nothing separates a row from its own detail — the pair
       reads as one block bounded by the panel's rule below it. */
    .data-table.data-table-expandable > tbody > tr.is-expanded > td:not(.col-detail) {
        border-bottom: none;
    }

/* Sizes a table to the intrinsic width of its columns and centres it in the row, instead of
   stretching to fill the available width. Falls back to full width when the columns would overflow. */
.table-autosize {
    width: max-content;
    max-width: 100%;
    margin-inline: auto;
}

.podium,
.wl-ratio {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    font-variant-numeric: tabular-nums;
}

/* Podium finishes: gold/silver/bronze trophy + count per place. */
.podium-slot {
    display: inline-flex;
    align-items: baseline;
    gap: 3px;
    font-weight: 700;
}

/* Keep every place in its fixed horizontal position across rows: an empty slot
   (player has none of that trophy kind) reserves its space but hides the trophy + count. */
.podium-slot.is-empty {
    visibility: hidden;
}

.podium-gold .bi {
    color: #d9a406;
}

.podium-silver .bi {
    color: #9ca3af;
}

.podium-bronze .bi {
    color: #cd7f32;
}

.wl-ratio .wl-pct {
    font-weight: 700;
}

.wl-ratio .wl-detail {
    color: var(--bs-secondary-color, #94a3b8);
    font-size: 0.85em;
}

.wl-ratio .wl-win {
    color: #15803d;
    font-weight: 700;
}

.wl-ratio .wl-loss {
    color: #b91c1c;
    font-weight: 700;
}

.wl-ratio .wl-sep {
    color: var(--bs-secondary-color, #94a3b8);
}

.row-action-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    margin-right: 6px;
    border-radius: 8px;
    cursor: pointer;
    background: #fff;
    color: #5b7280;
    border: 1px solid #d6dce3;
    transition: background-color 0.12s, color 0.12s, border-color 0.12s;
}

.row-action-btn:last-child {
    margin-right: 0;
}

.row-action-btn:hover {
    background: var(--brand-primary);
    color: #fff;
    border-color: var(--brand-primary);
}

.row-action-btn-danger:hover {
    background: var(--brand-danger);
    border-color: var(--brand-danger);
}

.quickgrid-load-more {
    margin-top: 16px;
}

.quickgrid-total {
    display: inline-block;
    padding: 6px 14px;
    color: #5b7280;
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

/* ── Shared form styling — floating-label inputs, grid, actions, switch ───── */
/* Used by Tournament/Venue forms (via FormInput* wrappers) and by static SSR
   Identity pages (via plain markup). Page-level width lives on the page. */

.form-row {
    margin-bottom: 16px;
}

.form-field {
    position: relative;
}

.form-row .form-control {
    width: 100%;
    box-sizing: border-box;
    font-size: 15px;
    color: #0f2a36;
    background: rgba(255, 255, 255, 0.78);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: none;
    border-radius: 14px;
    outline: none;
    padding: 24px 16px 10px;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.9),
        inset 0 0 0 1px rgba(10, 111, 125, 0.08),
        0 2px 8px rgba(10, 42, 54, 0.06);
    transition: background 0.18s, box-shadow 0.18s;
}

.form-row .form-control::placeholder {
    color: transparent;
}

.form-row .form-control:hover {
    background: rgba(255, 255, 255, 0.92);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.9),
        inset 0 0 0 1px rgba(10, 111, 125, 0.14),
        0 4px 12px rgba(10, 42, 54, 0.08);
}

.form-row .form-control:focus {
    background: #ffffff;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.9),
        0 0 0 1px rgba(14, 165, 183, 0.7),
        0 0 0 4px rgba(14, 165, 183, 0.18);
}

/* Dropdown-specific overlay on top of the shared .form-control look.
   `select.form-control` has higher specificity than `.form-row .form-control`
   for the type-specific bits, but all state rules (:required, :focus, :hover,
   :disabled, .invalid, label transforms) still apply from the rules above. */
.form-row select.form-control {
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    padding-right: 40px;
    cursor: pointer;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'><path d='M1 1.5L6 6.5L11 1.5' stroke='%230a6f7d' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>");
    background-repeat: no-repeat;
    background-position: right 16px center;
}

.form-row select.form-control.is-empty-value {
    color: transparent;
    text-shadow: none;
}

.form-row select.form-control.placeholder-selected {
    color: #94a3b8;
    font-weight: 400;
    font-style: italic;
}

.form-row select.form-control option.placeholder-option {
    color: #94a3b8;
    font-style: italic;
}

.form-row select.form-control option:not(.placeholder-option) {
    color: #0f2a36;
    font-style: normal;
    font-weight: 500;
}

.form-field > label {
    position: absolute;
    left: 16px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--brand-primary-dark);
    font-size: 15px;
    font-weight: 500;
    pointer-events: none;
    transition: top 0.15s ease, font-size 0.15s ease, color 0.15s ease, transform 0.15s ease, font-weight 0.15s ease;
    margin: 0;
}

.form-field .form-control:focus ~ label,
.form-field .form-control:not(:placeholder-shown) ~ label,
.form-field.has-empty-placeholder > label {
    top: 9px;
    transform: none;
    font-size: 10.5px;
    font-weight: 700;
    color: var(--brand-primary-dark);
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.label-optional {
    font-weight: 400;
    color: #94a3b8;
    font-style: italic;
}

.form-field .form-control:focus ~ label .label-optional,
.form-field .form-control:not(:placeholder-shown) ~ label .label-optional,
.form-field.has-empty-placeholder > label .label-optional {
    display: none;
}

.form-field.has-empty-placeholder .form-control:not(:focus)::-webkit-datetime-edit {
    color: transparent;
}

.form-field.has-empty-placeholder .empty-placeholder {
    position: absolute;
    left: 16px;
    right: 40px;
    bottom: 10px;
    font-size: 15px;
    color: #7a8a93;
    pointer-events: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.form-field.has-empty-placeholder .form-control:focus ~ .empty-placeholder {
    display: none;
}

.form-row .form-control:disabled,
.form-row .form-control[disabled] {
    background: rgba(226, 232, 236, 0.6);
    color: #94a3b8;
    cursor: not-allowed;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.5),
        inset 0 0 0 1px rgba(10, 111, 125, 0.06);
}

.form-row .form-control:disabled:hover,
.form-row .form-control[disabled]:hover {
    background: rgba(226, 232, 236, 0.6);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.5),
        inset 0 0 0 1px rgba(10, 111, 125, 0.06);
}

.form-field .form-control:disabled ~ label,
.form-field .form-control[disabled] ~ label {
    color: #94a3b8;
}

/* ── Required field cue: teal left-edge bar on the floating-label control.
   Driven entirely by the HTML `required` attribute (no extra classes), which
   the FormInput* wrappers set automatically from the bound property's
   [Required] data annotation or non-nullable value type.
   Implemented as an inset box-shadow so the bar follows the control's own
   rounded corners. Applies uniformly to text, number, date, textarea, and
   select inputs — the dropdown overlay above only adds appearance/chevron/
   padding-right and does not redeclare box-shadow. ────── */
.form-row .form-control:required {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.9),
        inset 0 0 0 1px rgba(10, 111, 125, 0.08),
        inset 3px 0 0 var(--brand-primary),
        0 2px 8px rgba(10, 42, 54, 0.06);
}

.form-row .form-control:required:hover {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.9),
        inset 0 0 0 1px rgba(10, 111, 125, 0.14),
        inset 3px 0 0 var(--brand-primary),
        0 4px 12px rgba(10, 42, 54, 0.08);
}

.form-row .form-control:required:focus {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.9),
        inset 3px 0 0 var(--brand-primary),
        0 0 0 1px rgba(14, 165, 183, 0.7),
        0 0 0 4px rgba(14, 165, 183, 0.18);
}

/* Disabled required fields drop the accent — nothing to fill in. */
.form-row .form-control:required:disabled,
.form-row .form-control:required[disabled] {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.5),
        inset 0 0 0 1px rgba(10, 111, 125, 0.06);
}

.form-row .form-control.invalid {
    background-color: rgba(254, 226, 226, 0.7);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6), 0 0 0 4px rgba(220, 38, 38, 0.12);
}

.form-field .form-control.invalid:focus ~ label,
.form-field .form-control.invalid:not(:placeholder-shown) ~ label {
    color: #dc2626;
}

.form-row .validation-message {
    font-size: 12px;
    font-weight: 600;
    color: #dc2626;
    margin: 6px 6px 0;
}

.form-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    margin-bottom: 16px;
}

.form-grid > .form-row {
    margin-bottom: 0;
}

.form-hint {
    margin: -8px 0 16px;
    font-size: 0.8rem;
    color: #5b7280;
}

/* ── Field info icon: an optional tooltip affordance rendered by FormField at the
   trailing edge of the control. The control gains right padding so its text never
   slides under the icon. ────── */
.form-field.has-info .form-control {
    padding-right: 42px;
}

.form-field-info {
    position: absolute;
    top: 50%;
    right: 14px;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    font-size: 15px;
    line-height: 1;
    color: var(--brand-primary, #0a6f7d);
    opacity: 0.65;
    cursor: help;
    transition: opacity 0.15s ease;
}

.form-field-info:hover,
.form-field-info:focus-visible {
    opacity: 1;
    outline: none;
}

/* Number fields: drop the native spin buttons so the glassy control stays clean and
   leaves room for an optional info icon. */
.form-row input[type=number]::-webkit-outer-spin-button,
.form-row input[type=number]::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.form-row input[type=number] {
    -moz-appearance: textfield;
    appearance: textfield;
}

.form-grid.form-grid-3 {
    grid-template-columns: 1fr 1fr 1fr;
}

@media (max-width: 960px) {
    .form-grid.form-grid-3 {
        grid-template-columns: 1fr 1fr;
    }
}

@media (max-width: 720px) {
    .form-grid,
    .form-grid.form-grid-3 {
        grid-template-columns: 1fr;
    }
}

.form-radio-group {
    display: flex;
    flex-direction: column;
    margin-bottom: 16px;
}

/* Sizes below are expressed in em relative to the control's own font-size (anchor: 14px),
   so a wrapper that overrides font-size proportionally scales paddings, label offset,
   pills, gaps, and the focus/checked shadows — no separate size variant needed. */
.form-radio-control {
    position: relative;
    font-size: 14px;
    border: none;
}

/* The white card + abs-positioned label chrome only renders when the control is inside
   a real <form>. Outside forms (e.g. inline organiser controls, popovers), the label
   sits inline above the pills and there's no surrounding card. */
form .form-radio-control {
    padding: 1.714em 0.286em 0.143em;
    background: rgba(255, 255, 255, 0.78);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-radius: 1em;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.9),
        inset 0 0 0 1px rgba(10, 111, 125, 0.08),
        0 2px 8px rgba(10, 42, 54, 0.06);
    transition: background 0.18s, box-shadow 0.18s;
}

form .form-radio-control:has(.form-radio-pill-input:disabled) {
    background: #f4f7f9;
    box-shadow: inset 0 0 0 1px #e5ecef;
}

.form-radio-control .form-radio-group-label {
    display: block;
    margin-bottom: 0.762em; /* ≈8px when label font-size is 10.5px */
    font-size: 0.75em;
    font-weight: 700;
    letter-spacing: 0.08em;
    color: var(--brand-primary-dark);
    text-transform: uppercase;
}

form .form-radio-control .form-radio-group-label {
    position: absolute;
    /* top/left are in the label's own em (0.75em of parent), so 0.667em ≈ 7px and
       1.333em ≈ 14px at the default 14px control anchor. */
    top: 0.667em;
    left: 1.333em;
    margin-bottom: 0;
}

/* Inset-track radio group: the pills container is a recessed gray groove and each
   pill is a peel that rises from it on hover or when checked. All sizing in em
   relative to the .form-radio-control 14px anchor so a wrapper font-size override
   scales the whole control proportionally. */
.form-radio-pills {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
    grid-auto-rows: 1fr; /* all rows equal to the tallest — pills match across wraps */
    align-items: stretch;
    gap: 0.214em; /* ≈3px */
    padding: 0.214em; /* ≈3px */
    border-radius: 0.857em; /* ≈12px */
    background: linear-gradient(180deg, #eaeff2 0%, #f1f4f6 100%);
    box-shadow:
        inset 0 1px 2px rgba(10, 42, 54, 0.10),
        inset 0 0 0 1px rgba(10, 42, 54, 0.04);
    transition: background 0.18s, box-shadow 0.18s;
}

.form-radio-control:has(.form-radio-pill-input:disabled) .form-radio-pills {
    background: #eef1f3;
    box-shadow: inset 0 0 0 1px #e5ecef;
}

.form-radio-pill {
    position: relative;
    margin: 0;
    cursor: pointer;
    border-radius: 0.643em; /* ≈9px */
    display: flex;
    align-items: stretch; /* inner span fills the pill height */
    align-self: stretch; /* pill fills the grid row when a sibling wraps */
    min-width: 0;
}

.form-radio-pill-input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
    pointer-events: none;
}

.form-radio-pill-label {
    flex: 1;
    align-self: stretch; /* peel matches pill height — equal rows across wraps */
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 0.143em 1em; /* ≈2px 14px — matches single-line input height */
    border-radius: 0.643em;
    font-size: 1em;
    font-weight: 600;
    color: #5b7280;
    user-select: none;
    transition: color 0.18s, background 0.18s, box-shadow 0.18s, transform 0.18s;
}

.form-radio-pill-icon {
    margin-right: 0.5em;
    font-size: 1.071em; /* ≈15px */
    line-height: 1;
}

.form-radio-pill-tooltip {
    margin-left: 0.5em;
    font-size: 0.9em;
    opacity: 0.7;
}

/* Hover — peel rises out of the groove */
.form-radio-pill:hover .form-radio-pill-label {
    background: #fff;
    color: #0f2a36;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.9),
        0 1px 2px rgba(10, 42, 54, 0.08),
        0 0.286em 0.714em rgba(10, 42, 54, 0.06);
    transform: translateY(-1px);
}

/* Selected — teal peel */
.form-radio-pill.is-checked .form-radio-pill-label {
    background: var(--gradient-primary);
    color: #fff;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.25),
        0 0.429em 1em rgba(14, 165, 183, 0.35);
    transform: translateY(-1px);
}

.form-radio-pill-input:focus-visible + .form-radio-pill-label {
    box-shadow: 0 0 0 0.286em rgba(14, 165, 183, 0.25);
}

/* Disabled — flatten and desaturate; suppress hover effect via :has() below */
.form-radio-pill-input:disabled + .form-radio-pill-label {
    color: #9aa6ad;
    cursor: not-allowed;
    background: transparent;
    box-shadow: none;
    transform: none;
}

.form-radio-pill-input:disabled:checked + .form-radio-pill-label {
    background: #cdd6da;
    color: #fff;
    box-shadow: none;
}

.form-radio-pill:has(.form-radio-pill-input:disabled):hover .form-radio-pill-label {
    background: transparent;
    color: #9aa6ad;
    box-shadow: none;
    transform: none;
}

.form-radio-pill:has(.form-radio-pill-input:disabled:checked):hover .form-radio-pill-label {
    background: #cdd6da;
    color: #fff;
}

.form-check.form-switch {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 16px;
    padding: 14px 16px;
}

.form-check.form-switch .form-check-input:not(:checked) {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%231f2937'/%3e%3c/svg%3e");
    border-color: var(--brand-primary-dark);
}

.form-check.form-switch .form-check-input {
    margin: 0;
    flex-shrink: 0;
    width: 2.25em;
    height: 1.25em;
    cursor: pointer;
}

.form-check.form-switch .form-check-input:checked {
    background-color: var(--brand-primary-dark);
    border-color: var(--brand-primary-dark);
}

.form-check.form-switch .form-check-input:focus {
    box-shadow: 0 0 0 4px rgba(14, 165, 183, 0.18);
    border-color: rgba(14, 165, 183, 0.7);
}

.form-check.form-switch .form-check-label {
    color: #0f2a36;
    font-size: 15px;
    font-weight: 500;
    margin: 0;
    cursor: pointer;
}

/* Auth pages (Login/Register) — narrower than Tournament/Venue forms */
.auth-page {
    max-width: 480px;
    margin: 24px auto;
}

.auth-page h2 {
    font-size: 18px;
    font-weight: 700;
    color: var(--brand-primary-dark);
    letter-spacing: 0.04em;
    margin: 0 0 18px;
}

.auth-page hr {
    display: none;
}

.auth-page .form-actions .btn {
    flex: 1;
}

.auth-page .auth-links {
    margin-top: 18px;
    text-align: center;
    font-size: 14px;
}

.auth-page .auth-links a {
    color: var(--brand-primary-dark);
    font-weight: 600;
    text-decoration: none;
}

.auth-page .auth-links a:hover {
    text-decoration: underline;
}

/* Explanatory copy on the passwordless pages — the flow needs a sentence of context that a password form did not. */
.auth-page .auth-lede {
    font-size: 15px;
    line-height: 1.5;
    color: var(--text-muted, #6b7683);
    margin: 0 0 18px;
}

.auth-page .auth-note {
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-muted, #6b7683);
    margin: 0 0 18px;
}

/* Separator between the two ways in (emailed link / passkey). */
.auth-divider {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 22px 0 18px;
    color: var(--text-muted, #6b7683);
    font-size: 12px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.auth-divider::before,
.auth-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--border-subtle, #e4e8ec);
}

.auth-page .btn-passkey {
    width: 100%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
}

/* Shown only when the browser reports no WebAuthn support, via the script on the login page. */
.passkey-unsupported {
    font-size: 13px;
    color: var(--text-muted, #6b7683);
    margin: 8px 0 0;
}

.passkey-list {
    list-style: none;
    margin: 0 0 22px;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.passkey-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 14px;
    border: 1px solid var(--border-subtle, #e4e8ec);
    border-radius: 10px;
}

.passkey-item-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.passkey-item-name {
    font-size: 15px;
    font-weight: 600;
    overflow-wrap: anywhere;
}

.passkey-item-meta {
    font-size: 12px;
    color: var(--text-muted, #6b7683);
}

/* ── Tournament hero cards (glass + dotted) ────────────────────────────── */
.tournament-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    grid-auto-rows: 1fr;
    gap: 20px;
    margin-top: 16px;
}

.t-card {
    position: relative;
    border-radius: 22px;
    overflow: hidden;
    min-height: 220px;
    height: 100%;
    padding: 18px;
    gap: 12px;
    text-decoration: none;
    color: #fff;
    display: flex;
    flex-direction: column;
    isolation: isolate;
    transition: transform 0.15s, box-shadow 0.15s;
    box-shadow: 0 12px 30px rgba(10, 111, 125, 0.18);
}

.t-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 20px 44px rgba(10, 111, 125, 0.28);
    text-decoration: none;
    color: #fff;
}

.t-bg {
    position: absolute;
    inset: 0;
    background:
        radial-gradient(circle at 70% 18%, rgba(255, 255, 255, 0.55), transparent 38%),
        linear-gradient(135deg, var(--brand-accent) 0%, var(--brand-accent-warm) 45%, var(--brand-primary) 100%);
}

.t-card-draft .t-bg {
    background:
        radial-gradient(circle at 70% 18%, rgba(255, 255, 255, 0.4), transparent 38%),
        linear-gradient(135deg, #94a3b8 0%, #475569 100%);
}

.t-card-closed .t-bg {
    background:
        radial-gradient(circle at 30% 20%, rgba(255, 255, 255, 0.45), transparent 40%),
        linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-primary-dark) 100%);
}

.t-bg::after {
    content: "";
    position: absolute;
    width: 220px;
    height: 220px;
    border-radius: 50%;
    right: -50px;
    top: -50px;
    background: radial-gradient(circle at 30% 30%, #fff 0%, rgba(255, 255, 255, 0.6) 30%, transparent 70%);
    opacity: 0.55;
    mix-blend-mode: screen;
    filter: blur(2px);
}

.t-bg::before {
    content: "";
    position: absolute;
    inset: 0;
    background-image: radial-gradient(rgba(255, 255, 255, 0.22) 1px, transparent 1px);
    background-size: 8px 8px;
    mix-blend-mode: overlay;
    pointer-events: none;
}

.t-day {
    flex: 0 0 auto;
    z-index: 2;
    background: rgba(255, 255, 255, 0.70);
    backdrop-filter: blur(1px);
    -webkit-backdrop-filter: blur(14px);
    border: 1px solid rgba(255, 255, 255, 0.6);
    box-shadow: 0 8px 22px rgba(15, 42, 54, 0.18);
    color: #0f2a36;
    padding: 6px 10px;
    border-radius: 10px;
    line-height: 1;
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
}

.t-day b {
    font-size: 22px;
    font-weight: 900;
    letter-spacing: -0.02em;
}

.t-day em {
    font-style: normal;
    font-size: 12px;
    font-weight: 800;
    letter-spacing: 0.14em;
    opacity: 0.7;
}

.t-head {
    position: relative;
    z-index: 1;
    flex: 0 0 auto;
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    color: #fff;
    text-shadow: 0 2px 12px rgba(15, 42, 54, 0.35);
}

.t-titles {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
    flex: 1 1 auto;
}

.t-title {
    min-width: 0;
    font-size: 20px;
    font-weight: 300;
    line-height: 1.2;
    margin: 0;
    color: #fff;
}

.t-panel {
    position: relative;
    margin-top: auto;
    padding: 14px 16px;
    background: rgba(255, 255, 255, 0.70);
    color: #0f2a36;
    border-radius: 16px;
    backdrop-filter: blur(1px);
    -webkit-backdrop-filter: blur(14px);
    border: 1px solid rgba(255, 255, 255, 0.6);
    box-shadow: 0 8px 22px rgba(15, 42, 54, 0.18);
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.t-row {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 15px;
    color: #334155;
}

.t-row svg {
    width: 14px;
    height: 14px;
    color: var(--brand-accent);
    flex: 0 0 14px;
}

.t-cats {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.t-cat {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    font-size: 13px;
    font-weight: 700;
    padding: 3px 4px 3px 10px;
    border-radius: 999px;
    background: rgba(15, 42, 54, 0.08);
    color: #1f2937;
}

.t-cat .count {
    font-size: 13px;
    font-weight: 800;
    background: #fff;
    color: inherit;
    padding: 3px 8px;
    border-radius: 999px;
    line-height: 1;
    box-shadow: inset 0 0 0 1px rgba(15, 42, 54, 0.12);
    font-variant-numeric: tabular-nums;
}

.t-cat.full .count {
    background: #1f2937;
    color: #fff;
    box-shadow: none;
}

.t-reg-status {
    display: inline-flex;
    flex-direction: column;
    line-height: 1.2;
    padding: 4px 0;
    font-size: 13px;
    font-weight: 700;
    color: #15803d;
    margin-left: auto;
    text-align: right;
}

.t-reg-status small {
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: #94a3b8;
}

.t-reg-status.closed {
    color: #64748b;
}

.t-venue-name {
    min-width: 0;
    overflow-wrap: anywhere;
    word-break: break-word;
}

/* ── Venue-colored tournament cards (open state) ───────────────────────── */
.t-card.t-venue {
    color: var(--venue-fg);
    box-shadow: 0 12px 30px color-mix(in srgb, var(--venue-accent) 35%, transparent);
}

.t-card.t-venue:hover {
    color: var(--venue-fg);
    box-shadow: 0 20px 44px color-mix(in srgb, var(--venue-accent) 50%, transparent);
}

.t-card.t-venue .t-bg {
    background:
        radial-gradient(circle at 70% 18%, rgba(255, 255, 255, 0.30), transparent 42%),
        linear-gradient(135deg, var(--venue-bg) 0%, color-mix(in srgb, var(--venue-bg) 55%, var(--venue-accent)) 100%);
}

.t-card.t-venue .t-bg::after {
    background: radial-gradient(circle at 30% 30%, #fff 0%, rgba(255, 255, 255, 0.65) 30%, transparent 70%);
    opacity: 0.6;
}

.t-card.t-venue .t-head,
.t-card.t-venue .t-title {
    color: var(--venue-fg);
    text-shadow: none;
}

.t-card.t-venue .t-day {
    background: rgba(255, 255, 255, 0.7);
    color: var(--venue-fg);
    border-color: rgba(255, 255, 255, 0.85);
    text-shadow: none;
}

.t-card.t-venue .t-row svg {
    color: var(--venue-accent);
}

/* Draft state: desaturate venue color slightly for a quieter look */
.t-card.t-venue.t-card-draft .t-bg {
    filter: saturate(0.55);
}

.t-card.t-venue.t-card-draft {
    opacity: 0.92;
}

/* ── Venue hue picker ──────────────────────────────────────────────────── */
.hue-field {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.hue-label {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #5b7280;
}

.hue-slider {
    appearance: none;
    -webkit-appearance: none;
    width: 100%;
    height: 24px;
    border-radius: 999px;
    background: linear-gradient(to right,
        hsl(0   85% 78%) 0%,
        hsl(30  85% 78%) 8.33%,
        hsl(60  85% 78%) 16.66%,
        hsl(90  85% 78%) 25%,
        hsl(120 85% 78%) 33.33%,
        hsl(150 85% 78%) 41.66%,
        hsl(180 85% 78%) 50%,
        hsl(210 85% 78%) 58.33%,
        hsl(240 85% 78%) 66.66%,
        hsl(270 85% 78%) 75%,
        hsl(300 85% 78%) 83.33%,
        hsl(330 85% 78%) 91.66%,
        hsl(360 85% 78%) 100%);
    box-shadow: inset 0 0 0 1px rgba(15, 42, 54, 0.12);
    cursor: pointer;
    outline: none;
    padding: 0;
}

.hue-slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: #fff;
    border: 3px solid var(--venue-accent, var(--brand-primary-dark));
    box-shadow: 0 2px 8px rgba(15, 42, 54, 0.25);
    cursor: grab;
}

.hue-slider::-moz-range-thumb {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: #fff;
    border: 3px solid var(--venue-accent, var(--brand-primary-dark));
    box-shadow: 0 2px 8px rgba(15, 42, 54, 0.25);
    cursor: grab;
}

.hue-slider:focus-visible {
    box-shadow: inset 0 0 0 1px rgba(15, 42, 54, 0.12), 0 0 0 3px rgba(14, 165, 183, 0.35);
}

.hue-row .hue-field {
    --venue-bg: var(--venue-bg);
    --venue-fg: var(--venue-fg);
    --venue-accent: var(--venue-accent);
}

/* Registration: live avatar preview sitting in a single row to the left of the hue picker. */
.reg-avatar-row {
    display: flex;
    align-items: center;
    gap: 16px;
}

.reg-avatar-row .hue-field {
    flex: 1;
    min-width: 0;
}

.reg-avatar-preview {
    --avatar-size: 52px;
    flex: 0 0 auto;
}

.hue-field .hue-slider {
    --thumb-accent: var(--venue-accent);
}


/* Generic value-range slider (FormInputRange): label + live value badge above a filled track. */
.form-range-row {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.form-range-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
}

.form-range-label {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #5b7280;
}

.form-range-value {
    font-size: 14px;
    font-weight: 700;
    color: var(--brand-primary-dark);
    min-width: 3ch;
    text-align: right;
}

.form-range-input {
    appearance: none;
    -webkit-appearance: none;
    width: 100%;
    height: 10px;
    border-radius: 999px;
    background: #e2e8ec;
    box-shadow: inset 0 0 0 1px rgba(15, 42, 54, 0.1);
    cursor: pointer;
    outline: none;
    padding: 0;
}

.form-range-input::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: #fff;
    border: 3px solid var(--brand-primary);
    box-shadow: 0 2px 8px rgba(15, 42, 54, 0.25);
    cursor: grab;
}

.form-range-input::-moz-range-thumb {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: #fff;
    border: 3px solid var(--brand-primary);
    box-shadow: 0 2px 8px rgba(15, 42, 54, 0.25);
    cursor: grab;
}

.form-range-input:focus-visible {
    box-shadow: inset 0 0 0 1px rgba(15, 42, 54, 0.12), 0 0 0 3px rgba(14, 165, 183, 0.35);
}


.loading-indicator {
    display: flex;
    justify-content: center;
    padding: 1rem 0;
}

.loading-circle-brand {
    color: var(--brand-primary);
}

.loading-overlay-host {
    position: relative;
}

.loading-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    background: rgba(255, 255, 255, 0.6);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    z-index: 5;
    border-radius: inherit;
    cursor: wait;
}

.loading-overlay .spinner-border {
    position: sticky;
    top: 40vh;
    width: 2.5rem;
    height: 2.5rem;
    border-width: 0.25em;
}

/* ── Status pages (404 / 500) ──────────────────────────────────────────── */
.status-page {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 12px;
    padding: 64px 24px;
}

.status-page-code {
    font-size: 96px;
    font-weight: 800;
    line-height: 1;
    color: var(--brand-primary);
    letter-spacing: -2px;
}

.status-page-title {
    margin: 0;
}

.status-page-message {
    margin: 0;
    max-width: 38ch;
    color: #5b7280;
}

.status-page-ref {
    margin: 0;
    color: #5b7280;
    font-size: 14px;
}

.status-page-ref code {
    padding: 2px 8px;
    border-radius: 6px;
    background: rgba(14, 165, 183, 0.12);
    color: var(--brand-primary-dark);
    font-weight: 600;
    letter-spacing: 1px;
}

.status-page-link {
    margin-top: 12px;
    display: inline-block;
    padding: 12px 22px;
    border-radius: 999px;
    font-weight: 700;
    text-decoration: none;
    background: var(--brand-primary);
    color: #fff;
}

.status-page-link:hover,
.status-page-link:focus-visible {
    filter: brightness(1.08);
    color: #fff;
}

/* ── Chrome-free layout + startup holding page ─────────────────────────── */
.blank-layout {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    padding: 24px;
}

.seeding-page {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 16px;
}

.seeding-page .spinner-border {
    width: 3rem;
    height: 3rem;
    border-width: 0.25em;
}

/* The brand lockup on light paper — the white-on-colour variant belongs on the coloured nav bar, not here.
   The mark is the same tintable silhouette as the nav's: brand-logo.svg as a mask, colour from the
   background. Taking that colour from currentColor keeps the mark and the wordmark in lockstep off the one
   declaration on .seeding-page-brand. */
.seeding-page-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0;
    font-family: 'Outfit', 'Manrope', system-ui, sans-serif;
    font-weight: 900;
    font-size: 34px;
    line-height: 1;
    color: #0f1620;
}

.seeding-page-brand-icon {
    width: 40px;
    height: 40px;
    display: block;
    background-color: currentColor;
    -webkit-mask: url(/brand-logo.svg) center / contain no-repeat;
    mask: url(/brand-logo.svg) center / contain no-repeat;
}

.seeding-page-message {
    margin: 0;
    max-width: 38ch;
    color: #5b7280;
}

.page-footer {
    position: fixed;
    left: 50%;
    bottom: 1rem;
    transform: translateX(-50%);
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    background: var(--surface-frost-dark);
    color: var(--surface-frost-dark-ink);
    border: 1px solid var(--surface-frost-dark-border);
    backdrop-filter: var(--surface-frost-blur);
    -webkit-backdrop-filter: var(--surface-frost-blur);
    border-radius: 10px;
    padding: 0.4rem 0.75rem;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
    z-index: 1000;
    animation: page-footer-slide-up 0.25s ease-out;
    max-width: calc(100% - 2rem);
}

@keyframes page-footer-slide-up {
    from { transform: translate(-50%, 20px); opacity: 0; }
    to   { transform: translate(-50%, 0); opacity: 1; }
}

.page-footer-status {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.85rem;
    white-space: nowrap;
}

.page-footer-divider {
    width: 1px;
    align-self: stretch;
    background: rgba(255, 255, 255, 0.15);
    margin: 0 0.25rem;
}

.page-footer-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.input-search-group {
    display: inline-flex;
    align-items: stretch;
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 6px;
    overflow: hidden;
    transition: background-color 0.12s, border-color 0.12s;
}

.input-search-group:focus-within {
    background: rgba(255, 255, 255, 0.12);
    border-color: rgba(255, 255, 255, 0.25);
}

.input-search {
    background: transparent;
    border: none;
    color: #f3f4f6;
    padding: 0.3rem 0.7rem;
    font-size: 0.85rem;
    min-width: 240px;
    outline: none;
}

.input-search-btn {
    background: transparent;
    border: none;
    border-left: 1px solid rgba(255, 255, 255, 0.1);
    color: rgba(243, 244, 246, 0.75);
    padding: 0 0.65rem;
    font-size: 0.9rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    transition: color 0.12s, background-color 0.12s;
}

.input-search-btn:hover {
    color: #fff;
    background: rgba(255, 255, 255, 0.06);
}

.input-search::placeholder {
    color: rgba(243, 244, 246, 0.55);
}

.input-search::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
}

.input-search-clear {
    background: transparent;
    border: none;
    color: rgba(243, 244, 246, 0.55);
    padding: 0 0.4rem;
    font-size: 0.95rem;
    line-height: 1;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    transition: color 0.12s;
}

.input-search-clear:hover {
    color: #fff;
}

/* Light-surface variant: the defaults above ink the search group for dark bars (page
   footer, top nav); `on-light` re-inks it for white toolbars and cards. */
.input-search-group.on-light {
    background: #fff;
    border-color: var(--border-color, #e2e6ea);
}

.input-search-group.on-light:focus-within {
    background: #fff;
    border-color: var(--brand-primary, #0ea5b7);
}

.input-search-group.on-light .input-search {
    color: #0f2a36;
}

.input-search-group.on-light .input-search::placeholder {
    color: #9aa7b0;
}

.input-search-group.on-light .input-search-btn {
    border-left-color: var(--border-color, #e2e6ea);
    color: #5b7280;
}

.input-search-group.on-light .input-search-btn:hover {
    color: #0f2a36;
    background: #eef3f4;
}

.input-search-group.on-light .input-search-clear {
    color: #9aa7b0;
}

.input-search-group.on-light .input-search-clear:hover {
    color: #0f2a36;
}

/* Form-field variant: mirrors the frosted `.form-control` look used on the
   tournament/venue forms. The search icon leads at the start of the input and
   the clear button trails at the end (flex `order` reorders the source markup). */
.input-search-group.as-form-field {
    display: flex;
    width: 100%;
    background: rgba(255, 255, 255, 0.78);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: none;
    border-radius: 14px;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.9),
        inset 0 0 0 1px rgba(10, 111, 125, 0.08),
        0 2px 8px rgba(10, 42, 54, 0.06);
    transition: background 0.18s, box-shadow 0.18s;
}

.input-search-group.as-form-field:hover {
    background: rgba(255, 255, 255, 0.92);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.9),
        inset 0 0 0 1px rgba(10, 111, 125, 0.14),
        0 4px 12px rgba(10, 42, 54, 0.08);
}

.input-search-group.as-form-field:focus-within {
    background: #ffffff;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.9),
        0 0 0 1px rgba(14, 165, 183, 0.7),
        0 0 0 4px rgba(14, 165, 183, 0.18);
}

/* Leading search icon — purely decorative here, so no hover affordance. */
.input-search-group.as-form-field .input-search-btn {
    order: 0;
    border-left: none;
    padding: 0 0.35rem 0 14px;
    color: var(--brand-primary-dark);
    cursor: default;
}

.input-search-group.as-form-field .input-search-btn:hover {
    color: var(--brand-primary-dark);
    background: transparent;
}

.input-search-group.as-form-field .input-search {
    order: 1;
    flex: 1;
    min-width: 0;
    color: #0f2a36;
    font-size: 15px;
    padding: 13px 8px;
}

.input-search-group.as-form-field .input-search::placeholder {
    color: #7a8a93;
}

.input-search-group.as-form-field .input-search-clear {
    order: 2;
    color: #9aa7b0;
    padding: 0 14px 0 0.35rem;
}

.input-search-group.as-form-field .input-search-clear:hover {
    color: #0f2a36;
    background: transparent;
}

/* ── Tab control ─────────────────────────────── */
.tab-control {
    display: flex;
    flex-direction: column;
    background: #fff;
    border: 1px solid #e7ebee;
    border-radius: 18px;
    box-shadow: 0 14px 34px rgba(10, 111, 125, 0.12);
    /* No overflow: hidden here — descendants like the team-move popover need to
       escape the tab-control's bounds. Inner backgrounds that touch the rounded
       corners (.tab-strip top, .tab-body bottom) clip themselves. */
}

/* DisplayContainer=false: the host already provides a surface, so the control drops its card chrome and
   renders as a bare strip plus panel. The tab buttons keep their own styling. */
.tab-control.is-bare {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

.tab-strip {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
    gap: 8px;
    padding: 12px;
    background: #fafcfd;
    border-radius: 17px 17px 0 0;
}

.tab-control.is-bare .tab-strip {
    background: none;
    border-radius: 0;
    padding: 0 0 12px;
}

/* DisplayBody=false: the strip stands alone (the panel it drives lives elsewhere on the page), so it
   rounds on all four corners instead of only butting up against a body below it. Declared after .is-bare
   so it also wins over that variant's rules at equal specificity. */
.tab-control.is-strip-only .tab-strip {
    border-radius: 17px;
}

/* ...and the bare variant's bottom gap goes with the body it was spacing the strip from — otherwise a
   strip sharing a toolbar row sits visibly high against its neighbours. */
.tab-control.is-bare.is-strip-only .tab-strip {
    padding-bottom: 0;
}

.tab-item {
    min-width: 0;
    cursor: pointer;
    text-align: left;
    padding: 12px 14px;
    border-radius: 14px;
    background: #fff;
    border: 1px solid #e7ebee;
    color: inherit;
    font: inherit;
    transition: transform 0.12s, box-shadow 0.12s;
}

/* Caption layout inside a tab button — see the TabHeader control. Lives here rather than in a scoped file
   so every tab strip spaces its icon identically; a scoped copy would outrank this and drift. */
/* inline-flex, not flex, so the caption stays an inline-level box and therefore still obeys the tab
   button's own text-align — a strip that centres its tabs (PlayerTodayPage) keeps centring them, without
   needing a page-specific override of this rule. max-width caps it so a long caption ellipsises instead
   of overflowing the button. */
.tab-header {
    display: inline-flex;
    align-items: center;
    vertical-align: middle;
    gap: 10px;
    max-width: 100%;
}

.tab-header > i {
    font-size: 15px;
    line-height: 1;
    flex: none;
    opacity: 0.9;
}

/* Keeps a long caption on one line and ellipsised rather than wrapping the button taller than its row. */
.tab-header-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* On a phone the strip's columns get narrow enough that the icon's gap is what pushes a caption into an
   ellipsis — a three-column strip was clipping "Skupina A" to "Skupina…" by 3px. Buying those pixels back
   from the gap keeps the whole word, and the ellipsis stays as the fallback for genuinely long captions. */
@media (max-width: 560px) {
    .tab-header {
        gap: 6px;
    }

    /* The 210px minimum fits exactly one column in a phone's content width, so a strip of short captions
       ("Rast", "Rating") stacked into full-width bars that were mostly empty. A smaller minimum lets two
       tracks share the row; auto-fill still drops back to one column on the narrowest phones, where two
       tracks no longer fit, so nothing is squeezed below a readable width. Equal auto-rows because a
       narrow column is where a caption first wraps to a second line — without them a strip of category
       tabs renders rows of differing height. */
    .tab-strip {
        grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
        grid-auto-rows: 1fr;
    }
}

.tab-item:not(.is-active):hover {
    border-color: transparent;
    color: #fff;
    background: linear-gradient(135deg, var(--brand-accent-warm), var(--brand-accent));
    box-shadow: 0 6px 14px rgba(255, 94, 126, 0.25);
}

.tab-item.is-active {
    border-color: transparent;
    color: #fff;
    background: linear-gradient(135deg, var(--brand-accent-warm), var(--brand-accent));
    box-shadow: 0 8px 18px rgba(255, 94, 126, 0.3);
}

.tab-body {
    position: relative;
    padding: 20px 22px 24px;
}

.tab-control.is-bare .tab-body {
    padding: 0;
}

.tab-close {
    position: absolute;
    top: 10px;
    right: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.06);
    color: inherit;
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    transition: background 0.15s ease;
}

.tab-close:hover {
    background: rgba(0, 0, 0, 0.14);
}

/* ── Category tab content ────────────────────── */
.cat-tab {
    display: flex;
    align-items: center;
    gap: 12px;
}

.progress-ring {
    --progress-pct: 0;
    --progress-col: #16a34a;
    position: relative;
    flex: 0 0 44px;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: conic-gradient(var(--progress-col) calc(var(--progress-pct) * 1%), #e7ebee 0);
}

.progress-ring::before {
    content: "";
    position: absolute;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: #fff;
}

.progress-ring span {
    position: relative;
    font-size: 10px;
    font-weight: 800;
    color: #0f2a36;
}

.tab-item.is-active .progress-ring,
.tab-item:not(.is-active):hover .progress-ring {
    background: conic-gradient(var(--progress-col) calc(var(--progress-pct) * 1%), #fff 0);
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.55);
}

/* Horizontal completion bar (ProgressBar control) */
.progress-bar-row {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 4px;
}

.progress-bar-track {
    flex: 1;
    height: 8px;
    border-radius: 999px;
    background: #eef3f4;
    overflow: hidden;
}

.progress-bar-track > span {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: linear-gradient(135deg, var(--brand-primary, #0ea5b7), var(--brand-primary-deep, #0a6f7d));
}

.progress-bar-pct {
    font-size: 14px;
    font-weight: 800;
    color: var(--brand-primary-deep, #0a6f7d);
    font-variant-numeric: tabular-nums;
}

.cat-tab-name {
    font-size: 15px;
    font-weight: 800;
    letter-spacing: -0.01em;
}

.cat-tab-sub {
    font-size: 12px;
    font-weight: 600;
    color: #5b7280;
    margin-top: 2px;
}

.tab-item.is-active .cat-tab-sub,
.tab-item:not(.is-active):hover .cat-tab-sub {
    color: rgba(255, 255, 255, 0.82);
}

/* Two tabs per row on a phone (see the .tab-strip rule above) leaves a category tab ~150px wide, and at that
   width the 44px ring is what pushes "2×2 · Amatér" onto a second line. Shrinking the ring — only where it
   sits inside a tab, so its other hosts keep the full size — buys back the pixels the caption needs. */
@media (max-width: 560px) {
    .cat-tab {
        gap: 9px;
    }

    .tab-item .progress-ring {
        flex: 0 0 36px;
        width: 36px;
        height: 36px;
    }

    .tab-item .progress-ring::before {
        width: 26px;
        height: 26px;
    }
}

/* ── Category roster ─────────────────────────── */
.cat-roster-desc {
    font-size: 13px;
    color: #5b7280;
    margin: 0 0 10px;
}

.cat-roster-h {
    margin: 0 0 8px;
}

.cat-roster {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: 6px;
}

.cat-team {
    display: flex;
    align-items: center;
    gap: 8px;
    background: #f7fafb;
    border-radius: 10px;
    padding: 5px 10px;
}

.cat-team-seed {
    font-size: 12px;
    font-weight: 800;
    color: #5b7280;
    flex: 0 0 18px;
}

.cat-team-main {
    display: flex;
    flex-direction: column;
    gap: 0;
    min-width: 0;
    flex: 1 1 auto;
}

.cat-team-name {
    font-size: 13px;
    font-weight: 600;
    color: #102a43;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Roster: stack each team's player labels so every player gets a full-width rating bar. */
.cat-team-ratings {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}

/* ── Player label (name over a thin rating bar) ─────────────────────────────
   The 0–10 rating fills a 2px bar tucked tight under the name; its colour runs
   the hue wheel (cyan @0 → red @10, set inline by PlayerLabel). An unrated
   player leaves the track empty.

   Every box in the label is a WHOLE number of CSS px — name 14, gap 0, bar 2
   (label = 16, even). A fractional line-height (1.1 × 11.5px = 12.65px) would
   accumulate down the list so each successive bar lands on a different
   fractional row: at 100% some render 2px, some 3px. Integer, even metrics keep
   every bar on an integer pixel row so the 2px bar renders consistently. The
   name's 14px line-height matches the font's natural line box, so diacritics and
   descenders aren't clipped; the bar tucks directly under it (no gap needed). */
.player-label {
    display: flex;
    flex-direction: column;
    min-width: 0;
    gap: 0;
    position: relative;
}

.player-label-name {
    font-size: 11.5px;
    line-height: 14px;
    font-weight: 600;
    color: #102a43;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.player-label-bar {
    height: 2px;
    border-radius: 1px;
    background: #e7edf0;
    position: relative;
}

.player-label-bar > i {
    display: block;
    height: 100%;
    border-radius: 1px;
}

/* Waiting: a soft highlight sweeps across the empty track until the badge resolves. `overflow: hidden`
   is scoped to this state so the shimmer sweep is contained — the base bar stays unclipped, otherwise a
   fractional-DPR (e.g. 125% display scaling) clip rect rounds down and shaves ~1px off the fill's bottom. */
.player-label.is-loading .player-label-bar {
    overflow: hidden;
}

.player-label.is-loading .player-label-bar::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg, transparent 0%, rgba(120, 150, 180, 0.55) 50%, transparent 100%);
    transform: translateX(-100%);
    animation: player-label-shimmer 1.15s ease-in-out infinite;
}

@keyframes player-label-shimmer {
    to { transform: translateX(100%); }
}

/* Resolved + rated: the coloured bar eases open from zero to its rating width once, on first display. */
.player-label.is-rated .player-label-bar > i {
    animation: player-label-fill 1.1s cubic-bezier(0.22, 0.61, 0.36, 1);
}

@keyframes player-label-fill {
    from { width: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .player-label.is-loading .player-label-bar::after {
        animation: none;
        opacity: 0.5;
    }

    .player-label.is-rated .player-label-bar > i {
        animation: none;
    }
}

/* Hover tooltip: full name + rating (two decimals) over a conic ring that fills to the rating,
   coloured on the same hue wheel as the bar (set inline via RingStyle).

   The card is a manual popover, so while it is open the browser paints it in the top layer — above every
   z-index and outside every ancestor's overflow. Labels live inside deeply clipped boxes (the live
   timeline nests the card under .tl-names → .tl-id → .tl-block inside the .tl-scroll viewport, beneath a
   sticky header), and no amount of un-clipping ancestors can beat a scroll container that must clip. The
   trade is that the card no longer positions itself: bevoman.playerLabelTip (app.js) sets left/top from
   the label's viewport rect on open, and again on scroll and resize. `manual` (not `auto`) because that
   module owns dismissal — light-dismiss would close the card behind its back and desync its state.

   The UA popover styles have to be undone: they centre the card with inset:0 + margin:auto, draw a
   border, and clip it with overflow:auto (which would eat the arrow). Author rules win over UA ones in
   the cascade regardless of specificity, so declaring them here is enough. Keeping `display: flex` in
   BOTH states (rather than letting the UA hide a closed popover with display:none) leaves the closed card
   in the layout tree, which keeps the live pill tabbable and lets app.js measure the card before showing
   it — it stays invisible and inert via opacity/pointer-events. */
.player-label-tip {
    position: fixed;
    inset: auto;
    margin: 0;
    border: 0;
    overflow: visible;
    transform: translate(-50%, calc(-100% + 3px));
    opacity: 0;
    pointer-events: none;
    /* `overlay` keeps the card in the top layer for the fade-OUT; without it the browser drops it back
       into the clipped box the instant it is hidden and the last 0.16s plays truncated. */
    transition: opacity 0.16s ease, transform 0.16s ease, overlay 0.16s ease allow-discrete;
    background: #0f2233;
    border-radius: 12px;
    padding: 10px 13px 10px 10px;
    display: flex;
    align-items: center;
    gap: 11px;
    box-shadow: 0 14px 32px -12px rgba(9, 20, 35, 0.5);
    z-index: 30;
    /* Pinned, not inherited: the card hangs off two very different chips — a name label and an initials
       avatar, which sets line-height:1 for its own centring — and its rows must be the same height in both.
       1.5 is what it used to inherit from the page, so pinning it changes nothing about how it looks. */
    line-height: 1.5;
    /* Stated for the same reason: call sites put the chip inside a row link (the players grid), and that
       <a>'s hand cursor would otherwise carry into the card and imply all of it is clickable. Only
       .player-label-tip-action takes the hand back. */
    cursor: default;
}

.player-label-tip:popover-open {
    opacity: 1;
    transform: translate(-50%, -100%);
}

/* Flipped below the label when there is no room above (the timeline's first row sits right under the
   sticky header). The card hangs down from the anchor point and the arrow moves to its top edge. */
.player-label-tip.is-below {
    transform: translate(-50%, -3px);
}

.player-label-tip.is-below:popover-open {
    transform: translate(-50%, 0);
}

/* --tip-arrow-x is the arrow's offset from the card's left edge: normally half the card (dead centre),
   but app.js moves it when it has had to clamp a card back inside the viewport, so the arrow keeps
   pointing at the label rather than at where the card wanted to be. */
.player-label-tip::after {
    content: "";
    position: absolute;
    top: 100%;
    left: var(--tip-arrow-x, 50%);
    transform: translateX(-50%);
    border: 6px solid transparent;
    border-top-color: #0f2233;
}

.player-label-tip.is-below::after {
    top: auto;
    bottom: 100%;
    border-top-color: transparent;
    border-bottom-color: #0f2233;
}

.player-label-tip-ring {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
}

.player-label-tip-ring::before {
    content: "";
    position: absolute;
    inset: 4px;
    border-radius: 50%;
    background: #0f2233;
}

.player-label-tip-ring > b {
    position: relative;
    font-size: 12.5px;
    font-weight: 800;
    color: #fff;
}

.player-label-tip-info {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.player-label-tip-name {
    font-size: 13px;
    font-weight: 700;
    color: #fff;
    white-space: nowrap;
}

.player-label-tip-sub {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: #7f97ad;
    white-space: nowrap;
}

/* The card's two links: the name (the player's profile) and the live pill (their day). Everything else on
   it — the ring, the rating, the caption — is read-only, so the card itself keeps the arrow cursor and only
   these two claim the hand. Both are spans with role="link" (a call site may already wrap the chip in an
   <a>, which can't nest one), so they get none of an anchor's built-in affordance: the underline on
   hover/focus is what says "this is a link", and it is deliberately the same cue on both. */
.player-label-tip-action {
    cursor: pointer;
    align-self: flex-start;
}

.player-label-tip-action:hover,
.player-label-tip-action:focus-visible {
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* The card is inert by default (pointer-events: none above) so it never swallows clicks meant for the page
   beneath it. It takes pointer events back only while it has a link to offer AND is open, which both makes
   those links clickable and keeps the chip hovered while the pointer travels onto them. The :popover-open
   half matters: a closed card still occupies its last fixed position, so without it an invisible card would
   go on eating clicks over whatever it happens to cover. */
.player-label-tip.has-action:popover-open {
    pointer-events: auto;
}

/* "Live" pill on the card — opens the player's day. Rendered as a span so it can sit inside call sites
   that already wrap the whole chip in an <a> (e.g. the players grid); its cursor and hover underline come
   from .player-label-tip-action above. */
.player-label-tip-live {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin-top: 6px;
    padding: 4px 9px;
    border-radius: 20px;
    background: #ff5e7e;
    color: #fff;
    font-size: 10px;
    font-weight: 800;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    white-space: nowrap;
}

.player-label-tip-live:hover,
.player-label-tip-live:focus-visible {
    background: #ff7591;
}

.player-label-tip-live-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #fff;
    animation: player-label-live-pulse 1.8s infinite;
}

@keyframes player-label-live-pulse {
    0%, 100% { transform: scale(1); opacity: 1; }
    50% { transform: scale(1.3); opacity: 0.65; }
}

@media (prefers-reduced-motion: reduce) {
    .player-label-tip-live-dot {
        animation: none;
    }
}

/* ── Info icon + text tooltip ─────────────────────────────────────────── */
/* A small info glyph that reveals a multi-line text tooltip on hover/focus, sharing the dark card look of
   the player-label tooltip above (see InfoIcon.razor). */
.info-icon {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #829ab1;
    font-size: 13px;
    line-height: 1;
    cursor: help;
}

.info-icon:hover,
.info-icon:focus-visible {
    color: #486581;
}

/* The tooltip drops below the icon (info icons typically sit in headers/legends near the page top, where an
   upward tooltip would be clipped). */
.info-icon-tip {
    position: absolute;
    left: 50%;
    top: calc(100% + 8px);
    transform: translateX(-50%) translateY(-3px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.16s ease, transform 0.16s ease;
    background: #0f2233;
    color: #e6edf4;
    border-radius: 12px;
    padding: 10px 13px;
    width: max-content;
    max-width: 260px;
    font-size: 12px;
    font-weight: 500;
    line-height: 1.5;
    text-align: left;
    white-space: normal;
    box-shadow: 0 14px 32px -12px rgba(9, 20, 35, 0.5);
    z-index: 30;
}

.info-icon:hover .info-icon-tip,
.info-icon:focus-within .info-icon-tip {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}

.info-icon-tip::after {
    content: "";
    position: absolute;
    bottom: 100%;
    left: 50%;
    transform: translateX(-50%);
    border: 6px solid transparent;
    border-bottom-color: #0f2233;
}

/* Right-edge placement (e.g. a right-pinned legend): anchor the tooltip's right edge to the icon and let it
   grow leftward, and pin the arrow under the icon rather than under the tooltip's centre. */
.info-icon--end .info-icon-tip {
    left: auto;
    right: -8px;
    transform: translateX(0) translateY(-3px);
}

.info-icon--end:hover .info-icon-tip,
.info-icon--end:focus-within .info-icon-tip {
    transform: translateX(0) translateY(0);
}

.info-icon--end .info-icon-tip::after {
    left: auto;
    right: 10px;
    transform: none;
}

/* ── Registration countdown ───────────────────────────────────────────── */
.reg-countdown {
    display: inline-flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    padding: 8px 12px;
    border-radius: 10px;
    background: rgba(15, 23, 42, 0.04);
    border: 1px solid rgba(15, 23, 42, 0.08);
    color: #0f172a;
}

.reg-countdown-label {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: #475569;
}

.reg-countdown-time {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    font-variant-numeric: tabular-nums;
}

.reg-countdown-block {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    min-width: 28px;
}

.reg-countdown-block b {
    font-size: 18px;
    font-weight: 800;
    line-height: 1;
    color: #0f172a;
}

.reg-countdown-block em {
    font-style: normal;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: #64748b;
    margin-top: 2px;
}

.reg-countdown-sep {
    font-size: 16px;
    font-weight: 700;
    color: #94a3b8;
    line-height: 1;
    padding-bottom: 12px;
}

.reg-countdown.compact {
    flex-direction: row;
    align-items: center;
    gap: 8px;
    padding: 4px 8px;
}

.reg-countdown.compact .reg-countdown-label {
    font-size: 9px;
}

.reg-countdown.compact .reg-countdown-block {
    min-width: 22px;
}

.reg-countdown.compact .reg-countdown-block b {
    font-size: 13px;
}

.reg-countdown.compact .reg-countdown-block em {
    font-size: 8px;
}

.reg-countdown.compact .reg-countdown-sep {
    font-size: 12px;
    padding-bottom: 8px;
}

.reg-countdown-inline {
    display: inline-flex;
    flex-direction: column;
    align-items: flex-end;
    line-height: 1.2;
    padding: 4px 0;
    margin-left: auto;
    text-align: right;
    white-space: nowrap;
    color: #64748b;
}

.reg-countdown-inline small {
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: #94a3b8;
}

.reg-countdown-inline-time {
    font-size: 13px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Icon beside the message, never above it: a laid-out block of content (a caption over a quoted note, say)
   would otherwise push itself onto the line below a block-level icon. Baseline alignment sits the glyph on
   the first line of that content rather than in the middle of the whole banner. */
.banner {
    display: flex;
    align-items: baseline;
    gap: 6px;
    padding: 8px 12px;
    border-radius: 999px;
    font-size: 13px;
    margin: 0 0 12px;
    border: 1px solid transparent;
}

.banner > i {
    flex: 0 0 auto;
}

.banner-info {
    background: var(--bs-info-bg-subtle);
    border-color: var(--bs-info-border-subtle);
    color: var(--bs-info-text-emphasis);
}

.banner-warning {
    background: var(--bs-warning-bg-subtle);
    border-color: var(--bs-warning-border-subtle);
    color: var(--bs-warning-text-emphasis);
}

.banner-error {
    background: var(--bs-danger-bg-subtle);
    border-color: var(--bs-danger-border-subtle);
    color: var(--bs-danger-text-emphasis);
}

.banner-success {
    background: var(--bs-success-bg-subtle);
    border-color: var(--bs-success-border-subtle);
    color: var(--bs-success-text-emphasis);
}

.alert {
    border-radius: 999px;
}

/* ── Player avatar (initials in a circle) ───────────────────────────────── */
.player-avatar {
    flex: 0 0 auto;
    width: var(--avatar-size, 28px);
    height: var(--avatar-size, 28px);
    border-radius: 50%;
    background: var(--avatar-bg, var(--brand-primary, #0ea5b7));
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: calc(var(--avatar-size, 28px) * 0.38);
    font-weight: 700;
    line-height: 1;
}

.player-avatar-list {
    display: inline-flex;
    align-items: center;
}

.player-avatar-list.is-overlap .player-avatar {
    border: 2px solid var(--avatar-ring, #fff);
    margin-left: -8px;
}

.player-avatar-list.is-overlap .player-avatar:first-child {
    margin-left: 0;
}

/* Registration dialog avatar variants (global so they apply to the
   PlayerAvatar-rendered element). */
.reg-av {
    flex: 0 0 34px;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    color: #fff;
    display: grid;
    place-items: center;
    font-size: 12px;
    font-weight: 800;
}

.reg-av-me {
    background: linear-gradient(135deg, #0ea5b7, #0a6f7d);
}

.reg-av-mate {
    background: linear-gradient(135deg, #ff7e5f, #ff5e7e);
}

.reg-av-empty {
    background: #d8dee2;
    color: #5b7280;
}

.reg-av-sm {
    flex: 0 0 28px;
    width: 28px;
    height: 28px;
    font-size: 11px;
    background: #0ea5b7;
}

.reg-search-item.is-disabled .reg-av {
    opacity: 0.5;
}

/* ── Viewport-locked pages ────────────────────────────────────────────────
   A reusable opt-in: any page whose root carries `.app-viewport-locked` turns
   the app shell into a fixed full-viewport frame with NO body scrollbar — the
   hero and any footer stay fixed while the page owns its own internal
   scrolling. These rules re-plumb the shared layout height chain
   (body → .page → main → .content) from "min-height, grow-with-content" to
   "fixed 100dvh, overflow hidden", activating only via :has() when such a page
   is mounted. Every other page is untouched and keeps normal body scrolling.
   Consumers: the live board, schedule, pool-standings and bracket pages. */

/* The page root itself is the column-flex frame every locked page needs — it
   fills main's height and stacks a fixed chrome strip above a single
   self-scrolling region. Folded into the marker class so each page carries just
   `.app-viewport-locked` instead of re-declaring an identical `*-page` block. */
.app-viewport-locked {
    display: flex;
    flex-direction: column;
    flex: 1;
    height: 100%;
    min-height: 0;
}

body:has(.app-viewport-locked) {
    overflow: hidden;
}

/* The default `.page` is `width: fit-content` (MainLayout.razor.css) so wide
   content can push the page — and the body — wider than the viewport. A locked
   page must instead pin its header and footer to the viewport width and let the
   inner content own its horizontal scroll, so cap the width at the viewport.
   The `body:has()` prefix lifts specificity above the scoped `.page[b-…]` rule
   (loaded later) that would otherwise win the equal-specificity width tie. */
body:has(.app-viewport-locked) .page {
    height: 100dvh;
    min-height: 0;
    width: 100%;
    max-width: 100%;
    overflow: hidden;
}

main:has(.app-viewport-locked) {
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}

/* The `.page` ancestor lifts this above the equal-specificity scoped
   `.content[b-…]` rule (MainLayout) that sets the 80px bottom padding —
   without it the scoped rule, loaded later, wins the tie and the page's
   bottom-pinned footer floats 80px above the viewport edge. */
.page .content:has(.app-viewport-locked) {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    /* Full-bleed: a viewport-locked page owns every edge, so drop the layout's
       side/top/bottom padding entirely. The StatusBar bleeds out with a
       negative margin keyed off --content-pad-x, so zero that here too — else
       it would overhang the now-padding-free edges by 28px. */
    --content-pad-x: 0;
    padding: 0;
    overflow: hidden;
}

/* ── Scroll region ─────────────────────────────────────────────────────────
   A reusable flex child that claims the remaining height of its column-flex
   parent and owns the page's vertical scrolling — the sibling chrome (hero,
   status bar) stays put. Carries no padding or border so content sits flush
   to the edges; add spacing on the inner content if a page needs it. Pairs
   with .app-viewport-locked but works in any column-flex container. */
.app-scroll-region {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
}

/* ── Category-switcher page chrome ─────────────────────────────────────────
   The pool-standings (Groups) and single-elimination (bracket) pages share the
   same public, shareable shell: a viewport-locked frame with a pinned header
   row — back-to-schedule link + a centred category radio strip — above a
   self-scrolling content region. These classes carry that shared chrome so the
   two pages (and any future per-category public view) don't each re-declare it.
   Global rather than page-scoped so the selectors reach into the child
   AppButton / FormInputRadioGroup markup directly, without a per-page `::deep`.

   Three named zones: back link pinned hard left, category picker centred (the
   equal 1fr flanks keep it truly centred even when only one side has content),
   and the optional Groups legend pinned hard right. */
.catbar {
    display: grid;
    grid-template-columns: 1fr fit-content(48rem) 1fr;
    grid-template-areas: "back picker legend";
    align-items: center;
    gap: .5rem 1rem;
    padding: .75rem 1.25rem;
    flex: 0 0 auto;
}

/* Back link pinned to the left edge at its natural size. */
.catbar-back {
    grid-area: back;
    justify-self: start;
}

/* When the back area holds more than one link (e.g. Back + Matches on the results page),
   lay them out in a row instead of stacking them in the same grid cell. */
.catbar-nav {
    display: flex;
    align-items: center;
    gap: .5rem;
}

/* The category picker hugs its pills (the fit-content track caps it) and shrinks
   first when the row is tight so the back link and legend are never displaced. */
.catbar-picker {
    grid-area: picker;
    justify-self: center;
    width: auto;
    min-width: 0;
    max-width: 100%;
    margin: 0;
    display: flex;
    justify-content: center;
}

/* Lay the category pills out horizontally (centred), wrapping to more rows only
   when the row can't fit — overriding the shared radio control's auto-fit grid,
   which would collapse to a vertical stack at this auto width. fit-content keeps
   the grey groove sitting only behind the buttons instead of stretching the full
   control width; max-width still lets it wrap, and the auto margins keep it centred. */
.catbar-picker .form-radio-pills {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    width: fit-content;
    max-width: 100%;
    margin-inline: auto;
}

/* Same treatment for a tab strip used as the picker (results page): it hugs its tabs in one centred,
   wrapping row instead of the auto-fill 210px grid it uses when it heads a full-width panel. Mirrors the
   .form-radio-pills override directly above — the catbar is a toolbar, not a page-wide panel header. */
.catbar-picker .tab-strip {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    width: fit-content;
    max-width: 100%;
    margin-inline: auto;
}

/* Phone layout: the back link and legend share the top row (hard left / hard
   right) and the picker drops to a full-width second row. The pills revert to
   the shared control's auto-fit grid there — stretched equal columns fill the
   groove edge to edge, so the grey track never overhangs wrapped pills the way
   the fit-content flex row does. */
@media (max-width: 720px) {
    .catbar {
        grid-template-columns: 1fr auto;
        grid-template-areas:
            "back legend"
            "picker picker";
    }

    .catbar-picker {
        width: 100%;
    }

    .catbar-picker .form-radio-pills {
        display: grid;
        width: 100%;
    }
}

/* When the wide Groups legend is present (pool-standings page), the centred
   picker is trapped between the two equal 1fr flanks — the nowrap legend forces
   the right track wide and the mirrored left track squeezes the picker column so
   its pills stack one-per-row long before the phone breakpoint. Drop the picker
   to its own full-width second row much earlier so it lays the pills out
   horizontally across the row instead of growing vertically. Scoped via
   :has(.gsp-legend) so the legend-less bracket page keeps the roomy single row. */
@media (max-width: 1240px) {
    .catbar:has(.gsp-legend) {
        grid-template-columns: 1fr auto;
        grid-template-areas:
            "back legend"
            "picker picker";
    }

    .catbar:has(.gsp-legend) .catbar-picker {
        width: 100%;
    }

    .catbar:has(.gsp-legend) .catbar-picker .form-radio-pills {
        display: grid;
        width: 100%;
    }
}

/* Padded content inside the scroll region; min-width:0 lets a wide child (the
   group grid or the bracket tree) own its horizontal scroll instead of widening
   the page. */
.catbar-content {
    min-width: 0;
    padding: 0 1.25rem 1.5rem;
}

/* Centring variant (the bracket page): claims the scroll region's remaining height so the child
   (the tree) can float in the vertical middle of the visible area via auto margins — which
   collapse to 0 when the tree is taller, falling back to a normal scrolling flow. Top padding
   mirrors the bottom one so the centring is a true middle; horizontal centring lives on
   .se-bracket (justify-content: safe center). */
.catbar-content.catbar-center {
    flex: 1;
    display: flex;
    flex-direction: column;
    padding-top: 1.5rem;
}

.catbar-center > .se-root {
    margin-block: auto;
}

/* Loading / not-found / empty banner, centred in the content region. */
.catbar-empty {
    color: #64748b;
    font-style: italic;
    padding: 2rem 1.25rem;
    text-align: center;
}

/* Live status pill on tournament grid cards (mirrors the overview live badge). */
.t-card .t-titles {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
}

.t-headside {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 8px;
}

.t-card .t-status.live {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 4px 11px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 800;
    letter-spacing: .08em;
    text-transform: uppercase;
    white-space: nowrap;
    background: rgba(220, 38, 38, .95);
    color: #fff;
    border: 1px solid #f87171;
}

.t-card .t-status.live .dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 0 0 0 rgba(255, 255, 255, .7);
    animation: t-live-pulse 1.5s ease-out infinite;
}

@keyframes t-live-pulse {
    0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, .7); }
    70% { box-shadow: 0 0 0 7px rgba(255, 255, 255, 0); }
    100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
}

/* Respect reduced-motion: hold the live tournament-card dot steady, no outgoing wave. */
@media (prefers-reduced-motion: reduce) {
    .t-card .t-status.live .dot {
        animation: none;
    }
}

/* ── Schedule timeline: manual match drag & drop (insertion-gap / push-apart) ──
   Toggled by bevoman.scheduleDrag (app.js). The .tl-dropgap marker is injected
   by that script (so it lives here in the global sheet, not the timeline's
   scoped css, where a JS-created element wouldn't carry the scope attribute);
   .tl-dragging / .tl-shift ride on the component's own rendered blocks. */
.tl-block[draggable="true"] {
    cursor: grab;
}

.tl-block.tl-dragging {
    opacity: .4;
    cursor: grabbing;
}

/* Slide the blocks at/after the hovered insertion point aside to open the gap.
   Scoped to an active drag so the transform transition never touches the block's
   other states (seed-fade, search hit, etc.). */
.tl-dnd-active .tl-block {
    transition: transform .12s ease;
}

.tl-block.tl-shift {
    transform: translateX(26px);
}

/* The insertion marker: a glowing dashed bar between two matches. Its left (px)
   is set by the drag script; it spans the block band (top/bottom 7px). */
.tl-dropgap {
    position: absolute;
    top: 7px;
    bottom: 7px;
    width: 24px;
    margin-left: -12px;
    border-radius: 8px;
    border: 2px dashed var(--brand-primary-dark, #0a6f7d);
    background: rgba(10, 111, 125, .1);
    box-shadow: 0 0 0 3px rgba(10, 111, 125, .08);
    pointer-events: none;
    z-index: 5;
    animation: tl-dropgap-in .14s ease;
}

@keyframes tl-dropgap-in {
    from { opacity: 0; transform: scaleX(.5); }
    to { opacity: 1; transform: scaleX(1); }
}

/* ── Single-elimination bracket (RoundSingleEliminationFormat) ───────────────────────────
   A two-sided tree: left half grows left→centre, right half mirrors, the final + bronze sit
   in the centre. A node draws its feeder children (toward the outside) joined by a fork to its
   own box (toward the centre); a node with a single feeder uses a plain short horizontal line.
   The match boxes (.mbx-*) deliberately mirror the live timeline scoreboard look. */
.se-root {
    --se-line: #cbd5e1;
    /* Advancement-leaf colours, shared by the pill fill and its connector stroke (drawn in app.js) so the two
       can never drift apart. */
    --se-leaf-bye: #15803d;
    --se-leaf-direct: #7c3aed;
    --se-gutter: 22px;
    /* Half a match box's height + a small gap — a stacked advancement leaf is anchored this far above/below its
       sibling feeder box's centre so it hugs the box regardless of the feeder subtree's height. */
    --se-boxhalf: 39px;
    --se-leaf-gap: 7px;
    /* Vertical space a stacked leaf reserves on its side of the combo child (leaf height + gap), so it can't
       overhang into a neighbouring box when the feeder subtree is too short to absorb it. */
    --se-leaf-reserve: 28px;
    /* When a leaf sits on the TOP slot, its feeder fills the BOTTOM row. A margin here shifts the feeder down by
       half its value; sizing it to twice the bottom-row offset (~23px from the box centre) drops the feeder onto
       that row so its connector is a straight horizontal line — and it still exceeds --se-leaf-reserve, so the
       leaf above stays clear of neighbours. */
    --se-feeder-align: 46px;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    /* Breathing space below the tree before the round Description input. */
    margin-bottom: 1.25rem;
}

/* The start-dialog assignment preview has nothing below the tree, so the breathing margin is dead space that
   would push the dialog body past the viewport and raise a spurious vertical scrollbar — drop it there. */
.se-root--assign {
    margin-bottom: 0;
}

/* Live tree (the public /bracket page + its maximised dialog — i.e. .se-root that is NOT a preview): its boxes
   render real teams as two stacked player names per side, so each box is ~90px tall (half ≈ 46px) — much taller
   than the single-line "Seed N" box the compact preview draws, which the base leaf constants (--se-boxhalf 39px)
   are tuned for. With those preview-sized constants the stacked BYE/DIRECT leaf anchors ~46px above the feeder
   box's centre and collides with the taller box's top edge. Re-tune the box-height-dependent constants to the
   live box so the leaf clears it, narrow the (over-wide) default box, and pack sibling feeders closer — the
   three things that read as "different and broken" versus the tidy preview. */
.se-root:not(.se-root--preview) {
    --se-boxhalf: 47px;
    --se-feeder-align: 50px;
    /* Extra breathing room between the BYE/DIRECT pill and the match box it hugs. */
    --se-leaf-gap: 14px;
}

/* Narrower live boxes (the default min-width was sized for set-score columns; unplayed rounds don't need it —
   played boxes still grow past this floor to fit their score cells). */
.se-root:not(.se-root--preview) .mbx {
    min-width: 8rem;
}

/* Tighter vertical spacing between sibling feeder subtrees (the 20px default is roomier than the tall live
   boxes need). */
.se-root:not(.se-root--preview) .se-children {
    row-gap: 10px;
}

/* Maximised bracket dialog (the tree is the dialog body's only child): fill the body and float the
   tree in its vertical middle — auto margins collapse to 0 when the tree overflows, so a tall
   bracket scrolls normally. Horizontal centring lives on .se-bracket (justify-content: safe center). */
.app-dialog-fullscreen .app-dialog-body > .se-root {
    min-height: 100%;
    margin-bottom: 0;
}

.app-dialog-fullscreen .app-dialog-body > .se-root > .se-bracket-wrap {
    margin-block: auto;
}

.se-editor {
    border: 1px solid #d8dee6;
    border-radius: 12px;
    padding: .75rem 1rem;
    background: #f8fafc;
}

.se-team-count {
    max-width: 12rem;
    margin-top: .75rem;
}

.se-team-count .form-row { margin-bottom: 0; }

.se-editor-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}

.se-editor-title { margin: 0; }

.se-editor-hint {
    margin: .35rem 0 .6rem;
    font-size: .8rem;
    color: #64748b;
}

.se-tier-row {
    display: flex;
    align-items: center;
    gap: .75rem;
    margin-top: .4rem;
}

/* Drop the form-row's stacking margin inside the horizontal tier row so the seed-count input wrapper is exactly
   the input's height — the stretched colour chip then matches it (see .se-tier-caption--seeded). */
.se-tier-row > :first-child { min-width: 9rem; margin-bottom: 0; }

.se-tier-caption {
    font-size: .85rem;
    color: #475569;
    flex: 1;
    display: flex;
    align-items: center;
}

/* A tier with a colour: a filled chip in the tier's hue (white ink), stretched to the seed-count input's
   height and sharing its radius so the row reads as one control + its colour key. Background set inline. */
.se-tier-caption.se-tier-caption--seeded {
    align-self: stretch;
    flex: 0 1 auto;
    padding: 0 14px;
    border-radius: 14px;
    color: #fff;
    font-weight: 600;
}

.se-empty {
    color: #64748b;
    font-style: italic;
    padding: 1rem;
}

/* Holds the bracket + the absolutely-pinned maximise button. */
.se-bracket-wrap { position: relative; }

.se-maximise {
    position: absolute;
    top: 0;
    right: 0;
    z-index: 5;
}

.se-bracket {
    position: relative;
    display: flex;
    /* "safe" so an overflowing bracket falls back to start instead of clipping its left (outer) rounds,
       which centring would make unreachable by horizontal scroll. */
    justify-content: safe center;
    align-items: center;
    gap: 0;
    overflow-x: auto;
    padding: .75rem .25rem;
    /* Grab-anywhere panning (bevoman.bracketPan). Children that set their own cursor (draggable seed rows,
       buttons) keep theirs, so grab only shows over the empty tree canvas. */
    cursor: all-scroll;
}

.se-bracket--panning {
    cursor: grabbing;
    user-select: none;
}

/* The tree row: the two halves + the centre podium, laid out horizontally (the connector overlay selects the
   halves by `.se-stage > .se-half`). Positioned so the group legend can overlay its empty bottom-centre. */
.se-stage {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0;
}

/* Connector overlay — sized + filled by bevoman.bracketConnectors; sits behind the boxes. */
.se-svg {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 0;
    pointer-events: none;
    overflow: visible;
}

.se-svg path {
    fill: none;
    stroke: var(--se-line);
    stroke-width: 2;
}

/* Boxes paint above the connector overlay. */
.se-half,
.se-center { position: relative; z-index: 1; }

.se-half { display: flex; align-items: center; }

/* Centre column: the podium (final over the bronze 3rd-place match); the semifinal→{final, bronze}
   forks are drawn by the connector overlay. The side margins reserve the gutter those forks live in. */
.se-center {
    display: flex;
    align-items: center;
    flex: none;
    margin: 0 var(--se-gutter);
}

/* Group legend (start-dialog assignment preview): a "Groups" label followed by one pill per source group,
   tinted with that group's hue — replaces the per-team group pills on the seed cards. Absolutely positioned
   in the tree's empty bottom-centre so it never adds to the tree height, pinned 10px above the stage's
   bottom edge and capped to 60% of the tree width so a long group list wraps onto multiple rows. */
.se-grouplegend {
    position: absolute;
    bottom: 10px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 2;
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 10px;
    max-width: 60%;
}

.se-grouplegend-lbl {
    flex: none;
    font-size: 10px;
    font-weight: 800;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: #5b7280;
}

.se-grouplegend-pills {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 6px;
}

.se-grouppill {
    min-width: 22px;
    text-align: center;
    font-size: 11px;
    font-weight: 800;
    color: #fff;
    border-radius: 999px;
    padding: 1px 9px;
    line-height: 1.5;
}

.se-podium {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: .9rem;
    flex: none;
}

/* A node = its feeder children (outside) and its own box (toward the centre); the gap is the gutter the
   connector elbows are drawn into. */
.se-node {
    display: flex;
    align-items: center;
    gap: var(--se-gutter);
}

.se-node--mirror { flex-direction: row-reverse; }

.se-children {
    display: flex;
    flex-direction: column;
    justify-content: center;
    /* Siblings spaced well apart so the forks read clearly. */
    row-gap: 20px;
}

/* Right-align children (mirrored on the right half) so every child box's inner edge lines up, giving the
   measured connector elbows a consistent start. */
.se-node:not(.se-node--mirror) > .se-children { align-items: flex-end; }
.se-node--mirror > .se-children { align-items: flex-start; }

/* Positioning context so a stacked advancement leaf can anchor to its feeder box's centre. */
.se-child { position: relative; }

.se-node-box { flex: none; }

/* ── Match box — mirrors the live timeline scoreboard (.tl-block) ─────────────────────────
   Caption (round · optional time) over two team rows with per-set cells and a won-set gutter;
   gold/bronze medal treatments for the podium matches. No category spine. */
.mbx {
    position: relative;
    min-width: 10.5rem;
    background: #fff;
    border: 1px solid #e7ebee;
    border-radius: 10px;
    overflow: hidden;
    box-shadow: 0 1px 2px rgba(15, 23, 42, .05);
    display: flex;
    flex-direction: column;
}

/* A box carrying an advancement leaf (BYE/DIRECT) lets it overflow the card — the leaf is drawn outside the
   box, out in the gutter where a feeder subtree would otherwise connect. */
.mbx.mbx--haslead { overflow: visible; }

.mbx-cap {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 2px 6px;
    background: #eef3f4;
    border-bottom: 1px solid rgba(15, 42, 54, .08);
}

.mbx-when {
    font-size: 9px;
    font-weight: 800;
    color: #5b7280;
    white-space: nowrap;
}

.mbx-pool {
    font-size: 9px;
    font-weight: 800;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: #5b7280;
    opacity: .8;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-left: auto;
}

/* Start-dialog court picker: a compact pill at the left of a playable first-round box's caption. Category
   courts (and any other venue court) are picked here; "Auto" leaves the court to the scheduler. */
.mbx-court {
    appearance: none;
    -webkit-appearance: none;
    flex: none;
    max-width: 7.5rem;
    font-family: inherit;
    font-size: 9px;
    font-weight: 800;
    line-height: 1.25;
    letter-spacing: .02em;
    color: var(--brand-primary, #0a6f7d);
    background-color: #e3f1f3;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'><path d='M1 2.5 4 5.5 7 2.5' fill='none' stroke='%230a6f7d' stroke-width='1.4'/></svg>");
    background-repeat: no-repeat;
    background-position: right 5px center;
    border: 1px solid var(--brand-primary, #0a6f7d);
    border-radius: 999px;
    padding: 1px 16px 1px 8px;
    cursor: pointer;
}

.mbx-court:focus-visible {
    outline: 2px solid var(--brand-primary, #0a6f7d);
    outline-offset: 1px;
}

.mbx-court.mbx-court-auto {
    color: #5b7280;
    background-color: #fff;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'><path d='M1 2.5 4 5.5 7 2.5' fill='none' stroke='%235b7280' stroke-width='1.4'/></svg>");
    border-color: #cfd8dd;
    border-style: dashed;
}

/* Native dropdown list keeps default dark ink (the pill's own colour is teal/grey). */
.mbx-court optgroup,
.mbx-court option {
    color: #0f2a36;
    font-weight: 600;
}

.mbx-board {
    display: flex;
    flex-direction: column;
}

.mbx-trow {
    position: relative;
    display: flex;
    align-items: stretch;
    min-height: 22px;
}

.mbx-trow + .mbx-trow > * {
    border-top: 1px dashed rgba(15, 42, 54, .10);
}

.mbx-id {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    display: flex;
    align-items: center;
    padding: 4px 4px;
}

/* A concrete team's players, stacked one name per row inside the id cell (beach pairs, mirroring the live board). */
.mbx-players {
    display: flex;
    flex-direction: column;
    min-width: 0;
    gap: 1px;
}

.mbx-pl {
    display: inline-block;
    vertical-align: middle;
    font-size: 11px;
    font-weight: 700;
    color: #0f2a36;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}

/* Provisional slot (preview seeds + live "winner/loser of …"): a grey sweeping pill with grey ink,
   matching the live timeline placeholder treatment so all rounds read consistently. */
.mbx-pl.mbx-tbd {
    color: #80969f;
    font-weight: 700;
    background: linear-gradient(90deg, #f2f6f7 25%, #ebecec 37%, #f2f6f7 63%);
    background-size: 400% 100%;
    border-radius: 64px;
    padding: 2px 10px;
    animation: mbx-future-sweep 1.6s ease-in-out infinite;
}

/* Advancement leaf pill (BYE green / DIRECT purple, white ink). In the tree it's a stacked feeder-column
   sibling (BracketNodeView) that forks into the box via the SVG connector, so it never widens the tree; the
   final/bronze reuse the same pill inline (.mbx-lead) since they have no feeder column. No icon. */
.se-leaf {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    white-space: nowrap;
    color: #fff;
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: .03em;
    line-height: 1.2;
    padding: 4px 10px;
    border-radius: 999px;
    box-shadow: 0 1px 2px rgba(15, 23, 42, .22);
}

.se-leaf--bye { background: var(--se-leaf-bye); }
.se-leaf--direct { background: var(--se-leaf-direct); }

/* Stacked leaf (combo child): anchored to the sibling feeder box's centre (the child's 50%) and lifted half a
   box + a small gap above (top slot) or below (bottom slot) it, so it hugs the box whatever the subtree height.
   Pinned to the inner (box) edge — right on the left half, left on the mirrored half. */
.se-leaf--above,
.se-leaf--below { position: absolute; }
.se-leaf--above { bottom: calc(50% + var(--se-boxhalf) + var(--se-leaf-gap)); }
.se-leaf--below { top: calc(50% + var(--se-boxhalf) + var(--se-leaf-gap)); }
.se-node:not(.se-node--mirror) .se-leaf--above,
.se-node:not(.se-node--mirror) .se-leaf--below { right: 0; }
.se-node--mirror .se-leaf--above,
.se-node--mirror .se-leaf--below { left: 0; }

/* Leaf on the top slot → feeder on the bottom row: shift the feeder down onto that row (straight connector). */
.se-child--reserve-above { margin-top: var(--se-feeder-align); }
/* Leaf on the bottom slot → feeder on the top row (near the box centre); just reserve the leaf's space. */
.se-child--reserve-below { margin-bottom: var(--se-leaf-reserve); }

/* Inline placement for the directly-rendered final/bronze: stack the pill over its slot, clear of the board. */
.mbx-lead {
    position: absolute;
    right: 100%;
    top: 50%;
    transform: translateY(-50%);
    margin-right: 6px;
    z-index: 3;
    pointer-events: none;
}

.mbx--mirror .mbx-lead {
    right: auto;
    left: 100%;
    margin-right: 0;
    margin-left: 6px;
}

@keyframes mbx-future-sweep {
    0% { background-position: 100% 0; }
    100% { background-position: 0 0; }
}

/* Respect reduced-motion: hold the provisional-name skeleton steady, no shimmer sweep. */
@media (prefers-reduced-motion: reduce) {
    .mbx-pl.mbx-tbd {
        animation: none;
    }
}

/* Preview only: a directly-advancing seed's row is filled solid in its tier hue (background set inline) with
   white ink, mirroring the live timeline's seed highlight (.tl-seedfill) so direct advancements read at a glance.
   A low-contrast diagonal micro-texture lifts the fill off the page without competing with the white label. */
.mbx-id.mbx-seedfill {
    position: relative;
    border-radius: 4px;
}

.mbx-seed.mbx-seedfill { position: relative; }

.mbx-id.mbx-seedfill .mbx-pl { color: #fff; }

.mbx-id.mbx-seedfill::after,
.mbx-seed.mbx-seedfill::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
    background-image:
        repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.06) 0 7px, rgba(0, 0, 0, 0.05) 7px 14px),
        linear-gradient(135deg, rgba(255, 255, 255, 0.12), rgba(0, 0, 0, 0.12));
}

.mbx-id.mbx-seedfill > *,
.mbx-seed.mbx-seedfill > * { position: relative; z-index: 1; }

.mbx-set {
    width: 22px;
    display: grid;
    place-items: center;
    font-size: 10px;
    font-weight: 700;
    color: #5b7280;
    font-variant-numeric: tabular-nums;
}

.mbx-set.mbx-set-won { color: #0f2a36; font-weight: 800; }

.mbx-cnt {
    width: 26px;
    display: grid;
    place-items: center;
    font-size: 15px;
    font-weight: 900;
    color: #5b7280;
    background: #eef1f3;
    font-variant-numeric: tabular-nums;
}

/* Outcome ink cue (decided matches only) — winner bolds, loser fades, draw stays full ink. */
.mbx-trow.mbx-win .mbx-pl,
.mbx-trow.mbx-win .mbx-cnt { color: #0f2a36; font-weight: 800; }

.mbx-trow.mbx-lose .mbx-pl,
.mbx-trow.mbx-lose .mbx-cnt { color: #9aa0a4; }

.mbx-trow.mbx-draw .mbx-pl,
.mbx-trow.mbx-draw .mbx-cnt { color: #0f2a36; }

/* States — completed grey, scheduled white, live coral border, placeholder dashed. */
.mbx.mbx-completed { background: #eaeff1; border-color: #d7dee1; }
.mbx.mbx-scheduled { background: #fff; border-color: #d7dee1; }
.mbx.mbx-live { border-color: var(--brand-accent, #ff5e7e); }
.mbx.mbx-future { border-style: dashed; border-color: #bcd3d8; }
.mbx.mbx-future .mbx-pl { color: #80969f; }

/* In-play cue: a coral dot pulsing at the top-middle of a live box, echoing the live board's live dot.
   Sits just inside the top edge (the box clips its rounded corners), centred over the caption band. */
.mbx-livedot {
    position: absolute;
    top: 3px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 3;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--brand-accent, #ff5e7e);
    box-shadow: 0 0 7px rgba(255, 94, 126, .75);
    animation: mbx-livedot-pulse 1.4s infinite;
}

@keyframes mbx-livedot-pulse {
    0%, 100% { opacity: 1; transform: translateX(-50%) scale(1); }
    50% { opacity: .35; transform: translateX(-50%) scale(.65); }
}

/* Reduced-motion: hold the live dot steady — the coral border already signals the in-play state. */
@media (prefers-reduced-motion: reduce) {
    .mbx-livedot { animation: none; }
}

/* Podium medals — gold (final) / bronze (3rd place). */
.mbx.mbx-gold,
.mbx.mbx-bronze { padding-left: 34px; }

.mbx-medal {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 34px;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    /* Round the outer (left) corners so the strip stays inside the card even when the box drops its overflow
       clip to let a BYE-into-final leaf escape. */
    border-radius: 9px 0 0 9px;
    box-shadow: inset -1px 0 0 rgba(0, 0, 0, .12), inset 0 1px 1px rgba(255, 255, 255, .4);
}

.mbx-medal i {
    font-size: 16px;
    filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .25));
}

.mbx.mbx-gold {
    border-color: var(--gold, #e3b341);
    box-shadow: inset 0 0 0 1px var(--gold, #e3b341), 0 4px 12px rgba(154, 107, 0, .16);
}

.mbx.mbx-bronze {
    border-color: var(--bronze, #c07b3a);
    box-shadow: inset 0 0 0 1px var(--bronze, #c07b3a), 0 4px 12px rgba(124, 74, 30, .16);
}

.mbx.mbx-gold .mbx-medal { background: linear-gradient(160deg, var(--gold-light, #fde68a), var(--gold, #e3b341) 55%, var(--gold-deep, #9a6b00)); }
.mbx.mbx-bronze .mbx-medal { background: linear-gradient(160deg, var(--bronze-light, #e6c39a), var(--bronze, #c07b3a) 55%, var(--bronze-deep, #7c4a1e)); }

.mbx.mbx-gold .mbx-cap { background: linear-gradient(90deg, #fbf2d4, #f3e3b0); border-bottom-color: rgba(154, 107, 0, .22); }
.mbx.mbx-gold .mbx-cnt { background: #f3e3b0; }
.mbx.mbx-gold .mbx-when,
.mbx.mbx-gold .mbx-pool,
.mbx.mbx-gold .mbx-cnt { color: var(--gold-deep, #9a6b00); opacity: 1; }

.mbx.mbx-bronze .mbx-cap { background: linear-gradient(90deg, #f4e6d6, #ecd6c0); border-bottom-color: rgba(124, 74, 30, .22); }
.mbx.mbx-bronze .mbx-cnt { background: #ecd6c0; }
.mbx.mbx-bronze .mbx-when,
.mbx.mbx-bronze .mbx-pool,
.mbx.mbx-bronze .mbx-cnt { color: var(--bronze-deep, #7c4a1e); opacity: 1; }


.ers-loading {
    display: flex;
    justify-content: center;
    padding: 2rem;
}

.ers {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    width: 100%;
    /* Fill the fullscreen dialog body so the tree preview below the toolbar can centre itself
       vertically (see .ers-preview's auto margins). */
    min-height: 100%;
}

/* Header row: seeding label + toggle on the left, the Cancel / Start actions pushed to the right edge. */
.ers-top {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
}

.ers-actions {
    margin-left: auto;
    display: flex;
    gap: 0.6rem;
}

/* Compact, inline advancing-cap number input in the dialog toolbar — no form-row stacking spacing. */
.ers-cap {
    width: 15rem;
    margin: 0;
}

/* Seeding selector fills the toolbar's free width so its two pills lay out side-by-side, each label on
   a single line — instead of the default narrow grid columns squeezing the labels into stacked lines.
   No min-width:0, and columns are minmax(max-content, 1fr): each pill grows to share free space but never
   shrinks below its single-line label width, so the toolbar wraps rather than clipping the text. */
.ers-seeding {
    flex: 1 1 auto;
    margin: 0;
}

.ers-seeding .form-radio-pills {
    grid-auto-flow: column;
    grid-auto-columns: minmax(max-content, 1fr);
    grid-template-columns: none;
}

.ers-seeding .form-radio-pill-label {
    white-space: nowrap;
}

/* Group badge on the re-add button — a solid group hue (set inline) with white ink. */
.ers-chip-grp {
    color: #fff;
    background: rgba(255, 255, 255, 0.22);
    border-radius: 4px;
    padding: 0 0.35rem;
    font-size: 0.7rem;
    font-weight: 700;
    white-space: nowrap;
}

/* Removed-team re-add row. */
.ers-excluded {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
}

.ers-excluded-lbl {
    font-weight: 700;
    font-size: 0.9rem;
    color: var(--brand-primary-dark, #0f2a36);
    margin-right: 0.2rem;
}

.ers-readd {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    border: 1px dashed #cdd6db;
    border-radius: 999px;
    background: #f6f9fa;
    color: #56707b;
    padding: 0.22rem 0.55rem;
    font-size: 0.82rem;
    cursor: pointer;
    transition: border-color .12s, color .12s;
}

.ers-readd:hover {
    border-color: var(--brand-primary, #0ea5b7);
    color: var(--brand-primary, #0ea5b7);
}

.ers-readd-name {
    white-space: nowrap;
}

.ers-readd-seed {
    font-weight: 800;
    font-size: 0.72rem;
    color: var(--brand-primary-dark, #0f2a36);
    background: #eef3f4;
    border-radius: 4px;
    padding: 0 0.3rem;
}

.ers-preview {
    width: 100%;
    /* Float the tree in the vertical middle of the space left under the toolbar/excluded rows;
       auto margins collapse to 0 when the tree overflows, so a tall bracket scrolls normally.
       Horizontal centring lives on .se-bracket (justify-content: safe center). */
    margin-block: auto;
}

/* Floor the preview tree's height so the group legend overlaid at its bottom-centre never overlaps the
   bottom-most seed boxes in small fields. */
.ers-preview .se-stage {
    min-height: 250px;
}

.ers-empty {
    color: #6b7e86;
    font-style: italic;
    margin: 0;
}

/* A draggable seed box in the start dialog's tree preview. */
.mbx-draggable {
    cursor: grab;
}

.mbx-draggable:active {
    cursor: grabbing;
}

/* The ✕ remove control on a seed box in the start dialog's tree (right-aligned within the pill). */
.mbx-remove {
    margin-left: auto;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    border: 0;
    background: transparent;
    color: rgba(255, 255, 255, 0.8);
    cursor: pointer;
    padding: 0;
    font-size: 0.95em;
    line-height: 1;
}

.mbx-remove:hover {
    color: #fff;
}

/* Compact preview boxes — shared by BOTH preview modes (the tournament form's inline/maximised bracket
   preview and the start dialog's assignment tree): slightly narrower boxes (live-timeline feel) than the
   live board, which keeps the wider default so its set-score columns fit. In the assignment tree each team
   side is additionally a group-tinted card with its performance-seed pill and players one per row. */
.se-root--preview .mbx {
    min-width: 6rem;
}

.mbx-seedrow {
    min-height: 0;
}

.mbx-seedrow + .mbx-seedrow > .mbx-seed {
    border-top: 1px solid rgba(255, 255, 255, 0.22);
}

.mbx-seed {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 5px;
    padding: 2px 4px;
    color: #fff;
}

/* The seed pill, held in a flex wrapper so the team card stays vertically compact. */
.mbx-seedbadges {
    flex: 0 0 auto;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 3px;
}

.mbx-seedpill {
    min-width: 16px;
    text-align: center;
    font-size: 11px;
    font-weight: 800;
    color: var(--brand-primary-dark, #0f2a36);
    background: #fff;
    border-radius: 999px;
    padding: 0 3px;
    line-height: 1.5;
}

.mbx-seedplayers {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    line-height: 1.2;
}

/* Pack both preview trees' matches vertically closer so a large (e.g. 32-team) bracket stays short. */
.se-root--preview .se-children {
    row-gap: 8px;
}

.mbx-seedplayers span {
    font-size: 11px;
    font-weight: 700;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
