/* The `hidden` attribute must win. It is only `display: none` from the UA
   stylesheet, so any rule that sets `display` on the same element overrides it
   -- `.editgrid-columns { display: grid }` did exactly that, leaving a hidden
   container fully visible. Anything toggled with `el.hidden` relies on this. */
[hidden] {
    display: none !important;
}

/* ── Design Tokens ─────────────────────────────────────────── */
:root {
    /* Colors */
    --color-primary: #531882;
    --color-primary-hover: #3d1160;
    --color-primary-light: #f3e8fb;
    --color-danger: #c0392b;
    --color-danger-hover: #a93226;
    --color-success-bg: #d4edda;
    --color-success-text: #155724;
    --color-error-bg: #f8d7da;
    --color-error-text: #721c24;
    --color-muted: #666;
    --color-muted-bg: #999;
    --color-link: #0066cc;
    --color-border: #ccc;
    --color-bg-subtle: #f5f5f5;
    --color-bg-form: #f8f9fa;
    --color-bg-form-border: #dee2e6;
    --color-bg-hover: #f0f0f0;
    --color-test-bg: #fffde7;
    --color-test-badge: #ffc107;
    --color-deleted-bg: #fdecea;

    /* Round status colors */
    /* Status colours. Each is used as text on its own -light background
       (badges, resting round tabs) and as a background under white text
       (active round tab), so both directions have to clear WCAG AA 4.5:1.
       These are the original hues scaled toward black by the least amount
       that gets there — verified with Lighthouse, don't lighten them back. */
    --color-finished: #56707d;
    --color-finished-light: #eceff1;
    --color-in-progress: #aa5d19;
    --color-in-progress-light: #fef5e7;
    --color-pairable: #2574a8;
    --color-pairable-light: #ebf5fb;
    --color-published: #187e46;
    --color-published-light: #e6f6ee;
    --color-future: #657071;
    --color-future-light: #f2f3f4;

    /* Surfaces */
    --surface: #f6f5f3;          /* warm off-white page background */
    --surface-raised: #ffffff;   /* cards, table headers, sticky bars */
    --text: #1d1d1f;

    /* Spacing */
    --space-xs: 4px;
    --space-sm: 8px;
    --space-md: 16px;
    --space-lg: 24px;
    --space-xl: 32px;

    /* Radii */
    --radius-sm: 3px;
    --radius: 4px;

    /* Typography (font phase will swap these families; tokens stay) */
    --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    --font-display: var(--font-family);
    --font-body: var(--font-family);
    --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    --line-height: 1.45;

    /* Layout */
    --content-max: 1440px;       /* content fills the laptop, capped here */
    --pad-inline: clamp(16px, 4vw, 40px);
    --header-h: 88px;            /* approx sticky header height (scroll offset math) */
    --measure: 640px;           /* narrow column for form-only pages */
}

/* ── Base ──────────────────────────────────────────────────── */
body {
    font-family: var(--font-body);
    margin: 0;
    padding: 0;
    line-height: var(--line-height);
    background: var(--surface);
    color: var(--text);
}

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

h1 { font-size: 1.6rem; margin: 0 0 var(--space-md); }
h2 { font-size: 1.2rem; margin: var(--space-lg) 0 var(--space-sm); }
h3 { font-size: 1.02rem; margin: var(--space-md) 0 var(--space-sm); }

/* ── App shell ─────────────────────────────────────────────── */
.app-header {
    position: sticky;
    top: 0;
    z-index: 50;
    background: var(--surface-raised);
    border-bottom: 1px solid var(--color-border);
}

.app-main {
    display: block;
}

.page {
    max-width: var(--content-max);
    margin-inline: auto;
    padding: var(--space-lg) var(--pad-inline) var(--space-xl);
}

.page--narrow {
    max-width: var(--measure);
}

/* ── Embed (public published pages) ────────────────────────── */
.embed-header {
    padding: var(--space-md) var(--pad-inline) 0;
}

.embed-logo-link {
    display: inline-block;
}

.embed-logo {
    height: 44px;
    width: auto;
}

.embed-page {
    max-width: var(--measure);
    padding: var(--space-lg) var(--pad-inline) var(--space-xl);
}

.embed-eyebrow {
    margin: 0 0 var(--space-xs);
    font-size: 0.85rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-muted);
}

.pairings-updated {
    margin: 0 0 var(--space-md);
    font-size: 0.85rem;
    color: var(--color-muted);
}

/* Round jump-links */
.round-nav {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs) var(--space-md);
    margin-bottom: var(--space-md);
    padding-bottom: var(--space-sm);
    border-bottom: 1px solid var(--color-border);
    font-size: 0.9rem;
}

/* Give every round's pairings table identical column widths. Default (auto)
   layout sizes each table to its own content, so rounds with longer names get
   wider columns and the tables don't line up with each other. A fixed layout
   with set widths for the Table number and action columns leaves the two name
   columns to split the remaining space equally, identically in every table. */
.round-section table {
    table-layout: fixed;
}

.round-section th:first-child {
    width: 5rem;
}

.round-section th.no-print {
    width: 12rem;
}

/* Keep the round heading visible while scrolling that round's table.
   Scoped to the embed page: the in-nav tab has its own sticky app header
   that this would slide behind. */
.embed-page .round-section h2 {
    position: sticky;
    top: 0;
    z-index: 1;
    margin: 0;
    padding: var(--space-sm) 0;
    background: var(--surface);
}

/* ── Print ─────────────────────────────────────────────────── */
@media print {
    .no-print {
        display: none !important;
    }

    body {
        background: #fff;
    }

    .embed-header {
        padding: 0 0 var(--space-sm);
    }

    .embed-page {
        max-width: none;
        padding: 0;
    }

    .round-section {
        break-inside: avoid;
    }

    /* Clear the per-row rules, then draw only the heading/body separator and the
       table's bottom edge. Both lines are drawn on tbody `td` cells: Firefox
       won't print border-collapse borders on `thead`/`th` cells, so the header
       separator is the first body row's top border rather than the th's bottom. */
    .embed-page th,
    .embed-page td {
        border-bottom: none;
    }

    .embed-page tbody tr:first-child td {
        border-top: 1px solid var(--text);
    }

    .embed-page tbody tr:last-child td {
        border-bottom: 1px solid var(--text);
    }

    /* Anchored sticky headers waste ink and can overlap on paper */
    .embed-page .round-section h2 {
        position: static;
        background: none;
    }
}

/* ── Navbar ────────────────────────────────────────────────── */
.navbar {
    max-width: var(--content-max);
    margin-inline: auto;
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    padding: var(--space-sm) var(--pad-inline);
    font-size: 0.9em;
}

.nav-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.nav-left {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    white-space: nowrap;
}

.nav-brand {
    font-weight: bold;
    color: var(--color-primary);
    text-decoration: none;
}

.nav-brand:hover {
    text-decoration: underline;
}

.nav-sep {
    color: var(--color-border);
    user-select: none;
}

.nav-left a {
    text-decoration: none;
    color: var(--color-link);
}

.nav-left a:hover {
    text-decoration: underline;
}

.nav-tabs {
    display: flex;
    align-items: center;
    gap: 2px;
    flex-wrap: wrap;
}

.nav-tab {
    text-decoration: none;
    color: var(--color-muted);
    padding: var(--space-xs) var(--space-sm);
    border-radius: var(--radius);
    font-size: 0.9em;
}

.nav-tab:hover {
    background-color: var(--color-bg-subtle);
    color: var(--color-primary);
}

.nav-tab.active {
    background-color: var(--color-primary);
    color: white;
}

.nav-tab-sep {
    display: inline-block;
    width: 1px;
    height: 1em;
    background-color: var(--color-border);
    margin: 0 var(--space-xs);
    vertical-align: middle;
}

/* Secondary nav menu ("Settings ▾"), built on a native <details> so it opens
   with no JS. The panel floats below the summary; a click on the summary
   toggles it, and datastar closes it on an outside click. */
.nav-menu {
    position: relative;
}

.nav-menu-summary {
    list-style: none;   /* drop the default disclosure triangle */
    cursor: pointer;
    user-select: none;
}

.nav-menu-summary::-webkit-details-marker {
    display: none;
}

/* Keep the summary highlighted (like a hovered tab) while the menu is open. */
.nav-menu[open] > .nav-menu-summary:not(.active) {
    background-color: var(--color-bg-subtle);
    color: var(--color-primary);
}

.nav-menu-caret {
    font-size: 1.2em;
}

.nav-menu-panel {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 10;
    margin-top: var(--space-xs);
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 160px;
    padding: var(--space-xs);
    background-color: var(--surface-raised);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}

.nav-menu-item {
    text-decoration: none;
    color: var(--color-muted);
    padding: var(--space-xs) var(--space-sm);
    border-radius: var(--radius);
    font-size: 0.9em;
    white-space: nowrap;
}

.nav-menu-item:hover {
    background-color: var(--color-bg-subtle);
    color: var(--color-primary);
}

.nav-menu-item.active {
    background-color: var(--color-primary);
    color: white;
}

.nav-right {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    white-space: nowrap;
}

.nav-right a {
    text-decoration: none;
    color: var(--color-link);
}

.nav-right a:hover {
    text-decoration: underline;
}

.nav-logout-form {
    display: inline;
    margin: 0;
    padding: 0;
}

.nav-link-btn {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: var(--color-link);
    cursor: pointer;
    font-size: inherit;
}

.nav-link-btn:hover {
    text-decoration: underline;
    background: none;
}

.nav-logo {
    height: 28px;
    width: auto;
    vertical-align: middle;
}

/* ── Messages ─────────────────────────────────────────────── */
.messages {
    list-style: none;
    padding: 0;
}

.messages li {
    padding: 10px;
    margin-bottom: 10px;
    border-radius: var(--radius);
}

.messages .success {
    background-color: var(--color-success-bg);
    color: var(--color-success-text);
}

.messages .error {
    background-color: var(--color-error-bg);
    color: var(--color-error-text);
}

/* Inline validation error (e.g. an inline-edit form), highlighted like a flash
   message but usable on its own outside the .messages list. */
.error {
    background-color: var(--color-error-bg);
    color: var(--color-error-text);
    border: 1px solid var(--color-danger);
    border-radius: var(--radius);
    padding: 8px 12px;
}

/* Confirmation shown after a result is saved, with an Edit button to fix it. */
.result-saved {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    background-color: var(--color-success-bg);
    color: var(--color-success-text);
    border-radius: var(--radius);
    padding: 8px 12px;
    margin-bottom: 15px;
}

/* ── Forms ─────────────────────────────────────────────────── */
form p {
    margin-bottom: 15px;
}

label {
    display: block;
    margin-bottom: 5px;
    font-weight: bold;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="date"],
textarea {
    width: 100%;
    padding: var(--space-sm);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    box-sizing: border-box;
}

textarea {
    resize: vertical;
}

/* Native <select> and number inputs default to the browser's form-control font
   (~13px), which reads smaller than the surrounding body text. Inherit the page
   font and match the text-input padding/border so they line up in size. Width is
   left alone so inline selects (e.g. the fixed-pairing pickers) and compact
   score fields keep their natural sizing. */
select,
input[type="number"] {
    font: inherit;
    padding: var(--space-sm);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    box-sizing: border-box;
}

/* Explore comparison: the what-if and actual round tables side by side (stacked
   on narrow screens). */
.explore-compare {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-lg);
    align-items: start;
}
.explore-compare .pairings-main {
    max-width: none;
}
@media (max-width: 980px) {
    .explore-compare {
        grid-template-columns: 1fr;
    }
}

/* A pairing that appears on both the what-if and actual side. */
.common-mark {
    color: var(--color-finished);
    font-weight: bold;
}

/* The earlier rounds a repeat pairing already met in, shown in the Repeats cell:
   inline next to the count on wide screens, stacked under it on narrow. */
.repeat-rounds {
    font-size: 0.75em;
    font-weight: normal;
    color: var(--color-muted);
    white-space: nowrap;
    margin-left: 0.5em;
}

@media (max-width: 980px) {
    .repeat-rounds {
        display: block;
        margin-left: 0;
    }
}

.errorlist {
    color: var(--color-error-text);
    list-style: none;
    padding: 0;
    margin: 5px 0;
}

/* ── Buttons ──────────────────────────────────────────────── */
button,
input[type="submit"] {
    background-color: var(--color-primary);
    color: white;
    padding: 8px 14px;
    border: none;
    border-radius: var(--radius);
    cursor: pointer;
    font: inherit;
}

button:hover,
input[type="submit"]:hover {
    background-color: var(--color-primary-hover);
}

.btn-secondary {
    display: inline-block;
    background-color: #fff;
    color: var(--color-primary);
    border: 1px solid var(--color-primary);
    padding: 6px 14px;
    border-radius: var(--radius);
    cursor: pointer;
    font: inherit;
    text-decoration: none;
}

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

.btn-secondary:disabled {
    background-color: #fff;
    color: var(--color-border);
    border-color: var(--color-border);
    cursor: default;
}

.btn-secondary:disabled:hover {
    background-color: #fff;
}

/* Filled purple button, used on anchors that should read as a primary action
   (e.g. the "Submit results" links in the pairings table). */
.btn-primary {
    display: inline-block;
    background-color: var(--color-primary);
    color: white;
    border: none;
    padding: 6px 14px;
    border-radius: var(--radius);
    cursor: pointer;
    font: inherit;
    text-decoration: none;
}

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

.btn-cancel {
    background-color: #fff;
    color: var(--color-muted);
    border: 1px solid var(--color-border);
    padding: 6px 14px;
    border-radius: var(--radius);
    cursor: pointer;
    font: inherit;
}

.btn-cancel:hover {
    background-color: var(--color-bg-subtle);
}

/* Destructive actions (remove/delete). Without this the base filled-purple
   button style applies, which reads as the page's primary action. */
.btn-danger {
    background-color: var(--color-danger);
    color: white;
}

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

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

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

.btn-publish-current {
    background-color: var(--color-muted-bg);
    cursor: default;
}

.btn-publish-current:hover {
    background-color: var(--color-muted-bg);
}

/* Save buttons reflect unsaved-changes state: grey when clean, red when the
   form/grid has edits that haven't been saved yet. */
.btn-save {
    background-color: var(--color-muted-bg);
}

.btn-save:hover {
    background-color: var(--color-muted);
}

.btn-save.is-dirty {
    background-color: var(--color-danger);
}

.btn-save.is-dirty:hover {
    background-color: var(--color-danger-hover);
}

/* ── Tables ────────────────────────────────────────────────── */
table {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: var(--space-md);
}

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

td {
    font-variant-numeric: tabular-nums;
}

th {
    background-color: var(--surface-raised);
    font-weight: 600;
}

tbody tr:nth-child(even) {
    background-color: rgba(0, 0, 0, 0.02);
}

tbody tr:hover {
    background-color: var(--color-primary-light);
}

/* Numeric columns (scores, wins, spread, ratings): left-aligned like every
   other column, with tabular figures so digits still line up. `.num-left` is a
   kept alias for the same treatment. */
.num,
.num-left {
    text-align: left;
    font-variant-numeric: tabular-nums;
}

td a:hover {
    background-color: var(--color-bg-hover);
    border-radius: var(--radius-sm);
}

/* Bounded scroll region: long tables scroll internally so page-level controls
   stay on screen. Header row sticks to the top of the scroll box (no dependency
   on the sticky-header height). */
.table-scroll {
    max-height: calc(100vh - var(--header-h) - 96px);
    overflow: auto;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--surface-raised);
}

.table-scroll table {
    margin-bottom: 0;
}

.table-scroll thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    /* border-collapse drops borders on sticky cells; use an inset shadow. */
    box-shadow: inset 0 -1px 0 var(--color-border);
}

.table-narrow {
    max-width: 680px;
}

/* Let numeric columns take exactly the width of their widest value, including
   half-point tie records. The player cell receives the remaining space and is
   the only standings cell allowed to wrap. */
.standings-table th:not(.standings-player),
.standings-table td:not(.standings-player) {
    width: 1%;
    white-space: nowrap;
}

.standings-table .standings-player {
    width: 100%;
    text-align: left;
}

/* Quantities align on their trailing digits for quick comparison. A W-L
   record is a compact compound value, so center it rather than treating it as
   either prose or a single amount. Headers share the column classes and follow
   the alignment of their data. */
.standings-table .standings-rank,
.standings-table .standings-spread,
.standings-table .standings-firsts {
    text-align: right;
}

.standings-table .standings-record {
    text-align: center;
}

.standings-table td.standings-player {
    white-space: normal;
}

.standings-player-number,
.standings-table .tag {
    white-space: nowrap;
}

/* On phones, trade some type size and cell padding for enough horizontal room
   to keep every non-player column visible without a horizontal scrollbar. */
@media (max-width: 600px) {
    .standings-table {
        font-size: clamp(0.75rem, 3.2vw, 0.875rem);
    }

    .standings-table th,
    .standings-table td {
        padding: 4px clamp(1px, 0.35vw, 4px);
    }
}

/* ── Layout utilities ─────────────────────────────────────── */
/* Title row with right-aligned page actions (replaces stacked controls). */
.page-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-md);
    flex-wrap: wrap;
    margin-bottom: var(--space-md);
}

.page-head h1 {
    margin: 0;
}

.page-head-actions {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex-wrap: wrap;
}

/* Primary content + a contextual side panel. */
.split-main-side {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 340px);
    gap: var(--space-lg);
    align-items: start;
}

@media (max-width: 900px) {
    .split-main-side {
        grid-template-columns: 1fr;
    }
}

.card {
    background: var(--surface-raised);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: var(--space-md);
}

.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: var(--space-md);
}

/* Sticky action bar above a bounded grid (Tabulator edit pages). */
.table-toolbar {
    position: sticky;
    top: var(--header-h);
    z-index: 10;
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex-wrap: wrap;
    background: var(--surface);
    padding: var(--space-sm) 0;
    margin-bottom: var(--space-sm);
}

/* Keep the toolbar controls in scale with the 13px table rows below them. */
/* The toolbar shrinks its controls to one size. Link-buttons are named
   explicitly: they are anchors, so `button` alone left them at full size —
   noticeably taller and wider than the row they sit in.

   The transparent border is the last pixel of it. .btn-secondary draws a 1px
   outline and the others do not, so without this the outlined controls stand a
   pixel taller than the solid ones. */
.table-toolbar button,
.table-toolbar a.btn-primary,
.table-toolbar a.btn-secondary,
.table-toolbar a.btn-danger {
    font-size: 0.8rem;
    padding: 4px 10px;
    border: 1px solid transparent;
}

.table-toolbar .btn-secondary {
    border-color: var(--color-primary);
}

/* Inline simulate buttons in the pairings tables, sized to match the rows. */
.btn-sim {
    font-size: 0.8rem;
    padding: 4px 10px;
}

.table-toolbar #save-status {
    font-size: 0.8rem;
    color: var(--color-muted);
}

/* Schedule-method inputs share a toolbar; keep the numeric field compact so
   all controls wrap as units instead of forcing horizontal page overflow. */
.pairing-method-controls input[type="number"] {
    width: 7rem;
}

.pairing-method-controls select {
    width: auto;
    min-width: 10rem;
}

.pairing-method-controls label {
    margin: 0;
}

.form-narrow {
    max-width: 480px;
}

/* Pairings: table + fixed-pairings editor side by side on pairable rounds. */
.pairings-main {
    max-width: 860px;
}

.pairings-body--split {
    display: grid;
    grid-template-columns: minmax(0, 860px) 300px;
    gap: var(--space-lg);
    align-items: start;
}

/* The side panel comes first in the DOM but sits in column 2, so both items
   need an explicit row 1 — otherwise grid's sparse auto-placement drops the
   main table to row 2 (below the panel). */
.pairings-body--split .pairings-main {
    grid-column: 1;
    grid-row: 1;
    max-width: none;
}

.pairings-body--split .pairings-side {
    grid-column: 2;
    grid-row: 1;
    margin: 0;
}

.pairings-side h3 {
    margin-top: 0;
}

/* Keep the two player selects on one line ([name] vs [name]); the selects
   shrink to share the panel width (the dropdown still shows full names) and the
   Add button wraps to its own full-width line below. */
.pairings-side .fixed-pairing-add select {
    flex: 1 1 0;
    min-width: 0;
}

.pairings-side .fixed-pairing-add button {
    flex: 1 1 100%;
}

/* Narrow screens: stack into a single column. Clearing the explicit row/column
   placement lets the items flow in DOM order, floating the side panel on top of
   the main table. */
@media (max-width: 980px) {
    .pairings-body--split {
        grid-template-columns: 1fr;
    }
    .pairings-body--split .pairings-main,
    .pairings-body--split .pairings-side {
        grid-column: 1;
        grid-row: auto;
    }
}

/* Tournament detail: divisions beside a metadata card. */
.tournament-divisions h2 {
    margin-top: 0;
}

.card dl {
    margin: 0;
}

.card dl dt:first-child {
    margin-top: 0;
}

/* ── Definition Lists ─────────────────────────────────────── */
dl {
    margin-bottom: 20px;
}

dt {
    font-weight: bold;
    margin-top: 10px;
}

dd {
    margin-left: 0;
    margin-bottom: 5px;
}

/* ── Division Components ──────────────────────────────────── */
.division-test {
    background-color: var(--color-test-bg);
}

.division-deleted {
    background-color: var(--color-deleted-bg);
}

.division-toolbar {
    display: flex;
    gap: var(--space-sm);
    margin-bottom: 12px;
}

.division-inline-form {
    background-color: var(--color-bg-form);
    border: 1px solid var(--color-bg-form-border);
    border-radius: var(--radius);
    padding: 12px var(--space-md);
    margin-bottom: 12px;
    display: flex;
    align-items: center;
    gap: var(--space-sm);
}

.division-inline-form form {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    margin: 0;
}

.division-inline-form input[type="text"] {
    width: 220px;
    padding: 6px 10px;
}

.test-badge {
    display: inline-block;
    background-color: var(--color-test-badge);
    color: #333;
    font-size: 0.75em;
    font-weight: bold;
    padding: 1px 6px;
    border-radius: var(--radius-sm);
    vertical-align: middle;
}

/* A pairing row that is not a game. A bye and a forfeit are the same shape --
   one real entrant against the bye entrant -- so the label is what tells a
   director which they are looking at (plans/PLAN_FORFEITS.md). */
.tag-bye,
.tag-forfeit {
    display: inline-block;
    font-size: 0.75em;
    font-weight: bold;
    padding: 1px 6px;
    border-radius: var(--radius-sm);
    vertical-align: middle;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.tag-bye {
    background-color: var(--color-muted-bg);
    color: #fff;
}

.tag-forfeit {
    background-color: var(--color-danger);
    color: #fff;
}

/* Per-player forfeit buttons on a published round's unplayed games. */
.btn-forfeit {
    font-size: 0.8rem;
    padding: 4px 10px;
    margin-right: 4px;
}

/* Compact action button inside a table row (Withdraw / Rejoin). */
.btn-small {
    font-size: 0.8rem;
    padding: 3px 10px;
}

/* Marker for a withdrawn (dropped) entrant in standings/entrant lists. */
.tag-dropped {
    display: inline-block;
    background-color: var(--color-muted-bg);
    color: #fff;
    font-size: 0.75em;
    font-weight: bold;
    padding: 1px 6px;
    border-radius: var(--radius-sm);
    vertical-align: middle;
}

.row-dropped {
    color: var(--color-muted);
}

/* ── Round Tabs ───────────────────────────────────────────── */
.round-tabs {
    display: flex;
    gap: 2px;
    border-bottom: 2px solid var(--color-border);
    margin-bottom: 0;
}

/* Each round-tab status class sets --tab-color and --tab-bg; the base
   .round-tab rules then plug those into resting / active states. */
.round-tab {
    --tab-color: var(--color-future);
    --tab-bg: var(--color-future-light);

    display: inline-block;
    padding: var(--space-xs) var(--space-md);
    text-decoration: none;
    font-weight: bold;
    font-size: 0.9em;
    border-radius: var(--radius) var(--radius) 0 0;
    cursor: pointer;
    border: 1px solid transparent;
    border-bottom: none;
    margin-bottom: -2px;
    background-color: var(--tab-bg);
    color: var(--tab-color);
}

.round-tab-finished     { --tab-color: var(--color-finished);    --tab-bg: var(--color-finished-light); }
.round-tab-in_progress  { --tab-color: var(--color-in-progress); --tab-bg: var(--color-in-progress-light); }
.round-tab-pairable     { --tab-color: var(--color-pairable);    --tab-bg: var(--color-pairable-light); }
.round-tab-published    { --tab-color: var(--color-published);   --tab-bg: var(--color-published-light); }
.round-tab-future       { --tab-color: var(--color-future);      --tab-bg: var(--color-future-light); }
.round-tab-error_no_pairings { --tab-color: #b3261e; --tab-bg: #fdecea; }

.round-tab:hover {
    filter: brightness(0.95);
}

.round-tab.active {
    background-color: var(--tab-color);
    color: white;
    border-color: var(--color-border);
    border-bottom: 2px solid var(--tab-color);
    position: relative;
    z-index: 1;
    box-shadow: inset 0 3px 0 0 currentColor;
    padding-top: calc(var(--space-xs) + 2px);
}

#round-tab-content {
    border: 1px solid var(--color-border);
    border-top: none;
    padding: var(--space-md);
    margin-bottom: var(--space-md);
}

.round-header {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    margin-bottom: var(--space-md);
}

.round-header h2 {
    margin: 0;
}

.btn-publish-round {
    font-size: 0.85rem;
    padding: var(--space-xs) var(--space-md);
}

.btn-unpublish-round {
    font-size: 0.85rem;
    padding: var(--space-xs) var(--space-md);
    background-color: var(--color-muted-bg);
}

.btn-unpublish-round:hover {
    background-color: var(--color-muted);
}

.round-status-badge {
    display: inline-block;
    font-size: 0.6em;
    font-weight: bold;
    padding: 2px var(--space-sm);
    border-radius: var(--radius);
    vertical-align: middle;
    text-transform: uppercase;
}

.badge-finished { background-color: var(--color-finished-light); color: var(--color-finished); }
.badge-in-progress { background-color: var(--color-in-progress-light); color: var(--color-in-progress); }
.badge-pairable { background-color: var(--color-pairable-light); color: var(--color-pairable); }
.badge-published { background-color: var(--color-published-light); color: var(--color-published); }
.badge-future { background-color: var(--color-future-light); color: var(--color-future); }
.badge-error { background-color: #fdecea; color: #b3261e; }

/* ── Inline fixed pairings (pairings tab) ──────────────────── */
.fixed-pairings-section {
    margin: var(--space-md) 0;
    padding: var(--space-md);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
}

.fixed-pairings-section h3 {
    margin-top: 0;
}

.fixed-pairings-list {
    list-style: none;
    margin: 0 0 var(--space-md);
    padding: 0;
}

.fixed-pairings-list li {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 2px 0;
}

.fixed-pairing-remove {
    line-height: 1;
    padding: 0 var(--space-sm);
}

.fixed-pairings-empty {
    color: var(--color-future);
    margin: 0 0 var(--space-md);
}

.fixed-pairing-add {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex-wrap: wrap;
}

/* ── Utilities ────────────────────────────────────────────── */
.mt-sm { margin-top: var(--space-sm); }
.mt-md { margin-top: var(--space-md); }
.mt-xl { margin-top: var(--space-xl); }
.ml-md { margin-left: var(--space-md); }
.hidden { display: none; }
.inline { display: inline; }
.input-narrow { width: 5em; }

.inline-panel {
    margin-top: var(--space-md);
    padding: var(--space-md);
    border: 1px solid var(--color-border);
}

.toolbar {
    margin-bottom: var(--space-md);
    display: flex;
    align-items: center;
    gap: var(--space-md);
}

.board-table-map-layout {
    display: flex;
    align-items: flex-start;
    gap: var(--space-lg);
}

.board-table-map-main {
    flex: 1 1 auto;
    min-width: 0;
}

.board-table-map-explainer {
    flex: 0 0 auto;
    margin: 0;
}

.board-table-map-explainer figcaption {
    margin-bottom: var(--space-sm);
    font-size: 0.9rem;
    max-width: 220px;
}

.board-table-map-explainer .example-value {
    color: #dc2626;
    font-weight: 600;
}

.board-table-map-explainer img {
    width: 220px;
    max-width: 100%;
    height: auto;
}

.empty-state {
    color: var(--color-muted);
    font-style: italic;
    margin: var(--space-md) 0;
}

/* An editable checkbox column in an edit grid: the whole cell is the target,
   so it should look clickable. */
.editgrid-flag {
    cursor: pointer;
}

/* The sandbox groups (test tournaments, what-if imports) listed under the real
   ones. Separated so the eye can stop at the end of the first table. */
.sandbox-list {
    margin-top: var(--space-lg);
}

.sandbox-list h2 {
    margin-bottom: var(--space-xs);
}

/* ── Settings page ────────────────────────────────────────── */

/* Label, control and help text on one line each, in aligned columns.
   `.settings-field` is `display: contents` so its three cells join this grid
   directly — otherwise each field is its own block and the columns cannot line
   up across rows. The default form styling stacks all three (`label` is
   `display: block` with a margin, and `form p` adds 15px), which cost about
   three lines per field.

   **A grid here, flex in `.register-form .field-row` — same three parts, and
   the difference is deliberate.** A settings page is a long list of short
   values, so aligning the controls into a column is what makes it scannable.
   The registration form is mixed-width data entry (a name, a rating, a note)
   where a shared column would either crush the name field or strand the rating
   in a wide one, so its rows size independently and wrap. Do not merge them
   without deciding which of the two the merged form should be. */
/* `.page-section` (was `.settings-section`) has no rule of its own — it is a
   semantic hook for a titled block of a page, on the settings, playoff-setup,
   registration and round-pairings pages. Named for what it is because giving it
   a rule would restyle all four at once, which is exactly how `.note` below
   caught me out. */

/* A short explanatory paragraph in a secondary voice. Used three ways — the
   prose under a section heading, a footnote under a table, and the "last pull
   N ago" status line beside its `.error` counterpart — so the name says what it
   is rather than where it started (it was `.settings-intro`, in twenty-two
   templates and only two of them settings).
 
   Capping the measure is right everywhere: none of these is a heading and none
   should run the full width of a wide screen. */
.note {
    max-width: 62rem;
}

/* Tightened only when the note actually introduces something. A plain <p>
   carries 16px top and bottom, and a heading already provides the separation —
   but a footnote under a table or a status line mid-page needs that top margin
   to keep off what precedes it. Scoping it to the adjacency gets both without a
   second class or a per-template decision. */
h1 + .note,
h2 + .note,
h3 + .note {
    margin-top: 0;
}

.settings-fields {
    display: grid;
    /* The third column caps the help text's measure and takes whatever width is
       left. `minmax(0, ...)` rather than a floor, so a group with no help text
       at all (the playoff form's three selects) collapses it instead of
       reserving a column for nothing. */
    grid-template-columns: max-content max-content minmax(0, 44rem);
    gap: var(--space-xs) var(--space-md);
    align-items: baseline;
    margin-bottom: var(--space-md);
}

.settings-field {
    display: contents;
}

.settings-fields label {
    display: block;
    margin: 0;
    white-space: nowrap;
}

.settings-field-control {
    display: flex;
    align-items: baseline;
    gap: var(--space-sm);
}

/* Every setting on this page is a short number or a short choice, so sizing to
   content keeps the help column beside the control rather than pushed off to
   the right of a box wide enough for a sentence. */
.settings-fields input[type="number"] {
    width: 6em;
}

.settings-fields select {
    width: auto;
}

.settings-field-help {
    margin: 0;
    color: var(--color-muted);
    font-size: 0.9em;
    line-height: 1.35;
}

.settings-field-control ul.errorlist {
    margin: 0;
    padding-left: var(--space-md);
    color: var(--color-error-text);
}

.settings-advanced {
    margin: 0 0 var(--space-md);
}

.settings-advanced > summary {
    cursor: pointer;
    font-weight: 600;
    padding: var(--space-xs) 0;
}

.settings-advanced[open] > summary {
    margin-bottom: var(--space-sm);
}

/* The 404/403 pages. Narrow measure so the explanation reads as prose rather
   than stretching the full width of a wide screen. */
.error-page {
    max-width: 34rem;
}

.error-page h1 {
    margin-top: 0;
}

/* The dead address, on its own line. `anywhere` rather than `break-all` so a
   path only splits when it actually has to, instead of wherever the line
   happens to end. */
.error-page .error-path {
    overflow-wrap: anywhere;
}

.error-page code {
    background: var(--color-bg-subtle);
    padding: 2px 6px;
    border-radius: var(--radius-sm);
}

/* "Someone else is editing this" warning shown on the edit grids. */
.presence-banner {
    margin: 0 0 var(--space-md);
    padding: var(--space-sm) var(--space-md);
    background: var(--color-in-progress-light);
    color: var(--color-in-progress);
    border: 1px solid var(--color-in-progress);
    border-radius: var(--radius);
    font-weight: 600;
}

/* Stronger styling once the data has actually gone stale (someone saved). */
.presence-banner.is-stale {
    background: var(--color-error-bg);
    color: var(--color-error-text);
    border-color: var(--color-danger);
}

/* Two edit grids side by side on a wide window, stacked when narrow. The
   minmax(0, ...) tracks + min-width:0 columns let each Tabulator table shrink
   to its share of the width (it scrolls internally) rather than overflowing. */
.editgrid-columns {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-lg);
    align-items: start;
}

.editgrid-columns > * {
    min-width: 0;
}

@media (max-width: 900px) {
    .editgrid-columns {
        grid-template-columns: 1fr;
    }
}

/* ── Playoff bracket ───────────────────────────────────────
   A bracket reads as columns — one per stage window — so the rounds run left
   to right and each series sits under the window it is played in. The row
   scrolls sideways rather than wrapping, because a wrapped bracket loses the
   left-to-right reading that makes it a bracket at all; below the breakpoint
   the columns stack, which is the honest fallback on a phone. */
.playoff-bracket {
    display: flex;
    gap: var(--space-lg);
    align-items: flex-start;
    overflow-x: auto;
    padding-bottom: var(--space-sm);
    margin-bottom: var(--space-lg);
}

.playoff-window {
    flex: 1 1 0;
    min-width: 280px;
}

.playoff-window > h2 {
    margin: 0 0 var(--space-sm);
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-muted);
}

.playoff-series {
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: var(--space-md);
    margin-bottom: var(--space-md);
    background-color: var(--surface-raised);
}

.playoff-series > h3 {
    margin: 0;
}

.playoff-series-length {
    margin: 0 0 var(--space-sm);
    font-size: 0.85em;
    color: var(--color-muted);
}

/* A series that is over is settled; dim it slightly so live ones stand out. */
.playoff-series-clinched {
    background-color: var(--color-bg-subtle);
}

.playoff-series-pending {
    border-style: dashed;
}

/* The running series score: two rows, player and games won. */
.playoff-score {
    margin-bottom: var(--space-sm);
}

.playoff-score td {
    padding: 2px var(--space-xs);
    border-bottom: 0;
}

.playoff-score .playoff-winner {
    font-weight: 700;
}

.playoff-score .playoff-winner td:first-child::after {
    content: " ▸";
    color: var(--color-published);
}

.playoff-games {
    margin-bottom: 0;
    font-size: 0.85em;
}

.playoff-games th,
.playoff-games td {
    padding: 3px var(--space-xs);
}

/* A game that will never be played: present, but visibly not a fixture. */
.playoff-game-not_needed {
    color: var(--color-muted);
    font-style: italic;
}

.playoff-status {
    margin: 0 0 var(--space-sm);
}

/* Same shape as the round-status badges on the pairings tab, reusing their
   colour modifiers (badge-finished, badge-in-progress, …). */
.playoff-tag {
    display: inline-block;
    font-size: 0.7em;
    font-weight: bold;
    padding: 2px var(--space-sm);
    border-radius: var(--radius);
    vertical-align: middle;
    text-transform: uppercase;
}

/* A bracket-decided place, as opposed to one carried over from the standings. */
.playoff-placed td {
    font-weight: 600;
}

.playoff-unresolved td {
    color: var(--color-muted);
}

.playoff-delete {
    margin-top: var(--space-lg);
}

@media (max-width: 900px) {
    .playoff-bracket {
        flex-direction: column;
        overflow-x: visible;
    }

    .playoff-window {
        min-width: 0;
        width: 100%;
    }
}

/* ---------------------------------------------------------------------------
   Entrant list markers (plans/PLAN_ENTRANTS.md phase 4)
   --------------------------------------------------------------------------- */

/* Available to screen readers, invisible on screen. The entrant list marks
   tentative and playing-up entrants with punctuation, and #42 asks that the
   status never depend on punctuation alone — so each marked row also carries a
   textual equivalent in here. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* A rating taken from WESPA rather than CoCo. Italic is the owner's
   convention; the legend below the table says what it means. */
.rating-wespa {
    font-style: italic;
}

/* A grid cell whose value is chosen from a list. Tabulator's list editor is
   invisible until clicked, so the caret is the only thing saying the cell is a
   dropdown rather than text. */
.cell-select {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-xs);
}

.cell-select-caret {
    color: var(--color-muted);
    font-size: 0.75em;
    line-height: 1;
}

.tabulator-cell:hover .cell-select-caret {
    color: inherit;
}

/* Drift between an entrant's pinned seed and the player table. Editor-only:
   the public entrant list and the embed never render this column. */
.drift {
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-xs);
    white-space: nowrap;
}

.drift-quiet {
    color: var(--color-muted);
    font-style: italic;
}

.drift-banner {
    margin-bottom: var(--space-md);
    padding: var(--space-sm) var(--space-md);
    border-left: 3px solid var(--color-muted);
    background: transparent;
}

.entrant-legend {
    margin-top: var(--space-sm);
    color: var(--color-muted);
    font-size: 0.875em;
}

.entrant-legend dt {
    display: inline;
    font-weight: normal;
}

.entrant-legend dd {
    display: inline;
    margin: 0 var(--space-md) 0 0.25em;
}

/* The registration page is a desk: a director works down it with somebody
   standing in front of them, so it is laid out tight. The global
   `label { display: block }` puts every field on two lines, which pushed the
   "add as guest" row below the fold on a laptop; here a row is one flex line of
   label + control + help. Scoped to .register-form so no other form moves.
   Everything wraps rather than overflowing, so a narrow window stacks instead
   of scrolling sideways.

   The settings page does the same job with a grid (`.settings-fields`) because
   it wants the controls aligned in a column; this form wants each row to size
   to its own fields. See the note there. */
.register-form .field-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm) var(--space-md);
    margin-bottom: var(--space-md);
}

.register-form .field-row label {
    display: inline;
    margin: 0;
    white-space: nowrap;
}

/* A checkbox belongs to the word beside it, so the pair never breaks apart. */
.register-form .field-row .flag {
    display: inline-flex;
    align-items: center;
    gap: 0.35em;
}

.register-form .field-row .helptext {
    color: var(--color-muted);
    font-size: 0.875em;
}

/* The note and the guest name take the slack; the rating is three digits. */
.register-form .field-row input[type="number"] {
    width: 6em;
}

.register-form .field-row textarea,
.register-form .field-row input[type="text"],
.register-form .field-row input[type="search"] {
    width: auto;
    flex: 1 1 18em;
    min-width: 10em;
    /* Capped: a name is not 60 characters, and a box stretched across a
       laptop reads as a paragraph field rather than a one-line one. */
    max-width: 28em;
}

.register-form .field-row textarea {
    /* One line unless somebody drags it; a payment note is "cash, owes $5". */
    height: calc(1.5em + 2 * var(--space-sm));
    min-height: calc(1.5em + 2 * var(--space-sm));
}

/* The guest row is the odd one out: its label is a sentence, not a word. */
.register-form .register-guest label {
    white-space: normal;
}

/* A guest whose name is already taken: the question, and the ways out of it.
   Set apart from the row above so it does not read as more of the same form. */
.register-duplicate {
    margin: var(--space-md) 0;
    padding: var(--space-sm) var(--space-md);
    border-left: 3px solid var(--color-primary);
}

.register-duplicate p {
    margin-top: 0;
}

/* The activity log's per-event detail. The log line is a <summary>, so what an
   event changed unfolds under it with no JS; each row underneath is one line,
   because the page is read by someone looking quickly for the one thing that
   happened. */
.log-filters {
    display: flex;
    align-items: end;
    gap: var(--space-md);
    margin-bottom: var(--space-md);
    flex-wrap: wrap;
}

.log-entry > summary {
    cursor: pointer;
}

.log-when {
    white-space: nowrap;
}

.log-line {
    display: flex;
    gap: var(--space-xs);
    padding-left: var(--space-xs);
    font-size: 0.95em;
    line-height: 1.5;
}

/* A fixed gutter so the lines align whether or not they carry a mark. */
.log-mark {
    flex: 0 0 1em;
    text-align: center;
    color: var(--color-muted);
    font-weight: 600;
}

.log-mark-added { color: var(--color-success-text); }
.log-mark-removed { color: var(--color-error-text); }

.log-more {
    margin: var(--space-xs) 0 0;
    padding-left: calc(1em + var(--space-xs));
    font-size: 0.9em;
    color: var(--color-muted);
}

.log-pager {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    margin-top: var(--space-md);
}
