/* The dark ground is painted by plain CSS, not by the theme provider. Installed on an iPhone
   home screen the app is relaunched cold often, and anything Blazor has not rendered yet showed
   the browser's white default — the dark layout appearing to drop out at random. */
:root {
    color-scheme: dark;

    /* The edge of anything you can type in or press. MudBlazor's LinesDefault (#22304a) only
       reaches 1.3:1 against the surfaces around it, which made input borders all but
       invisible; WCAG 1.4.11 asks 3:1 for a control's boundary. LinesDefault stays as it is
       for dividers and table rules, which are decorative and exempt — a line that bright
       across every table would shout. tools/check-contrast.ps1 measures both. */
    --sweeper-control-border: #5a6c90;
}

html, body {
    background-color: var(--mud-palette-background, #0b1220);
    color: var(--mud-palette-text-primary, #e6ebf2);
    font-family: 'Inter', system-ui, 'Segoe UI', sans-serif;
    /* The page itself never scrolls sideways; wide content scrolls in containers.
       iOS Safari ignores overflow-x: hidden on the body for viewport panning —
       clip (the second declaration, where supported) is what actually stops it. */
    overflow-x: hidden;
    overflow-x: clip;
    /* Keep Safari from inflating text on its own, which widens layouts. */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

a, .btn-link {
    color: var(--mud-palette-primary, #f2b705);
}

/* Account-page buttons follow the app theme (amber on dark) instead of Bootstrap blue. */
.btn-primary {
    color: #131c2b;
    font-weight: 600;
    background-color: var(--mud-palette-primary, #f2b705);
    border: none;
}

.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;
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid var(--mud-palette-error, #ef5f63);
}

.validation-message {
    color: var(--mud-palette-error, #ef5f63);
}

.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."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.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;
}

/* Styling for the statically rendered account pages (login/register). */
.account-form {
    max-width: 380px;
}

.account-form .mb-3 {
    margin-bottom: 1rem;
}

.account-form .form-label {
    display: block;
    margin-bottom: 0.3rem;
    font-weight: 500;
}

.account-form .form-control {
    display: block;
    width: 100%;
    padding: 0.5rem 0.75rem;
    font-size: 1rem;
    color: var(--mud-palette-text-primary, #1f2430);
    background: var(--mud-palette-background, #ffffff);
    border: 1px solid var(--sweeper-control-border);
    border-radius: 4px;
}

.account-form .btn,
.btn.btn-primary {
    display: inline-block;
    padding: 0.5rem 1.25rem;
    font-size: 1rem;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

/* Both feedback colours come from the palette, which is where the contrast pass tuned them —
   the hardcoded #e50000 this replaced measured 3.9:1 on the dark ground. */
.text-danger {
    color: var(--mud-palette-error, #ef5f63);
}

.text-success {
    color: var(--mud-palette-success, #4ca36a);
}

/* --- Email verification banner ------------------------------------------------ */

.verify-banner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 16px;
}

.verify-resend-form {
    display: inline;
}

.verify-resend-button {
    padding: 2px 10px;
    font: inherit;
    font-weight: 600;
    color: inherit;
    background: transparent;
    border: 1px solid currentcolor;
    border-radius: 6px;
    cursor: pointer;
}

.mt-3 {
    margin-top: 1rem;
}

/* --- Skip to content --------------------------------------------------------- */

/* Moved off the top edge rather than hidden, so it keeps its place in the tab order and
   the browser can still scroll it into view. z-index clears MudBlazor's app bar (1100)
   and drawer (1200) without reaching its overlays. */
.skip-link {
    position: fixed;
    top: 0;
    left: 8px;
    z-index: 1250;
    padding: 10px 16px;
    font-weight: 600;
    text-decoration: none;
    color: var(--mud-palette-primary-text, #131c2b);
    background: var(--mud-palette-primary, #f2b705);
    border-radius: 0 0 6px 6px;
    transform: translateY(-110%);
    transition: transform 0.12s ease-out;
}

.skip-link:focus {
    transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
    .skip-link { transition: none; }
}

/* The main landmark takes focus from the skip link, but it is not a control — no ring. */
#main-content:focus {
    outline: none;
}

/* --- App bar ----------------------------------------------------------------- */

.logout-form {
    display: inline;
}

.logout-button {
    padding: 4px 12px;
    font-size: 0.8rem;
    font-weight: 500;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: inherit;
    background: transparent;
    border: 1px solid currentColor;
    border-radius: 6px;
    cursor: pointer;
}

/* --- Admin ------------------------------------------------------------------ */

.section-title {
    font-weight: 600;
    letter-spacing: 0.01em;
}

.admin-label {
    display: block;
    margin-bottom: 4px;
    font-size: 0.8rem;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

.datetime-input {
    height: 40px;
    padding: 0 10px;
    font-size: 0.9rem;
    color: var(--mud-palette-text-primary, #e6ebf2);
    background: var(--mud-palette-background, #0b1220);
    border: 1px solid var(--sweeper-control-border);
    border-radius: 6px;
    color-scheme: dark;
}

/* --- Match list (sportsbook-style dense rows) ------------------------------- */

.match-day {
    margin: 20px 0 6px;
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

.match-list {
    max-width: 880px;
    border: 1px solid var(--mud-palette-lines-default, #22304a);
    border-radius: 6px;
    overflow: hidden;
}

.match-day {
    max-width: 880px;
}

.match-row {
    display: grid;
    /* Flashscore-style: time | stacked teams | stacked scores | actions. */
    grid-template-columns: 118px minmax(180px, 1fr) 56px minmax(210px, 280px);
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    background: var(--mud-palette-surface, #131c2b);
    border-bottom: 1px solid var(--mud-palette-lines-default, #22304a);
    min-height: 64px;
}

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

.match-row:hover {
    background: color-mix(in srgb, var(--mud-palette-surface, #131c2b) 85%, white 5%);
}

.match-time {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    line-height: 1.25;
    min-width: 0;
}

.match-kickoff {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.match-stage,
.match-deadline {
    font-size: 0.72rem;
    color: var(--mud-palette-text-secondary, #8b98ab);
    overflow-wrap: break-word;
}

.match-venue {
    max-width: 100%;
    font-size: 0.68rem;
    line-height: 1.3;
    /* 75% of the secondary ink left this at 3.9:1 on the surface; 90% keeps it the quietest
       line in the row while clearing 4.5:1 at this size. */
    color: color-mix(in srgb, var(--mud-palette-text-secondary, #8b98ab) 90%, transparent);
    /* Wrap up to two lines before truncating — the full name is in the tooltip. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: break-word;
}

/* Both columns stretch to the full row height and split into two equal halves,
   so each team name is vertically centered against its score box (or score
   number) in the neighboring column — align-self: stretch is what keeps the
   halves in phase when another cell (e.g. the time cell) is the tallest. */
.match-teams {
    display: grid;
    grid-template-rows: 1fr 1fr;
    align-items: center;
    align-self: stretch;
    /* A fixed indent, identical on every row, so all teams share one left edge
       (nudged right from the time column rather than hugging it). */
    padding-left: 48px;
    gap: 6px;
    min-width: 0;
}

.match-team-line {
    display: flex;
    align-items: center;
    gap: 8px;
    font-weight: 500;
    line-height: 20px;
    min-width: 0;
}

.match-team-line > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.team-winner {
    font-weight: 700;
}

.team-badge {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    object-fit: contain;
}

/* Provider short names replace full names on narrow screens. */
.team-short { display: none; }

.highlights-link {
    font-size: 0.8rem;
    white-space: nowrap;
}

.standings-table {
    max-width: 720px;
}

.standings-table td,
.standings-table th {
    white-space: nowrap;
}

.standings-team {
    min-width: 200px;
}

.match-scores {
    display: grid;
    grid-template-rows: 1fr 1fr;
    align-items: center;
    align-self: stretch;
    justify-items: center;
    gap: 6px;
}

.score-num {
    font-size: 0.95rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 20px;
}

.score-live {
    color: var(--mud-palette-error, #ef5f63);
}

.score-empty {
    color: var(--mud-palette-text-secondary, #8b98ab);
    font-weight: 400;
}

.score-box {
    width: 44px;
    height: 30px;
    text-align: center;
    font-size: 0.95rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--mud-palette-text-primary, #e6ebf2);
    background: var(--mud-palette-background, #0b1220);
    border: 1px solid var(--sweeper-control-border);
    border-radius: 6px;
    -moz-appearance: textfield;
    appearance: textfield;
}

.score-box::-webkit-inner-spin-button,
.score-box::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.score-box:focus {
    outline: none;
    border-color: var(--mud-palette-primary, #f2b705);
}


.badge-live {
    display: inline-flex;
    align-items: center;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    /* Dark ink on the red rather than white: white on this red measured 3.9:1, and the
       badge now matches the filled chips and snackbars, which take the same ink. */
    color: var(--mud-palette-error-text, #0b1220);
    background: var(--mud-palette-error, #ef5f63);
    border-radius: 4px;
    padding: 2px 8px;
    /* A slow red glow that swells and fades every few seconds — a calmer "this is live" than a
       fast blink. Kept on even for reduce-motion: it is a gentle, non-positional breathe, not the
       kind of movement that setting is meant to stop, and it is the badge's whole point. */
    animation: live-glow 8s ease-in-out infinite;
}

/* The time column packs its lines 2px apart, which suits plain text but crowds a badge: the Live
   pill sat almost touching the venue above it and the half-time line below. A filled shape needs
   its own breathing room, a little more above than below so it reads as belonging to the score
   line under it rather than to the venue. */
.match-time .badge-live,
.match-time .badge-status {
    margin-top: 4px;
    margin-bottom: 3px;
}

/* Fixture status badges, deliberately quieter and a touch smaller than the Live one —
   they sit in the time column beside the venue, not in the match's own headline. */
.badge-status {
    display: inline-flex;
    align-items: center;
    align-self: flex-start;
    /* Sized to fit inside the 76px time column on a phone, where it sits under the venue. */
    font-size: 0.62rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    border-radius: 4px;
    padding: 1px 5px;
    white-space: nowrap;
}

/* Cancelled: settled, nothing more is coming, so it recedes. */
.badge-status-off {
    color: var(--mud-palette-text-secondary, #8b98ab);
    background: color-mix(in srgb, var(--mud-palette-text-secondary, #8b98ab) 16%, transparent);
    border: 1px solid color-mix(in srgb, var(--mud-palette-text-secondary, #8b98ab) 32%, transparent);
}

/* Postponed or abandoned: still open questions, so they carry the warning colour. */
.badge-status-waiting {
    color: var(--mud-palette-warning, #f2b705);
    background: color-mix(in srgb, var(--mud-palette-warning, #f2b705) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--mud-palette-warning, #f2b705) 34%, transparent);
}

@keyframes live-glow {
    /* Plain rgba, not color-mix: Chrome does not interpolate color-mix() inside an animated
       property smoothly, which snapped the glow back to its resting colour with a visible jump.
       The colour is the error red (#ef5f63). Peak at the midpoint, so the swell and the fade
       take equal time and neither end jerks. */
    0%, 100% {
        box-shadow: 0 0 0 0 rgba(239, 95, 99, 0);
        filter: saturate(1);
    }
    50% {
        box-shadow: 0 0 18px 4px rgba(239, 95, 99, 0.85);
        filter: saturate(1.5);
    }
}

.saved-label {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.8rem;
    color: var(--mud-palette-success, #4ca36a);
    white-space: nowrap;
}

.tbd {
    color: var(--mud-palette-text-secondary, #8b98ab);
    font-style: italic;
    cursor: help;
}

.summary-strip {
    display: flex;
    gap: 28px;
    flex-wrap: wrap;
}

/* Tab panel padding (MudTabs no longer accepts a PanelClass attribute). */
.mud-tabs .mud-tabs-panels {
    padding: 16px;
}

/* Tighter tab headers: Material's 160px tab minimum wastes width, pushes tabs
   behind tiny scroll arrows and leaves dead space to the right. With natural
   widths every tab is visible at once — on phones too. (Swiping the panel
   content left/right also switches tabs; see ui.js.) */
.mud-tabs .mud-tab {
    /* !important: MudBlazor sets the 160px minimum with higher specificity. */
    min-width: 0 !important;
    padding: 10px 14px;
    /* Uppercase (Material's default look) reads better here; at natural widths
       all tabs still fit a 360px phone — measured 331px total. */
    font-weight: 600;
    font-size: 0.95rem;
    /* Never squeezed: a tab either fits or the strip scrolls to it. */
    flex: 0 0 auto;
}

/* The strip scrolls sideways under a finger. MudBlazor renders no scroll arrows here,
   because at natural widths it does not think the tabs overflow — so a tab that did stick
   out past the edge was simply unreachable. The scrollbar itself is hidden; the tabs are
   their own affordance. */
.mud-tabs .mud-tabs-tabbar-inner {
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.mud-tabs .mud-tabs-tabbar-inner::-webkit-scrollbar {
    display: none;
}

/* A chip inside a paragraph is a block-level flex container, so it stretched to the full
   width of its card and read as a button. This is the inline form. */
.inline-chip {
    display: inline-flex;
    width: fit-content;
    vertical-align: middle;
}

.match-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 8px;
}

.advancer-select {
    height: 36px;
    padding: 0 8px;
    font-size: 0.85rem;
    color: var(--mud-palette-text-primary, #e6ebf2);
    background: var(--mud-palette-background, #0b1220);
    border: 1px solid var(--sweeper-control-border);
    border-radius: 6px;
}

.own-bet {
    font-size: 0.85rem;
    color: var(--mud-palette-text-secondary, #8b98ab);
    white-space: nowrap;
}

.own-bet-missed {
    font-style: italic;
}

/* Fixed-width points number so the live pills line up vertically in the scoreboard. */
.points-value {
    display: inline-block;
    min-width: 34px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.points-pill {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 8px;
    white-space: nowrap;
    font-size: 0.75rem;
    font-weight: 700;
    border-radius: 10px;
    background: var(--mud-palette-lines-default, #22304a);
    color: var(--mud-palette-text-primary, #e6ebf2);
}

.points-pill-scored {
    background: var(--mud-palette-success, #4ca36a);
    color: #08130a;
}

/* A points total that includes a match still in play: the same amber as the dashed pill beside
   it, which is what explains the colour. A white total is official. The tournament page, the
   player page and My page all use it, so the three read the same way. */
.points-live {
    color: var(--mud-palette-primary, #f2b705);
}

.points-pill-provisional {
    background: transparent;
    border: 1px dashed var(--mud-palette-primary, #f2b705);
    color: var(--mud-palette-primary, #f2b705);
}

.own-row {
    background: color-mix(in srgb, var(--mud-palette-primary, #f2b705) 8%, transparent);
}

/* --- Tournament cards (home) --------------------------------------------------- */

.tournament-card {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-height: 168px;
    padding: 18px;
    border-radius: 14px;
    text-decoration: none;
    color: var(--mud-palette-text-primary, #e6ebf2);
    background: linear-gradient(150deg,
        color-mix(in srgb, var(--mud-palette-surface, #131c2b) 90%, white 10%) 0%,
        var(--mud-palette-surface, #131c2b) 55%,
        color-mix(in srgb, var(--mud-palette-surface, #131c2b) 96%, black 4%) 100%);
    border: 1px solid rgba(255, 255, 255, 0.07);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.08),
        0 10px 28px rgba(0, 0, 0, 0.35);
    overflow: hidden;
    transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}

/* Soft glass sheen across the upper half. */
.tournament-card::after {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 45%;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0));
    pointer-events: none;
}

.tournament-card:hover {
    transform: translateY(-3px);
    border-color: color-mix(in srgb, var(--mud-palette-primary, #f2b705) 40%, transparent);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.1),
        0 14px 34px rgba(0, 0, 0, 0.45);
}

@media (prefers-reduced-motion: reduce) {
    .tournament-card,
    .tournament-card:hover { transform: none; }
}

.tournament-card-top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 8px;
}

.tournament-card-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border-radius: 12px;
    color: var(--mud-palette-primary, #f2b705);
    background: color-mix(in srgb, var(--mud-palette-primary, #f2b705) 14%, transparent);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
}

.tournament-card-title {
    margin-top: 4px;
    font-size: 1.05rem;
    font-weight: 600;
    letter-spacing: 0.01em;
}

.tournament-card-meta {
    font-size: 0.85rem;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

.tournament-card-cta {
    margin-top: auto;
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--mud-palette-primary, #f2b705);
}

/* --- Player profiles ---------------------------------------------------------- */

.stat {
    display: flex;
    flex-direction: column;
    min-width: 56px;
}

.stat-value {
    font-size: 1.3rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.stat-label {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

.sparkline {
    display: block;
    width: 100%;
    max-width: 260px;
    height: 44px;
}

.sparkline polyline {
    fill: none;
    stroke: var(--mud-palette-primary, #f2b705);
    stroke-width: 2;
    stroke-linejoin: round;
    stroke-linecap: round;
    vector-effect: non-scaling-stroke;
}

.badge-chip {
    display: inline-block;
    padding: 2px 10px;
    font-size: 0.8rem;
    border: 1px solid var(--mud-palette-lines-default, #22304a);
    border-radius: 12px;
    background: var(--mud-palette-background, #0b1220);
}

.recap-line {
    padding: 2px 0;
}

.history-table {
    max-width: 860px;
}

.history-table th,
.history-table td {
    white-space: nowrap;
}

.history-score {
    font-variant-numeric: tabular-nums;
    text-align: center;
}

.all-bets {
    padding: 4px 12px 10px 108px;
    background: var(--mud-palette-surface, #131c2b);
    border-bottom: 1px solid var(--mud-palette-lines-default, #22304a);
}

/* The list is one grid and each row contributes its cells to it, so a column is the same
   width on every row without having to be a fixed width. The name column fits the longest
   name (capped, with an ellipsis past that), which puts the score right after the names
   instead of 150px to the right of a short one; the points column holds "+12 p live". */
.all-bets-list {
    display: grid;
    /* The last column stretches so the reaction summary can sit against the row's right
       edge, where the eye finds it in one place down the whole list rather than at a
       different spot per row. */
    grid-template-columns: fit-content(180px) 56px 84px 1fr;
    align-items: center;
    column-gap: 10px;
    row-gap: 6px;
    font-size: 0.85rem;
}

.all-bets-row {
    display: contents;
}

.all-bets-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The button that opens the palette stays with the pick; what has come in is pushed out to
   the right edge. */
.bet-reactions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
}

/* Reactions carry no frame: a border around each emoji was chrome on every row for nothing.
   The 24x24 minimum from WCAG 2.5.8 is still met — the target is transparent padding rather
   than a drawn box, so the tap area survives while the box does not. */
.reaction-add {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 28px;
    min-height: 28px;
    color: var(--mud-palette-text-secondary, #8b98ab);
    cursor: pointer;
}

.reaction-add:hover {
    color: var(--mud-palette-primary, #f2b705);
}

/* The palette, opened from that button. Two rows of three on a phone, one row on a desktop. */
.reaction-picker {
    display: flex;
    flex-wrap: wrap;
    max-width: 200px;
    padding: 4px;
}

.reaction-choice {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 40px;
    min-height: 40px;
    padding: 0;
    font-size: 1.25rem;
    line-height: 1;
    background: none;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    transition: transform 120ms ease;
}

.reaction-choice:hover {
    transform: scale(1.25);
}

/* One you have already given. With no box to tint, the mark is a dot beneath it — enough to
   read at a glance, and it costs no width. */
.reaction-choice.reaction-mine::after {
    content: "";
    position: absolute;
    bottom: 4px;
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--mud-palette-primary, #f2b705);
}

.reaction-choice.reaction-mine {
    position: relative;
}

/* What has come in, at the end of the row: the commonest faces and a total, no chip around them. */
.reaction-summary {
    display: inline-flex;
    align-items: center;
    gap: 1px;
    min-height: 28px;
    padding-left: 2px;
    font-size: 0.78rem;
    cursor: pointer;
}

.reaction-face {
    font-size: 0.9rem;
    line-height: 1;
}

.reaction-total {
    margin-left: 3px;
    color: var(--mud-palette-text-secondary, #8b98ab);
    font-variant-numeric: tabular-nums;
}

.reaction-summary:hover .reaction-total {
    color: var(--mud-palette-text-primary, #e6ebf2);
}

/* Both panels: the popover is only a frame for them, so it carries no padding of its own. */
.reaction-pop .mud-popover,
.reaction-pop {
    border-radius: 10px;
}

/* Who reacted with what, behind the summary. */
.reactor-panel {
    padding: 6px 10px;
    max-width: 260px;
}

.reactor-line {
    display: flex;
    align-items: baseline;
    gap: 6px;
    padding: 3px 0;
    font-size: 0.8rem;
}

.reactor-count {
    min-width: 14px;
    color: var(--mud-palette-text-secondary, #8b98ab);
    font-variant-numeric: tabular-nums;
}

.reactor-emoji {
    flex: 0 0 auto;
}

.reactor-names {
    color: var(--mud-palette-text-secondary, #8b98ab);
}

.all-bets-score {
    font-variant-numeric: tabular-nums;
    text-align: center;
}

.all-bets-points {
    color: var(--mud-palette-text-secondary, #8b98ab);
}

/* --- Admin participants ------------------------------------------------------- */

.participant-list {
    max-width: 860px;
}

.participant-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 4px 12px;
    padding: 8px 0;
    border-bottom: 1px solid var(--mud-palette-lines-default, #22304a);
}

.participant-info {
    display: flex;
    flex-direction: column;
    flex: 1 1 220px;
    min-width: 0;
}

.participant-email {
    font-size: 0.8rem;
    color: var(--mud-palette-text-secondary, #8b98ab);
    overflow-wrap: anywhere;
}

.participant-message {
    font-size: 0.8rem;
    font-style: italic;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

.participant-actions {
    display: flex;
    align-items: center;
    /* A row can grow an action as it goes — cancelling a fixture adds "Hand back" — so the
       set has to fold rather than run off the right edge. */
    flex-wrap: wrap;
    gap: 4px;
    flex-shrink: 0;
}

/* The duplicate played-matches switch at the played/upcoming border. No horizontal
   padding: the switch has to start on exactly the same line as the one at the top of
   the list, or the two read as sloppily stacked when both are on screen. */
.played-divider {
    display: flex;
    justify-content: flex-start;
    padding: 2px 0;
    margin: 4px 0;
    background: color-mix(in srgb, var(--mud-palette-surface, #131c2b) 60%, transparent);
    border-top: 1px dashed var(--mud-palette-lines-default, #22304a);
    border-bottom: 1px dashed var(--mud-palette-lines-default, #22304a);
}

/* --- Match comments -------------------------------------------------------------- */

.comment-toggle {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 28px;
    padding: 0 10px;
    /* Desktop: flows as the last item of the actions column, so it shares a
       line with "View predictions" (or the Save button). Phones give it a line
       of its own — see the media query. */
    font: inherit;
    font-size: 0.8rem;
    font-variant-numeric: tabular-nums;
    color: var(--mud-palette-text-secondary, #8b98ab);
    background: transparent;
    border: 1px solid var(--sweeper-control-border);
    border-radius: 10px;
    cursor: pointer;
}

.comment-icon {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    fill: currentColor;
}

.comment-toggle:hover {
    color: var(--mud-palette-text-primary, #e6ebf2);
}

.comment-toggle-open {
    color: var(--mud-palette-primary, #4f8ef7);
    border-color: var(--mud-palette-primary, #4f8ef7);
}

.match-comments {
    padding: 8px 12px 12px 108px;
    background: var(--mud-palette-surface, #131c2b);
    border-bottom: 1px solid var(--mud-palette-lines-default, #22304a);
}

.comment-empty {
    font-size: 0.85rem;
    font-style: italic;
    color: var(--mud-palette-text-secondary, #8b98ab);
    padding: 4px 0;
}

.comment-row {
    padding: 6px 0;
    border-bottom: 1px solid color-mix(in srgb, var(--mud-palette-lines-default, #22304a) 50%, transparent);
}

.comment-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.78rem;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

.comment-author {
    font-weight: 600;
    font-size: 0.82rem;
}

/* Pushed to the far end of the meta line and given a target of its own: a 24px icon button
   sitting right beside the author's link is easy to hit by accident on a phone. */
.comment-delete {
    margin-left: auto;
    min-width: 32px;
    min-height: 32px;
}

.comment-body {
    font-size: 0.9rem;
    overflow-wrap: anywhere;
    white-space: pre-line;
}

.comment-form {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 10px;
    max-width: 480px;
}

.comment-form .mud-input-control {
    flex: 1;
}

@media (max-width: 800px) {
    .match-comments { padding-left: 12px; }
}

.auto-tag {
    margin-left: 6px;
    padding: 0 6px;
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--mud-palette-text-secondary, #8b98ab);
    border: 1px dashed var(--mud-palette-lines-default, #22304a);
    border-radius: 8px;
}

/* --- Swish payment box ---------------------------------------------------------- */

.swish-qr {
    border-radius: 8px;
    background: #ffffff;
    padding: 6px;
}

.swish-info {
    flex: 1 1 240px;
    min-width: 0;
}

/* --- Pick distribution (shown with the public predictions) -------------------- */

.dist {
    padding: 6px 0 10px;
}

.dist-bar {
    display: flex;
    height: 6px;
    max-width: 420px;
    border-radius: 3px;
    overflow: hidden;
    background: var(--mud-palette-background, #0b1220);
}

.dist-seg.dist-home { background: var(--mud-palette-secondary, #7d97b5); }
.dist-seg.dist-draw { background: #4a586e; }
.dist-seg.dist-away { background: var(--mud-palette-primary, #f2b705); }

.dist-labels {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 8px;
}

/* Each outcome is its own bordered chip, so symbol and percentage read as one unit. */
.dist-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 10px;
    font-size: 0.78rem;
    color: var(--mud-palette-text-secondary, #8b98ab);
    background: var(--mud-palette-background, #0b1220);
    border: 1px solid var(--mud-palette-lines-default, #22304a);
    border-radius: 10px;
}

.dist-chip b {
    color: var(--mud-palette-text-primary, #e6ebf2);
}

.dist-most {
    margin-top: 6px;
    font-size: 0.78rem;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

.dist-most b {
    color: var(--mud-palette-text-primary, #e6ebf2);
}

.dist-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    flex-shrink: 0;
    border-radius: 50%;
}

/* --- How it works ---------------------------------------------------------------- */

.howto {
    display: flex;
    flex-direction: column;
    gap: 18px;
    max-width: 640px;
}

.howto-step {
    display: flex;
    gap: 14px;
    align-items: flex-start;
}

.howto-num {
    flex-shrink: 0;
    width: 34px;
    height: 34px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 800;
    color: var(--mud-palette-primary, #f2b705);
    background: color-mix(in srgb, var(--mud-palette-primary, #f2b705) 14%, transparent);
    border-radius: 50%;
}

/* --- Rank race chart ------------------------------------------------------------ */

/* The chart is drawn at its natural width and scrolls sideways inside this box. The two
   lightened edges are the affordance that says so — a shadow the usual way round would be
   darker than a background that is already nearly black, so it lifts instead. The first two
   layers are the covers: `local` attachment pins them to the content, so each one slides
   over its edge exactly when there is nothing left to scroll to on that side. The SVG has no
   background of its own, so the edges read through it. */
.rank-race {
    overflow-x: auto;
    padding-bottom: 4px;
    background:
        linear-gradient(to right, var(--mud-palette-background, #0b1220), transparent) left center,
        linear-gradient(to left, var(--mud-palette-background, #0b1220), transparent) right center,
        linear-gradient(to right, rgba(255, 255, 255, 0.13), transparent) left center,
        linear-gradient(to left, rgba(255, 255, 255, 0.13), transparent) right center;
    background-repeat: no-repeat;
    background-size: 30px 100%, 30px 100%, 18px 100%, 18px 100%;
    background-attachment: local, local, scroll, scroll;
}

.race-grid {
    stroke: color-mix(in srgb, var(--mud-palette-lines-default, #22304a) 70%, transparent);
    stroke-width: 1;
}

.race-axis {
    fill: var(--mud-palette-text-secondary, #8b98ab);
    font-size: 11px;
}

/* The pack shares one quiet ink, so the highlighted lines carry the story. */
.race-series {
    cursor: pointer;
    transition: opacity 0.15s ease;
}

.race-hit {
    fill: none;
    stroke: transparent;
    stroke-width: 14;
    pointer-events: stroke;
}

/* 45% of the secondary ink put the resting lines at 2.2:1 against the page; 65% is still
   clearly the background pack but reaches the 3:1 a meaningful graphic needs. */
.race-line {
    fill: none;
    stroke: color-mix(in srgb, var(--mud-palette-text-secondary, #8b98ab) 65%, transparent);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: stroke 0.15s ease, stroke-width 0.15s ease;
}

.race-end,
.race-node {
    fill: color-mix(in srgb, var(--mud-palette-text-secondary, #8b98ab) 70%, transparent);
    stroke: var(--mud-palette-surface, #131c2b); /* surface ring: stays legible where lines cross */
    stroke-width: 2;
}

.race-node {
    display: none; /* per-matchday dots only on the lines being followed */
}

.race-name {
    fill: var(--mud-palette-text-secondary, #8b98ab);
    font-size: 11px;
}

/* Followed line: pinned by tap/click (.race-focus) or hovered. It takes the player's own colour —
   the same one their avatar wears — which is the one place colour can be trusted here, since only
   one line carries it at a time. Colouring the whole pack was tried and abandoned: no five of the
   palette's hues clear the all-pairs separation floors against this surface, and a bump chart's
   lines cross constantly, so it would have promised a distinction it could not keep. */
.race-series:hover .race-line,
.race-focus .race-line {
    stroke: var(--race-colour, var(--mud-palette-text-primary, #e6ebf2));
    /* Exactly the pack's weight. Colour and the dimming of everyone else separate a followed line
       completely on their own — every extra half-pixel only made the chart heavier for nothing. */
    stroke-width: 2;
}

.race-series:hover .race-end,
.race-focus .race-end,
.race-series:hover .race-node,
.race-focus .race-node {
    fill: var(--race-colour, var(--mud-palette-text-primary, #e6ebf2));
    display: inline;
}

/* The name stays in ink rather than the series colour: text carries meaning, the mark beside it
   carries identity. */
.race-series:hover .race-name,
.race-focus .race-name {
    fill: var(--mud-palette-text-primary, #e6ebf2);
    font-weight: 700;
}

/* A long season packs the columns tighter than a full-sized marker can stand: at 26px apart a
   10px dot every matchday stopped reading as marks on a line and started reading as a dashed one.
   Shrinking them rather than dropping them keeps what they are for — showing where each matchday
   falls — while letting the line read as a line again. The marker at the end, which says where
   somebody finished, keeps its full size. */
.race-tight .race-node {
    r: 3;
    stroke-width: 1.5;
}



/* My own line: always the accent, painted on top, never dimmed. */
/* Your own line also wears your own colour, rather than the app's gold. Gold is one of the eight
   player colours, so a player who happened to own it looked exactly like you whenever they were
   followed. One rule — everybody's colour is theirs — has no such collision, and you are still the
   obvious line: coloured while the pack is grey, thicker, and drawn on top of everyone. */
.race-mine .race-line,
.race-mine:hover .race-line,
.race-mine.race-focus .race-line {
    stroke: var(--race-colour, var(--mud-palette-primary, #f2b705));
    /* Was 4px, to tell your line from a followed one back when the two could share a colour. No
       two players share a colour any more, so the weight bought nothing; every line is 2px and
       colour alone says which is which. */
    stroke-width: 2;
}

.race-mine .race-end,
.race-mine .race-node,
.race-mine:hover .race-end,
.race-mine:hover .race-node,
.race-mine.race-focus .race-end,
.race-mine.race-focus .race-node {
    fill: var(--race-colour, var(--mud-palette-primary, #f2b705));
    display: inline;
}

.race-mine .race-name {
    fill: var(--mud-palette-text-primary, #e6ebf2);
    font-weight: 700;
    font-size: 12px;
}

/* While one line is followed, the rest of the pack steps back. */
.race-dim,
.rank-race svg:has(.race-series:hover) .race-series:not(:hover):not(.race-focus):not(.race-mine) {
    opacity: 0.3;
}

.race-series:hover {
    opacity: 1;
}

/* --- The goal tiebreak row ----------------------------------------------------- */

.tiebreak-block {
    max-width: 880px;
}

.tiebreak-row {
    display: flex;
    align-items: center;
    gap: 10px;
}

.tiebreak-field {
    width: 92px;
    flex: 0 0 auto;
}

/* --- Winner podium (finished tournaments) -------------------------------------- */

.podium {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: 14px;
    margin: 20px 0 10px;
}

.podium-place {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    min-width: 0;
    max-width: 34%;
}

.podium-medal {
    font-size: 2rem;
    line-height: 1.2;
}

.podium-name {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
}

.podium-points {
    font-size: 0.85rem;
    color: var(--mud-palette-text-secondary, #8b98ab);
    font-variant-numeric: tabular-nums;
}

.podium-prize {
    font-size: 0.9rem;
    font-weight: 700;
    color: var(--mud-palette-primary, #f2b705);
}

.podium-step {
    width: 92px;
    max-width: 100%;
    margin-top: 8px;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding-top: 6px;
    font-size: 1.2rem;
    font-weight: 800;
    /* Large bold text, so 3:1 is the bar — 60% of the secondary ink missed it at 2.9:1. */
    color: color-mix(in srgb, var(--mud-palette-text-secondary, #8b98ab) 70%, transparent);
    background: linear-gradient(180deg, color-mix(in srgb, var(--mud-palette-primary, #f2b705) 16%, var(--mud-palette-surface, #131c2b)), var(--mud-palette-surface, #131c2b));
    border: 1px solid var(--mud-palette-lines-default, #22304a);
    border-radius: 8px 8px 0 0;
}

.podium-step-1 { height: 96px; }
.podium-step-2 { height: 68px; }
.podium-step-3 { height: 48px; }

/* Wide tables scroll inside their own container instead of the page. */
.standings-table,
/* The same measure as the match list and the rank race, so the scoreboard does not run the
   full width of a desktop window while everything under it stops short. */
.scoreboard-table,
.scoreboard-note {
    max-width: 880px;
}

.scoreboard-table .mud-table-container {
    overflow-x: auto;
}

/* Read-once reference — what the columns mean and what a pick is worth — folded away so it stops
   sitting between the standings and everything below them. Deliberately quiet: it should read as
   a footnote you can open, not as a control competing with the table. */
.board-key > summary {
    cursor: pointer;
    padding: 4px 0;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

.board-key > summary:hover {
    color: var(--mud-palette-text-primary, #e6ebf2);
}

.board-key[open] > summary {
    margin-bottom: 2px;
}

/* The scoring list is a second thought from the column key, so a hairline separates them. */
.board-key .column-key + .column-key {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--mud-palette-divider, rgba(255, 255, 255, 0.08));
}

/* The key to the scoreboard's columns: one line each. As a single run-on sentence it took
   re-reading to find the column you were actually after. */
.column-key {
    margin: 0 0 0.5rem;
    font-size: 0.75rem;
    line-height: 1.55;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

/* The phone's pointer to the player page is a hint, not another column, so it stands slightly
   apart from the lines above it. */
.column-key .narrow-only {
    margin-top: 0.35rem;
}

.column-key > div {
    display: flex;
    gap: 6px;
}

.column-key dt {
    /* A shared measure puts every description at the same left edge, which is most of what
       makes a list like this quicker to read than a paragraph. */
    flex: 0 0 3.6rem;
    font-weight: 600;
    color: var(--mud-palette-text-primary, #e6ebf2);
}

.column-key dd {
    margin: 0;
}

/* Scoped rather than global helpers: these two only ever mean "the columns this width shows".
   The 800px line is the same one that hides the Exact and Goals columns — explaining a column
   that is not on screen was the confusing part. */
.column-key .narrow-only {
    display: none;
}

@media (max-width: 800px) {
    .column-key .wide-only {
        display: none;
    }

    .column-key .narrow-only {
        display: flex;
    }
}

.standings-table th,
.standings-table td,
.scoreboard-table th,
.scoreboard-table td:last-child,
.scoreboard-table .points-cell,
.scoreboard-table .goals-cell {
    white-space: nowrap;
}

/* The tiebreak guess is a column of numbers, so the digits line up. */
.scoreboard-table .goals-cell {
    font-variant-numeric: tabular-nums;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

@media (max-width: 800px) {
    /* Phones: an admin row's actions take a full-width line of their own beneath the person or
       fixture they belong to. Pinned beside it they were simply pushed off the right edge, out
       of reach, which is what hid the button that appears once a match is cancelled. */
    .participant-actions {
        flex-basis: 100%;
        justify-content: flex-start;
    }
}

@media (max-width: 800px) {
    /* The stacked layout carries over; only the actions move to their own footer row. */
    .match-row {
        grid-template-columns: 76px 1fr 56px;
        grid-template-areas:
            "time teams scores"
            "actions actions actions";
        row-gap: 6px;
        padding: 10px 12px;
    }

    .match-time { grid-area: time; }
    .match-teams { grid-area: teams; padding-left: 32px; }
    .match-scores { grid-area: scores; }

    /* The narrow phone time column gets a third line before the venue truncates. */
    .match-venue { -webkit-line-clamp: 3; line-clamp: 3; }

    /* Provider short names replace full names on small screens. */
    .match-team-line:has(> .team-short) .team-full { display: none; }
    .team-short { display: inline; }

    /* Phones: the actions hug the right edge, so "View predictions" (or Save) ends on
       the same vertical line as the score box above it — that box is 48px wide and
       centered in its 56px column, hence the 4px inset. */
    .match-actions {
        grid-area: actions;
        justify-content: flex-end;
        row-gap: 6px;
        padding-right: 4px;
    }

    /* Explicit running order for the whole column, so the layout below never depends on
       which of the buttons, selects and labels a given match happens to show. */
    .match-actions > * {
        order: 3;
    }

    /* The prediction text opens the column and keeps the left of its line. */
    .match-actions > .own-bet {
        order: 0;
        margin-right: auto;
    }

    /* With a prediction shown, a zero-height full-width item breaks the line after it, so
       the bubble and "View predictions" reliably share the line below rather than being
       packed onto one line at wider phone widths. */
    .match-actions:has(> .own-bet)::after {
        content: "";
        order: 1;
        flex-basis: 100%;
        height: 0;
    }

    /* The bubble opens that line on the left, and its auto margin pushes whatever follows
       (View predictions, Save, Highlights) to the right edge. It used to be pinned to the
       row's right edge, where a bubble carrying a comment count reached back over the
       "View predictions" button and the two overlapped. */
    .match-actions > .comment-toggle {
        order: 2;
        margin-right: auto;
    }

    .all-bets { padding-left: 12px; }

    /* Reactions used to drop onto a line of their own here, because four permanent buttons could
       not share a phone's width with the pick. One small button and a short summary can, so the
       row stays a row — a list of picks reads far better without every other line being reactions.
       Tighter columns buy the width that needs. */
    .all-bets-list {
        grid-template-columns: fit-content(120px) 52px minmax(40px, auto) 1fr;
        column-gap: 6px;
        row-gap: 2px;
    }

    /* Bigger touch targets for thumbs. The 16px font matters: anything smaller
       makes iOS Safari auto-zoom the page when a field gets focus. */
    .score-box {
        width: 48px;
        height: 36px;
        font-size: 16px;
    }

    .advancer-select {
        height: 40px;
        font-size: 16px;
    }

    /* The rest of the row's controls, all measured cramped at 390px: the reaction pills
       were 28x22, the comment bubble 28 high and every Size.Small button 31. These bring
       them to the 32–40 a thumb wants. Not 44: that is the AAA level, and the rows do not
       have the height to spend on it. */
    .reaction-add,
    .reaction-summary {
        min-width: 32px;
        min-height: 32px;
    }

    .comment-toggle {
        height: 38px;
        padding: 0 12px;
    }

    .comment-delete {
        min-width: 36px;
        min-height: 36px;
    }

    /* Text buttons only — an icon button's own square is set where it is used. */
    .mud-button-root.mud-button-filled-size-small:not(.mud-icon-button),
    .mud-button-root.mud-button-outlined-size-small:not(.mud-icon-button),
    .mud-button-root.mud-button-text-size-small:not(.mud-icon-button) {
        min-height: 38px;
    }

    .summary-strip { gap: 16px; }
    .summary-strip .stat-value { font-size: 1.05rem; }

    /* Tighter scoreboard cells and pills so the columns fit a phone screen. MudBlazor puts 16px
       and 24px of padding on the cells themselves, which is 160px of a 390px screen spent on
       air — the earlier .mud-table-cell selector never matched and this is why the columns
       overflowed. */
    .scoreboard-table .mud-table-container .mud-table-row th.mud-table-cell,
    .scoreboard-table .mud-table-container .mud-table-row td.mud-table-cell {
        padding: 10px 6px;
        /* MudBlazor sets the logical properties too, so setting only `padding` loses. */
        padding-inline: 6px;
    }
    .points-pill { margin-left: 4px; padding: 0 6px; font-size: 0.7rem; }
    .pill-live-label { display: none; }

    /* The tiebreak guess decides nothing until the tournament ends, and the running total is
       written under the table anyway — so on a phone the width goes to the columns that count.
       Counted in the DOM, so hiding the exact-scores column above does not shift this. */
    .scoreboard-table th.mud-table-cell:nth-child(5),
    .scoreboard-table td.mud-table-cell:nth-child(5) { display: none; }

    /* One line per row, so the rows keep an even height. Names that still do not fit are cut
       with an ellipsis rather than wrapping — the row links to the player's own page anyway. */
    .scoreboard-table th.mud-table-cell:nth-child(2),
    .scoreboard-table td.mud-table-cell:nth-child(2) {
        max-width: 150px;
        min-width: 118px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    /* Phones get the width back that desktop spends on breathing room. */
    .mud-tabs .mud-tabs-panels { padding: 12px 4px; }

    /* All four tab headers fit a 390px screen without scroll arrows. */
    .mud-tabs .mud-tab { padding: 10px 9px; font-size: 0.85rem; }
}

@media (max-width: 560px) {
    /* The app bar must stay a single row: drop the username (the account is
       obvious to its owner) and tighten the paddings around what remains. */
    .appbar-username { display: none; }
    .mud-appbar .mud-toolbar { padding-left: 10px; padding-right: 6px; }
    .logout-button { padding: 4px 10px; }
    .appbar-title { font-size: 1.05rem; margin-left: 0 !important; }
    .mud-appbar .mud-icon-button { padding: 8px; }

    /* …except the menu button, which is the only way into navigation on a phone. The
       tightened padding above left it about 40px; a 24px icon plus 10px keeps it at 44. */
    .mud-appbar .menu-button {
        min-width: 44px;
        min-height: 44px;
        padding: 10px;
    }

    /* The exact-scores column doesn't fit a phone screen — dropping it lets the
       scoreboard show rank, player, points and prize without sideways scrolling.
       The stat is still on the player profile. */
    .scoreboard-table th:nth-child(4),
    .scoreboard-table td:nth-child(4) { display: none; }
}

@media (max-width: 380px) {
    /* At 320px the four tab labels no longer fit the 288px the page padding leaves, so the
       strip scrolls — and the active label was getting clipped at the edge. Dropping the
       uppercasing and the letter spacing is what buys the room back; the scroll padding
       keeps a whole label on screen when the strip is scrolled to either end. */
    .mud-tabs .mud-tab {
        padding: 10px 6px;
        font-size: 0.8rem;
        letter-spacing: 0;
        text-transform: none;
    }

    .mud-tabs .mud-tabs-tabbar-inner {
        scroll-padding-inline: 8px;
    }
}

@media (max-width: 360px) {
    /* On the narrowest screens the ball alone carries the brand. */
    .appbar-name { display: none; }
}

/* --- My page: device list ------------------------------------------------------- */

.device-list {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Name and status share the first line; the dates wrap onto their own full-width line,
   so nothing is ever cut off however narrow the screen is. */
.device-row {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 2px 8px;
    padding: 8px 0;
    border-bottom: 1px solid color-mix(in srgb, var(--mud-palette-lines-default, #22304a) 60%, transparent);
}

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

.device-name {
    font-size: 0.875rem;
    font-weight: 600;
    min-width: 0;
    overflow-wrap: anywhere;
}

.device-status {
    margin: 0;
    justify-self: end;
}

.device-meta {
    grid-column: 1 / -1;
    font-size: 0.8rem;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

/* --- How it works: the badge catalogue ------------------------------------------- */

.badge-catalogue {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 10px 20px;
    max-width: 640px;
}

.badge-entry {
    display: flex;
    align-items: baseline;
    gap: 10px;
}

.badge-entry-emoji {
    font-size: 1.15rem;
    line-height: 1;
}

.badge-entry-name {
    display: block;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--mud-palette-text-primary, #e6ebf2);
}

.badge-entry-desc {
    display: block;
    font-size: 0.8rem;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

/* --- Admin: the manual overrides, each with its own explanation ------------------- */

.admin-actions {
    display: flex;
    flex-direction: column;
    gap: 12px;
    max-width: 720px;
}

/* The button keeps a fixed column so the explanations line up as a readable list. */
.admin-action {
    display: grid;
    grid-template-columns: 210px 1fr;
    align-items: start;
    gap: 4px 14px;
}

.admin-action-help {
    font-size: 0.8rem;
    line-height: 1.55;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

.admin-action-help b {
    display: block;
    color: var(--mud-palette-text-primary, #e6ebf2);
    font-weight: 600;
}

@media (max-width: 800px) {
    /* Phones: the button sits above its explanation instead of beside it. */
    .admin-action {
        grid-template-columns: 1fr;
        justify-items: start;
    }
}

/* --- Admin: the allowed email domains ------------------------------------------- */

.domain-list {
    display: flex;
    flex-wrap: wrap;
    gap: 2px;
    margin: 2px 0;
}

/* --- Admin: what an admin can see about one user --------------------------------- */

/* Indented under the user's row, with a rule tying it to the row it belongs to. */
.user-detail {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 14px 28px;
    margin: 0 0 10px 12px;
    padding: 10px 0 12px 14px;
    border-left: 2px solid color-mix(in srgb, var(--mud-palette-primary, #f2b705) 45%, transparent);
}

.user-detail-heading {
    display: block;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--mud-palette-text-secondary, #8b98ab);
    margin-bottom: 4px;
}

.user-detail-line {
    display: block;
    font-size: 0.8rem;
    line-height: 1.55;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

.user-detail-line b {
    color: var(--mud-palette-text-primary, #e6ebf2);
    font-weight: 600;
}

.user-detail-empty {
    font-size: 0.8rem;
    font-style: italic;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

/* The notification indicator beside a user's name. */
.user-flag {
    vertical-align: text-bottom;
    margin-left: 4px;
}

/* --- My page: the tournaments you are in ---------------------------------------- */

/* One grid for every row in the box, so the rank and points columns share edges down the
   list instead of each row aligning to its own content. */
.my-tournaments {
    display: grid;
    grid-template-columns: 1fr auto auto;
    align-items: baseline;
    column-gap: 14px;
}

.my-tournament-name,
.my-tournament-rank,
.my-tournament-points {
    padding-top: 9px;
}

.my-tournament-name {
    font-size: 0.9rem;
    font-weight: 600;
    min-width: 0;
}

/* The two number columns are right-aligned with lining digits, so 1st of 1 and 10th of 11
   end on the same pixel. One weight each: the points carry the emphasis, nothing else. */
.my-tournament-rank,
.my-tournament-points {
    font-size: 0.85rem;
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.my-tournament-rank {
    color: var(--mud-palette-text-secondary, #8b98ab);
}

.my-tournament-points {
    min-width: 42px;
    font-weight: 600;
    color: var(--mud-palette-text-primary, #e6ebf2);
}

/* Last cell of each row: carries the row's divider, and the note when there is one. */
.my-tournament-note {
    grid-column: 1 / -1;
    padding-bottom: 9px;
    font-size: 0.8rem;
    color: var(--mud-palette-text-secondary, #8b98ab);
    border-bottom: 1px solid color-mix(in srgb, var(--mud-palette-lines-default, #22304a) 60%, transparent);
}

.my-tournament-note:empty {
    padding-bottom: 9px;
}

.my-tournament-note:last-child {
    border-bottom: none;
    padding-bottom: 0;
}

/* Settings boxes: one text size for the explanations, switch labels included, so a box does
   not read as three different kinds of text. */
.settings-rows .mud-switch .mud-typography,
.settings-rows label.mud-switch .mud-switch-label {
    font-size: 0.875rem;
}

@media (max-width: 800px) {
    /* Four stats read as two even rows rather than three and a lonely fourth. */
    .summary-strip {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 10px 16px;
    }
}

/* The dismiss layer for an open reaction panel: it only needs to catch a press, so it stays
   invisible and sits below the popovers it closes. */
.reaction-overlay {
    background: transparent;
    z-index: 1200;
}

/* --- Suggestions and bugs ---------------------------------------------------- */

.feedback-form {
    max-width: 520px;
}

/* MudBlazor's own 1rem made this form read much heavier than the pages around it, which sit at
   0.85-0.87rem. Scoped to the form so no other input moves. */
/* The size comes down from .mud-input, which carries MudBlazor's subtitle1 typography at 1rem
   and is what the field inherits — setting it on the input alone changed nothing. */
.feedback-form .mud-input,
.feedback-form input.mud-input-slot,
.feedback-form textarea.mud-input-slot,
.feedback-form label.mud-input-label {
    font-size: 0.875rem;
}

.feedback-form .mud-input-helper-text {
    font-size: 0.72rem;
}

.feedback-form .mud-radio-group label {
    font-size: 0.875rem;
}

@media (max-width: 800px) {
    /* Back to 16px on a phone: anything smaller makes iOS Safari zoom the page in when a field
       takes focus, the same reason the score boxes hold that size. */
    .feedback-form .mud-input,
    .feedback-form input.mud-input-slot,
    .feedback-form textarea.mud-input-slot,
    .feedback-form label.mud-input-label {
        font-size: 16px;
    }
}

.feedback-actions {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

.feedback-notice {
    font-size: 0.8rem;
}

.feedback-sent {
    color: var(--mud-palette-success, #4ca36a);
}

.feedback-problem {
    color: var(--mud-palette-warning, #f2b705);
}

.feedback-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: 720px;
}

.feedback-item {
    padding: 10px 12px;
    background: var(--mud-palette-surface, #0e1626);
    border-radius: 8px;
    /* A quiet left edge, gold only while it is still unread — see .feedback-new. */
    border-left: 3px solid transparent;
}

.feedback-new {
    border-left-color: var(--mud-palette-primary, #f2b705);
}

.feedback-item-head {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.feedback-title {
    font-weight: 600;
}

/* The kind reads as a word, not a coloured box: an idea and a bug want telling apart at a
   glance without two more frames on the page. */
.feedback-kind {
    font-size: 0.65rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.feedback-kind-idea {
    color: var(--mud-palette-primary, #f2b705);
}

.feedback-kind-bug {
    color: var(--mud-palette-error, #ef5f63);
}

.feedback-handled {
    font-size: 0.7rem;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

.feedback-body {
    margin-top: 4px;
    font-size: 0.87rem;
    /* Keeps the line breaks someone typed, without letting a long word break the layout. */
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.feedback-when {
    margin-top: 6px;
    font-size: 0.72rem;
    color: var(--mud-palette-text-secondary, #8b98ab);
}

/* MudBlazor hangs a tab's badge past the label's right edge, but the tab itself clips whatever
   overflows — so the count came out with its right side sliced off. The tab stops clipping, and
   a tab that carries a badge gets the room the number needs so it cannot touch the next one. */
.mud-tabs .mud-tab {
    overflow: visible;
}

.mud-tabs .mud-tab:has(.mud-badge) {
    padding-right: 30px;
}

/* The account header puts the mark beside the name rather than above it, so the block reads as
   one identity on a phone as well as a desktop. */
.account-identity {
    display: flex;
    align-items: flex-start;
    gap: 12px;
}
