/* app.css — rowOps Race operator theme.
 *
 * Layered on top of Bootstrap 5 via the CDN base. Defines three modes:
 *   .theme-light  — default operator daytime (Bootstrap defaults + brand tint)
 *   .theme-dark   — office / evening (deep slate background)
 *   .theme-sun    — race-day high-contrast (pure white-on-black; jet-yellow accents)
 *
 * Theme class is applied to BOTH <html> and <body> so Bootstrap chrome,
 * form controls, and our custom widgets all flip together. ThemeToggle
 * persists choice to localStorage.
 *
 * Brand CSS vars are emitted by Brand::cssBlock() in layout.php BEFORE
 * this stylesheet so var(--brand-*) references resolve at parse time.
 */

/* -------------------------------------------------------------------------
   Base — applies to every theme
   ------------------------------------------------------------------------- */
:root {
    --rowops-surface: var(--brand-bg, #fafaf8);
    --rowops-surface-2: var(--brand-cream, #f5f1e8);
    --rowops-surface-3: #ffffff;
    --rowops-ink: var(--brand-text, #1f2a33);
    --rowops-ink-muted: rgba(0, 0, 0, 0.6);
    --rowops-ink-faint: rgba(0, 0, 0, 0.35);
    --rowops-border: rgba(0, 0, 0, 0.1);
    --rowops-border-strong: rgba(0, 0, 0, 0.18);
    --rowops-card: #ffffff;
    --rowops-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 0 0 1px rgba(0, 0, 0, 0.04);
    --rowops-shadow-lift: 0 8px 24px rgba(0, 0, 0, 0.06), 0 2px 6px rgba(0, 0, 0, 0.04);
    --rowops-accent: var(--brand-primary, #b07d33);
    --rowops-accent-fg: #ffffff;
    --rowops-accent-tint: rgba(var(--brand-primary-rgb, 176, 125, 51), 0.08);
    /* T-race-TODO-315 (part 2): small text in the club's colour (the public pages' club line, chips, current tab, place counts
       and outlined actions). The plain accent is 3.6:1 on the page with the default bronze, under the 4.5:1 the public pages
       keep; the accent mixed with the theme's ink is about 6:1 by day and 7:1 by night (Night's ink is light), and still reads
       as the club's colour. Borders, lines and fills stay --rowops-accent. Without color-mix() (the @supports below) it is the
       ink. A pale or saturated yellow tenant colour stays under 4.5:1 by day either way. */
    --rowops-accent-text: var(--rowops-ink);
    --rowops-good: #157347;
    /* D-race-267: a warning is orange, with a triangle on its badge, never the gold of the rowOps brand (it was #ad6a00). */
    --rowops-warn: #c2410c;
    --rowops-warn-fg: #ffffff;
    --rowops-bad: #b02a37;
    --rowops-link: var(--rowops-accent);

    /* Aliases for names already consumed across templates/JS. Until
       2026-07-30 these were undefined, so 70+ call sites silently
       rendered their var() fallbacks — including retired V1 red
       #AD2F1E. Defining them routes every consumer through the brand
       system (kit: rowOps_home/rowOps_logo/BRAND-SYSTEM.md). */
    --rowops-muted: #6c757d;
    --rowops-text-muted: var(--rowops-ink-muted);
    --rowops-primary: var(--rowops-accent);
    --rowops-success: var(--rowops-good);
    --rowops-ok: var(--rowops-good);
    --rowops-danger: var(--rowops-bad);

    /* UI redesign 2026-05-26 — design tokens for the new shell.
       Spacing rhythm: 4 / 8 / 12 / 16 / 24 / 32 / 48. Use these
       instead of ad-hoc px values so a future tweak lands once. */
    --space-1: 0.25rem;  /*  4 px */
    --space-2: 0.5rem;   /*  8 px */
    --space-3: 0.75rem;  /* 12 px */
    --space-4: 1rem;     /* 16 px */
    --space-6: 1.5rem;   /* 24 px */
    --space-8: 2rem;     /* 32 px */
    --space-12: 3rem;    /* 48 px */
    --radius-sm: 0.3rem;
    --radius-md: 0.5rem;
    --radius-lg: 0.75rem;
    --top-bar-h: 56px;
    --rail-w: 240px;
    --ease-snappy: cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* T-race-TODO-315 (part 2): on :root, so it is mixed from html's own ink, which the theme classes on <html> set. */
@supports (color: color-mix(in srgb, red 50%, blue)) {
    :root { --rowops-accent-text: color-mix(in srgb, var(--rowops-accent) 55%, var(--rowops-ink)); }
}

html, body { background: var(--rowops-surface); color: var(--rowops-ink); }

/* Widen the layout container — operator-stated 2026-05-19 night:
   "both this and the start screen could use more of the available
   screen space. aiming for 80% width of what is available on any
   given device. aiming for full page on a standard 15inch laptop
   (currently being tested on twin 27 inch desk monitors)".

   Bootstrap's default .container caps at 1320px on xxl — that leaves
   a 27" 2560-wide monitor with ~50% empty gutter on each side.

   Strategy: under 1500 viewport (covers laptops up to ~15") use
   ~95vw so the page is effectively full-bleed; on bigger monitors
   capped at 80vw / 2200px so two-monitor desktops use the screen
   without going edge-to-edge.

   !important needed because Bootstrap defines per-breakpoint .container
   max-widths inside @media queries that would otherwise win.  */
.container { max-width: 95vw !important; }
@media (min-width: 1500px) {
    .container { max-width: min(80vw, 2200px) !important; }
}
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI", Roboto, "Helvetica Neue", system-ui, sans-serif;
       font-feature-settings: "kern" 1, "calt" 1; letter-spacing: -0.005em; }
h1, h2, h3 { letter-spacing: -0.025em; font-weight: 650; }
a { color: var(--rowops-link); text-decoration: none; border-bottom: 1px dotted transparent; transition: border-color 0.15s; }
a:hover { color: var(--rowops-accent); border-bottom-color: currentColor; text-decoration: none; }

.tabular { font-variant-numeric: tabular-nums; }
.numeric-large { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; letter-spacing: -0.02em; }

/* Cards take a soft elevation that holds in dark mode too. */
.card {
    background: var(--rowops-card);
    border: 1px solid var(--rowops-border);
    border-radius: 0.5rem;
    box-shadow: var(--rowops-shadow);
}
.card-header {
    background: var(--rowops-surface-2);
    border-bottom: 1px solid var(--rowops-border);
    font-weight: 600;
    color: var(--rowops-ink);
}

/* Brand-aware primary buttons. */
.btn-primary,
.btn-primary:hover,
.btn-primary:focus,
.btn-primary:active {
    background: var(--rowops-accent);
    border-color: var(--rowops-accent);
    color: var(--rowops-accent-fg);
}
.btn-outline-primary {
    color: var(--rowops-accent);
    border-color: var(--rowops-accent);
}
.btn-outline-primary:hover { background: var(--rowops-accent); color: var(--rowops-accent-fg); }

/* D-race-191 — the setup flow's NEXT STEP (Brian, 16 Oct: "all the key st up flow buttons need to stay on the upper part
   of the page in the same colour differnt to other colours so that they can be easily idenitfied as the blockers or next
   step"). One button, rendered only by partials/primary_cta.php and partials/ladder_primary_cta.php. Its colour was a
   violet of its own until D-race-267 (Brian, 19 Oct: "go green everywhere for the moment"): it is now the setup action's
   green, drawn in D-race-201's block below, so .btn-next-step has no colour rule here and no tokens of its own. */
/* The slot: straight under the breadcrumb on every page that shows the next step, a child of the content panel. On a
   screen at least 992 px wide it stays in view under the top bar (one 56 px row there: the two-row bar is 768 px and
   below, where the slot is not sticky) down a long page (Divisions with 100+ events, Bow numbers with 500+ crews). It
   sits below the phase strip in the page, so it never covers it; z-index 30 keeps it under the top bar (50) and the
   off-canvas rail (40, up to 1100 px). On a phone it is simply the top of the page. */
.next-step-slot { margin-bottom: var(--space-4); }
@media (min-width: 992px) {
    .next-step-slot {
        position: sticky;
        top: var(--top-bar-h);
        z-index: 30;
        background: var(--rowops-surface);
        padding: var(--space-2) 0;
        border-bottom: 1px solid var(--rowops-border);
    }
    /* A next step that is an anchor on the dashboard (#draw, #publish, #online-entry) lands its heading below the top
       bar AND the stuck slot (about 115 px in Sun), not under them. */
    html { scroll-padding-top: calc(var(--top-bar-h) + 4.5rem); }
    /* D-race-267 slice 2: on the dashboard the slot is the Next step card, too tall to hang under the top bar. */
    .next-step-slot:has(> .next-step-card) { position: static; padding: 0; border-bottom: 0; }
}
@media print {
    .next-step-slot { position: static; border: 0; }
}

/* D-race-201 — the setup flow's ACTION buttons (Brian, 16 Oct, on D-race-191's question 4: "colour them a bright green
   perhaps, the natural next step ?  not branded as they can get missed in the overall interface", then "Violet signpost,
   green actions"). Violet (D-race-191's next step, above) says where to go; on the page it lands on, the button that DOES that
   step is .btn-setup-action: a bright green on its own --setup-action-* tokens, never the tenant's --brand-primary and
   not Bootstrap's btn-success (#198754, which approve / accept / promote buttons keep). SetupActionButtonTest pins the
   buttons that carry it, that the class, tokens and hexes appear nowhere else, and the contrast:
     Day    #22c55e with #052e16 text, 6.5:1; outline #14803c on the page, 4.8:1
     Night  #5be38f with #052e16 text, 9.1:1; outline #5be38f on the page, 10.1:1
     Sun    #5be38f with black text, 12.8:1; outline #5be38f on black, 12.8:1
   A performing button drawn disabled (the beach seeding and bracket buttons while the draw is locked) is a dashed
   outline at full opacity, its reason kept in its title, as D-race-191's disabled next step is. The filled button's edge
   is the line colour too, so Day's light green is outlined 4.8:1 against the page, not 2.2:1 (the review's P2). */
:root {
    --setup-action-bg: #22c55e;
    --setup-action-fg: #052e16;
    --setup-action-line: #14803c;
    --setup-action-border-w: 1px;
}
html.theme-dark, body.theme-dark {
    --setup-action-bg: #5be38f;
    --setup-action-fg: #052e16;
    --setup-action-line: #5be38f;
    --setup-action-border-w: 1px;
}
html.theme-sun, body.theme-sun {
    --setup-action-bg: #5be38f;
    --setup-action-fg: #000000;
    --setup-action-line: #5be38f;
    --setup-action-border-w: 3px;
}
/* D-race-267 (T-race-TODO-266; Brian, 19 Oct: "go green everywhere for the moment"): the next step under the breadcrumb
   (.btn-next-step, D-race-191) is this green too; the violet is retired. One green step is drawn per page:
   - the step is done on ANOTHER page: the signpost is this page's green, and this page's own setup actions step down to a
     plain outline (partials/primary_cta.php marks the slot data-next-step-elsewhere; a browser without :has(), Firefox
     before 121 or Safari before 15.4, keeps them green, so it shows two greens there);
   - the step is done on THIS page: the slot is a plain line pointing at it (.next-step-here), and the page's own action is
     the green. */
.btn.btn-setup-action,
.btn.btn-setup-action:hover,
.btn.btn-setup-action:focus,
.btn.btn-setup-action:active,
.btn.btn-next-step,
.btn.btn-next-step:hover,
.btn.btn-next-step:focus,
.btn.btn-next-step:active {
    background: var(--setup-action-bg);
    border: var(--setup-action-border-w) solid var(--setup-action-line);
    color: var(--setup-action-fg);
    font-weight: 700;
    text-decoration: none;
}
.btn.btn-next-step-outline,
.btn.btn-next-step-outline:focus {
    background: transparent;
    border: var(--setup-action-border-w) solid var(--setup-action-line);
    color: var(--setup-action-line);
    font-weight: 700;
    text-decoration: none;
}
.btn.btn-next-step-outline:not(.disabled):hover,
.btn.btn-next-step-outline:not(.disabled):active {
    background: var(--setup-action-bg);
    border-color: var(--setup-action-line);
    color: var(--setup-action-fg);
}
.btn.btn-setup-action:disabled,
.btn.btn-setup-action.disabled,
.btn.btn-next-step-outline.disabled {
    background: transparent;
    border: var(--setup-action-border-w) dashed var(--setup-action-line);
    color: var(--setup-action-line);
    opacity: 1;
}
/* T-race-TODO-303 (D-race-267's amendment, point 2; Brian, 19 Oct ~17:37): a setup action that is not the step steps down to the
   one standard action button, the club's outline (as .btn-outline-primary), not a grey. The green is still the page's only solid. */
.content-panel:has(.next-step-slot[data-next-step-elsewhere]) .btn.btn-setup-action:not(:disabled):not(.disabled) {
    background: transparent;
    border: 1px solid var(--rowops-accent);
    color: var(--rowops-accent);
    font-weight: 600;
}
html.theme-sun .content-panel:has(.next-step-slot[data-next-step-elsewhere]) .btn.btn-setup-action:not(:disabled):not(.disabled) {
    color: #fde047;
    border: 3px solid #fde047;
    font-weight: 700;
}
.next-step-here { color: var(--rowops-ink); font-weight: 600; text-decoration: none; }
.next-step-here:hover, .next-step-here:focus-visible { text-decoration: underline; }
/* Bootstrap's .btn:focus-visible ring is empty on a class it does not know (D-race-191's review P1). */
.btn.btn-next-step:focus-visible,
.btn.btn-next-step-outline:focus-visible,
.btn.btn-setup-action:focus-visible {
    outline: 3px solid var(--setup-action-line);
    outline-offset: 2px;
}

/* D-race-267 slice 2 (T-race-TODO-266, Brian's mock, 19 Oct): the dashboard's Next step card, its row of steps from the
   entries to race day, and the folds that hold the rest. The club's colour edges the step now (a border, not text); the one
   green on the page is the step's own button (D-race-201's block above). */
.next-step-card {
    background: var(--rowops-card);
    border: 1px solid var(--rowops-border);
    border-radius: 12px;
    padding: var(--space-4);
    display: grid;
    gap: var(--space-3);
    box-shadow: var(--rowops-shadow-lift);
}
.next-step-card-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--space-2); }
.next-step-card-title { margin: 0; font-size: 1.05rem; font-weight: 800; color: var(--rowops-ink); }
.next-steps { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: var(--space-2); }
.next-steps-item { border: 1px solid var(--rowops-border); border-radius: 8px; padding: var(--space-2) var(--space-3); display: grid; gap: 2px; min-width: 0; }
.next-steps-name { font-weight: 700; font-size: 0.9rem; }
.next-steps-item.is-done .next-steps-name::before { content: "✓ "; color: var(--rowops-good); }
.next-steps-item.is-now { border: 2px solid var(--rowops-accent); }
.next-steps-item.is-later { color: var(--rowops-ink-muted); }
.next-steps-note { font-size: 0.78rem; color: var(--rowops-ink-muted); font-variant-numeric: tabular-nums; }
.dash-fold { border: 1px solid var(--rowops-border); border-radius: 10px; background: var(--rowops-card); margin-bottom: var(--space-3); }
.dash-fold > summary {
    list-style: none;
    cursor: pointer;
    padding: var(--space-3) var(--space-4);
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-1) var(--space-3);
}
.dash-fold > summary::-webkit-details-marker { display: none; }
.dash-fold > summary::before { content: "▸"; color: var(--rowops-ink-muted); transition: transform 0.15s; }
.dash-fold[open] > summary::before { transform: rotate(90deg); }
.dash-fold > summary:focus-visible { outline: 3px solid var(--rowops-accent); outline-offset: 2px; border-radius: 10px; }
.dash-fold-title { font-weight: 800; }
.dash-fold-sub { color: var(--rowops-ink-muted); font-size: 0.88rem; }
/* T-race-TODO-266, after the head's live dashboard (Brian, 2026-10-19 ~16:57, of a club's: "little bit of a formatting error near the
   publish the draw bitton"): a fold's body sits inside the fold, inset as its summary is, not on its border. The panel's own
   heading, which repeated the fold's title ("Publish the draw" twice), is not drawn in a fold; its hint line stays. */
.dash-fold > .dash-action-panel, .dash-fold > .dash-quick-links { margin: 0; border: 0; box-shadow: none; padding: 0 var(--space-4) var(--space-4); }
.dash-fold > .dash-action-panel > .dash-section-head > h2,
.dash-fold > .dash-quick-links > .dash-section-head > h2 { display: none; }
@media (prefers-reduced-motion: reduce) { .dash-fold > summary::before { transition: none; } }

.badge.bg-brand { background: var(--rowops-accent) !important; color: var(--rowops-accent-fg); }
.badge.bg-dark { background: var(--brand-dark, #1f2a33) !important; }

/* ──────────────────────────────────────────────────────────────────
   Semantic badge family — M5b (2026-05-26 dashboard redesign).
   Replaces the ad-hoc bg-warning / bg-danger / bg-success / bg-info
   sprawl with five colour roles that mean the same thing on every
   surface: error / warning / ok / info / neutral. Pairs with the
   five-colour rule from the design doc (cockpit + dashboard parity).
   Existing Bootstrap classes still work — these are additive, opt-in
   per-callsite. Sun-mode + dark-mode parity is enforced below.
   ────────────────────────────────────────────────────────────────── */
.badge.is-error    { background: var(--rowops-bad);    color: #fff; }
.badge.is-warning  { background: var(--rowops-warn);   color: var(--rowops-warn-fg); }
.badge.is-ok       { background: var(--rowops-good);   color: #fff; }
.badge.is-info     { background: var(--rowops-accent); color: var(--rowops-accent-fg); }
.badge.is-neutral  { background: var(--rowops-surface-2); color: var(--rowops-ink-muted); border: 1px solid var(--rowops-border); }

/* D-race-267 (T-race-TODO-266; Brian, 19 Oct, asked "Is orange with a triangle right for warnings?": "yes"). Every warning is
   orange, and Bootstrap's warning family is moved off its yellow-gold (#ffc107) with it, so no warning reads as the rowOps
   brand. A fill is #f97316 with black text (7.5:1); warning TEXT and outlines use --rowops-warn, the darker orange, since
   #f97316 is 2.8:1 as text on white:
     Day    --rowops-warn #c2410c, white on it 5.2:1, on the page 4.9:1
     Night  #fb923c, #1c0a00 on it 8.6:1, on the page 7.2:1
     Sun    #fb923c, black on it 9.3:1
   A warning badge or chip carries a triangle drawn in CSS (a clip-path, no image, no glyph font). */
:root {
    --bs-warning: #f97316;
    --bs-warning-rgb: 249, 115, 22;
    --bs-warning-text-emphasis: #7c2d12;
    --bs-warning-bg-subtle: #fff1e6;
    --bs-warning-border-subtle: #fdba74;
}
.text-warning { color: var(--rowops-warn) !important; }
.btn-warning {
    --bs-btn-color: #000; --bs-btn-bg: #f97316; --bs-btn-border-color: #f97316;
    --bs-btn-hover-color: #000; --bs-btn-hover-bg: #fb8a3c; --bs-btn-hover-border-color: #fb8a3c;
    --bs-btn-active-color: #000; --bs-btn-active-bg: #ea6a0c; --bs-btn-active-border-color: #ea6a0c;
    --bs-btn-disabled-color: #000; --bs-btn-disabled-bg: #f97316; --bs-btn-disabled-border-color: #f97316;
    --bs-btn-focus-shadow-rgb: 249, 115, 22;
}
.btn-outline-warning {
    --bs-btn-color: var(--rowops-warn); --bs-btn-border-color: var(--rowops-warn);
    --bs-btn-hover-color: var(--rowops-warn-fg); --bs-btn-hover-bg: var(--rowops-warn); --bs-btn-hover-border-color: var(--rowops-warn);
    --bs-btn-active-color: var(--rowops-warn-fg); --bs-btn-active-bg: var(--rowops-warn); --bs-btn-active-border-color: var(--rowops-warn);
    --bs-btn-disabled-color: var(--rowops-warn); --bs-btn-disabled-border-color: var(--rowops-warn);
    --bs-btn-focus-shadow-rgb: 194, 65, 12;
}
.badge.is-warning::before,
.badge.bg-warning::before,
.badge.text-bg-warning::before,
.dash-day-chip.is-warning::before {
    content: "";
    display: inline-block;
    width: 0.8em;
    height: 0.7em;
    margin-right: 0.3em;
    background-color: currentColor;
    clip-path: polygon(50% 0, 100% 100%, 0 100%);
    vertical-align: -0.05em;
}

/* Tiny uppercase label utility — replaces the dozen-or-so inline
   style="font-size:.72rem;letter-spacing:.04em;" attributes scattered
   across the dashboard. Single class, single source of truth. */
.kpi-label {
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    font-weight: 600;
    color: var(--rowops-ink-muted);
}
.kpi-label-row {
    /* Small uppercase label inside a card body — matches `.kpi-label`
       but inherits the surrounding spacing the row context provides. */
    font-size: 0.65rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-weight: 600;
    color: var(--rowops-ink-muted);
}

/* Section header — replaces the repeated
   <h2 class="h6 mt-4 mb-2 text-uppercase text-muted" style="letter-spacing:0.06em;">
   pattern that anchors each phase block on the dashboard. Single
   utility class so a typography tweak lands in one place. */
.section-heading {
    font-size: 0.95rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--rowops-ink-muted);
    margin-top: 1.5rem;
    margin-bottom: 0.5rem;
}

/* Control hub glyph — large emoji/icon centred in each surface tile.
   `line-height: 1` keeps the tile body compact; `display: block` lets
   the surrounding text rhythm stay predictable. */
.control-glyph {
    font-size: 2.5rem;
    line-height: 1;
    display: block;
}

/* Form-control "natural-width" helpers — replaces ad-hoc
   style="width: auto" / "max-width: 260px" on filter rows. Operator
   filters across the dashboard now share one footprint. */
.form-natural-width { width: auto !important; }
.form-search-width { max-width: 260px; }
.form-label-mw-384 { max-width: 24rem; }

/* Control hub surface tile — invites a tap. Hover lifts the card +
   stronger shadow + brand-red border accent. Race-day operators on a
   tablet should feel "this is the obvious thing to tap". */
.control-tile {
    transition: transform 0.12s ease-out, box-shadow 0.12s ease-out, border-color 0.12s ease-out;
    border-width: 2px;
    cursor: pointer;
}
.control-tile:hover {
    transform: translateY(-3px);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
    border-color: var(--rowops-accent);
}
.control-tile.is-locked {
    opacity: 0.45;
    cursor: not-allowed;
}
.control-tile.is-locked:hover {
    transform: none;
    box-shadow: var(--rowops-shadow);
    border-color: var(--rowops-border);
}

/* Large variant for the regatta-dashboard race-day cockpit (Starter /
   Finish tile pair). 200px min-height matches the previous inline
   style; the .cockpit-tile-large class also bumps the glyph one step
   so the tap target reads at a tablet's distance. */
.cockpit-tile-large {
    min-height: 200px;
}
.cockpit-tile-large .control-glyph {
    font-size: 3.5rem;
}

/* Inline brand-accent colour helper for inline counters
   (e.g. on-water count on the race-day cockpit). */
.text-accent { color: var(--rowops-accent) !important; }

/* Fraction-denominator helper — used next to a tabular count to
   render the "/ N" denominator at a smaller, muted size. */
.fraction-denominator {
    font-size: 0.7em;
    font-variant-numeric: tabular-nums;
}

/* Gap-spread chips on /draw-decisions pair tables. tight/slim/ok
   are the three margin states the analyser computes per race-pair
   instance; the colour rule follows the brand's five-colour family
   (bad / warn / good) so an operator's eye reads spread severity at
   peripheral-vision distance. Sun-mode parity preserved through the
   existing `html.theme-sun .text-danger` override. */
.gap-spread-tight { color: var(--rowops-bad);  font-weight: 600; }
.gap-spread-slim  { color: var(--rowops-warn); font-weight: 600; }
.gap-spread-ok    { color: var(--rowops-good); font-weight: 600; }

/* Inline badge sized down to ride next to an H1/H2 without
   throwing the baseline off — used on /draw-decisions and similar
   surfaces where a state pill rides inside the page title. */
.badge.badge-h1-inline {
    font-size: 0.55em;
    font-weight: 600;
    vertical-align: middle;
}

/* Card with a brand-accent left border bar — used on action / config
   strips (e.g. /draw-decisions rerun toolbar) to set them apart from
   informational cards without changing background. */
.card.card-accent-bar {
    border-left: 4px solid var(--rowops-accent);
}

/* Pill-shaped button — race-day cross-surface nav links (Starter ↔
   Finish ↔ Settings). Used on control/start.php + control/finish.php
   header strips for the contextual "go to the other tablet's surface"
   shortcuts. Pairs with btn-outline-secondary or btn-sm. */
.btn.btn-pill { border-radius: 999px; }

/* Compact form-help text — tighter line-height + smaller size than
   Bootstrap's default .form-text. Used inline next to inputs where
   the help fits in a tight vertical band. */
.form-help-tight {
    font-size: 0.72rem;
    line-height: 1.2;
}

/* Generic display-none helper — for elements toggled by JS (recall
   form, lane-flag form, cancel button initial state, etc). Single
   class so a future "show/hide animation" lands here, not in every
   inline-style site. */
.is-hidden { display: none !important; }

/* Strong warning text — used for camera-state error banners +
   similar mid-page alerts that need to read at a glance but
   don't warrant a full alert component. Pairs with the brand's
   warning colour token so sun-mode parity holds. */
.text-warn-strong {
    color: var(--rowops-warn);
    font-weight: 600;
}

/* Tablet-friendly board action button — large hit-target buttons on
   the finish board (camera Start / Record clip). Sized for thumb
   taps on a propped iPad; the underlying btn-primary / btn-danger
   colours carry the role-meaning. */
.btn.btn-board-action {
    font-size: 1.05rem;
    padding: 0.55rem 1.1rem;
    border-radius: 0.4rem;
}

/* Force-cancel form on /races sits directly under the draw-job
   banner and needs a slight negative top + reserved bottom spacing
   to look like a continuation of the banner instead of a separate
   block. JS toggles is-hidden, this carries the position. */
.draw-cancel-form-position {
    margin-top: -0.5rem;
    margin-bottom: 1rem;
}

/* ──────────────────────────────────────────────────────────────────
   Sun-mode + night-mode parity for the new M5 utility classes.
   Operator-stated 2026-05-24: red text is hard to read in direct
   sun glare. The existing .text-danger override flips red → jet
   yellow on white border; new utilities that carry semantic red,
   amber, or accent colour need the same treatment so an operator
   on a tablet under the sun reads them at distance.
   ────────────────────────────────────────────────────────────────── */

/* SUN MODE — high-contrast outdoor variant */

/* text-accent (brand-red on white normally) → jet-yellow in sun */
html.theme-sun .text-accent, body.theme-sun .text-accent {
    color: #fde047 !important;
}

/* card-accent-bar — preserve the operator's expected 3px white border
   on every card edge; substitute jet-yellow for the accent bar so it
   reads as an action/config marker without competing with the white
   card border. */
html.theme-sun .card.card-accent-bar,
body.theme-sun .card.card-accent-bar {
    border: 3px solid #fff;
    border-left: 4px solid #fde047;
}

/* Gap-spread chips on /draw-decisions — red/amber/green meanings
   change to white/yellow/yellow in sun (severity inferred from chip
   position + count, not colour, when read at distance in glare). */
html.theme-sun .gap-spread-tight, body.theme-sun .gap-spread-tight {
    color: #fde047;
    font-weight: 800;
    text-transform: uppercase;
}
html.theme-sun .gap-spread-slim, body.theme-sun .gap-spread-slim {
    color: #fde047;
    font-weight: 700;
}
html.theme-sun .gap-spread-ok, body.theme-sun .gap-spread-ok {
    color: #fff;
    font-weight: 700;
}

/* text-warn-strong — amber/brown unreadable in sun, flip to jet-yellow */
html.theme-sun .text-warn-strong, body.theme-sun .text-warn-strong {
    color: #fde047;
    font-weight: 800;
}

/* control-tile hover border — operator preference is no red accents in
   sun (jet-yellow is the action colour). The :hover state still lifts
   the tile via transform; the border just switches accent colour. */
html.theme-sun .control-tile:hover,
body.theme-sun .control-tile:hover {
    border-color: #fde047;
}

/* NIGHT MODE — red text on dark slate is muddy; lift the bad/warn
   tokens for the same utilities so the cockpit reads clearly when an
   operator is in low light (early-morning setup, evening wrap-up). */

html.theme-dark .text-accent, body.theme-dark .text-accent {
    color: #ff7a66 !important;
}
html.theme-dark .gap-spread-tight, body.theme-dark .gap-spread-tight {
    color: #ff7a66;
}
html.theme-dark .gap-spread-slim, body.theme-dark .gap-spread-slim {
    color: #f0c674;
}
html.theme-dark .gap-spread-ok, body.theme-dark .gap-spread-ok {
    color: #6ed28b;
}
html.theme-dark .text-warn-strong, body.theme-dark .text-warn-strong {
    color: #f0c674;
}

/* ──────────────────────────────────────────────────────────────────
   Print sheet utilities — M5b slice 7 (2026-05-26)
   The /print/* templates are A4 sheets distributed to clubs +
   captains. Inline styles accumulated here for tight per-row
   margins + small print-text buttons. Centralise so a future
   typography or spacing tweak on the captain sheets lands in
   one place.
   ────────────────────────────────────────────────────────────────── */

/* Day-block spacing — used inside availability + affected-club
   sheets to separate per-day rows. Existing .day-block class
   gains a default 0.25rem margin-bottom; .day-block-tight is
   for the inner seat-list variant (0.15rem). */
.day-block { margin-bottom: 0.25rem; }
.day-block-tight { margin-bottom: 0.15rem; }

/* Small button text — print sheets reduce button text by ~25%
   so per-club mailer controls don't dominate the A4 layout. */
.btn-print-text { font-size: 0.78rem; }

/* Flush-top heading — print h2/h3 sometimes need the top margin
   stripped to butt against a preceding rule. Pairs with .border-0
   when removing the visual border too. */
.print-heading-flush { margin-top: 0; border: 0; }

/* Captain-mailer controls float to the right of the club row
   header. Bootstrap's .float-end works fine; this aliases it
   semantically for the "no-print + mailer" pattern. */
.mailer-controls-row { margin-left: auto; }

/* Club-totals subhead — small muted parenthetical that rides next
   to the club name h3 ("(8 rowers, 12 races)"). Pairs with the
   .print-heading-flush h3 above it. */
.club-totals-subhead {
    font-weight: 400;
    font-size: 0.85rem;
}

/* Tables get a tighter top-padding + uppercase th. */
.table th {
    font-weight: 600; font-size: 0.78rem; text-transform: uppercase;
    letter-spacing: 0.04em; color: var(--rowops-ink-muted);
}
.table > :not(caption) > * > * { vertical-align: middle; }

/* Alerts with a thicker accent border. */
.alert { border-radius: 0.5rem; border-left-width: 4px; }
.alert-danger  { border-left-color: var(--rowops-bad); }
.alert-warning { border-left-color: var(--rowops-warn); }
.alert-success { border-left-color: var(--rowops-good); }
.alert-info    { border-left-color: var(--rowops-accent); }

code { color: var(--rowops-accent); background: var(--rowops-surface-2); padding: 0.1em 0.35em; border-radius: 3px; }

/* Navbar branding. */
.navbar {
    background: var(--rowops-surface) !important;
    border-bottom: 1px solid var(--rowops-border);
}
.navbar-brand {
    font-weight: 700; letter-spacing: -0.02em; color: var(--rowops-ink) !important;
}
.navbar-brand:hover { color: var(--rowops-accent) !important; }
.navbar .brand-mark {
    display: inline-block; width: 28px; height: 28px; margin-right: 8px;
    background: var(--rowops-accent); border-radius: 4px; vertical-align: -8px;
}

/* Indicator pills (clock-drift + queue-status). */
.clock-drift, .queue-status, .theme-pill {
    font-size: 0.75rem; padding: 2px 8px; border-radius: 10px;
}
.clock-drift.ok    { background: rgba(0, 0, 0, 0.05); color: var(--rowops-ink); }
.clock-drift.warn  { background: var(--rowops-warn); color: var(--rowops-warn-fg); } /* D-race-267: a warning, orange */
.clock-drift.fail  { background: var(--rowops-bad); color: #fff; }
.queue-status         { background: var(--rowops-surface-2); color: var(--rowops-ink); }
.queue-status.queued  { background: var(--rowops-warn); color: var(--rowops-warn-fg); } /* D-race-267: a warning, orange */
.queue-status.offline { background: var(--rowops-bad); color: #fff; }
.queue-status.stuck   { background: var(--rowops-bad); color: #fff; }

/* Form controls inherit theme. */
.form-control, .form-select {
    background: var(--rowops-card); color: var(--rowops-ink); border-color: var(--rowops-border);
}
.form-control::placeholder { color: var(--rowops-ink-muted); opacity: 0.6; }
.form-control:focus, .form-select:focus {
    background: var(--rowops-card); color: var(--rowops-ink);
    border-color: var(--rowops-accent); box-shadow: 0 0 0 0.2rem rgba(var(--brand-primary-rgb, 176, 125, 51), 0.15);
}

/* Race-day specific widgets. */
.race-card {
    background: var(--rowops-card); border: 1px solid var(--rowops-border);
    border-radius: 0.5rem; padding: 0.75rem 1rem; margin-bottom: 0.5rem;
    transition: transform 0.05s, box-shadow 0.05s;
}
.race-card:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06); }
.race-status-on-water { border-left: 4px solid var(--brand-warning, #ffc107); }
.race-status-finished { border-left: 4px solid var(--rowops-good); opacity: 0.85; }
.race-status-pending  { border-left: 4px solid var(--rowops-border); }

.lane-grid { display: grid; gap: 0.5rem; }
.lane-cell {
    background: var(--rowops-card); border: 1px solid var(--rowops-border);
    border-radius: 0.4rem; padding: 0.6rem 0.8rem;
    display: grid; grid-template-columns: 2.2rem 1fr auto; gap: 0.75rem; align-items: center;
}
.lane-cell .lane-num {
    font-size: 1.5rem; font-weight: 700; text-align: right;
    font-variant-numeric: tabular-nums; color: var(--rowops-ink-muted);
}
.lane-cell.finished { opacity: 0.6; }
.lane-cell.flagged  { background: rgba(176, 42, 55, 0.08); } /* --rowops-bad tint — flag is a problem state, not a brand accent */

/* Big-button race-day controls — sized for tablets at the start tower. */
.btn-race-action {
    min-width: 6rem; padding: 0.6rem 1.2rem; font-weight: 600;
    font-size: 1.1rem; letter-spacing: 0.02em;
    border-radius: 0.4rem; border: 0;
}
.btn-start { background: var(--brand-warning, #ffc107); color: #000; }
.btn-finish { background: var(--rowops-accent); color: var(--rowops-accent-fg); }
.btn-go { background: var(--rowops-good); color: #fff; }
.btn-flag { background: transparent; color: var(--rowops-ink-muted); border: 1px solid var(--rowops-border); }

/* -------------------------------------------------------------------------
   Dark theme
   ------------------------------------------------------------------------- */
html.theme-dark, body.theme-dark {
    --rowops-surface: #16202A;
    --rowops-surface-2: #1f232a;
    --rowops-ink: #ecedf0;
    --rowops-ink-muted: rgba(236, 237, 240, 0.6);
    --rowops-muted: rgba(236, 237, 240, 0.6);  /* was stuck at light-theme #6c757d via var() fallback */
    --rowops-border: rgba(255, 255, 255, 0.1);
    --rowops-card: #1c2026;
    --rowops-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.04);
    --rowops-link: #ff7a66;
    --rowops-warn: #fb923c;
    --rowops-warn-fg: #1c0a00;
}
html.theme-dark .badge.bg-light, body.theme-dark .badge.bg-light {
    background: var(--rowops-surface-2) !important; color: var(--rowops-ink);
}
html.theme-dark .table-light, body.theme-dark .table-light { --bs-table-bg: var(--rowops-surface-2); }
html.theme-dark .table, body.theme-dark .table { --bs-table-bg: var(--rowops-card); color: var(--rowops-ink); }
html.theme-dark .table-striped > tbody > tr:nth-of-type(odd) > * { --bs-table-accent-bg: rgba(255,255,255,0.03); }
html.theme-dark .navbar, body.theme-dark .navbar { background: #0e1014 !important; }

/* -------------------------------------------------------------------------
   Sun mode (race-day high contrast — outdoor / bright)
   Pure white-on-jet-black; jet-yellow start; accent-red finish.
   Fatter borders + larger crew names so the start tower can read at distance.
   ------------------------------------------------------------------------- */
html.theme-sun, body.theme-sun {
    --rowops-surface: #000000;
    --rowops-surface-2: #000000;            /* was #0a0a0a — eliminate near-black secondary surface that washed out on screen */
    --rowops-ink: #ffffff;
    --rowops-ink-muted: #f0f0f0;            /* was #cfcfcf — operator-stated 2026-05-23 race day: not enough contrast outdoors */
    --rowops-muted: #f0f0f0;                /* track ink-muted in glare — mid-grey #6c757d is illegible outdoors */
    --rowops-border: #ffffff;
    --rowops-card: #000000;
    --rowops-shadow: none;
    --rowops-link: #fde047;                 /* was #facc15 — slightly brighter yellow for direct-sun visibility */
    --rowops-warn: #fb923c;
    --rowops-warn-fg: #000000;
}
/* Body-wide thicker baseline weight so text reads at distance in glare.
   Operator-stated 2026-05-24 race day: "make sure all text is bold and
   strong bold". Bumped 500 → 700 baseline; headings + key controls still
   reach 800-900 via their own rules below. */
html.theme-sun, body.theme-sun { font-weight: 700; }
html.theme-sun p, html.theme-sun li, html.theme-sun span, html.theme-sun div,
body.theme-sun p, body.theme-sun li, body.theme-sun span, body.theme-sun div { font-weight: inherit; }
html.theme-sun strong, html.theme-sun b, html.theme-sun h1, html.theme-sun h2, html.theme-sun h3,
body.theme-sun strong, body.theme-sun b, body.theme-sun h1, body.theme-sun h2, body.theme-sun h3 { font-weight: 800; }
html.theme-sun .small, html.theme-sun small,
body.theme-sun .small, body.theme-sun small { font-weight: 600; }
html.theme-sun *, body.theme-sun * { text-shadow: none; }
html.theme-sun .navbar, body.theme-sun .navbar { background: #000 !important; border-bottom: 3px solid #fff; }
html.theme-sun .card, body.theme-sun .card { border: 3px solid #fff; box-shadow: none; }
html.theme-sun .card-header, body.theme-sun .card-header {
    background: #000; color: #fff; border-bottom: 3px solid #fff; font-weight: 700;
}
html.theme-sun .table, body.theme-sun .table { --bs-table-color: #fff; --bs-table-bg: #000; --bs-table-border-color: #fff; }
html.theme-sun .table th, body.theme-sun .table th { color: #fff; border-color: #fff; font-weight: 700; }
html.theme-sun .table > :not(caption) > * > * { border-color: #fff; }
html.theme-sun .form-control, html.theme-sun .form-select,
body.theme-sun .form-control,  body.theme-sun .form-select {
    background: #000; color: #fff; border: 3px solid #fff; font-weight: 600;
}
html.theme-sun .form-control::placeholder, body.theme-sun .form-control::placeholder { color: #bbb; opacity: 0.85; }
html.theme-sun .btn-primary, body.theme-sun .btn-primary {
    background: #fde047; color: #000; border: 3px solid #fff; font-weight: 800;
}
html.theme-sun .btn-outline-primary, body.theme-sun .btn-outline-primary {
    color: #fde047; border: 3px solid #fde047; font-weight: 700;
}
html.theme-sun .btn-finish, body.theme-sun .btn-finish { background: #fde047; color: #000; font-weight: 800; }
html.theme-sun .btn-start,  body.theme-sun .btn-start  { background: #fde047; color: #000; font-weight: 800; }
html.theme-sun .btn-go,     body.theme-sun .btn-go     { background: #fde047; color: #000; font-weight: 800; }
/* Bootstrap utility buttons we surface in race-day controls. */
html.theme-sun .btn-secondary, body.theme-sun .btn-secondary,
html.theme-sun .btn-outline-secondary, body.theme-sun .btn-outline-secondary {
    background: #000; color: #fff; border: 3px solid #fff; font-weight: 700;
}
html.theme-sun .btn-success, body.theme-sun .btn-success {
    background: #fde047; color: #000; border: 3px solid #fff; font-weight: 800;
}
/* Operator-stated 2026-05-24: red text is hard to read in direct sun
   ("red writing is not great in the sun mode"). Switched both
   .btn-danger and .btn-outline-danger to high-contrast yellow on black
   with a thick white border + 900 weight; the danger semantics are
   carried by an outer ⚠ glyph and the action copy itself. */
html.theme-sun .btn-danger, body.theme-sun .btn-danger {
    background: #fde047; color: #000; border: 3px solid #fff; font-weight: 900;
    text-transform: uppercase; letter-spacing: 0.02em;
}
html.theme-sun .btn-outline-danger, body.theme-sun .btn-outline-danger {
    background: #000; color: #fde047; border: 3px solid #fff; font-weight: 900;
    text-transform: uppercase; letter-spacing: 0.02em;
}
html.theme-sun .btn-outline-danger:hover, body.theme-sun .btn-outline-danger:hover {
    background: #fde047; color: #000;
}
html.theme-sun .text-danger, body.theme-sun .text-danger { color: #fde047 !important; font-weight: 800; }
html.theme-sun .btn-warning, body.theme-sun .btn-warning {
    background: #fde047; color: #000; border: 3px solid #fff; font-weight: 800;
}
html.theme-sun .badge, body.theme-sun .badge { border: 2px solid #fff; font-weight: 700; }
/* Sun-mode parity for the M5 semantic badge tones (2026-06-02 audit).
   Used on head-mode finish-board state pills + the OTW lane flag chip
   + erg-finish DNF/DSQ. Default Bootstrap variant colours (muted
   orange/red on dark) vanish in glare; jet-yellow / pure-red on black
   reads at distance. */
html.theme-sun .badge.is-ok, body.theme-sun .badge.is-ok {
    background: #fde047; color: #000; border-color: #fff; font-weight: 800;
}
html.theme-sun .badge.is-info, body.theme-sun .badge.is-info {
    background: #001a33; color: #fff; border-color: #38f; font-weight: 800;
}
html.theme-sun .badge.is-warning, body.theme-sun .badge.is-warning {
    background: var(--rowops-warn); color: var(--rowops-warn-fg); border-color: #fff; font-weight: 800;
}
html.theme-sun .badge.is-error, body.theme-sun .badge.is-error {
    background: #ff3030; color: #fff; border-color: #fff; font-weight: 800;
}
html.theme-sun .badge.is-neutral, body.theme-sun .badge.is-neutral {
    background: #000; color: #f0f0f0; border-color: #fff; font-weight: 700;
}
html.theme-sun .lane-cell, body.theme-sun .lane-cell {
    background: #000; border: 3px solid #fff; padding: 1rem 1.2rem;
}
html.theme-sun .lane-cell .lane-num, body.theme-sun .lane-cell .lane-num {
    color: #fde047; font-size: 2.2rem; font-weight: 800;
}
html.theme-sun .lane-cell .lane-crew, body.theme-sun .lane-cell .lane-crew {
    color: #fff; font-weight: 700;
}
html.theme-sun .race-card, body.theme-sun .race-card { background: #000; border: 3px solid #fff; }
html.theme-sun a, body.theme-sun a { color: #fde047; text-decoration: underline; font-weight: 700; }
html.theme-sun code, body.theme-sun code { background: #000; color: #fde047; border: 1px solid #fff; padding: 0.05rem 0.3rem; font-weight: 700; }
html.theme-sun .alert, body.theme-sun .alert { border: 3px solid #fff; background: #000; color: #fff; font-weight: 600; }
html.theme-sun .alert-info, body.theme-sun .alert-info { border-color: #38f; }
html.theme-sun .alert-warning, body.theme-sun .alert-warning { border-color: #fde047; }
html.theme-sun .alert-danger, body.theme-sun .alert-danger { border-color: #ff3030; }
html.theme-sun .alert-success, body.theme-sun .alert-success { border-color: #fde047; }
html.theme-sun .text-muted, body.theme-sun .text-muted { color: #f0f0f0 !important; }
html.theme-sun .navbar-brand, body.theme-sun .navbar-brand { color: #fff !important; font-weight: 800; }
html.theme-sun .navbar .brand-mark, body.theme-sun .navbar .brand-mark { background: #fde047; }
/* Bootstrap small-text utility renders at #666 in default themes — too
   low contrast on jet black. Operator-facing meta lines use .small a lot. */
html.theme-sun .small, body.theme-sun .small,
html.theme-sun small, body.theme-sun small { color: #f0f0f0; }
/* Form-check (checkboxes) — defaults invisible on black. */
html.theme-sun .form-check-input, body.theme-sun .form-check-input {
    background-color: #000; border: 2px solid #fff;
}
html.theme-sun .form-check-input:checked, body.theme-sun .form-check-input:checked {
    background-color: #fde047; border-color: #fff;
}

/* -------------------------------------------------------------------------
   Breadcrumbs — `.crumbs` (used at the top of every operator page)
   ------------------------------------------------------------------------- */
.crumbs {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.55rem;
    font-size: 0.85rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    margin-bottom: 1.4rem;
    padding: 0.5rem 0.85rem;
    background: var(--rowops-surface-2);
    border-radius: 0.5rem;
    border-left: 3px solid var(--rowops-accent);
    box-shadow: 0 1px 0 var(--rowops-border);
}
.crumbs a {
    color: var(--rowops-ink-muted);
    border-bottom: none;
    transition: color 0.15s;
}
.crumbs a:hover { color: var(--rowops-accent); }
.crumbs .sep {
    color: var(--rowops-ink-muted);
    opacity: 0.45;
    font-weight: 400;
}
.crumbs .current {
    color: var(--rowops-ink);
    font-weight: 700;
}
html.theme-sun .crumbs, body.theme-sun .crumbs {
    background: #000; border-left-color: var(--brand-warning, #facc15);
    box-shadow: 0 1px 0 #fff;
}
html.theme-sun .crumbs a, body.theme-sun .crumbs a { color: #cfcfcf; }
html.theme-sun .crumbs .current, body.theme-sun .crumbs .current { color: #fff; }

/* Back-compat — existing `nav.small.mb-3` strips that haven't been
   migrated yet pick up the same look so the page chrome is uniform. */
nav.small.mb-3 {
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--rowops-ink-muted);
    padding-bottom: 0.6rem;
    border-bottom: 1px solid var(--rowops-border);
    margin-bottom: 1.25rem !important;
}
nav.small.mb-3 a { color: var(--rowops-ink-muted); border-bottom: none; }
nav.small.mb-3 a:hover { color: var(--rowops-accent); }

/* -------------------------------------------------------------------------
   Theme toggle pill in the navbar
   ------------------------------------------------------------------------- */
.theme-toggle {
    display: inline-flex; gap: 0.15rem; padding: 0.15rem;
    background: var(--rowops-surface-2); border-radius: 999px;
    font-size: 0.8rem; line-height: 1;
}
.theme-toggle button {
    background: transparent; border: 0; padding: 0.25rem 0.6rem; border-radius: 999px;
    color: var(--rowops-ink-muted); cursor: pointer;
}
.theme-toggle button.active {
    background: var(--rowops-card); color: var(--rowops-ink);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
html.theme-sun .theme-toggle, body.theme-sun .theme-toggle { background: #111; border: 1px solid #fff; }
html.theme-sun .theme-toggle button.active, body.theme-sun .theme-toggle button.active {
    background: #facc15; color: #000;
}

/* Print: force light theme, hide chrome. */
@media print {
    html, body { background: #fff !important; color: #000 !important; }
    .navbar, footer, .theme-toggle, .no-print { display: none !important; }
    .card, .table, .lane-cell { box-shadow: none !important; border-color: #ccc !important; }
}

/* In-page confirmation overlay (RowOps.confirm + data-confirm). Lives
   in app.css so every theme + page picks up the same shell, and the
   page-level CSP isn't bypassed by an inline <style> the helper would
   otherwise need to emit. */
.rowops-confirm-overlay {
    position: fixed; inset: 0; z-index: 1080;
    display: flex; align-items: center; justify-content: center;
    background: rgba(0, 0, 0, 0.45);
    padding: 1rem;
}
.rowops-confirm-overlay[hidden] { display: none; }
/* D-race-249 Part 2 (rowops-1b's TRACE-2): while the head finish board's undo question is open, a finger on its backdrop over
   FINISH, TRAP or the pad is that control's press, so the backdrop takes no pan, as FINISH itself takes none. Otherwise a finger
   that slides is cancelled as a scroll. finish_head.php sets the class only for the undo's question: every other question pans
   as before. */
body.hb-undo-asking .rowops-confirm-overlay { touch-action: none; }
.rowops-confirm-card {
    background: var(--rowops-card);
    color: var(--rowops-ink);
    border-radius: 0.5rem;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
    max-width: 460px; width: 100%;
    padding: 1.25rem 1.5rem;
}
.rowops-confirm-title {
    font-size: 1.05rem; font-weight: 700; margin: 0 0 0.5rem;
    letter-spacing: -0.01em;
}
.rowops-confirm-body { color: var(--rowops-ink-muted); margin-bottom: 1.25rem; font-size: 0.95rem; line-height: 1.4; }
.rowops-confirm-actions { display: flex; justify-content: flex-end; gap: 0.5rem; }
.rowops-confirm-actions button {
    border: 1px solid var(--rowops-border);
    background: transparent; color: var(--rowops-ink);
    padding: 0.4rem 0.9rem; border-radius: 0.375rem;
    font-weight: 600; font-size: 0.9rem; cursor: pointer;
}
.rowops-confirm-actions button.rowops-confirm-ok {
    background: var(--rowops-accent); color: var(--rowops-accent-fg);
    border-color: var(--rowops-accent);
}
.rowops-confirm-actions button.rowops-confirm-ok.is-danger {
    background: #dc2626; border-color: #dc2626; color: #fff;
}
.rowops-confirm-actions button:hover { filter: brightness(0.95); }
html.theme-sun .rowops-confirm-card, body.theme-sun .rowops-confirm-card {
    background: #000; color: #fff; border: 1px solid #fff;
}
html.theme-sun .rowops-confirm-actions button, body.theme-sun .rowops-confirm-actions button {
    color: #fff; border-color: #fff;
}
html.theme-sun .rowops-confirm-actions button.rowops-confirm-ok, body.theme-sun .rowops-confirm-actions button.rowops-confirm-ok {
    background: #facc15; color: #000; border-color: #facc15;
}

/* Lane-numbering convention note. Per RI Rule 2.12.2 ("Lanes always
   count from the Starters left") and WR bye-law to Rule 33 b)(ii):
   Lane 1 is on the Starter's left looking down the course. Surface
   on every operator + public surface where lane numbers appear, so
   visiting crews + spectators at an unfamiliar course aren't guessing. */
.lane-convention-note {
    display: inline-block; padding: 0.25rem 0.65rem;
    background: var(--rowops-surface-2);
    border: 1px solid var(--rowops-border);
    border-left: 3px solid var(--rowops-accent);
    border-radius: 0.3rem; font-size: 0.78rem;
    color: var(--rowops-ink); margin-bottom: 0.75rem;
}
.lane-convention-note .cite {
    color: var(--rowops-ink-muted); margin-left: 0.35rem;
    font-size: 0.72rem;
}
html.theme-sun .lane-convention-note, body.theme-sun .lane-convention-note {
    background: #000; color: #fff; border-color: #fff;
    border-left-color: #facc15;
}

/* ──────────────────────────────────────────────────────────────────
   Dashboard race-day cockpit (M5)
   ────────────────────────────────────────────────────────────────── */
.cockpit-tile {
    transition: transform 0.08s ease-out, box-shadow 0.08s ease-out;
    cursor: pointer;
}
.cockpit-tile:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
html.theme-sun .cockpit-tile, body.theme-sun .cockpit-tile {
    background: #000; color: #fff; border: 2px solid #facc15 !important;
}
html.theme-sun .cockpit-tile .text-body, body.theme-sun .cockpit-tile .text-body { color: #fff !important; }
html.theme-sun .cockpit-tile .text-muted, body.theme-sun .cockpit-tile .text-muted { color: #fde68a !important; }

/* ──────────────────────────────────────────────────────────────────
   Phase chevron — M1 of the 2026-05-26 dashboard redesign
   Renders inline in the regatta strip (templates/partials/phase_chevron.php).
   Salesforce-Path style horizontal track of 8 workflow phases. Past
   nodes are muted-green checks; current node is brand-red pulsing;
   future nodes are dim outlines with a help cursor + title tooltip.
   ────────────────────────────────────────────────────────────────── */
.phase-chevron {
    display: inline-flex;
    align-items: center;
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
}
.phase-chevron::-webkit-scrollbar { display: none; }
.phase-chevron-list {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    margin: 0; padding: 0;
    list-style: none;
}
.phase-sep {
    color: var(--rowops-ink-muted);
    font-size: 1rem;
    opacity: 0.55;
    padding: 0 0.05rem;
    -webkit-user-select: none;
    user-select: none;
}
.phase-node { display: inline-flex; }
.phase-link {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.85rem;
    border-radius: 999px;
    font-size: 0.86rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--rowops-ink-muted);
    border: 1px solid var(--rowops-border);
    background: var(--rowops-surface);
    border-bottom: 1px solid var(--rowops-border);
    transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
/* D-race-186: the hover is a link's alone. A past or current node whose page this role may not open is a span (.is-static):
   status, drawn like the node, with no hover and no pointer. */
a.phase-link:hover { color: var(--rowops-ink); background: var(--rowops-surface-2); border-color: var(--rowops-ink-muted); }
.phase-link.is-static { cursor: default; }
.phase-glyph {
    font-size: 0.92rem;
    line-height: 1;
}
.phase-label { line-height: 1.1; }

/* past — completed phases: readable green chip; a link for a role that holds the page, a status chip (span.is-static) for any other (D-race-186) */
.phase-node.is-past .phase-link {
    color: var(--rowops-good);
    border-color: var(--rowops-good);
    background: color-mix(in srgb, var(--rowops-good) 8%, var(--rowops-surface));
}
.phase-node.is-past a.phase-link:hover {
    background: color-mix(in srgb, var(--rowops-good) 16%, var(--rowops-surface));
}
.phase-node.is-past .phase-glyph { color: var(--rowops-good); }

/* current — pulsing brand-red */
.phase-node.is-current .phase-link {
    background: var(--rowops-accent);
    color: var(--rowops-accent-fg);
    font-weight: 700;
}
.phase-node.is-current a.phase-link:hover {
    background: var(--rowops-accent);
    color: var(--rowops-accent-fg);
    filter: brightness(1.08);
}
.phase-node.is-current .phase-glyph {
    color: var(--rowops-accent-fg);
    animation: phase-pulse 2.2s ease-in-out infinite;
}
@keyframes phase-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.55; }
}

/* future — dim, non-clickable, help cursor on hover (tooltip via title) */
.phase-node.is-future .phase-link.is-disabled {
    color: var(--rowops-ink-muted);
    opacity: 0.55;
    cursor: help;
}
.phase-node.is-future .phase-link.is-disabled:hover {
    background: transparent;
    color: var(--rowops-ink-muted);
}

/* Tablet + mobile — collapse labels, keep glyphs to preserve the
   track at narrow widths. < 768px hides labels; < 540px also hides
   separators so the track compacts to a row of glyphs. */
@media (max-width: 768px) {
    .phase-label { display: none; }
    .phase-link { padding: 0.15rem 0.3rem; gap: 0; }
    .phase-glyph { font-size: 0.85rem; }
}
@media (max-width: 540px) {
    .phase-sep { display: none; }
    .phase-chevron-list { gap: 0.25rem; }
}

/* Dark theme — re-anchor the past-green so it reads against #16202A
   without needing a different colour token. */
html.theme-dark .phase-node.is-past .phase-link,
body.theme-dark .phase-node.is-past .phase-link { color: #2dd4bf; }
html.theme-dark .phase-node.is-past .phase-glyph,
body.theme-dark .phase-node.is-past .phase-glyph { color: #2dd4bf; }

/* Sun mode — high-contrast outdoor variant. Borders + bigger glyphs.
   Yellow for current (matches start/finish buttons), white outline for
   past + future to stay readable in glare. */
html.theme-sun .phase-link, body.theme-sun .phase-link {
    border: 2px solid #fff;
    color: #fff;
    font-weight: 800;
    padding: 0.2rem 0.55rem;
}
html.theme-sun .phase-glyph, body.theme-sun .phase-glyph { font-size: 0.85rem; }
html.theme-sun .phase-node.is-past .phase-link,
body.theme-sun .phase-node.is-past .phase-link {
    color: #fff; border-color: #fff; opacity: 0.65;
}
html.theme-sun .phase-node.is-past .phase-glyph,
body.theme-sun .phase-node.is-past .phase-glyph { color: #fff; }
html.theme-sun .phase-node.is-current .phase-link,
body.theme-sun .phase-node.is-current .phase-link {
    background: #fde047; color: #000; border-color: #fff;
}
html.theme-sun .phase-node.is-current .phase-glyph,
body.theme-sun .phase-node.is-current .phase-glyph { color: #000; }
html.theme-sun .phase-node.is-future .phase-link.is-disabled,
body.theme-sun .phase-node.is-future .phase-link.is-disabled {
    color: #888; border-color: #444; opacity: 1;
}
html.theme-sun .phase-sep, body.theme-sun .phase-sep { color: #fff; opacity: 0.6; }

/* Single-source chevron wrapper — 2026-06-18. The workflow phase
   chevron now renders ONCE from templates/layout.php at the top of the
   content panel (above the hero) on every regatta- AND ladder-scoped
   page, instead of being included ad-hoc in ~6 templates. This wrapper
   gives it consistent top-of-panel spacing regardless of which chevron
   partial (regatta .phase-chevron / erg + beach .dash-chevron / ladder
   .phase-chevron) renders inside it. Day / dark / sun parity comes from
   the chevron partials' own theme rules above — the wrapper is layout
   spacing only, so it needs no per-theme override. */
.content-chevron {
    margin-bottom: var(--space-4);
}
.content-chevron:empty { display: none; }
/* The dash-style chevrons (erg + beach) already carry their own
   bottom margin; collapse the wrapper's so spacing stays even. */
.content-chevron .dash-chevron { margin-bottom: 0; }

/* Print — hide the chevron; it's operational chrome, not document
   content. Matches the existing nav.crumbs.no-print discipline. */
@media print {
    .phase-chevron { display: none !important; }
    .content-chevron { display: none !important; }
}

/* ──────────────────────────────────────────────────────────────────
   Operator home grid (templates/home.php) — 2026-05-26
   Phase-aware regatta cards with operational deep-links. Pairs the
   visual language of the unauth landing (home_public.php) with
   operator-only enrichment: phase pill, KPI strip, multi-link footer.
   This is the first surface every operator (and every SaaS prospect
   on a free trial) sees post-login, so it carries more visual
   weight than a Bootstrap table.
   ────────────────────────────────────────────────────────────────── */

.hp-hero {
    padding: 1.25rem 0 1rem;
}
.hp-hero h1 { font-weight: 700; letter-spacing: -0.02em; }
/* Higher specificity than the legacy centred `.hp-hero` further down the
   sheet so the operator landing reads left-aligned (heading + button on
   one row), while the public spectator landing keeps its centred hero. */
.hp-hero.hp-hero-operator {
    margin-bottom: 1.2rem;
    text-align: left;
}

.hp-section { margin-top: 1.5rem; }
.hp-section-title {
    font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--rowops-ink-muted); font-weight: 700;
    margin-bottom: 0.7rem; display: flex; align-items: center; gap: 0.5rem;
}
.hp-section-title .pulse {
    display: inline-block; width: 10px; height: 10px; border-radius: 50%;
    background: var(--rowops-bad, #c0392b);
    animation: hp-pulse 1.6s ease-in-out infinite;
}
@keyframes hp-pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50%      { opacity: 0.5; transform: scale(1.25); }
}

/* Card grid layouts. The unauth landing already defines .hp-grid via
   inline styles; we redefine + extend here so both landings share the
   pattern. Auto-fit + minmax keeps the grid responsive without media
   queries; the operator grid uses a slightly tighter minmax (310px)
   because the cards carry more density. */
.hp-grid {
    display: grid; gap: 0.9rem;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
/* auto-fill (not auto-fit) so a lone regatta card keeps its natural width
   on a wide screen instead of stretching across the whole row. */
.hp-grid-operator {
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}

/* Live-now rail — only renders when ≥1 regatta is in race_day phase.
   Compact 2-3 column grid sized for at-a-glance scanning from across
   the room (operator on a tablet). */
.hp-live-grid {
    display: grid; gap: 0.9rem;
    grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
}
.hp-live-card {
    border-left: 4px solid var(--rowops-accent);
    transition: transform 0.12s ease-out, box-shadow 0.12s ease-out;
}
.hp-live-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 22px rgba(0, 0, 0, 0.08);
}
.hp-live-headline { margin-bottom: 0.85rem; }
.hp-live-stats {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.5rem;
    padding: 0.7rem 0.9rem;
    background: var(--rowops-surface-2);
    border-radius: 0.4rem;
    margin-bottom: 0.85rem;
}
.hp-live-cta-row {
    display: flex; flex-wrap: wrap; gap: 0.4rem;
}
.hp-live-cta-row .btn { flex: 1 1 auto; min-width: 100px; }

/* Operator-card layout — head row (title + phase pill) over a meta
   line over a 3-cell KPI strip, with a primary CTA + secondary
   link-row pinned at the bottom. Full card surface is hoverable so
   the operator can hit anywhere then hand off to a specific link. */
.hp-op-card {
    transition: transform 0.12s ease-out, box-shadow 0.16s ease-out, border-color 0.12s ease-out;
    height: 100%;
}
.hp-op-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
    border-color: var(--rowops-accent);
}
.hp-op-card .card-body {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    padding: 1rem 1.1rem;
}
.hp-op-card-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.6rem;
}
.hp-op-card-title {
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1.2;
    flex: 1 1 auto;
    min-width: 0;
}
.hp-op-card-title a:hover { color: var(--rowops-accent); }
.hp-op-phase-pill {
    flex-shrink: 0;
    font-size: 0.7rem;
    font-weight: 700;
    padding: 0.28rem 0.55rem;
    letter-spacing: 0.02em;
    white-space: nowrap;
}
.hp-op-meta {
    display: flex; flex-wrap: wrap; gap: 0.4rem 0.8rem;
    align-items: center;
}
.hp-op-stats {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.5rem;
    padding: 0.6rem 0.8rem;
    background: var(--rowops-surface-2);
    border-radius: 0.4rem;
}
.hp-op-cta { margin-top: 0.3rem; }
.hp-op-links {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 0.75rem;
    padding-top: 0.4rem;
    border-top: 1px solid var(--rowops-border);
    font-size: 0.82rem;
}
.hp-op-link {
    color: var(--rowops-ink-muted);
    text-decoration: none;
    border-bottom: 1px dotted transparent;
    padding: 0.05rem 0;
}
.hp-op-link:hover {
    color: var(--rowops-accent);
    border-bottom-color: currentColor;
}
.hp-op-link.is-highlight {
    color: var(--rowops-accent);
    font-weight: 600;
}
/* D-race-167 (the platform operator's cross-tenant home, the audit's r52): a card of a club the session is not acting as is ONE
   act-as form, and its title and quick links are submit buttons. They are drawn as the links they stand for. */
.hp-op-card-title .hp-op-title-btn {
    appearance: none;
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.hp-op-card-title .hp-op-title-btn:hover { color: var(--rowops-accent); }
button.hp-op-link {
    appearance: none;
    background: none;
    border: 0;
    border-bottom: 1px dotted transparent;
    font: inherit;
    cursor: pointer;
}

/* Empty-state — large friendly card for "no regattas yet". Saas-style
   first-run pitch, since brand-new tenants are increasingly common. */
.hp-empty-state .hp-empty-glyph {
    font-size: 3rem;
    line-height: 1;
    opacity: 0.6;
}

/* Theme parity */
html.theme-dark .hp-op-card:hover, body.theme-dark .hp-op-card:hover {
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
}
html.theme-dark .hp-live-card, body.theme-dark .hp-live-card {
    border-left-color: #ff7a66;
}
html.theme-sun .hp-op-card, body.theme-sun .hp-op-card {
    border: 3px solid #fff;
}
html.theme-sun .hp-op-card:hover, body.theme-sun .hp-op-card:hover {
    border-color: #fde047;
    transform: none;
    box-shadow: none;
}
html.theme-sun .hp-op-stats, body.theme-sun .hp-op-stats,
html.theme-sun .hp-live-stats, body.theme-sun .hp-live-stats {
    background: #000;
    border: 2px solid #fff;
}
html.theme-sun .hp-op-link, body.theme-sun .hp-op-link {
    color: #fff;
    font-weight: 700;
}
html.theme-sun .hp-op-link.is-highlight, body.theme-sun .hp-op-link.is-highlight {
    color: #fde047;
}
html.theme-sun .hp-live-card, body.theme-sun .hp-live-card {
    border: 3px solid #fff;
    border-left: 5px solid #fde047;
}

/* Print — hide hover transforms (operators don't print this surface,
   but kill any visual flutter just in case). */
@media print {
    .hp-op-card, .hp-live-card { transform: none !important; box-shadow: none !important; }
    .hp-section-title .pulse { animation: none; }
}

/* =================================================================
   UI redesign 2026-05-26 — clean-slate operator shell
   Persistent left rail + slim top bar + content panel. Best-in-class
   pattern (Linear / Stripe / Vercel / Cloudflare). Old `.navbar` +
   `.regatta-strip` rules above are retained for back-compat with
   any non-operator surface but the operator chrome now goes
   through .top-bar + .left-rail + .content-panel.
   ================================================================= */

/* --- Top bar ----------------------------------------------------- */
.top-bar {
    height: var(--top-bar-h);
    background: var(--rowops-card);
    border-bottom: 1px solid var(--rowops-border);
    position: sticky;
    top: 0;
    z-index: 50;
}
.top-bar-inner {
    height: 100%;
    max-width: none;
    padding: 0 var(--space-6);
    display: flex;
    align-items: center;
    gap: var(--space-3);
}
/* D-race-149 (the 2026-10-11 menu audit, P0-1): the menu button layout.php draws in
   the top bar wherever a rail is drawn. Hidden while the rail is on screen; at
   1100 px or less the rail is off-canvas (see the responsive block below) and this
   is the only thing that opens it. */
.top-bar-menu-toggle { display: none; }
.top-bar-brand {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-weight: 700;
    font-size: 0.95rem;
    letter-spacing: -0.01em;
    color: var(--rowops-ink) !important;
    text-decoration: none;
    border-bottom: 0;
}
.top-bar-brand:hover { color: var(--rowops-accent) !important; }
/* The product beside the platform name — the kit's `rowOps | race` lockup
   (RACE-HANDOFF.md §2): rowOps heavy, race one step lighter. */
.top-bar-brand .top-bar-brand-product {
    font-weight: 500;
    margin-left: -0.15rem;
}
/* D-race-267 (T-race-TODO-266): where a club's brand shows, the club leads the bar in its own colour, and the platform
   follows in small, muted type. */
/* The name is the page's text colour, not the club's: a pale club colour would fall under 4.5:1 on the bar (rowops-c7's P3; Brand
   does not guard the accent's contrast). The logo carries the colour. A long name is cut with an ellipsis, not pushed off a phone. */
.top-bar-brand .top-bar-club-name {
    color: var(--rowops-ink);
    font-weight: 800;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.top-bar-brand:has(> .top-bar-club-name) { min-width: 0; }
.top-bar-brand .top-bar-club-on { font-size: 0.75rem; font-weight: 500; color: var(--rowops-ink-muted); white-space: nowrap; }
@media (max-width: 576px) { .top-bar-brand .top-bar-club-on { display: none; } }
/* T-race-TODO-323 / D-race-275: the public header has room (the brand and the theme toggle only), so there the platform line
   sits under the club's name and stays on a phone. */
.top-bar-brand:has(> .top-bar-club-lockup) { min-width: 0; }
.top-bar-brand .top-bar-club-lockup { display: flex; flex-direction: column; min-width: 0; line-height: 1.15; }
.top-bar-brand .top-bar-club-lockup .top-bar-club-on { display: block; }
.spectator-platform-link a { color: inherit; font-weight: 600; }
/* T-race-TODO-342 / D-race-286 (the audit's T12): where a club leads the public bar its logo and name are one link to the club's home
   page, and a link is a finger high: 2.75rem (44 px) inside the bar's 56 px row. */
.top-bar-spectator a.top-bar-brand { min-height: 2.75rem; }
.top-bar-brand .brand-mark {
    width: 24px; height: 24px;
    background: var(--rowops-accent);
    border-radius: 5px;
    flex-shrink: 0;
}
/* Brand / club logo image in the top bar (apex = rowOps mark, tenant =
   uploaded club logo). Height-constrained so any aspect ratio fits. */
.top-bar-brand .brand-logo {
    height: 26px; width: auto; max-width: 180px;
    display: block; flex-shrink: 0; border-radius: 4px;
}
/* Super-admin "acting as a tenant" context banner (impersonation). */
.superadmin-actas-banner {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-3);
    background: var(--brand-dark, #1f2a33); color: #f5f1e8;
    padding: 0.4rem 1rem; font-size: 0.88rem;
}
.superadmin-actas-banner a { color: var(--brand-accent, #c4543b); text-decoration: none; font-weight: 600; }
.superadmin-actas-banner a:hover { text-decoration: underline; }
.superadmin-actas-actions { display: flex; align-items: center; gap: var(--space-3); }
.superadmin-actas-stop {
    background: var(--brand-primary, #c4543b); color: #fff; border: 0;
    border-radius: 4px; padding: 0.25rem 0.7rem; font-size: 0.82rem; font-weight: 600; cursor: pointer;
}
.superadmin-actas-stop:hover { filter: brightness(1.1); }
/* Platform-wide announcement banner (super-admin set in /control_hub). Full
   width, square corners, centred — sits above the app shell on every page.
   Colour comes from the bootstrap .alert-info/-warning/-danger class. */
.platform-banner {
    display: flex; align-items: center; justify-content: center;
    gap: var(--space-3); text-align: center;
    border-radius: 0; border-left: 0; border-right: 0;
    padding: 0.5rem 1rem; font-size: 0.92rem;
}
.platform-banner-text { font-weight: 600; }
.platform-banner-close {
    background: none; border: 0; color: inherit; cursor: pointer;
    font-size: 1.25rem; line-height: 1; padding: 0 0.4rem; opacity: 0.7;
}
.platform-banner-close:hover { opacity: 1; }
@media print { .platform-banner { display: none !important; } }
.top-bar-tenant {
    color: var(--rowops-ink-muted);
    font-size: 0.85rem;
    padding-left: var(--space-3);
    border-left: 1px solid var(--rowops-border);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 22ch;
}
.top-bar-spacer { flex: 1 1 auto; }
.top-bar-utils {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}
/* D-race-157 — the Tenant menu: the club's own pages (Tenant admin, Users, Clubs, Club captains, Custom domain, Masters
   handicap), in every mode. A <details>, so it opens on a tap with no script; its panel hangs from the button's right edge
   and never wider than the screen. */
.top-bar-menu { position: relative; }
.top-bar-menu > summary { list-style: none; cursor: pointer; white-space: nowrap; }
.top-bar-menu > summary::-webkit-details-marker { display: none; }
.top-bar-menu > summary::marker { content: ""; }
.top-bar-menu > summary::after {
    content: "\25BE";
    margin-left: 0.35em;
    font-size: 0.8em;
}
.top-bar-menu-list {
    position: absolute;
    right: 0;
    top: calc(100% + 6px);
    z-index: 60;
    min-width: 15rem;
    max-width: calc(100vw - var(--space-6));
    margin: 0;
    padding: var(--space-1);
    list-style: none;
    background: var(--rowops-card);
    border: 1px solid var(--rowops-border);
    border-radius: var(--radius-md);
    box-shadow: var(--rowops-shadow-lift);
}
.top-bar-menu-list a {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 40px;
    padding: 0 var(--space-3);
    color: var(--rowops-ink);
    font-size: 0.9rem;
    text-decoration: none;
    border-bottom: 0;
    border-radius: var(--radius-sm);
}
.top-bar-menu-list a:hover { background: var(--rowops-surface-2); color: var(--rowops-ink); }
.top-bar-user {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--rowops-ink-muted);
    font-size: 0.85rem;
    text-decoration: none;
    border-bottom: 0;
    padding: 0 var(--space-2);
    border-radius: var(--radius-sm);
}
.top-bar-user:hover { color: var(--rowops-ink); background: var(--rowops-surface-2); }
.top-bar-user-avatar {
    width: 26px; height: 26px;
    background: var(--rowops-accent);
    color: var(--rowops-accent-fg);
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 0.72rem;
}
.top-bar-user-name {
    max-width: 14ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Spectator top bar variant — same dimensions, neutral content. */
.top-bar-spectator .top-bar-inner { padding: 0 var(--space-4); }

/* --- App shell layout ------------------------------------------- */
.app-shell {
    display: block;
    min-height: calc(100vh - var(--top-bar-h));
}
.app-shell-with-rail {
    display: grid;
    grid-template-columns: var(--rail-w) 1fr;
    min-height: calc(100vh - var(--top-bar-h));
}

/* --- Left rail -------------------------------------------------- */
.left-rail {
    background: var(--rowops-surface);
    border-right: 1px solid var(--rowops-border);
    padding: var(--space-4) 0;
    position: sticky;
    top: var(--top-bar-h);
    align-self: start;
    height: calc(100vh - var(--top-bar-h));
    overflow-y: auto;
    scrollbar-width: thin;
}
.left-rail::-webkit-scrollbar { width: 6px; }
.left-rail::-webkit-scrollbar-thumb {
    background: var(--rowops-border-strong);
    border-radius: 3px;
}
.rail-head {
    padding: 0 var(--space-4) var(--space-3);
    border-bottom: 1px solid var(--rowops-border);
    margin-bottom: var(--space-3);
}
.rail-regatta {
    display: flex;
    flex-direction: column;
    gap: 2px;
    text-decoration: none;
    border-bottom: 0;
}
.rail-regatta:hover { background: transparent; }
.rail-regatta-name {
    color: var(--rowops-ink) !important;
    font-weight: 700;
    font-size: 0.9rem;
    letter-spacing: -0.01em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.rail-regatta:hover .rail-regatta-name { color: var(--rowops-accent) !important; }
.rail-phase-tag {
    font-size: 0.65rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--rowops-ink-muted);
    font-weight: 700;
}

.rail-nav { padding: 0 var(--space-2); }

/* D-race-189 — the menu search (water modes; /js/rail-menu.js filters what the rail drew). 16px text so a phone does not zoom
   into it on focus; the clear button is a full tap target. */
.rail-search {
    position: relative;
    padding: 0 var(--space-2) var(--space-2);
}
.rail-search-icon {
    position: absolute;
    left: calc(var(--space-2) + 9px);
    top: 9px;
    width: 16px;
    height: 16px;
    color: var(--rowops-ink-muted);
    pointer-events: none;
    display: flex;
}
.rail-search-icon .glyph { width: 16px; height: 16px; }
.rail-search-input {
    width: 100%;
    min-height: 34px;
    padding: 4px 36px 4px 32px;
    font-size: 16px;
    color: var(--rowops-ink);
    background: var(--rowops-surface-2);
    border: 1px solid var(--rowops-border);
    border-radius: var(--radius-sm);
}
.rail-search-input::placeholder { color: var(--rowops-ink-muted); opacity: 1; }
.rail-search-input:focus {
    outline: 2px solid var(--rowops-accent);
    outline-offset: 1px;
    background: var(--rowops-surface);
}
.rail-search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.rail-search-clear {
    position: absolute;
    right: var(--space-2);
    top: 0;
    width: 34px;
    height: 34px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    color: var(--rowops-ink-muted);
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
}
.rail-search-clear[hidden] { display: none; }
.rail-search-clear:hover, .rail-search-clear:focus-visible { color: var(--rowops-ink); background: var(--rowops-surface-2); }
.rail-search-clear .glyph { width: 16px; height: 16px; }
.rail-search-empty {
    margin: var(--space-2) var(--space-1) 0;
    font-size: 0.82rem;
    color: var(--rowops-ink-muted);
}
.rail-search-empty:empty { display: none; }

/* Collapsible group wrapper — <details> shell. Each group remembers
   its open/closed state in localStorage via layout.php inline JS.
   The current-phase group is always rendered open server-side and
   bypasses localStorage so the operator's eye lands on the active
   section on every page load. */
.rail-group-wrap {
    margin: var(--space-3) 0 var(--space-1);
}
.rail-group-wrap > summary { list-style: none; }
.rail-group-wrap > summary::-webkit-details-marker { display: none; }
.rail-group-wrap > summary::marker { display: none; }

/* The group heading itself — bigger, bolder, confidently sized
   compared to the old micro-label. Reads as a real section
   heading rather than an apologetic mouse-grey caption. */
.rail-group {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: 0.82rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--rowops-ink);
    font-weight: 800;
    padding: 6px 8px;
    border-radius: var(--radius-sm);
    cursor: pointer;
    -webkit-user-select: none;
    user-select: none;
    transition: background 0.12s var(--ease-snappy), color 0.12s var(--ease-snappy);
}
.rail-group::after {
    content: "›";
    margin-left: auto;
    font-size: 1rem;
    color: var(--rowops-ink-muted);
    transition: transform 0.18s var(--ease-snappy);
    transform: rotate(90deg);
}
.rail-group-wrap:not([open]) > .rail-group::after {
    transform: rotate(0deg);
}
.rail-group:hover {
    background: var(--rowops-surface-2);
}
/* T-race-TODO-286 item 2: before the draw is published the first group is "Setup", with the stage in small type under it. */
.rail-group-heading {
    display: flex;
    flex-direction: column;
    gap: 1px;
}
.rail-group-stage {
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0;
    text-transform: none;
    color: var(--rowops-ink-muted);
}
.rail-group:hover::after { color: var(--rowops-ink); }

/* Inside an open group the child list gets a tiny left inset so the
   visual hierarchy reads as "heading → indented items". */
.rail-group-wrap[open] > .rail-list {
    padding-left: var(--space-2);
    padding-bottom: var(--space-2);
    margin-top: 2px;
}

/* Current-phase group — accent ring on the LEFT edge of the whole
   group (heading + list) so the operator can scan vertically and
   land on "I'm in this phase". Heading text shifts to brand-red. */
.rail-group-wrap.is-current-phase {
    border-left: 3px solid var(--rowops-accent);
    padding-left: var(--space-1);
    margin-left: -2px;
    background: var(--rowops-accent-tint);
    border-radius: var(--radius-sm);
}
.rail-group-wrap.is-current-phase > .rail-group {
    color: var(--rowops-accent);
}
.rail-group-wrap.is-current-phase > .rail-group::after {
    color: var(--rowops-accent);
}
.rail-group-wrap.rail-group-danger > .rail-group {
    color: var(--rowops-bad);
}

.rail-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.rail-list li { margin: 0; }
.rail-link {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 6px var(--space-3);
    color: var(--rowops-ink-muted);
    text-decoration: none;
    font-size: 0.86rem;
    font-weight: 500;
    border-radius: var(--radius-sm);
    border-bottom: 0;
    position: relative;
    transition: background 0.12s var(--ease-snappy), color 0.12s var(--ease-snappy);
}
.rail-link:hover {
    background: var(--rowops-surface-2);
    color: var(--rowops-ink);
    border-bottom: 0;
}
.rail-link.is-active {
    background: var(--rowops-accent-tint);
    color: var(--rowops-accent);
    font-weight: 600;
}
.rail-link.is-active::before {
    content: "";
    position: absolute;
    left: -2px;
    top: 6px;
    bottom: 6px;
    width: 3px;
    background: var(--rowops-accent);
    border-radius: 2px;
}
.rail-ext {
    margin-left: auto;
    opacity: 0.5;
    font-size: 0.75rem;
}
.rail-link-danger {
    color: var(--rowops-bad);
}
.rail-link-danger:hover {
    background: rgba(176, 42, 55, 0.08);
    color: var(--rowops-bad);
}

/* D-race-157 — the refined menu for the water modes (templates/partials/regatta_rail.php, Services/OperatorMenu): a phase's
   primary item, the next phase on one line, and "More" (a closed <details> with the rest, by section). */
.rail-link.is-primary {
    color: var(--rowops-ink);
    font-weight: 600;
}
.rail-next {
    margin: var(--space-2) 0 var(--space-1);
}
.rail-next-link {
    flex-wrap: wrap;
    gap: 0 var(--space-2);
    font-size: 0.82rem;
    border: 1px dashed var(--rowops-border-strong);
}
.rail-next-phase {
    font-size: 0.65rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-weight: 700;
    color: var(--rowops-ink-muted);
}
.rail-more-body {
    padding: 0 var(--space-2) var(--space-2);
}
.rail-subhead {
    margin: var(--space-3) 0 2px;
    padding: 0 var(--space-3);
    font-size: 0.65rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-weight: 700;
    color: var(--rowops-ink-muted);
}
/* D-race-189: each of More's sections is its own <details>, so More opens onto its headings and each opens on its own
   (/js/rail-menu.js keeps one open at a time). The heading is a tap target on a phone, so it gets a row's height. */
.rail-section > summary { list-style: none; }
.rail-section > summary::-webkit-details-marker { display: none; }
.rail-section > summary::marker { display: none; }
.rail-section > .rail-subhead {
    display: flex;
    align-items: center;
    margin: var(--space-1) 0 0;
    padding: 6px var(--space-3);
    min-height: 32px;
    border-radius: var(--radius-sm);
    cursor: pointer;
    -webkit-user-select: none;
    user-select: none;
}
.rail-section > .rail-subhead::after {
    content: "›";
    margin-left: auto;
    font-size: 0.9rem;
    transition: transform 0.18s var(--ease-snappy);
}
.rail-section[open] > .rail-subhead::after { transform: rotate(90deg); }
.rail-section > .rail-subhead:hover {
    background: var(--rowops-surface-2);
    color: var(--rowops-ink);
}
.rail-section[open] > .rail-subhead { color: var(--rowops-ink); }
.rail-section[open] > .rail-list { padding-bottom: var(--space-1); }

/* T-race-TODO-271 (Brian, 2026-10-18: "menu needs to read clearer , with headers more prominitnet"): the rail's headers read as
   headers. Each group heading (Ready, Setup, More) is now larger than the items under it (it was 0.82rem against their 0.86rem),
   with clear space and a rule above it. More's section headings (Before the day … Admin) are no longer small grey capitals:
   larger, in the ink colour, at the weight of a heading. An open group or section is marked with a tint behind its heading.
   CSS only, on purpose: no label, order or markup changes, because the menu walker and the Mon 19 sheet quote the labels.
   The look itself is T-race-TODO-266's, later. Pinned by RailHeadersLegibleCssTest. */
.rail-group-wrap { margin-top: var(--space-4); }
.rail-group-wrap + .rail-group-wrap { border-top: 1px solid var(--rowops-border); padding-top: var(--space-3); }
.rail-group { font-size: 0.95rem; letter-spacing: 0.05em; padding: 8px; }
.rail-group-wrap[open] > .rail-group { background: var(--rowops-surface-2); }
.rail-subhead { font-size: 0.78rem; font-weight: 800; color: var(--rowops-ink); }
.rail-section > .rail-subhead { margin-top: var(--space-2); color: var(--rowops-ink); }
.rail-section[open] > .rail-subhead { background: var(--rowops-surface-2); }

/* --- Content panel ---------------------------------------------- */
.content-panel {
    padding: var(--space-6) var(--space-8) var(--space-12);
    max-width: 1400px;
    width: 100%;
    margin: 0 auto;
    background: transparent;
}
.content-panel-tenant {
    /* Tenant-wide pages (no rail) get a centred panel mirroring
       the operator content width. */
    max-width: 1200px;
}

/* The legacy .container max-width override is now bypassed inside
   .content-panel since the shell already constrains the working
   width. Override it back to behave naturally so nested .container
   blocks (legacy pages) don't punch outside the panel. */
.content-panel .container,
.content-panel-tenant .container {
    max-width: 100% !important;
    padding-left: 0;
    padding-right: 0;
}

/* --- App footer ------------------------------------------------- */
.app-footer {
    border-top: 1px solid var(--rowops-border);
    padding: var(--space-4) var(--space-6);
    color: var(--rowops-ink-muted);
    font-size: 0.78rem;
}
.app-footer-inner {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    align-items: baseline;
    max-width: 1400px;
    margin: 0 auto;
}
.app-footer a {
    color: var(--rowops-ink-muted);
    border-bottom: 0;
}
.app-footer a:hover { color: var(--rowops-accent); }

/* --- "Powered by rowOps" link ------------------------------------
   SaaS-stage footer rendered when BRAND_POWERED_BY_URL is set.
   Lives at the very end of <body> so it never disturbs operator
   workflow. Three-theme aware via var(--rowops-ink-muted). */
.rowops-powered-by {
    text-align: center;
    padding: 0.5rem 0;
    font-size: 0.75rem;
    color: var(--rowops-ink-muted);
}
.rowops-powered-by a {
    color: var(--rowops-ink-muted);
    text-decoration: none;
    border-bottom: 0;
}
.rowops-powered-by a:hover { color: var(--rowops-accent); }

/* Responsive collapse — at 1100 px or less (tablets and phones) the rail is off-canvas
   altogether: no icon strip. The top bar's menu button (layout.php, [data-rail-toggle])
   slides it in as body.rail-expanded; a click outside it, or Escape, slides it out.
   D-race-149: until then nothing drew that button, so the rail did not exist here.
   A closed rail is hidden as well as moved aside: the button says aria-expanded="false",
   and links a keyboard or a screen reader could still reach would make that a lie. The
   visibility change waits for the slide on the way out. */
@media (max-width: 1100px) {
    .app-shell-with-rail {
        grid-template-columns: 1fr;
    }
    .top-bar-menu-toggle {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: var(--space-2);
        flex: 0 0 auto;
        min-width: 40px;
        min-height: 40px;
    }
    .left-rail {
        position: fixed;
        top: var(--top-bar-h);
        left: 0;
        width: var(--rail-w);
        background: var(--rowops-card);
        box-shadow: var(--rowops-shadow-lift);
        z-index: 40;
        transform: translateX(-100%);
        visibility: hidden;
        transition: transform 0.18s var(--ease-snappy), visibility 0s linear 0.18s;
    }
    body.rail-expanded .left-rail { transform: translateX(0); visibility: visible; transition-delay: 0s; }
    .content-panel { padding: var(--space-4) var(--space-4) var(--space-8); }
}
/* A phone's top bar has no room for the word: the button is its icon, and keeps its aria-label. */
@media (max-width: 576px) {
    .top-bar-menu-toggle-label { display: none; }
}
/* D-race-157 (T-race-TODO-146 item 8, the Tenant menu now in the bar): at 390 px the bar ran off its right edge, so the Tenant
   button, the account and Sign out were off screen (and the page scrolled sideways). At 768 px and below it is TWO rows:
     row 1  the menu button, the brand, the offline-queue pill, the account;
     row 2  the theme, the clock, the Tenant menu, (the platform operator's Hub), Sign out.
   `.top-bar-utils` stops being a box (display: contents), so its children are placed with `order` among the bar's own; the
   spacer becomes the full-width break between the rows, and the queue pill and the Tenant button take the free space to
   their left (margin-left: auto). Wider than a row, the bar wraps a third row (a platform operator's Hub) instead of
   overflowing, and --top-bar-h, which the off-canvas rail hangs from, is the two-row height. TopBarLayoutTest pins every
   rule here: the shapes are what keeps the bar on the screen.

   WHY 768 and not 576 (the w4 review, P2-6). The first build stopped at 576 px, the phone, and the bar was never measured above
   it. Shown in an iframe of each width in headless Edge (scrollWidth against clientWidth), the ONE-row bar overflows from 577
   px up to about 650 px for an admin, 705 px for a platform operator (the Hub), 715 px with the offline-queue pill showing
   ("offline (12 queued, 2 stuck)": the state a tablet at the start tower is in when it matters), and all the way to 768 px
   and past it with a club logo as wide as .brand-logo allows (180 px). Two rows fit every one of those at every width from
   390 to 768 px. 768 px is also where the bar already drops the tenant chip and the account's name (below), so the compact
   bar has one breakpoint. Above 768 the bar is one row, as before.

   The public spectator header (`.top-bar-spectator`: the brand and the theme toggle, nothing that can overflow) is not
   re-flowed: this block used to catch it too, making it 97 px high with the theme toggle ahead of the brand. */
@media (max-width: 768px) {
    :root { --top-bar-h: 96px; }
    .top-bar:not(.top-bar-spectator) { height: auto; min-height: var(--top-bar-h); }
    .top-bar-spectator { height: 56px; }
    .top-bar:not(.top-bar-spectator) .top-bar-inner {
        flex-wrap: wrap;
        align-content: center;
        height: auto;
        min-height: var(--top-bar-h);
        gap: var(--space-1) var(--space-2);
    }
    .top-bar-utils { display: contents; }
    .top-bar-menu-toggle { order: 0; }
    .top-bar:not(.top-bar-spectator) .top-bar-brand { order: 1; min-width: 0; margin-right: auto; }
    .top-bar:not(.top-bar-spectator) .top-bar-brand-name { max-width: 12ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .queue-status { order: 2; }
    .top-bar-user { order: 3; }
    .top-bar:not(.top-bar-spectator) .top-bar-spacer { order: 4; flex: 0 0 100%; height: 0; }
    .top-bar-utils > [data-theme-toggle] { order: 5; }
    .top-bar-utils > .clock-drift { order: 6; }
    .top-bar-utils > .top-bar-menu { order: 7; margin-left: auto; }
    .top-bar-utils > .top-bar-hub { order: 8; }
    .top-bar-utils > form { order: 9; }
    .top-bar:not(.top-bar-spectator) .theme-toggle button { padding-left: 0.5rem; padding-right: 0.5rem; }
}
@media (max-width: 768px) {
    .top-bar-inner { padding: 0 var(--space-3); }
    .top-bar-tenant { display: none; }
    .top-bar-user-name { display: none; }
    .content-panel { padding: var(--space-3); }
}

/* =================================================================
   Dashboard summary surfaces — templates/regatta_show.php
   ================================================================= */

/* Hero header */
.dash-hero {
    margin-bottom: var(--space-6);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--rowops-border);
}
.dash-hero-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
    flex-wrap: wrap;
}
.dash-hero-titles { flex: 1 1 auto; min-width: 0; }
.dash-hero-name {
    font-size: 1.75rem;
    font-weight: 700;
    line-height: 1.15;
    letter-spacing: -0.02em;
    margin: 0 0 var(--space-2);
}
.dash-hero-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-4);
    color: var(--rowops-ink-muted);
    font-size: 0.86rem;
    margin: 0;
}
.dash-hero-cta { flex: 0 0 auto; }
.dash-hero .phase-chevron { margin-top: var(--space-3); }

/* Active draw-job banner */
.banner {
    border: 1px solid var(--rowops-border);
    border-left: 4px solid var(--rowops-warn);
    border-radius: var(--radius-md);
    background: var(--rowops-card);
    margin-bottom: var(--space-4);
    box-shadow: var(--rowops-shadow);
}
.banner-warning { border-left-color: var(--rowops-warn); }
.banner-body {
    padding: var(--space-3) var(--space-4);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    flex-wrap: wrap;
}
.banner-headline { font-size: 1rem; font-weight: 600; margin: var(--space-1) 0; }
.banner-sub { color: var(--rowops-ink-muted); font-size: 0.82rem; }

/* Smart-guide toggle line */
.dash-smart-guide-toggle {
    text-align: right;
    margin-bottom: var(--space-2);
    font-size: 0.78rem;
}

/* KPI strip */
.dash-kpis {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-3);
    margin-bottom: var(--space-4);
}
.dash-kpi {
    display: block;
    padding: var(--space-3) var(--space-4);
    background: var(--rowops-card);
    border: 1px solid var(--rowops-border);
    border-radius: var(--radius-md);
    text-decoration: none;
    color: inherit;
    transition: border-color 0.12s var(--ease-snappy), transform 0.12s var(--ease-snappy);
    border-bottom: 0;
}
a.dash-kpi:hover {
    border-color: var(--rowops-border-strong);
    transform: translateY(-1px);
    color: inherit;
    border-bottom: 0;
}
.dash-kpi-value-row {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    margin-top: var(--space-1);
}
.dash-kpi-value {
    font-size: 1.5rem;
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.02em;
}
.dash-kpi-sub {
    color: var(--rowops-ink-muted);
    font-size: 0.78rem;
    margin-top: var(--space-1);
}

/* Per-day issue + race-day-progress strips */
.dash-strip {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-3);
    padding: var(--space-2) var(--space-4);
    background: var(--rowops-card);
    border: 1px solid var(--rowops-border);
    border-radius: var(--radius-md);
    margin-bottom: var(--space-4);
    font-size: 0.86rem;
}
.dash-day-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 2px var(--space-2);
    border: 1px solid;
    border-radius: var(--radius-sm);
    font-weight: 600;
    text-decoration: none;
}
.dash-day-chip.is-error   { border-color: var(--rowops-bad);  color: var(--rowops-bad); }
.dash-day-chip.is-warning { border-color: var(--rowops-warn); color: var(--rowops-warn); }
.dash-day-chip.is-ok      { border-color: var(--rowops-good); color: var(--rowops-good); }
.dash-day-chip:hover { background: var(--rowops-surface-2); }

/* Action panel — the operator's working surface */
.dash-action-panel {
    margin: var(--space-6) 0;
}
.dash-section-head {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
    flex-wrap: wrap;
}
.dash-section-head h2 {
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    margin: 0;
}
.dash-section-hint {
    color: var(--rowops-ink-muted);
    font-size: 0.82rem;
}

.dash-action-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-3);
}
.dash-action-card {
    background: var(--rowops-card);
    border: 1px solid var(--rowops-border);
    border-radius: var(--radius-md);
    padding: var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}
.dash-action-title {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 700;
    color: var(--rowops-ink-muted);
}
.dash-action-help {
    color: var(--rowops-ink-muted);
    font-size: 0.82rem;
    margin: 0;
}
.dash-action-divider {
    border-top: 1px solid var(--rowops-border);
    padding-top: var(--space-2);
    margin-top: var(--space-2);
    color: var(--rowops-ink-muted);
    font-size: 0.78rem;
    font-weight: 600;
}
.dash-action-button-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}
.dash-day-buttons {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}
.dash-day-button-group {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 2px var(--space-1);
    border-radius: var(--radius-sm);
}
.dash-day-button-group.is-locked {
    background: rgba(220, 200, 80, 0.1);
}
.dash-inline-form {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

/* Race-day cockpit — big primary tiles */
.dash-race-day { background: transparent; }
.dash-cockpit-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-3);
    margin-bottom: var(--space-3);
}
/* T-race-TODO-298: one board (a finish tablet's login on the Control page) fills the row, not half of it. */
.dash-cockpit-grid.is-one { grid-template-columns: 1fr; }
.dash-cockpit-tile {
    background: var(--rowops-card);
    border: 2px solid var(--rowops-accent);
    border-radius: var(--radius-lg);
    padding: var(--space-8) var(--space-4);
    text-align: center;
    text-decoration: none;
    color: inherit;
    transition: transform 0.12s var(--ease-snappy), box-shadow 0.12s var(--ease-snappy);
    border-bottom: 2px solid var(--rowops-accent);
    min-height: 200px;
    display: flex;
    flex-direction: column;
    justify-content: center;
}
.dash-cockpit-tile:hover {
    transform: translateY(-2px);
    box-shadow: var(--rowops-shadow-lift);
    color: inherit;
    border-bottom: 2px solid var(--rowops-accent);
}
.dash-cockpit-glyph {
    font-size: 3rem;
    line-height: 1;
    margin-bottom: var(--space-3);
    color: var(--rowops-accent);
}
.dash-cockpit-title {
    font-size: 1.4rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    margin-bottom: var(--space-1);
}
.dash-cockpit-sub { color: var(--rowops-ink-muted); font-size: 0.86rem; }
.dash-race-day-extras {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
    padding: var(--space-3) var(--space-4);
    background: var(--rowops-card);
    border: 1px solid var(--rowops-border);
    border-radius: var(--radius-md);
}

/* Publish panel */
.dash-publish-grid {
    display: grid;
    grid-template-columns: 2fr 1fr;
    gap: var(--space-4);
}
.dash-publish-list,
.dash-publish-side {
    background: var(--rowops-card);
    border: 1px solid var(--rowops-border);
    border-radius: var(--radius-md);
    padding: var(--space-4);
}
.dash-publish-row {
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--rowops-border);
}
.dash-publish-row:last-of-type { border-bottom: 0; }
.dash-publish-row-head {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}
.dash-publish-row-spacer { flex: 1 1 auto; }
.dash-publish-row-help {
    color: var(--rowops-ink-muted);
    font-size: 0.78rem;
    margin-top: var(--space-1);
}
.dash-publish-row-days {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-top: var(--space-2);
    font-size: 0.82rem;
}
.dash-publish-row-days label {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    margin: 0;
}
.dash-publish-side-title {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 700;
    color: var(--rowops-ink-muted);
    margin-bottom: var(--space-2);
}
.dash-publish-side .btn { margin-top: var(--space-1); }

/* Quick deep-links grid (tertiary nav) */
.dash-quick-links { margin: var(--space-6) 0; }
.dash-quick-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-3);
}
.dash-quick-card {
    background: var(--rowops-card);
    border: 1px solid var(--rowops-border);
    border-radius: var(--radius-md);
    padding: var(--space-4);
}
.dash-quick-title {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 700;
    color: var(--rowops-ink-muted);
    margin: 0 0 var(--space-2);
}
.dash-quick-card ul {
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: 0.88rem;
}
.dash-quick-card li {
    padding: 3px 0;
    border-bottom: 1px solid var(--rowops-border);
}
.dash-quick-card li:last-child { border-bottom: 0; }
.dash-quick-card a {
    color: var(--rowops-ink);
    text-decoration: none;
    border-bottom: 0;
    display: block;
}
.dash-quick-card a:hover { color: var(--rowops-accent); }

/* Danger zone */
.dash-danger {
    margin-top: var(--space-8);
    padding-top: var(--space-4);
    border-top: 1px solid var(--rowops-border);
}
.dash-danger-summary {
    color: var(--rowops-bad);
    font-weight: 700;
    cursor: pointer;
    font-size: 0.92rem;
}
.dash-danger-body {
    margin-top: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: rgba(176, 42, 55, 0.04);
    border: 1px solid rgba(176, 42, 55, 0.2);
    border-radius: var(--radius-md);
}

/* Responsive collapses for dashboard surfaces */
@media (max-width: 1100px) {
    .dash-action-grid,
    .dash-quick-grid { grid-template-columns: repeat(2, 1fr); }
    .dash-publish-grid { grid-template-columns: 1fr; }
}
@media (max-width: 768px) {
    .dash-kpis,
    .dash-action-grid,
    .dash-quick-grid,
    .dash-cockpit-grid { grid-template-columns: 1fr; }
    .dash-hero-name { font-size: 1.4rem; }
}

/* --- Sun mode parity for the new shell + dashboard --------------- */
html.theme-sun .top-bar, body.theme-sun .top-bar {
    background: #000;
    border-bottom: 3px solid #fff;
}
html.theme-sun .top-bar-brand, body.theme-sun .top-bar-brand { color: #fff !important; }
html.theme-sun .top-bar-brand .brand-mark, body.theme-sun .top-bar-brand .brand-mark { background: #fde047; }
html.theme-sun .top-bar-tenant, body.theme-sun .top-bar-tenant { color: #fde047; }
html.theme-sun .top-bar-user, body.theme-sun .top-bar-user { color: #fff; }
html.theme-sun .top-bar-user-avatar, body.theme-sun .top-bar-user-avatar {
    background: #fde047; color: #000;
}

html.theme-sun .left-rail, body.theme-sun .left-rail {
    background: #000;
    border-right: 3px solid #fff;
}
html.theme-sun .rail-link, body.theme-sun .rail-link {
    color: #fff;
    font-weight: 700;
}
html.theme-sun .rail-link.is-active, body.theme-sun .rail-link.is-active {
    background: #fde047;
    color: #000;
}
html.theme-sun .rail-link.is-active::before, body.theme-sun .rail-link.is-active::before {
    background: #fff;
}
html.theme-sun .rail-group, body.theme-sun .rail-group { color: #fff; }
html.theme-sun .rail-group.is-current-phase, body.theme-sun .rail-group.is-current-phase {
    color: #fde047;
    border-left-color: #fde047;
}

html.theme-sun .dash-kpi, body.theme-sun .dash-kpi,
html.theme-sun .dash-strip, body.theme-sun .dash-strip,
html.theme-sun .dash-action-card, body.theme-sun .dash-action-card,
html.theme-sun .dash-quick-card, body.theme-sun .dash-quick-card,
html.theme-sun .dash-publish-list, body.theme-sun .dash-publish-list,
html.theme-sun .dash-publish-side, body.theme-sun .dash-publish-side,
html.theme-sun .dash-race-day-extras, body.theme-sun .dash-race-day-extras,
html.theme-sun .banner, body.theme-sun .banner {
    background: #000;
    border: 3px solid #fff;
}
html.theme-sun .dash-cockpit-tile, body.theme-sun .dash-cockpit-tile {
    background: #000;
    border-color: #fde047;
}
html.theme-sun .dash-cockpit-glyph, body.theme-sun .dash-cockpit-glyph { color: #fde047; }
html.theme-sun .dash-cockpit-title, body.theme-sun .dash-cockpit-title { color: #fff; }

/* --- Dark mode parity -------------------------------------------- */
html.theme-dark .top-bar, body.theme-dark .top-bar {
    background: #0e1014;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
html.theme-dark .left-rail, body.theme-dark .left-rail {
    background: #16202A;
    border-right: 1px solid rgba(255, 255, 255, 0.08);
}
html.theme-dark .rail-link.is-active, body.theme-dark .rail-link.is-active {
    background: rgba(255, 122, 102, 0.12);
    color: #ff7a66;
}
html.theme-dark .rail-link.is-active::before, body.theme-dark .rail-link.is-active::before {
    background: #ff7a66;
}
html.theme-dark .dash-cockpit-tile, body.theme-dark .dash-cockpit-tile {
    border-color: #ff7a66;
}
html.theme-dark .dash-cockpit-glyph, body.theme-dark .dash-cockpit-glyph { color: #ff7a66; }

/* ──────────────────────────────────────────────────────────────────
   IC4 (2026-05-27) — Head-mode theme overrides
   Head-mode templates (templates/control/start_head.php +
   finish_head.php + head_finish_audit.php + head_*.php) define their
   selectors inline. CSS custom props (var(--rowops-surface-2), etc.)
   re-theme automatically; the explicit overrides below cover the
   state colours + accent backgrounds that don't follow the base
   custom-prop chain.

   Sun mode swap rule (operator-stated 2026-05-24): red text is
   unreadable in direct sun → jet-yellow #fde047 on solid black for
   accent backgrounds + state highlights; #fff base for body text.

   Night mode (dark) rule: red text → warm-orange #ff7a66 to match
   .dash-cockpit-tile pattern; amber/yellow → #f0c674; green → #6ed28b
   (matches existing .gap-spread-* dark overrides).
   ────────────────────────────────────────────────────────────────── */

/* --- Sun-mode parity for head-mode operator + admin surfaces -----
   D-race-190 point 1: the division tabs, the hint's kbd chips, the result
   line (the flash banners), the overdue countdown and the finish rail's
   position pill are no longer styled here. Their colours are the --hb-*
   board tokens (the "HEAD BOARDS" block further down), defined once for
   Day, Night and Sun, so a theme rule written here would out-rank a token
   and put the old colour back (specificity 0,3,1 against the template's
   0,2,0). Add a colour for those to the token block, not to a theme. */

/* Bow-input card — accent border-left becomes jet-yellow; card body
   is black-on-white so the operator's typed bow numeral reads at
   distance. */
html.theme-sun .head-bow-card, body.theme-sun .head-bow-card {
    background: #000;
    border: 3px solid #fff;
    border-left: 4px solid #fde047;
}
html.theme-sun .head-bow-input, body.theme-sun .head-bow-input {
    background: #000;
    color: #fff;
    border-color: #fff;
}
html.theme-sun .head-bow-hint, body.theme-sun .head-bow-hint { color: #f0f0f0; }

/* Countdown chip — fixed-mode interval cue on start_head.php. Sun
   keeps the existing jet-yellow .is-due cue but rebases the neutral
   baseline to high-contrast B/W with a white border. (.is-overdue is a
   board token: --hb-flash-err-*.) */
html.theme-sun .head-countdown, body.theme-sun .head-countdown {
    background: #000;
    color: #fff;
    border: 2px solid #fff;
}
html.theme-sun .head-countdown.is-due, body.theme-sun .head-countdown.is-due {
    background: #fde047;
    color: #000;
    border-color: #fde047;
}

/* Queue table — row tint colours (rgba green/blue/grey) lose contrast
   on the sun-mode black baseline; swap to jet-yellow accent stripes. */
html.theme-sun .head-queue, body.theme-sun .head-queue { color: #fff; }
html.theme-sun .head-queue th, body.theme-sun .head-queue th { color: #fff; border-color: #fff; }
html.theme-sun .head-queue td, body.theme-sun .head-queue td { border-color: #fff; }
html.theme-sun .head-queue tr.is-released, body.theme-sun .head-queue tr.is-released {
    background: rgba(253, 224, 71, 0.18);
}
html.theme-sun .head-queue tr.is-called, body.theme-sun .head-queue tr.is-called {
    background: rgba(253, 224, 71, 0.10);
}
html.theme-sun .head-queue tr.is-finished, body.theme-sun .head-queue tr.is-finished {
    background: rgba(255, 255, 255, 0.10);
    color: #f0f0f0;
}

/* Right-rail card + recent list. */
html.theme-sun .head-rail-card, body.theme-sun .head-rail-card {
    background: #000;
    border: 3px solid #fff;
}
html.theme-sun .head-rail-card h2, body.theme-sun .head-rail-card h2 { color: #fff; }
html.theme-sun .head-recent li, body.theme-sun .head-recent li {
    border-bottom-color: #fff;
}
html.theme-sun .head-recent .r-bow, body.theme-sun .head-recent .r-bow { color: #fff; }
html.theme-sun .head-recent .r-name, body.theme-sun .head-recent .r-name { color: #fff; }
html.theme-sun .head-recent .r-club, body.theme-sun .head-recent .r-club { color: #f0f0f0; }
html.theme-sun .head-recent .r-meta, body.theme-sun .head-recent .r-meta { color: #f0f0f0; }

/* Audit-mode action pills — head_finish_audit.php uses per-action
   colour pills (release/call/recall/finish/undo). Sun-mode flattens
   them to jet-yellow-on-black so they read as same-class tags;
   semantic distinction lives in the textual label. */
html.theme-sun .action-pill, body.theme-sun .action-pill {
    background: #000;
    color: #fde047;
    border: 1px solid #fde047;
    font-weight: 800;
}
html.theme-sun .audit-table tr.dup-bright, body.theme-sun .audit-table tr.dup-bright {
    background: rgba(253, 224, 71, 0.18);
}
html.theme-sun .audit-table tr.dup-soft, body.theme-sun .audit-table tr.dup-soft {
    background: rgba(253, 224, 71, 0.08);
}

/* --- Night-mode (dark) parity for head-mode surfaces -------------
   D-race-190 point 1: the division tabs, the result line and the overdue
   countdown moved to the --hb-* board tokens (see the Sun note above). */

html.theme-dark .head-bow-card, body.theme-dark .head-bow-card {
    border-left-color: #ff7a66;
}
/* .head-countdown.is-due keeps its jet-yellow baseline — it's a cue,
   not a warning, and reads correctly on dark slate. */

/* Action pills carry baked-in state colours that need a dark-mode
   rebase so dark-on-light pills don't read as washed-out. */
html.theme-dark .action-pill.release, body.theme-dark .action-pill.release { color: #6ed28b; }
html.theme-dark .action-pill.call, body.theme-dark .action-pill.call { color: #7fb6ff; }
html.theme-dark .action-pill.recall, body.theme-dark .action-pill.recall { color: #f0c674; }
html.theme-dark .action-pill.finish, body.theme-dark .action-pill.finish { color: #6ed28b; }
html.theme-dark .action-pill.undo, body.theme-dark .action-pill.undo { color: var(--rowops-ink-muted); }

/* Audit dup-row tints (red rgba on dup-bright) need a warm-orange
   rebase so they still pop on dark slate. */
html.theme-dark .audit-table tr.dup-bright, body.theme-dark .audit-table tr.dup-bright {
    background: rgba(255, 122, 102, 0.12);
}
html.theme-dark .audit-table tr.dup-soft, body.theme-dark .audit-table tr.dup-soft {
    background: rgba(255, 122, 102, 0.05);
}

/* ═══════════════════════════════════════════════════════════════════════════
   HEAD BOARDS — board tokens (D-race-190 point 1, "C1: contrast and board tokens")

   Brian, 2026-10-16: "the text isnt even clear, white on cream background in
   some cases". The head start board, the head finish board and the finish
   audit page (templates/control/start_head.php, finish_head.php,
   templates/head_finish_audit.php) are read at arm's length, outdoors, by
   volunteers. Three families of defect:
     - Bootstrap's reboot paints `kbd` with the page background as its TEXT
       colour, so the Space / Esc chips were white on white (1.00:1);
     - `btn-outline-warning` is pale yellow (#ffc107): Recall and Reset were
       1.45:1 on the cream card;
     - white text on the tenant's brand colour (the active division tab, the
       Called / On course chip, Start camera, the confirm OK) is 3.6:1 on the
       platform bronze and nothing at all on a pale tenant colour.

   RULES OF THIS BLOCK.
     1. Every colour the boards paint is a --hb-* token, defined three times
        below: Day on :root, Night, Sun. HeadBoardsContrastTest reads these
        blocks and pins each text / fill pair: 4.5:1 at least, 7:1 for key
        race-day text (bow, crew, state, action, result line). The pairs on
        the rendered pages, in Chrome, on a set of tenant colours, are a
        by-hand run of tools/head-boards-contrast/run.py.
     2. TEXT NEVER SITS ON THE TENANT COLOUR. --rowops-accent shows on the
        boards only as a bar or a border (the active tab's underline, the bow
        card's left edge). Never write `color:` or `background:` from
        --rowops-accent, --rowops-accent-fg, --rowops-link or --brand-* in a
        board rule. The test fails on a tenant colour that makes the pair
        unreadable (a pale or a saturated yellow one does).
     3. SUN reduces every fill to one yellow, so its states also differ in
        SHAPE (S-21): border style, border width and corner shape, on top of
        the words every chip, line and button carries. The test fails when two
        states of one group share a shape.
     4. A board rule is scoped by .hb-scope, the wrapper the three templates
        open (display: contents, so it adds no box). What lies outside it and
        belongs to the page (the control tabs, the top bar and crumbs, the
        confirm dialog, the timing-queue.js strips) is reached with
        body:has(.hb-scope). A browser without :has() keeps the platform look
        there.
     5. The old Night / Sun rules for the same components are deleted, not
        left to fight: a theme rule such as `html.theme-sun .div-tab.is-active`
        (0,3,1) out-ranks a template rule (0,2,0) and would put an old colour
        back over a token.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Day. The edge tokens are `border` shorthands; the -r tokens are corner radii. */
:root {
    --hb-ink: #1f2a33;
    --hb-on-ink: #ffffff;
    --hb-muted: #4a4f55;
    --hb-edge: #8a8578;
    --hb-focus: #1f2a33;
    --hb-btn-bw: 1px;

    --hb-kbd-bg: #e9e4d8;
    --hb-kbd-fg: #1f2a33;

    /* State chips: WAITING, CALLED, AWAY / ON COURSE, FINISHED, DNS / DNF, DQ, PENDING (queued or not confirmed). */
    --hb-neutral-bg: #e9e4d8;  --hb-neutral-fg: #1f2a33;  --hb-neutral-edge: 1px solid #8a8578;   --hb-neutral-r: 0.375rem;
    --hb-info-bg: #0b4f8a;     --hb-info-fg: #ffffff;     --hb-info-edge: 1px solid #0b4f8a;      --hb-info-r: 0.375rem;
    --hb-go-bg: #0b5d33;       --hb-go-fg: #ffffff;       --hb-go-edge: 1px solid #0b5d33;        --hb-go-r: 0.375rem;
    --hb-done-bg: #1f2a33;     --hb-done-fg: #ffffff;     --hb-done-edge: 1px solid #1f2a33;      --hb-done-r: 0.375rem;
    --hb-hold-bg: #6b3f00;     --hb-hold-fg: #ffffff;     --hb-hold-edge: 1px solid #6b3f00;      --hb-hold-r: 0.375rem;
    --hb-bad-bg: #8a1c27;      --hb-bad-fg: #ffffff;      --hb-bad-edge: 1px solid #8a1c27;       --hb-bad-r: 0.375rem;
    --hb-pending-bg: #ffffff;  --hb-pending-fg: #1f2a33;  --hb-pending-edge: 2px dashed #1f2a33;  --hb-pending-r: 0.375rem;

    /* The result line (#head-flash) and the GONE card. */
    --hb-flash-ok-bg: #e8f5ec;       --hb-flash-ok-fg: #0b5d33;       --hb-flash-ok-edge: 1px solid #0b5d33;
    --hb-flash-warn-bg: #fff4dc;     --hb-flash-warn-fg: #6b3f00;     --hb-flash-warn-edge: 1px solid #6b3f00;
    --hb-flash-err-bg: #fdecee;      --hb-flash-err-fg: #8a1c27;      --hb-flash-err-edge: 1px solid #8a1c27;
    --hb-flash-neutral-bg: #f0f0f0;  --hb-flash-neutral-fg: #1f2a33;  --hb-flash-neutral-edge: 1px solid #8a8578;

    /* The big action buttons and their blocked / disabled states (point 5 draws them). */
    --hb-release-bg: #0b5d33;  --hb-release-fg: #ffffff;  --hb-release-edge: 3px solid #0b5d33;
    --hb-finish-bg: #0b3d6b;   --hb-finish-fg: #ffffff;   --hb-finish-edge: 3px solid #0b3d6b;
    --hb-blocked-bg: #fff4dc;  --hb-blocked-fg: #6b3f00;  --hb-blocked-edge: 3px solid #6b3f00;
    --hb-dis-bg: #e9e4d8;      --hb-dis-fg: #4a4f55;      --hb-dis-edge: 2px solid #8a8578;

    --hb-tab-on-bg: #1f2a33;  --hb-tab-on-fg: #ffffff;  --hb-tab-on-bar: var(--rowops-accent);
    --hb-match-bg: #fff1b8;   --hb-match-fg: #1f2a33;   --hb-match-edge: 2px solid #6b3f00;
    --hb-link: #0b4f8a;

    --hb-danger-ink: #8a1c27;  --hb-danger-bg: #8a1c27;  --hb-danger-fg: #ffffff;
    --hb-primary-ink: #1f2a33;
    --hb-cam-info: #0b4f8a;    --hb-cam-warn: #6b3f00;
    --hb-strip-warn-bg: #6b3f00;  --hb-strip-warn-fg: #ffffff;  --hb-strip-warn-edge: 4px solid #6b3f00;
    --hb-confirm-ok-bg: #1f2a33;  --hb-confirm-ok-fg: #ffffff;
}

/* Night. */
html.theme-dark, body.theme-dark {
    --hb-ink: #ecedf0;
    --hb-on-ink: #16202a;
    --hb-muted: #b9bdc4;
    --hb-edge: #6b7684;
    --hb-focus: #ecedf0;
    --hb-btn-bw: 1px;

    --hb-kbd-bg: #2a3340;
    --hb-kbd-fg: #ecedf0;

    --hb-neutral-bg: #2a3340;  --hb-neutral-fg: #ecedf0;  --hb-neutral-edge: 1px solid #6b7684;   --hb-neutral-r: 0.375rem;
    --hb-info-bg: #8cc4ff;     --hb-info-fg: #0a1a2e;     --hb-info-edge: 1px solid #8cc4ff;      --hb-info-r: 0.375rem;
    --hb-go-bg: #6ed28b;       --hb-go-fg: #04160c;       --hb-go-edge: 1px solid #6ed28b;        --hb-go-r: 0.375rem;
    --hb-done-bg: #ecedf0;     --hb-done-fg: #16202a;     --hb-done-edge: 1px solid #ecedf0;      --hb-done-r: 0.375rem;
    --hb-hold-bg: #ffd27a;     --hb-hold-fg: #2b1d00;     --hb-hold-edge: 1px solid #ffd27a;      --hb-hold-r: 0.375rem;
    --hb-bad-bg: #ff9a8c;      --hb-bad-fg: #2e0a0f;      --hb-bad-edge: 1px solid #ff9a8c;       --hb-bad-r: 0.375rem;
    --hb-pending-bg: #16202a;  --hb-pending-fg: #ecedf0;  --hb-pending-edge: 2px dashed #ecedf0;  --hb-pending-r: 0.375rem;

    --hb-flash-ok-bg: #12261b;       --hb-flash-ok-fg: #7ee2a0;       --hb-flash-ok-edge: 1px solid #7ee2a0;
    --hb-flash-warn-bg: #2b2208;     --hb-flash-warn-fg: #ffd27a;     --hb-flash-warn-edge: 1px solid #ffd27a;
    --hb-flash-err-bg: #2e1518;      --hb-flash-err-fg: #ff9a8c;      --hb-flash-err-edge: 1px solid #ff9a8c;
    --hb-flash-neutral-bg: #232b34;  --hb-flash-neutral-fg: #ecedf0;  --hb-flash-neutral-edge: 1px solid #6b7684;

    --hb-release-bg: #6ed28b;  --hb-release-fg: #04160c;  --hb-release-edge: 3px solid #6ed28b;
    --hb-finish-bg: #8cc4ff;   --hb-finish-fg: #0a1a2e;   --hb-finish-edge: 3px solid #8cc4ff;
    --hb-blocked-bg: #2b2208;  --hb-blocked-fg: #ffd27a;  --hb-blocked-edge: 3px solid #ffd27a;
    --hb-dis-bg: #2a3340;      --hb-dis-fg: #b9bdc4;      --hb-dis-edge: 2px solid #6b7684;

    --hb-tab-on-bg: #ecedf0;  --hb-tab-on-fg: #16202a;  --hb-tab-on-bar: var(--rowops-accent);
    --hb-match-bg: #2e2a14;   --hb-match-fg: #ecedf0;   --hb-match-edge: 2px solid #ffd27a;
    --hb-link: #8cc4ff;

    --hb-danger-ink: #ff9a8c;  --hb-danger-bg: #ff9a8c;  --hb-danger-fg: #2e0a0f;
    --hb-primary-ink: #ecedf0;
    --hb-cam-info: #8cc4ff;    --hb-cam-warn: #ffd27a;
    --hb-strip-warn-bg: #6b3f00;  --hb-strip-warn-fg: #ffffff;  --hb-strip-warn-edge: 4px solid #ffd27a;
    --hb-confirm-ok-bg: #ecedf0;  --hb-confirm-ok-fg: #16202a;
}

/* Sun: black, white and jet yellow, and every state also has its own SHAPE (S-21). */
html.theme-sun, body.theme-sun {
    --hb-ink: #ffffff;
    --hb-on-ink: #000000;
    --hb-muted: #f0f0f0;
    --hb-edge: #ffffff;
    --hb-focus: #fde047;
    --hb-btn-bw: 3px;

    --hb-kbd-bg: #000000;
    --hb-kbd-fg: #ffffff;

    --hb-neutral-bg: #000000;  --hb-neutral-fg: #ffffff;  --hb-neutral-edge: 2px solid #ffffff;   --hb-neutral-r: 0.25rem;
    --hb-info-bg: #000000;     --hb-info-fg: #8cc4ff;     --hb-info-edge: 4px double #8cc4ff;     --hb-info-r: 0.25rem;
    --hb-go-bg: #fde047;       --hb-go-fg: #000000;       --hb-go-edge: 2px solid #ffffff;        --hb-go-r: 999px;
    --hb-done-bg: #ffffff;     --hb-done-fg: #000000;     --hb-done-edge: 2px solid #ffffff;      --hb-done-r: 0;
    --hb-hold-bg: #fde047;     --hb-hold-fg: #000000;     --hb-hold-edge: 3px dashed #000000;     --hb-hold-r: 0.25rem;
    --hb-bad-bg: #ff6b6b;      --hb-bad-fg: #000000;      --hb-bad-edge: 3px solid #ffffff;       --hb-bad-r: 0.9em 0 0.9em 0;
    --hb-pending-bg: #000000;  --hb-pending-fg: #fde047;  --hb-pending-edge: 3px dotted #fde047;  --hb-pending-r: 0.25rem;

    --hb-flash-ok-bg: #000000;       --hb-flash-ok-fg: #ffffff;       --hb-flash-ok-edge: 3px solid #ffffff;
    --hb-flash-warn-bg: #000000;     --hb-flash-warn-fg: #fde047;     --hb-flash-warn-edge: 4px dashed #fde047;
    --hb-flash-err-bg: #fde047;      --hb-flash-err-fg: #000000;      --hb-flash-err-edge: 5px double #ffffff;
    --hb-flash-neutral-bg: #000000;  --hb-flash-neutral-fg: #ffffff;  --hb-flash-neutral-edge: 2px dotted #ffffff;

    --hb-release-bg: #fde047;  --hb-release-fg: #000000;  --hb-release-edge: 4px solid #ffffff;
    --hb-finish-bg: #fde047;   --hb-finish-fg: #000000;   --hb-finish-edge: 4px solid #ffffff;
    --hb-blocked-bg: #000000;  --hb-blocked-fg: #fde047;  --hb-blocked-edge: 4px dashed #fde047;
    --hb-dis-bg: #000000;      --hb-dis-fg: #f0f0f0;      --hb-dis-edge: 2px dotted #f0f0f0;

    --hb-tab-on-bg: #fde047;  --hb-tab-on-fg: #000000;  --hb-tab-on-bar: #000000;
    --hb-match-bg: #000000;   --hb-match-fg: #ffffff;   --hb-match-edge: 4px solid #fde047;
    --hb-link: #fde047;

    --hb-danger-ink: #fde047;  --hb-danger-bg: #fde047;  --hb-danger-fg: #000000;
    --hb-primary-ink: #fde047;
    --hb-cam-info: #8cc4ff;    --hb-cam-warn: #fde047;
    --hb-strip-warn-bg: #000000;  --hb-strip-warn-fg: #fde047;  --hb-strip-warn-edge: 4px dashed #fde047;
    --hb-confirm-ok-bg: #facc15;  --hb-confirm-ok-fg: #000000;
}

.hb-scope { display: contents; }

/* Keys: Space, Esc, R. Bootstrap's reboot makes a kbd's TEXT the page background colour. */
.hb-scope kbd {
    background: var(--hb-kbd-bg);
    color: var(--hb-kbd-fg);
    border: 1px solid var(--hb-edge);
}

/* The bow box: a placeholder that reads, and a focus ring that is not the tenant's colour. */
.hb-scope .head-bow-input::placeholder { color: var(--hb-muted); opacity: 1; }

/* State chips. The pill's classes are `badge is-<tone> state-pill <state>`: the state decides the colours (the tone
   is shared: Released and Finished are both is-ok). The extra .badge keeps these ahead of the global Sun tones. */
.hb-scope .badge.state-pill.queued {
    background: var(--hb-neutral-bg); color: var(--hb-neutral-fg); border: var(--hb-neutral-edge); border-radius: var(--hb-neutral-r);
}
.hb-scope .badge.state-pill.called {
    background: var(--hb-info-bg); color: var(--hb-info-fg); border: var(--hb-info-edge); border-radius: var(--hb-info-r);
}
.hb-scope .badge.state-pill.released {
    background: var(--hb-go-bg); color: var(--hb-go-fg); border: var(--hb-go-edge); border-radius: var(--hb-go-r);
}
.hb-scope .badge.state-pill.finished {
    background: var(--hb-done-bg); color: var(--hb-done-fg); border: var(--hb-done-edge); border-radius: var(--hb-done-r);
}
.hb-scope .badge.state-pill.flag-dns,
.hb-scope .badge.state-pill.flag-dnf {
    background: var(--hb-hold-bg); color: var(--hb-hold-fg); border: var(--hb-hold-edge); border-radius: var(--hb-hold-r);
}
.hb-scope .badge.state-pill.flag-dq {
    background: var(--hb-bad-bg); color: var(--hb-bad-fg); border: var(--hb-bad-edge); border-radius: var(--hb-bad-r);
}
/* A press the board is still waiting on ("queued", "not confirmed"): the row carries is-queued-offline. */
.hb-scope tr.is-queued-offline .badge.state-pill {
    background: var(--hb-pending-bg); color: var(--hb-pending-fg); border: var(--hb-pending-edge); border-radius: var(--hb-pending-r);
}

/* The fixed-mode interval cue, past its interval: the result line's error pair (it was red on a pink tint, 4.89:1). */
.hb-scope .head-countdown.is-overdue {
    background: var(--hb-flash-err-bg); color: var(--hb-flash-err-fg); border: var(--hb-flash-err-edge);
}

/* The matched row (point 4 sets .is-match on the typed bow's row). Sun keeps the black row and marks it with a yellow
   rule above and below: a yellow fill would put the row's muted text and the yellow buttons on yellow. */
.hb-scope .head-queue tr.is-match > td {
    background: var(--hb-match-bg); color: var(--hb-match-fg);
    border-top: var(--hb-match-edge); border-bottom: var(--hb-match-edge);
}

/* Links and code on a board page read in the board's own ink, not in the tenant's colour (a link was --rowops-accent:
   3.6:1 on the platform bronze, 1.4:1 on a yellow one). */
.hb-scope a:not(.btn):not(.div-tab) { color: var(--hb-link); }
.hb-scope a:not(.btn):not(.div-tab):hover { color: var(--hb-link); border-bottom-color: currentColor; }
.hb-scope code { color: var(--hb-ink); background: var(--hb-kbd-bg); }

/* Buttons. Properties are set directly, not through Bootstrap's --bs-btn-* variables: the global Sun rules set them
   directly, and three classes here out-rank those two-class rules. */
.hb-scope .btn.btn-outline-secondary,
body:has(.hb-scope) .control-tabs .btn.btn-outline-secondary {
    background: transparent; color: var(--hb-muted); border-color: var(--hb-muted);
}
.hb-scope .btn.btn-outline-secondary:hover,
.hb-scope .btn.btn-outline-secondary:focus-visible,
body:has(.hb-scope) .control-tabs .btn.btn-outline-secondary:hover {
    background: var(--hb-ink); color: var(--hb-on-ink); border-color: var(--hb-ink);
}
.hb-scope .btn.btn-outline-danger {
    background: transparent; color: var(--hb-danger-ink); border-color: var(--hb-danger-ink);
}
.hb-scope .btn.btn-outline-danger:hover,
.hb-scope .btn.btn-outline-danger:focus-visible {
    background: var(--hb-danger-bg); color: var(--hb-danger-fg); border-color: var(--hb-danger-bg);
}
.hb-scope .btn.btn-danger {
    background: var(--hb-danger-bg); color: var(--hb-danger-fg); border-color: var(--hb-danger-bg);
}
/* Recall and Reset test race: amber ink on the surface, the HOLD chip's pair when pressed. Bootstrap's
   btn-outline-warning is #ffc107, 1.45:1 on the cream card. */
.hb-scope .btn.btn-recall {
    background: transparent; color: var(--hb-hold-bg); border: var(--hb-btn-bw) solid var(--hb-hold-bg);
}
.hb-scope .btn.btn-recall:hover,
.hb-scope .btn.btn-recall:focus-visible {
    background: var(--hb-hold-bg); color: var(--hb-hold-fg); border-color: var(--hb-hold-bg);
}
/* Start camera and the control tabs: the ink fill with the tenant's colour as a bar under it, never behind the text. */
.hb-scope .btn.btn-primary,
body:has(.hb-scope) .control-tabs .btn.btn-primary {
    background-color: var(--hb-tab-on-bg); color: var(--hb-tab-on-fg); border-color: var(--hb-tab-on-bg);
    /* The bar is a gradient, not a box-shadow: Bootstrap draws the keyboard focus ring with box-shadow. */
    background-image: linear-gradient(var(--hb-tab-on-bar), var(--hb-tab-on-bar));
    background-repeat: no-repeat; background-position: 0 100%; background-size: 100% 4px;
}
.hb-scope .btn.btn-outline-primary,
body:has(.hb-scope) .control-tabs .btn.btn-outline-primary {
    background: transparent; color: var(--hb-primary-ink); border-color: var(--hb-primary-ink);
}
.hb-scope .btn.btn-outline-primary:hover,
.hb-scope .btn.btn-outline-primary:focus-visible,
body:has(.hb-scope) .control-tabs .btn.btn-outline-primary:hover {
    background: var(--hb-tab-on-bg); color: var(--hb-tab-on-fg); border-color: var(--hb-tab-on-bg);
}
/* A disabled control keeps readable words (it was opacity .65, 2.68:1) and a shape of its own. */
.hb-scope .btn:disabled,
.hb-scope .btn.disabled {
    opacity: 1; background: var(--hb-dis-bg); color: var(--hb-dis-fg); border: var(--hb-dis-edge); box-shadow: none;
}

/* The big action button (point 5): RELEASE / FINISH, and what a blocked or disabled one looks like in each theme. */
.hb-scope .hb-action {
    background: var(--hb-release-bg); color: var(--hb-release-fg); border: var(--hb-release-edge);
}
.hb-scope .hb-action.is-finish {
    background: var(--hb-finish-bg); color: var(--hb-finish-fg); border: var(--hb-finish-edge);
}
.hb-scope .hb-action.is-blocked {
    background: var(--hb-blocked-bg); color: var(--hb-blocked-fg); border: var(--hb-blocked-edge);
}
.hb-scope .hb-action:disabled,
.hb-scope .hb-action[aria-disabled="true"] {
    opacity: 1; background: var(--hb-dis-bg); color: var(--hb-dis-fg); border: var(--hb-dis-edge);
}

/* The camera note under the finish board's camera bar (JS toggles .is-info; it used to set an inline colour). */
.hb-scope #head-cam-warn { color: var(--hb-cam-warn); font-weight: 600; }
.hb-scope #head-cam-warn.is-info { color: var(--hb-cam-info); }

/* What is drawn outside the board's own wrapper, on a head board page only. */
body:has(.hb-scope) .rowops-confirm-actions button.rowops-confirm-ok {
    background: var(--hb-confirm-ok-bg); color: var(--hb-confirm-ok-fg); border-color: var(--hb-confirm-ok-bg);
}
body:has(.hb-scope) .rowops-confirm-actions button.rowops-confirm-ok.is-danger {
    background: var(--hb-bad-bg); color: var(--hb-bad-fg); border-color: var(--hb-bad-bg);
}
/* The platform's own top bar and crumbs, on a board page only. Not the boards', but the same defects on the same screen:
   the account's initial was white on the tenant's colour, the bar's outline buttons were #6c757d (3.9:1 in Night), and the
   crumb separators were drawn at half strength. */
body:has(.hb-scope) .top-bar .btn.btn-outline-secondary {
    background: transparent; color: var(--hb-muted); border-color: var(--hb-muted);
}
body:has(.hb-scope) .top-bar .btn.btn-outline-secondary:hover {
    background: var(--hb-ink); color: var(--hb-on-ink); border-color: var(--hb-ink);
}
body:has(.hb-scope) .top-bar-user-avatar {
    background-color: var(--hb-tab-on-bg); color: var(--hb-tab-on-fg); box-shadow: 0 0 0 2px var(--hb-tab-on-bar);
}
body:has(.hb-scope) .crumbs .sep { color: var(--hb-muted); opacity: 1; }
/* timing-queue.js draws the NOT APPLIED / NOTHING CHANGED strip with an inline style (white on --rowops-warn, 4.34:1). */
body:has(.hb-scope) #rowops-outcome-strips > [id^="rowops-event-not-applied-"] {
    background: var(--hb-strip-warn-bg) !important;
    color: var(--hb-strip-warn-fg) !important;
    border-top: var(--hb-strip-warn-edge) !important;
}

/* ═══════════════════════════════════════════════════════════════════════════
   HEAD BOARDS — the chassis (D-race-216 W-A): number pad, dock, phone layout, Wake Lock

   Brian, 2026-10-16: "no way to start or stop a crew on a tablet or phone".
   templates/control/partials/head_number_pad.php draws the parts, public/js/head-number-pad.js
   brings them to life, both boards include the partial inside .head-bow-card.

   RULES OF THIS BLOCK.
     1. THE POINTER DECIDES FIRST, THEN THE WIDTH (S-13). Every touch rule is under
        `@media (pointer: coarse)`; a fine pointer (a laptop, a mouse) keeps the laptop
        table and the laptop flow at EVERY width. The phone card layout is coarse AND
        up to 768 px wide.
     2. NO COLOUR IS WRITTEN HERE: the --hb-* board tokens only (rule 1 of the block
        above; HeadNumberPadMarkupTest pins this block against hex, text-white and the
        tenant's colour).
     3. THE DOCK IS .head-bow-card ITSELF on a coarse pointer: fixed to the foot of the
        screen, holding the status row, the bow box, the crew card slot
        ([data-head-dock-card]), the result line, RESEND, the pad and the action slot
        ([data-head-dock-action]), in that order. It pads for the home indicator and
        keeps the action out of the bottom 20 px. IT DOES NOT SCROLL: what changes height
        sits above the pad and the buttons, which keep their place under the thumb; only
        the crew card's own area shrinks and scrolls. The trapped-crossings list floats
        above it, out of the dock. The script publishes the dock's height (and the list's)
        as --hb-dock-h and the page pads by it, so the queue is never under either.
     4. EVERY POINTER sets the bow card out in named grid areas (row, flash, resend, bar,
        trap, card, pad, action); a finger's layout reuses the names.
   ═══════════════════════════════════════════════════════════════════════════ */

/* The status row, the slots and the pad, for every pointer. */
.hb-scope .hb-dock { display: contents; }
.hb-scope .hb-dockbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem 0.6rem; margin-top: 0.6rem; font-size: 0.85rem; }
.hb-scope .hb-queue,
.hb-scope .hb-menu { display: none; }
.hb-scope .hb-wake,
.hb-scope .hb-queue {
    padding: 0.2rem 0.55rem; font-weight: 600; line-height: 1.25;
    background: var(--hb-neutral-bg); color: var(--hb-neutral-fg); border: var(--hb-neutral-edge); border-radius: var(--hb-neutral-r);
}
.hb-scope .hb-wake[data-state="on"] { background: var(--hb-flash-ok-bg); color: var(--hb-flash-ok-fg); border: var(--hb-flash-ok-edge); }
.hb-scope .hb-wake[data-state="missing"],
.hb-scope .hb-wake[data-state="refused"],
.hb-scope .hb-wake[data-state="lost"] { background: var(--hb-flash-warn-bg); color: var(--hb-flash-warn-fg); border: var(--hb-flash-warn-edge); }
.hb-scope .hb-queue[data-state="queued"],
.hb-scope .hb-queue[data-state="offline"] { background: var(--hb-flash-warn-bg); color: var(--hb-flash-warn-fg); border: var(--hb-flash-warn-edge); }
.hb-scope .hb-queue[data-state="stuck"] { background: var(--hb-flash-err-bg); color: var(--hb-flash-err-fg); border: var(--hb-flash-err-edge); }
.hb-scope .hb-pad-toggle,
.hb-scope .hb-menu {
    min-height: 2.75rem; padding: 0.3rem 0.9rem; font-size: 0.95rem; font-weight: 700; cursor: pointer;
    background: transparent; color: var(--hb-muted); border: var(--hb-btn-bw) solid var(--hb-muted); border-radius: 0.375rem;
}
.hb-scope .hb-pad-toggle[aria-pressed="true"] { background: var(--hb-ink); color: var(--hb-on-ink); border-color: var(--hb-ink); }
.hb-scope .hb-pad-toggle[hidden] { display: none; }
.hb-scope .hb-dock-card { margin-top: 0.6rem; }
.hb-scope .hb-dock-card:empty,
.hb-scope .hb-dock-action:empty { display: none; }
.hb-scope .hb-dock-action { margin-top: 0.6rem; }

/* The big action button W-B / W-C put in the slot: full width, tall, never a text selection or a callout, and the browser
   leaves the touch alone (S-6: the press is stamped at pointerdown). Its colours are .hb-action's (above). */
.hb-scope .hb-dock-action .hb-action,
.hb-scope .hb-action {
    touch-action: none; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent;
}
.hb-scope .hb-dock-action .hb-action { display: block; width: 100%; min-height: 3.5rem; padding: 0.6rem 1rem; font-size: 1.25rem; font-weight: 800; border-radius: 0.5rem; cursor: pointer; }

/* The pad. The keys are spans, not buttons (see the partial); a press on one is cancelled by the script, so the box keeps its
   focus, and nothing zooms, selects or pops a callout. */
.hb-scope .hb-pad {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.4rem; margin-top: 0.6rem; max-width: 22rem;
    touch-action: manipulation; -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent;
}
.hb-scope .hb-pad[hidden] { display: none; }
.hb-scope .hb-pad-key {
    display: flex; align-items: center; justify-content: center; min-height: 3rem;
    font-size: 1.5rem; font-weight: 800; font-variant-numeric: tabular-nums; cursor: pointer;
    background: var(--hb-neutral-bg); color: var(--hb-neutral-fg); border: var(--hb-neutral-edge); border-radius: var(--hb-neutral-r);
    touch-action: manipulation; -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent;
}
.hb-scope .hb-pad-key[data-pad-key="C"] { background: var(--hb-hold-bg); color: var(--hb-hold-fg); border: var(--hb-hold-edge); border-radius: var(--hb-hold-r); }
.hb-scope .hb-pad-key:active { background: var(--hb-ink); color: var(--hb-on-ink); border-color: var(--hb-ink); }

/* What the page never does on a board page: pull to refresh in the middle of a session (C9). */
body:has(.hb-scope) { overscroll-behavior-y: contain; }

/* ── Every pointer: where the parts of the bow card sit ─────────────────────────────────────────────────────────────────── */
/* The bow card is a grid of named areas for EVERY pointer (D-race-216 round 2, R3 P2-3). The trapped-crossings list has an area of its
   own: beside the card on the finish board's wide laptop screen, and in one column BELOW the buttons, so a list that grows with every
   Space chip never pushes the crew card or the buttons down (above them it moved TRAP 118 px in three clicks at 1024 px, RR2 P2-2).
   A finger's layout (below) reuses the same names. */
.hb-scope .head-bow-card {
    display: grid; grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "row" "flash" "bar" "card" "pad" "action" "resend" "trap" "undo";
}
.hb-scope .head-bow-row { grid-area: row; }
.hb-scope .head-bow-flash { grid-area: flash; }
/* A laptop's result line has ONE height, shown or hidden (three lines of its 0.95rem text, its padding and Sun's 4 px edges; a longer one
   scrolls inside itself): a judge in a close finish may click TRAP twice with the mouse, and a line that appears after the first click
   must not move TRAP out from under the second. It was two (3.4rem): every two-line answer lost the end of its second line (RR2 P2-1). A
   finger's dock (below) reserves its own, one line at a time, and lets a longer one grow upwards. */
.hb-scope .head-bow-flash { height: calc(3 * 1.5 * 0.95rem + 1rem + 8px); overflow-y: auto; }
/* !important: Bootstrap's reboot says [hidden] { display: none !important }. */
.hb-scope .head-bow-flash[hidden] { display: block !important; visibility: hidden; }
.hb-scope .head-resend { grid-area: resend; }
.hb-scope .hb-dockbar { grid-area: bar; }
.hb-scope .head-trap-panel { grid-area: trap; align-self: start; }
.hb-scope .hb-dock-card { grid-area: card; }
.hb-scope .hb-pad { grid-area: pad; }
.hb-scope .hb-dock-action { grid-area: action; }
/* D-race-231 Part 2 (C11): the finish board's undo of its last capture has an area of its own. With a mouse it is LAST: under FINISH,
   TRAP, the RESEND offers and the trapped-crossings list, so shown or hidden it moves nothing above it, not even a RESEND or a chip
   (each a click target too); on the wide laptop, under RESEND in the left column. A finger's dock puts it above RESEND, the pad and
   the buttons (order 4, below), and the dock grows upwards from the foot of the screen, so they stay under the thumb. A normal-size
   button (btn-recall). */
.hb-scope .head-undo { grid-area: undo; margin-top: 0.6rem; }
.hb-scope .head-undo-btn { min-height: 2.75rem; padding: 0.3rem 0.9rem; font-size: 0.95rem; font-weight: 700; }
/* The start board with a mouse: RELEASE sits above the crew card. A blocked click ("already away") writes its words into the card, and
   below a card that grows, RELEASE would move out from under the second click that answers it. It is also 113 px higher at load. */
@media (pointer: fine) {
    .hb-scope .head-bow-card:not(:has(> .head-trap-panel)) {
        grid-template-areas: "row" "flash" "bar" "action" "resend" "card" "pad" "trap";
    }
}
/* Only the finish board has the list, and only a mouse uses this grid: the start board drew an empty 20rem column here, its bow card 232 px
   taller at 1280x800, and a finger's dock (below) carried the list's height: 0 into a floating panel as tall as the dock (RR2 P2-2, P3-1).
   The list's column is narrower than it was (20rem): beside the rail and the side card the bow card is 640 px at 1280. */
@media (min-width: 1100px) and (pointer: fine) {
    .hb-scope .head-bow-card:has(> .head-trap-panel) {
        grid-template-columns: minmax(0, 1fr) minmax(13rem, 16rem); column-gap: 1rem; align-content: start;
        /* The bow box and the result line span both columns: the longest answer (the server's own time, about 190 characters) is three lines
           at the card's width and five in the left column alone. */
        grid-template-areas: "row row" "flash flash" "bar trap" "card trap" "pad trap" "action trap" "resend trap" "undo trap";
    }
    /* The list is as tall as the rows beside it and scrolls inside itself: a height of 0 with a minimum of the whole area adds nothing to
       the rows' own heights, so a chip added by a Space never pushes the crew card or the buttons down. */
    .hb-scope .head-trap-panel { align-self: stretch; height: 0; min-height: 100%; overflow-y: auto; }
}
/* A slot the script has emptied (it moved the card and the buttons into the dock) takes no room and leaves no margin behind. */
.hb-scope .head-crew-dock:not(:has(*)),
.hb-scope .head-action-wrap:not(:has(*)) { display: none; }
/* The queue's own [hidden] rows: a filter hides a row with the attribute, and the phone layout below sets display on the same
   element at a higher specificity, so the attribute needs its own rule at the strength of the phone's (R3 P3). */
.hb-scope table.head-queue tr[hidden] { display: none; }
/* The matched row scrolls into view above the dock, not under it. */
.hb-scope .head-queue tr.is-match { scroll-margin-bottom: calc(var(--hb-dock-h, 0px) + 1rem); }
/* The crew card's PENDING chip (a press the server has not answered yet) gets its own look: `.badge.state-pill.queued` out-ranks the
   board's own rule for it, so the pending pair is set here, above it (R3 P3). */
.hb-scope .head-crew-card[data-pending="1"] .badge.state-pill.hcc-state {
    background: var(--hb-pending-bg); color: var(--hb-pending-fg); border: var(--hb-pending-edge); border-radius: var(--hb-pending-r);
}

/* ── A FINGER (coarse pointer): the dock ────────────────────────────────────────────────────────────────────────────── */
/* THE DOCK DOES NOT SCROLL (R3 P1-1). It is a column anchored to the foot of the screen, so what changes height sits ABOVE the
   thumb: the status row, the bow box, the crew card and the result line. The pad and the big buttons are the last two items and keep
   their place however much grows above them. Only the crew card's own area gives way (it scrolls inside itself, and is the one
   child that may shrink); the rest is flex: none. The trapped-crossings list is not in the dock at all: it floats above it. */
@media (pointer: coarse) {
    .hb-scope .head-bow-card {
        position: fixed; left: 0; right: 0; bottom: 0; z-index: 45; margin: 0;
        display: flex; flex-direction: column; gap: 0.4rem;
        border-radius: 0.75rem 0.75rem 0 0;
        /* Out of the bottom 20 px, and out of the home indicator when it is taller than that (S-13). */
        padding: 0.6rem 0.8rem max(20px, env(safe-area-inset-bottom, 0px));
        max-height: 88vh; overflow: visible; touch-action: manipulation;
    }
    @supports (height: 1dvh) { .hb-scope .head-bow-card { max-height: 88dvh; } }
    .hb-scope .head-bow-card > *,
    .hb-scope .hb-dock > * { min-width: 0; margin: 0; flex: none; }
    .hb-scope .hb-dock { display: contents; }
    .hb-scope .head-bow-hint { display: none; }
    .hb-scope .head-bow-input { flex: 1 1 6rem; height: 3.4rem; font-size: 2.2rem; }
    .hb-scope .hb-dockbar { order: 1; flex-wrap: nowrap; align-items: stretch; gap: 0.4rem; }
    .hb-scope .head-bow-row { order: 2; flex-wrap: nowrap; }
    .hb-scope .hb-dock-card {
        order: 3; flex: 0 1 auto; min-height: 3.2rem; max-height: 14rem; overflow-y: auto; overscroll-behavior: contain;
    }
    /* The result line keeps its room when it is hidden: nothing above it jumps each time a press is answered. */
    .hb-scope .head-bow-flash { order: 4; min-height: 2.1rem; height: auto; overflow-y: visible; }
    .hb-scope .head-bow-flash[hidden] { display: block !important; visibility: hidden; height: 2.1rem; overflow: hidden; padding-top: 0; padding-bottom: 0; border-width: 0; }
    /* The RESEND offers are bounded too, and give way before the crew card does: one for each press with no answer, 81 px each, and they come
       on exactly when a riverbank link stalls. Unbounded, two of them put the big button below a 548 px screen with nothing to scroll to it
       (RR2 P1-1). Past one row they scroll inside themselves. */
    .hb-scope .head-resend { order: 5; flex: 0 4 auto; min-height: 3rem; max-height: 6.5rem; overflow-y: auto; overscroll-behavior: contain; }
    .hb-scope .hb-pad { order: 6; width: 100%; max-width: none; }
    .hb-scope .hb-dock-action { order: 7; }
    .hb-scope .head-bow-card > :not(.head-bow-row):not(.head-bow-flash):not(.head-resend):not(.hb-dock):not(.head-trap-panel) { order: 5; }
    /* D-race-231 Part 2 (its review, F1): the undo sits ABOVE RESEND, right after the result line (order 4, with it; the markup puts the
       undo after it). Shown or hidden, it moves nothing below it: RESEND, the pad and the buttons. Under RESEND (the generic order 5) it
       pushed a RESEND on offer up 49 px each time it appeared, and a tap meant for that RESEND landed on the undo. An id, to out-rank
       the generic rule above. */
    .hb-scope .head-bow-card > #head-undo { order: 4; }
    /* D-race-231 Part 2 (its review, F2): when the dock is fuller than its 88% of the screen (three RESEND rows, the longest answer and the
       undo on a 548 px phone), what does not fit goes out of the TOP, over the page, never out of the bottom under FINISH and TRAP. Packed
       from the top, FINISH was 27 px below the screen there (the probe). While the dock fits, this changes nothing: it is as tall as what
       it holds. */
    .hb-scope .head-bow-card { justify-content: flex-end; }
    .hb-scope .hb-queue { display: inline-block; flex: none; }
    .hb-scope .hb-wake { flex: 1 1 0; min-width: 0; font-size: 0.75rem; line-height: 1.15; display: flex; align-items: center; }
    .hb-scope .hb-menu { flex: none; }
    .hb-scope .hb-pad-key { min-height: 3rem; }
    /* The big buttons are as tall as the dock can afford: 56 px here (the acceptance size), 48 px in the compact dock. */
    .hb-scope .hb-dock-action .hb-action,
    .hb-scope .hb-dock-action .head-finish-btn,
    .hb-scope .hb-dock-action .head-trap-btn { min-height: 3.5rem; padding-top: 0.4rem; padding-bottom: 0.4rem; }
    /* The trapped-crossings list floats above the dock, one row of chips that scrolls sideways, and takes nothing from the dock: a
       chip or a line added by a TRAP can never move TRAP or FINISH (R3 P1-2). With nothing in it, it is not there. */
    .hb-scope .head-trap-panel {
        position: absolute; left: 0; right: 0; bottom: 100%; grid-area: auto; margin: 0; z-index: 1;
        max-height: 9rem; overflow: hidden; padding: 0.35rem 0.8rem 0.4rem;
        background: var(--rowops-surface-2); border-top: 2px solid var(--hb-edge);
    }
    .hb-scope .head-trap-panel:not(:has(.head-chip)):not(:has(.head-trap-warn:not([hidden]))) { display: none; }
    .hb-scope .head-trap-panel { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.6rem; }
    .hb-scope .head-trap-panel .head-trap-head { flex: 1 1 100%; flex-wrap: nowrap; }
    /* A warning (storage not working, a second tab) sits between the heading and the chips, and the list grows to hold it: capped at 9rem it
       was cut mid-instruction, "… Do not" (RR2 P2-3). The chips stay on the dock's edge whether or not a warning shows. */
    .hb-scope .head-trap-panel .head-trap-warn { flex: 1 1 100%; order: 1; margin-top: 0; }
    .hb-scope .head-trap-panel:has(.head-trap-warn:not([hidden])) { max-height: 16rem; overflow-y: auto; }
    .hb-scope .head-trap-panel .head-trap-list { flex: 1 1 100%; order: 2; flex-direction: row; overflow-x: auto; margin-top: 0; padding-bottom: 0.15rem; -webkit-overflow-scrolling: touch; }
    .hb-scope .head-trap-panel .head-chip { flex: 0 0 auto; min-width: 9.5rem; min-height: 2.75rem; padding: 0.25rem 0.6rem; }
    /* The page pads for the dock and the list above it; the outcome strips (timing-queue.js, inline bottom:0) sit above both. */
    body:has(.hb-scope) .content-panel { padding-bottom: calc(var(--hb-dock-h, 0px) + 1rem); }
    body:has(.hb-scope) #rowops-outcome-strips { bottom: var(--hb-dock-h, 0px) !important; }
    /* iOS Safari zooms the page when a field under 16 px takes the focus: every field on a board is at least 16 px on a finger. */
    .hb-scope .form-control-sm,
    .hb-scope .form-select-sm,
    .hb-scope .head-umpire-panel form select,
    .hb-scope .head-umpire-panel form input[type="text"] { font-size: 1rem; }
}
/* The rail is on screen from 1101 px: the dock starts where the page does. */
@media (pointer: coarse) and (min-width: 1101px) {
    .hb-scope .head-bow-card { left: var(--rail-w); }
}
/* A tablet, or a phone on its side (700 px or more): the crew card and the box on the left, the pad and the action on the right.
   The pad and the buttons sit at the foot of their areas, so a result line that grows above them moves nothing under the thumb. */
@media (pointer: coarse) and (min-width: 700px) {
    .hb-scope .head-bow-card {
        display: grid; grid-template-columns: minmax(0, 1fr) minmax(15rem, 22rem); column-gap: 1rem; row-gap: 0.4rem; align-content: end;
        grid-template-areas: "bar bar" "card pad" "row pad" "flash pad" "undo pad" "resend action";
    }
    .hb-scope .hb-dockbar { grid-area: bar; }
    .hb-scope .hb-dock-card { grid-area: card; }
    .hb-scope .head-bow-row { grid-area: row; }
    .hb-scope .head-bow-flash { grid-area: flash; }
    .hb-scope .head-resend { grid-area: resend; }
    .hb-scope .hb-pad { grid-area: pad; align-self: end; }
    .hb-scope .hb-dock-action { grid-area: action; align-self: end; }
    .hb-scope .head-bow-card > :not(.head-bow-row):not(.head-bow-flash):not(.head-resend):not(.hb-dock):not(.head-trap-panel) { grid-column: 1 / -1; }
    /* D-race-231 Part 2: the undo has its own row in the left column, inside the pad's rows and above RESEND's and the action's. The
       pad sits at the foot of its rows, on the action's row, which the undo is not in: the undo shown or hidden, neither the pad nor
       the action moves (a row beside the action grew it when the undo was the taller, and moved the pad). An id, to out-rank the
       full-width rule above. */
    .hb-scope .head-bow-card > #head-undo { grid-area: undo; }
}
/* A phone on its side (short and wide): the crew card gets its own middle column, the bow box and the result line sit left of it, the pad
   and the big button right: the dock is about half of a 390 px screen instead of three quarters. The trapped-crossings list is one row
   (its heading beside the chips). */
@media (pointer: coarse) and (min-width: 700px) and (max-height: 500px) {
    .hb-scope .head-bow-card {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(15rem, 22rem);
        grid-template-areas: "bar bar bar" "row card pad" "flash card pad" "undo card pad" "resend card action";
    }
    .hb-scope .hb-dock-card { align-self: stretch; max-height: 8.5rem; }
    .hb-scope .head-trap-panel .head-trap-head { flex: 0 0 auto; }
    .hb-scope .head-trap-panel .head-trap-list { flex: 1 1 0; min-width: 0; }
}
@media (pointer: coarse) and (min-width: 700px) {
    .hb-scope .head-trap-panel .head-trap-head { flex: 0 0 auto; }
    .hb-scope .head-trap-panel .head-trap-list { flex: 1 1 0; min-width: 0; }
}
/* Below 860 px of height (every phone upright but the tallest, and every phone on its side) the dock is compact: a smaller crew card
   and result line, the pad in two rows of six keys at 44 px or more, buttons 48 px, so the crew card, the pad and the big button
   are all on the screen together (S-14, R3 P1-1). */
@media (pointer: coarse) and (max-height: 860px) {
    .hb-scope .head-bow-card { padding-top: 0.4rem; gap: 0.3rem; }
    .hb-scope .hb-pad { grid-template-columns: repeat(6, 1fr); gap: 0.3rem; }
    .hb-scope .hb-pad-key { min-height: 2.75rem; font-size: 1.25rem; }
    .hb-scope .head-bow-input { height: 2.9rem; font-size: 1.8rem; }
    .hb-scope .hb-dock-card { font-size: 0.9rem; line-height: 1.2; min-height: 3rem; max-height: 7.5rem; }
    .hb-scope .hb-dock-card .head-crew-card { min-height: 0; padding: 0.3rem 0.6rem; }
    .hb-scope .hb-dock-card .hcc-bow { font-size: 1.6rem; }
    .hb-scope .hb-dock-card .hcc-name { font-size: 1.05rem; margin-top: 0.1rem; }
    .hb-scope .hb-dock-card .hcc-club, .hb-scope .hb-dock-card .hcc-event, .hb-scope .hb-dock-card .hcc-meta, .hb-scope .hb-dock-card .hcc-hint { font-size: 0.85rem; }
    .hb-scope .hb-dock-card .hcc-warn, .hb-scope .hb-dock-card .hcc-note { margin-top: 0.2rem; padding: 0.2rem 0.5rem; font-size: 0.85rem; line-height: 1.25; }
    /* One line each for what only names the crew; ellipsis there. The warnings and notes are S-4's only guards at the finish and
       are never cut: they wrap, and the card scrolls (R3 P2-1). */
    .hb-scope .hb-dock-card .hcc-name, .hb-scope .hb-dock-card .hcc-club,
    .hb-scope .hb-dock-card .hcc-event, .hb-scope .hb-dock-card .hcc-meta { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .hb-scope .head-bow-flash { font-size: 0.85rem; padding: 0.25rem 0.6rem; min-height: 1.9rem; }
    .hb-scope .head-bow-flash[hidden] { height: 1.9rem; }
    .hb-scope .hb-dock-action .hb-action,
    .hb-scope .hb-dock-action .head-finish-btn,
    .hb-scope .hb-dock-action .head-trap-btn { min-height: 3rem; padding: 0.3rem 0.8rem; font-size: 1.05rem; }
}

/* A tablet (700 px or more each way): the crew card sits beside the pad and the dock grows upwards, so the card can keep the picked chip's
   line in view under S-4's warnings without moving anything under the thumb (RR2: it scrolled out of the card at 1024x768-1180x820). */
@media (pointer: coarse) and (min-width: 700px) and (min-height: 700px) and (max-height: 860px) {
    .hb-scope .hb-dock-card { max-height: 11rem; }
}

/* ── A PHONE (coarse pointer, 768 px or less) ───────────────────────────────────────────────────────────────────────── */
@media (pointer: coarse) and (max-width: 768px) {
    /* The queue is a stack of cards, not a table that runs off the screen: no sideways scroll at 360-412 px. A fine pointer
       keeps the table at every width. */
    .hb-scope table.head-queue,
    .hb-scope table.head-queue tbody { display: block; width: 100%; }
    .hb-scope table.head-queue thead { display: none; }
    .hb-scope table.head-queue tr {
        display: flex; flex-wrap: wrap; align-items: center; gap: 0.2rem 0.6rem;
        padding: 0.5rem 0.2rem; border-bottom: 1px solid var(--rowops-border);
    }
    .hb-scope table.head-queue tr[hidden] { display: none; }
    .hb-scope table.head-queue td { display: block; padding: 0; border: 0; min-width: 0; overflow-wrap: anywhere; }
    .hb-scope table.head-queue td.bow { flex: 0 0 3rem; font-size: 1.2rem; }
    .hb-scope table.head-queue td:nth-child(2) { flex: 1 1 8rem; }
    .hb-scope table.head-queue td.actions,
    .hb-scope table.head-queue td.head-flag-cell { flex: 1 1 100%; text-align: left; white-space: normal; }
    .hb-scope table.head-queue .btn { min-height: 2.75rem; }
    /* The top bar scrolls away on a phone (it was 96 px of a 640 px screen). The board's own bar, in the dock, carries what the
       work needs: the queue light, the screen note and a second Menu button; the rail comes in over everything from the top. */
    body:has(.hb-scope) .top-bar { position: static; }
    body:has(.hb-scope) .left-rail { top: 0; height: 100%; z-index: 60; }
    .hb-scope .hb-menu { display: inline-flex; align-items: center; justify-content: center; margin-left: auto; }
    /* A safety net, not the fix: whatever else a board draws wider than a phone is cut at the page edge instead of making the
       whole page scroll sideways. */
    body:has(.hb-scope) .content-panel { overflow-x: clip; }
}
/* The umpire panel's form is two columns of fields at 800 px and below, four above it, and its text field has a width of its own: at
   360 px it ran 74 px off the screen and at 800 and 844 px 34. One column, and the fields take the width they are given. */
@media (pointer: coarse) and (max-width: 900px) {
    .hb-scope .head-umpire-panel form { grid-template-columns: minmax(0, 1fr); }
    .hb-scope .head-umpire-panel form select,
    .hb-scope .head-umpire-panel form input[type="text"] { width: 100%; min-width: 0; }
    /* The same in the queue's action cell: the clear-flag / DNF / DQ forms hold a text field whose own width ran 50 px past the
       table at 844 px. */
    .hb-scope .head-queue td form.d-grid { grid-template-columns: minmax(0, 12rem); }
    .hb-scope .head-queue td form .form-control-sm { width: 100%; min-width: 0; }
}

/* ──────────────────────────────────────────────────────────────────
   Per-regatta logo strip (mig 0055).
   ─────────────────────────────────────────────────────────────── */
.regatta-logo-strip {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 0.5rem 1rem;
    margin-bottom: 1rem;
    background: var(--rowops-surface-2);
    border: 1px solid var(--rowops-border);
    border-radius: 0.4rem;
}
.regatta-logo-strip img {
    max-height: 72px;
    max-width: min(100%, 480px);
    height: auto;
    width: auto;
    object-fit: contain;
}
.regatta-logo-preview {
    max-height: 120px;
    max-width: 320px;
    height: auto;
    width: auto;
    object-fit: contain;
    padding: 0.5rem;
    background: var(--rowops-surface-2);
    border: 1px solid var(--rowops-border);
    border-radius: 0.4rem;
}
html.theme-sun .regatta-logo-strip,
body.theme-sun .regatta-logo-strip,
html.theme-sun .regatta-logo-preview,
body.theme-sun .regatta-logo-preview {
    background: #000;
    border-color: #fde047;
}
html.theme-dark .regatta-logo-strip,
body.theme-dark .regatta-logo-strip,
html.theme-dark .regatta-logo-preview,
body.theme-dark .regatta-logo-preview {
    background: var(--rowops-surface-2);
    border-color: var(--rowops-border);
}

/* --- Sponsor strip (spec 03) ------------------------------------- */
.sponsor-strip {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
    justify-content: center;
    padding: 0.4rem 1rem;
    margin-bottom: 1rem;
    background: var(--rowops-surface-2);
    border: 1px solid var(--rowops-border);
    border-radius: 0.4rem;
}
.sponsor-strip-label {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--rowops-muted);
}
.sponsor-strip-logos {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    flex-wrap: wrap;
    justify-content: center;
}
.sponsor-item { display: inline-flex; align-items: center; }
.sponsor-item img {
    max-height: 46px;
    max-width: min(40vw, 200px);
    height: auto;
    width: auto;
    object-fit: contain;
}
.sponsor-tier-title img { max-height: 60px; }
.sponsor-intro { max-width: 48rem; }
.sponsor-admin-thumb { max-height: 38px; max-width: 120px; object-fit: contain; }
html.theme-sun .sponsor-strip,
body.theme-sun .sponsor-strip { background: #000; border-color: #fde047; }
html.theme-dark .sponsor-strip,
body.theme-dark .sponsor-strip { background: var(--rowops-surface-2); border-color: var(--rowops-border); }

/* --- Print suppresses chrome ------------------------------------- */
@media print {
    .top-bar, .left-rail, .app-footer, .rowops-powered-by { display: none !important; }
    .app-shell-with-rail { display: block; }
    .content-panel { padding: 0; max-width: none; }
}

/* ──────────────────────────────────────────────────────────────────
   CSP `style-src 'unsafe-inline'` cleanup (ROADMAP HIGH-3, 2026-05-29)
   ────────────────────────────────────────────────────────────────────
   These utilities replace the last ~238 inline `style="..."` attribute
   sites across templates so the CSP header can drop `'unsafe-inline'`
   from `style-src`. Defence-in-depth — every output already goes
   through `Support\e()`, but if a hostile string slipped past escape,
   a `<style>` injection cannot land.
   Naming follows the existing project pattern: intent-named, kebab-
   case, no visual aliases. Reach for an existing utility (`is-hidden`,
   `text-accent`, `kpi-label`, etc.) before adding a new one here.
   ──────────────────────────────────────────────────────────────── */

/* Width helpers for `<th>` column sizing in operator + print tables. */
.col-w-20  { width: 20px; }
.col-w-30  { width: 30px; }
.col-w-55  { width: 55px; }
.col-w-65  { width: 65px; }
.col-w-40  { width: 40px; }
.col-w-50  { width: 50px; }
.col-w-60  { width: 60px; }
.col-w-70  { width: 70px; }
.col-w-80  { width: 80px; }
.col-w-90  { width: 90px; }
.col-w-100 { width: 100px; }
.col-w-110 { width: 110px; }
.col-w-120 { width: 120px; }
.col-w-130 { width: 130px; }
.col-w-140 { width: 140px; }
.col-w-160 { width: 160px; }
.col-w-165 { width: 165px; }
.col-w-170 { width: 170px; }
.col-w-175 { width: 175px; }
.col-w-200 { width: 200px; }
.col-w-240 { width: 240px; }
.col-w-260 { width: 260px; }
.col-w-pct-25  { width: 25%; }
.col-w-pct-30  { width: 30%; }
.col-w-pct-32  { width: 32%; }
.col-w-pct-50  { width: 50%; }

/* Print-table proportional column widths — % column widths used inside
   the availability-windows + per-club print sheets. */
.col-w-pct-7   { width: 7%; }
.col-w-pct-8   { width: 8%; }
.col-w-pct-9   { width: 9%; }
.col-w-pct-12  { width: 12%; }
.col-w-pct-17  { width: 17%; }
.col-w-pct-22  { width: 22%; }
.col-w-pct-24  { width: 24%; }
.col-w-pct-25-share { width: 25%; }   /* alias kept distinct so a 25% tweak in availability sheets lands in one place */
.col-w-pct-28  { width: 28%; }

/* Em-sized table column widths — used on bow / state / actions columns
   on the head-mode start + finish tables where em scales with the
   surrounding font-size for tablet readability. */
.col-w-em-3   { width: 3.2em; }
.col-w-em-8   { width: 8em; }

/* Rem-sized table column widths — narrow # / position columns. */
.col-w-3rem  { width: 3rem; }
.col-w-6rem  { width: 6rem; }
.col-w-7rem  { width: 7rem; }
.col-w-8rem  { width: 8rem; }
.col-w-11rem { width: 11rem; }

/* Amber card-accent variant — pairs with `.card` to render a 4px
   warning-coloured left border. Mirrors `.card.card-accent-bar` but
   keys off Bootstrap's `--bs-warning` instead of the brand accent. */
.card.card-accent-warning {
    border-left: 4px solid var(--bs-warning);
}

/* Operator-displayed secret block (device pair token, share-link URL,
   etc.) — soft-wrapping monospace card with surface-2 background. */
.token-display {
    white-space: pre-wrap;
    word-break: break-all;
    padding: 0.75rem;
    background: var(--rowops-surface-2);
    border-radius: 0.5rem;
}

/* Dashed-border helper — used to visually signal optional / opt-in
   form sections (e.g. delegate details on the ladder entry form). */
.border-dashed { border-style: dashed; }

/* Centred emoji / glyph hero on status pages — e.g. the 🔒 on the
   timing-locked screen. Pairs with `.text-center` on the parent card. */
.icon-hero { font-size: 3rem; line-height: 1; margin-bottom: 0.75rem; }

/* Mid-size icon — for inline emoji glyphs that need ~2× body text
   (e.g. Logbook upsell card). */
.icon-md { font-size: 2rem; }

/* Spectator-screen muted text — soft-white on the black background.
   Used for boat-class suffixes / metadata on the projector views. */
.spectator-meta { color: rgba(255, 255, 255, 0.55); font-weight: 500; }

/* Form-help text max-width caps — narrow caption text under a wider
   form control should not stretch the full container width. */
.form-help-mw-260 { max-width: 260px; }

/* Em-sized min-widths for inline counter / icon slots inside list rows
   (smart-guide steps, etc.) — keeps numbers / glyphs from collapsing. */
.col-min-em-1-25 { min-width: 1.25em; }
.col-min-em-1-5  { min-width: 1.5em;  }

/* Very-subtle row background — used to faintly differentiate stacked
   list items without imposing a heavy stripe. 1.5% black on light,
   inverse on dark via theme-aware overrides isn't required because
   the alpha is too low for the difference to read. */
.bg-row-subtle { background: rgba(0, 0, 0, 0.015); }

/* Inline chip-style highlight for "locked" day buttons on the draw-
   decisions toolbar — soft amber wash + tight rounded corners. */
.day-locked-chip {
    background: rgba(220, 200, 80, 0.08);
    border-radius: 0.3rem;
    padding: 0 0.2rem;
}

/* Day-locked badge variant for tabbed labels — slightly larger
   padding + rounded corners than the toolbar chip, used on
   /day-assignments tab strip. */
.day-locked-tab {
    background: rgba(220, 200, 80, 0.08);
    border-radius: 0.35rem;
    padding: 0.1rem 0.3rem;
}

/* Microcaps label tag — small uppercase wrap with widened tracking.
   Pair with `.small` and `.text-muted` for the typical render. */
.text-tracked { letter-spacing: 0.05em; }

/* Honeypot field — visually hidden, screen-readable. Bots fill it and
   the server discards the submission. Off-screen rather than display:
   none so accessibility tools and naïve bots still see the field. */
.honeypot {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
}

/* Soft-info wash for opt-in form sections (e.g. Logbook upsell card,
   Concept2 connect prompt) — Bootstrap-info colour at low alpha so the
   section reads as "extra" without competing with the primary fields. */
.bg-info-soft { background: rgba(13, 110, 253, 0.04); }

/* Event-grid relay badge — RELAY · N legs marker on the erg event
   tiles. Bootstrap-info at 15% alpha + the source-of-truth blue. */
.event-badge-relay {
    background: rgba(13, 110, 253, 0.15);
    color: #0d6efd;
}

/* Section-heading microcaps — small uppercase label above grouped
   forms (e.g. "umpire decisions" header on /control/run). */
.text-tag-upper {
    letter-spacing: 0.05em;
    font-size: 0.75rem;
}

/* Sticky composer card on operator broadcast surfaces (WhatsApp
   captains). Top offset keeps it just below the global top bar. */
.sticky-top-tight {
    position: sticky;
    top: 0.5rem;
    z-index: 5;
}

/* Reserved minimum height — keeps a row's vertical space stable when
   the status message inside is empty (avoids layout jank on flash). */
.h-min-1-1em { min-height: 1.1em; }

/* Dark-grey print intro paragraph — between `.text-muted` (too light
   on print) and body text. Used under section headings on print
   sheets to give "small caption" body context. */
.text-print-meta {
    color: #444;
    font-size: 0.88rem;
    margin-bottom: 0.4rem;
}

/* Min-width zero — lets a flex child shrink past its content's intrinsic
   min-width (otherwise long crew names overflow inside flex rows).
   Bootstrap 5.2+ ships this; we ship it ourselves so older Bootstrap
   bundles also have it available. */
.min-w-0 { min-width: 0; }

/* Empty-state padded card body — used when the primed-race slot has
   no pending races to show. Visually balanced large-vertical padding. */
.primed-card-empty {
    text-align: center;
    padding: 3rem 1.5rem;
}

/* Lock-bar fill initial state — full width before the JS countdown
   animation kicks in. Pairs with `.lock-bar-fill`. */
.lock-bar-fill-init { width: 100%; }

/* Camera preview tiles on the race-control-settings page. Two sizes
   — medium (calibration card) + large (live preview). Both share the
   black background + soft-cropped border-radius. Hidden by default;
   toggled visible via .d-none class swap when the preview starts. */
.camera-preview-md,
.camera-preview-lg {
    width: 100%;
    background: #000;
    border-radius: 0.4rem;
}
.camera-preview-md { max-width: 480px; max-height: 270px; }
.camera-preview-lg { max-width: 720px; max-height: 405px; }

/* Finish-line preview wrapper — relative-positioned for the overlay,
   sized to fit the largest preview. */
.fl-preview-wrap {
    display: inline-block;
    position: relative;
    max-width: 100%;
}

/* In-page JS overlay flash background — semi-opaque dark wash on
   inline code in the recall / cancel toast templates. */
.flash-code-wash {
    background: rgba(0, 0, 0, 0.25);
    padding: 0 0.3rem;
    border-radius: 0.2rem;
}

/* Small sub-text inside a flash toast — slightly faded relative to
   the main message but still readable. */
.flash-subtext {
    opacity: 0.85;
    display: block;
    margin-top: 0.2rem;
}

/* Reason text inside the recall flash — slightly larger than caption,
   slightly faded. */
.flash-reason {
    font-size: 0.9rem;
    opacity: 0.95;
}

/* Danger-coloured meta line under starter-dashboard primed cards. */
.meta-line-danger { color: var(--rowops-danger, #c53030); }

/* Form-input width helpers — replace `style="max-width:Xrem"` /
   `style="width:Xrem"` on inline filter / reason / fee inputs.
   The `-` prefix variants apply `max-width` (caps growth without
   pinning narrow on responsive layouts); the bare ones apply
   `width` directly. */
.input-w-3rem  { width: 3rem; }
.input-w-5rem  { width: 5rem; }
.input-w-60   { width: 60px; }
.input-w-80   { width: 80px; }
.input-w-140  { width: 140px; }
.input-mw-5-5rem  { max-width: 5.5rem; }
.input-mw-6rem    { max-width: 6rem; }
.input-mw-8rem    { max-width: 8rem; }
.input-mw-10rem   { max-width: 10rem; }
.input-mw-11rem   { max-width: 11rem; }
.input-mw-12rem   { max-width: 12rem; }
.input-min-130    { min-width: 130px; }
.input-min-140    { min-width: 140px; }
.input-min-220    { min-width: 220px; }
.input-min-240    { min-width: 240px; }
.input-min-280    { min-width: 280px; }
.input-min-260    { max-width: 260px; }

/* Card max-width helpers — used on the standalone-page card layouts
   (regatta_new, regatta_edit, ladder_new, etc). Form cards traditionally
   pinned at 560/680/720/780/800 px. */
.card-mw-560 { max-width: 560px; }
.card-mw-640 { max-width: 640px; }
.card-mw-680 { max-width: 680px; }
.card-mw-720 { max-width: 720px; }
.card-mw-780 { max-width: 780px; }
.card-mw-800 { max-width: 800px; }
.card-mw-960 { max-width: 960px; }

/* Standalone error / forbid pages (rendered inline from controller hot
   paths — no layout chrome). Mirrors the operator card pattern; lives
   in app.css so the inline-style CSP test stays at zero offenders. */
.forbid-page { max-width: 640px; }
.forbid-glyph { font-size: 3rem; line-height: 1; }

/* cm-finish pixel-review tool (2026-06-02) — the canvas overlays the
   snapshot image with a 1:1 coordinate mapping. The image flows
   normally; the canvas sits absolutely on top so click coordinates
   translate cleanly to pixel positions in the underlying frame. */
.cm-review-frame-wrap {
    position: relative;
    display: inline-block;
    max-width: 100%;
}
.cm-review-frame-wrap img {
    display: block;
    max-width: 100%;
    height: auto;
}
.cm-review-frame-wrap canvas {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    cursor: crosshair;
}

/* Input-group max-width for the search / lookup widgets on dashboards. */
.input-group-mw-480 { max-width: 480px; }
.input-group-mw-520 { max-width: 520px; }
.input-group-mw-640 { max-width: 640px; }

/* USB-stick + erg-application + import-log secondary column min-widths. */
.col-min-18rem { min-width: 18rem; }

/* Audit-log word-break + max-width on context cells. */
.cell-wrap-200 { max-width: 200px; word-break: break-word; }
.cell-wrap-420 { max-width: 420px; word-break: break-word; }

/* Generic monospace + break-all (URL display, DNS records, share URLs). */
.font-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.font-mono-sm { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.95rem; }
.break-all { word-break: break-all; }
.nowrap { white-space: nowrap; }
.text-end-right { text-align: right; }
.cursor-pointer { cursor: pointer; }
.cursor-help { cursor: help; }
.cursor-crosshair { cursor: crosshair; }

/* Small-text / micro-label utility. The dashboard uses `.kpi-label`
   for the proper micro-label pattern; `.text-xs` is the tiny-text
   utility used on inline meta + view-source summaries. */
.text-xs { font-size: 0.72rem; }
.text-xs-bold { font-size: 0.72rem; font-weight: 700; }
.text-2xs { font-size: 0.65rem; }
.text-sm { font-size: 0.78rem; }
.text-md-narrow { font-size: 0.82rem; }
.text-rem-09 { font-size: 0.9rem; }
.text-rem-095 { font-size: 0.95rem; }
.text-rem-105 { font-size: 1.05rem; }
.text-bold-500 { font-weight: 500; }
.text-bold-600 { font-weight: 600; }
.text-bold-700 { font-weight: 700; }
.text-italic { font-style: italic; }

/* Status-line min-height — prevents reflow when an inline success / error
   message paints under a card body. */
.status-line-min { min-height: 1.1em; }

/* Min-height helpers for the various copy-status / save-status spots. */
.line-height-narrow { line-height: 1.35; }
.line-height-tight { line-height: 1.2; }
.line-height-roomy { line-height: 1.5; }

/* Inline-form flex helpers — used inside ladder_me / smart_guide / etc
   where a 1fr+auto two-cell layout is needed inside a row. */
.flex-grow { flex: 1 1 auto; min-width: 0; }
.flex-1   { flex: 1; }
.min-w-220 { min-width: 220px; }
.min-w-1-5em { min-width: 1.5em; }
.min-w-1-25em { min-width: 1.25em; }

/* Word-break / nowrap on table cells + share-URL code blocks. */
.code-break-all { word-break: break-all; }
.cell-nowrap { white-space: nowrap; }

/* Margin-left helpers — chip / inline-badge separation. */
.ms-mini { margin-left: 0.3rem; }
.ms-tight { margin-left: 0.4rem; }
.ms-room { margin-left: 0.6rem; }

/* Padding helpers used inside notes panels. */
.p-0-4 { padding: 0.4rem; }
.pl-0 { padding-left: 0; }
.section-intro-tight { margin: 0 0 0.35rem; }
.gap-mt { margin-top: 0.3rem; }
.alert-radius { border-radius: 0.4rem; }
.notes-gap { padding: 0.4rem; }

/* Camera + zoom-slider visibility helpers — these elements start hidden
   and JS toggles them on user action. `.is-revealed` is a no-op pair
   for symmetry. */
.is-hidden-revealable { display: none; }

/* Video preview frames — race-control-settings page two-sized previews. */
.cam-preview-480 {
    width: 100%; max-width: 480px; max-height: 270px;
    background: #000; border-radius: 0.4rem; display: none;
}
.cam-preview-720 {
    width: 100%; max-width: 720px; max-height: 405px;
    background: #000; border-radius: 0.4rem; display: none; cursor: crosshair;
}
.fl-preview-wrap {
    display: inline-block; position: relative; max-width: 100%;
}

/* Centered hero-style primed card on starter dashboard when no race
   is queued — large padding + centered text so the empty-state reads
   from the tablet rest position. */
.primed-card-empty {
    text-align: center;
    padding: 3rem 1.5rem;
}

/* Day-block locked highlight — replaces conditional inline style on
   day_assignments + draw_decisions when per-day draw lock is active. */
.day-locked-highlight {
    background: rgba(220, 200, 80, 0.08);
    border-radius: 0.35rem;
    padding: 0.1rem 0.3rem;
}
.day-locked-highlight-mini {
    background: rgba(220, 200, 80, 0.08);
    border-radius: 0.3rem;
    padding: 0 0.2rem;
}

/* Lock-bar fill — used on starter dashboard's GONE state for the 60 s
   recall-window countdown. Initial width = 100% via the class; JS
   animates via direct `el.style.width` updates which are CSP-safe. */
.lock-bar-fill-init { width: 100%; }

/* Sticky positioning for the WhatsApp composer + similar always-visible
   on-page cards. */
.sticky-top-card {
    position: sticky;
    top: 0.5rem;
    z-index: 5;
}

/* Pre-block formatting for code-token panes (pair-token, share URL). */
.pre-code-block {
    white-space: pre-wrap;
    word-break: break-all;
    padding: 0.75rem;
    background: var(--rowops-surface-2);
    border-radius: 0.5rem;
}

/* Visually-hidden honeypot field — keeps the form input in the DOM
   for attack bots to find while never showing on screen. Mirrors the
   classic Bootstrap `.visually-hidden` but positioned far off-screen
   so even autofill doesn't reveal it. */
.honeypot-field {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
}

/* Border-left accent variants used on warning + brand cards. */
.card-warning-accent { border-left: 4px solid var(--rowops-warn); }
.card-accent-strong { border-left: 4px solid var(--rowops-accent); }

/* Ladder-new card-template picker — `<label class="card">` becomes a
   tappable selection card; cursor: pointer makes the affordance clear. */
.card-template-picker { cursor: pointer; }

/* Dashed-border fieldset (logbook upsell + ladder-enter team panel). */
.fieldset-dashed { border-style: dashed; }
.fieldset-dashed-info { border-style: dashed; background: rgba(13, 110, 253, 0.04); }

/* Erg-mode glyph + flex-stack used on the entry form's logbook card. */
.glyph-2rem { font-size: 2rem; }
.flex-stack-info-grow { flex: 1; min-width: 220px; }
.flex-grow-base { flex: 1; }

/* erg-enter relay/event chip — blue-tinted pill background. */
.event-gender-relay {
    background: rgba(13, 110, 253, 0.15);
    color: #0d6efd;
}

/* erg-enter masters-only options info banner — small top-margin. */
.alert-tight-top { margin-top: 0.3rem; }
.row-align-end { align-items: end; }

/* Substitute page edit-icon — tiny inline link size. */
.icon-link-mini { font-size: 0.78rem; }
.help-narrow-260 {
    font-size: 0.72rem;
    line-height: 1.2;
    max-width: 260px;
}

/* Timing-locked + comm modal centre column. */
.modal-card-centre {
    border-width: 2px;
    max-width: 720px;
    margin: 2rem auto;
}
.glyph-3rem {
    font-size: 3rem;
    line-height: 1;
    margin-bottom: 0.75rem;
}

/* Print-rationale + affected-clubs subdued meta text. */
.print-meta-tight {
    color: #444;
    font-size: 0.88rem;
    margin-bottom: 0.4rem;
}
.print-emdash { color: #888; }
.print-emdash-strong { color: #999; }
.print-emdash-weak { color: #888; font-size: 0.82rem; }
.print-emdash-italic { color: #888; }
.print-section-count {
    color: #888; font-weight: 400; font-size: 0.85rem;
}

/* Spectator screen / spectator dashboard small-text muted variants
   (translucent over a dark backdrop). */
.spectator-meta-faint { color: rgba(255, 255, 255, 0.55); font-weight: 500; }
.spectator-meta-soft { opacity: 0.7; }

/* Smart-guide list-item highlight + tabular index column. */
.sg-row-highlight { background: rgba(0, 0, 0, 0.015); }
.sg-row-tabular { min-width: 1.5em; }
.sg-row-icon { min-width: 1.25em; }

/* Pill margin-left for FYI marker on affected-clubs print. */
.pill-fyi-spacing { margin-left: 0.3rem; }

/* Audit gap-list inside affected-clubs print. */
.gap-list-tight {
    margin: 0.3rem 0 0;
    padding-left: 1.1rem;
    font-size: 0.78rem;
    line-height: 1.35;
}
.gap-list-numeric { font-variant-numeric: tabular-nums; }

/* Race controls notes column empty state. */
.rc-empty-pad { padding: 0.4rem; }
.alert-radius-tight { border-radius: 0.4rem; }
.text-meta-tiny { font-size: 0.75rem; }

/* Lane convention 100% start_list table. */
.lane-table-full { width: 100%; }

/* Recently-finished compact meta. */
.text-rem-07 { font-size: 0.7rem; font-weight: 500; }

/* Camera-tally code label — small monospace badge under finish board. */
.snap-tally-mono { font-family: ui-monospace, Menlo, Consolas, monospace; }
.deploy-mark    { font-size: 0.7rem; }

/* Spectator-screen layout helpers for the small-meta line + label. */
.club-margin-left { margin-left: 0.4rem; }
.label-margin-top { margin-top: 0.4rem; }

/* Letter-spacing helper used by day_assignments section headings. */
.letter-spaced-05 { letter-spacing: 0.05em; }

/* Border accent for the per-row refine-draw warning card. */
.refine-warn-accent { border-left: 4px solid var(--bs-warning, #ffc107); }

/* Public-ladder accent strong border. */
.public-ladder-accent { border-left: 4px solid var(--rowops-accent); }

/* Mobile self-service ladder surface (templates/ladder_me.php). Lifted
   from a 17-line inline <style nonce> block per the 2026-06-02 UI/UX
   audit Wave 4.17 follow-up. Tap-friendly, chrome-light. */
.ladder-me-hero { padding: 1rem 0; }
.ladder-me-hero h1 { font-size: 1.5rem; margin-bottom: 0.25rem; }
.ladder-me-hero p  { font-size: 0.9rem; margin-bottom: 0; }
.ladder-me-rank   { font-size: 1.25rem; min-width: 2.2rem; display: inline-block; text-align: right; }
.ladder-me-name   { font-weight: 600; }
.ladder-me-row    {
    padding: 0.6rem 0.75rem;
    border-bottom: 1px solid var(--rowops-border);
    display: flex;
    align-items: center;
    gap: 0.6rem;
}
.ladder-me-row:last-child { border-bottom: 0; }
.ladder-me-row.withdrawn  { opacity: 0.55; }
.ladder-me-club   { color: var(--rowops-muted); font-size: 0.85rem; }
.ladder-me-tile-link {
    display: block;
    padding: 0.9rem 1rem;
    min-height: 64px;
    background: var(--rowops-surface-2);
    border-radius: 8px;
    text-decoration: none;
    color: inherit;
    border: 1px solid var(--rowops-border);
}
.ladder-me-cta-row {
    display: flex;
    gap: 0.6rem;
    flex-wrap: wrap;
    margin-top: 1rem;
}
.ladder-me-cta-row a { flex: 1 1 8rem; text-align: center; }
.ladder-me-deadline { font-size: 0.8rem; color: var(--rowops-muted); }
.ladder-me-section  { margin-top: 1.25rem; }
.ladder-me-section h2 { font-size: 1rem; margin-bottom: 0.4rem; }

/* Catch-all line-height variants used by spectator scoreboards. */
.line-height-line-narrow { line-height: 1.35; }

/* erg-categories preview pill inline — overrides previous inline
   display:none; JS toggles `.is-hidden`. */
.erg-category-preview-hidden { display: none; }

/* Locked-finals running-order auto-save state cues on
   templates/events.php — subtle green flash on save, persistent red on
   failure. Mirrors the `.session-band-saving` pattern in
   templates/day_assignments.php. Lifted from inline <style nonce> to
   close the 2026-06-02 UI/UX audit Wave 4.17 finding. */
.final-block-input,
.final-block-order-input { width: 4.5rem; padding-left: 0.4rem; padding-right: 0.2rem; }
.final-block-order-input { margin-left: 0.25rem; }
.final-block-input.fb-saving,
.final-block-order-input.fb-saving { opacity: 0.6; }
.final-block-input.fb-saved,
.final-block-order-input.fb-saved {
    background-color: rgba(34, 139, 84, 0.12);
    transition: background-color 0.6s;
}
.final-block-input.is-invalid,
.final-block-order-input.is-invalid {
    border-color: #dc2626;
    background-color: rgba(220, 38, 38, 0.06);
}
/* Per-event masters handicap override dropdown — same auto-save cue
   palette as the final-block inputs above. */
.masters-handicap-input { min-width: 12rem; }
.masters-handicap-input.fb-saving { opacity: 0.6; }
.masters-handicap-input.fb-saved {
    background-color: rgba(34, 139, 84, 0.12);
    transition: background-color 0.6s;
}
.masters-handicap-input.is-invalid {
    border-color: #dc2626;
    background-color: rgba(220, 38, 38, 0.06);
}

/* =========================================================================
   Theme-7 lift 2026-06-02 — inline <style nonce> blocks across 7 templates
   consolidated here. Audit followup to the 2026-05-30 CSP no-inline sweep.
   Each block was bypassing app.css design tokens + theme overrides; sun-
   mode parity was missing on every one. Rules grouped by surface so a
   future tweak lands once.
   ========================================================================= */

/* ------- home_public.php — pre-login public regattas landing -------
   Rebuilt 2026-06-02 as the SaaS-pivot first-impression page (hero +
   feature strip + enriched card grid). Shared section-title / pulse /
   grid classes carry through to templates/home.php (operator-side). */
.hp-hero { padding: 2rem 0 1rem; text-align: center; }
.hp-hero h1 { font-weight: 700; letter-spacing: -0.02em; }
.hp-hero .lead { color: var(--rowops-ink-muted); }
.hp-section { margin-top: 2rem; }
.hp-section-title {
    font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--rowops-ink-muted); font-weight: 700;
    margin-bottom: 0.6rem; display: flex; align-items: center; gap: 0.5rem;
}
.hp-section-title .pulse {
    display: inline-block; width: 10px; height: 10px; border-radius: 50%;
    background: var(--rowops-bad);
    animation: hp-pulse 1.6s ease-in-out infinite;
}
@keyframes hp-pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50%      { opacity: 0.5; transform: scale(1.25); }
}
/* The platform's card grid. min(320px, 100%): a column is never wider than the grid itself, so on a 320 px phone (a grid under 320 px) a card
   does not push the page sideways; with room for a second column it is the 320 px it was. (T-race-TODO-366, the Opus review's finding 9.2:
   minmax(320px, 1fr) overflowed a 320 px phone, and the ring round the list made the overflow visible.) */
.hp-grid {
    display: grid; gap: 0.9rem;
    grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
}
.hp-card {
    transition: transform 0.12s, box-shadow 0.12s, border-color 0.12s;
    color: var(--rowops-ink);
}
.hp-card:hover { transform: translateY(-2px); box-shadow: var(--rowops-shadow-lift); }
.hp-empty { padding: 2.5rem 1rem; text-align: center; color: var(--rowops-ink-muted); }
.hp-footer { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--rowops-border); text-align: center; }
.hp-footer .small { color: var(--rowops-ink-muted); }

/* Hero strapline — centred, generous vertical padding. The slogan
   rides below the brand name as a softer-weight clause; positioning
   subtext sits under it. Two primary CTAs side-by-side, stack on
   phones via flex-wrap. */
.hp-landing-hero {
    padding: 2.8rem 1rem 2rem;
    text-align: center;
    max-width: 56rem;
    margin: 0 auto;
}
.hp-landing-hero-title {
    font-size: clamp(1.9rem, 4.5vw, 2.8rem);
    font-weight: 800;
    letter-spacing: -0.02em;
    margin: 0 0 0.4rem;
    line-height: 1.15;
}
.hp-landing-hero-slogan {
    display: block;
    font-size: clamp(1.05rem, 2.2vw, 1.35rem);
    font-weight: 500;
    color: var(--rowops-ink-muted);
    letter-spacing: -0.01em;
    margin-top: 0.35rem;
}
.hp-landing-hero-subtext {
    font-size: clamp(0.98rem, 1.6vw, 1.12rem);
    color: var(--rowops-ink-muted);
    margin: 0 0 1.5rem;
    line-height: 1.5;
}
.hp-landing-hero-ctas {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    justify-content: center;
}

/* Three-feature strip — equal-weight cards with emoji + headline +
   one-line body. Single column on phones; three columns desktop.
   Tile shape echoes the dashboard's dash-action-tile look. */
.hp-feature-strip {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    margin: 0.5rem auto 2.5rem;
    max-width: 72rem;
}
.hp-feature-card {
    background: var(--rowops-card);
    border: 1px solid var(--rowops-border);
    border-radius: 0.6rem;
    padding: 1.4rem 1.25rem;
    text-align: left;
}
.hp-feature-glyph {
    font-size: 2rem;
    line-height: 1;
    margin-bottom: 0.6rem;
}
/* Brand-coloured line icons (replace the old emoji glyphs) — inherit the
   tenant/platform brand primary so they're on-brand in every theme. */
.hp-feature-glyph svg {
    width: 2.6rem; height: 2.6rem;
    color: var(--brand-primary);
    display: block;
}
html.theme-sun .hp-feature-glyph svg, body.theme-sun .hp-feature-glyph svg,
html.theme-sun .hp-empty-landing-glyph svg, body.theme-sun .hp-empty-landing-glyph svg { color: #fde047; }
.hp-feature-headline {
    font-size: 1.05rem;
    font-weight: 700;
    margin: 0 0 0.4rem;
    letter-spacing: -0.01em;
}
.hp-feature-body {
    font-size: 0.92rem;
    color: var(--rowops-ink-muted);
    margin: 0;
    line-height: 1.5;
}

/* Regatta card grid wrapper + enriched card body. */
.hp-regattas { margin-bottom: 3rem; }
/* T-race-TODO-337 (Brian, 2026-10-20, of the Head's card: the "HEAD" chip stretched across the card): the head was a three-column
   grid (logo, title, chip), and a card with no logo has two children, so the chip landed in the 1fr column and stretched to fill
   it. A flex row has no column to land in: the title takes what is left, and the chip (below) is as wide as its text, whether or
   not there is a logo. */
.hp-card-head {
    display: flex;
    gap: 0.7rem;
    align-items: flex-start;
    margin-bottom: 0.6rem;
}
.hp-card-logo {
    width: 44px; height: 44px;
    object-fit: contain;
    border-radius: 0.3rem;
    background: var(--rowops-surface-2);
    flex: none;
}
.hp-card-title-block {
    flex: 1 1 auto;
    min-width: 0; /* allow ellipsis */
}
.hp-card-title {
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    margin: 0 0 0.25rem;
    line-height: 1.2;
    color: var(--rowops-ink);
}
.hp-card-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.65rem;
    font-size: 0.82rem;
    color: var(--rowops-ink-muted);
}
.hp-card-meta-item { white-space: nowrap; }
.hp-card-kpis {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(72px, 1fr));
    gap: 0.5rem;
    padding: 0.55rem 0.7rem;
    background: var(--rowops-surface-2);
    border-radius: 0.4rem;
    margin-bottom: 0.75rem;
}
.hp-card-kpi-value {
    font-size: 1.15rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--rowops-ink);
    line-height: 1.1;
}
.hp-card-kpi-accent { color: var(--rowops-accent); }
/* T-race-TODO-337: "24 crews · 12 clubs" on every card, in place of the races and events tiles. */
.hp-card-counts { margin: 0 0 0.75rem; font-size: 0.95rem; font-weight: 600; color: var(--rowops-ink); }
.hp-card-cta { margin-top: auto; }

/* Mode-coloured chips on regatta cards. Brand red = regatta + division
   (default); green = head; blue = erg; purple = ladder. Sun-mode parity
   below — every chip flips to jet-yellow on black so it reads at
   distance. */
.hp-mode-chip {
    flex: 0 0 auto;
    align-self: flex-start;
    font-size: 0.7rem;
    font-weight: 700;
    padding: 0.28rem 0.55rem;
    letter-spacing: 0.02em;
    white-space: nowrap;
    text-transform: uppercase;
    border-radius: 0.3rem;
}
.hp-mode-regatta  { background: var(--rowops-accent); color: var(--rowops-accent-fg); }
.hp-mode-division { background: var(--rowops-accent); color: var(--rowops-accent-fg); }
.hp-mode-head     { background: #198754; color: #fff; }
.hp-mode-erg      { background: #1d4ed8; color: #fff; }
/* D-race-201: rose, not violet (the next step), green (the setup actions and the head chip), blue (erg) or the brand accent. */
.hp-mode-ladder   { background: #be185d; color: #fff; }

/* Constructive empty-state landing — large friendly card replacing the
   former bland "No published regattas right now" line. */
.hp-empty-landing {
    background: var(--rowops-card);
    border: 1px solid var(--rowops-border);
    border-radius: 0.7rem;
    padding: 3rem 1.5rem;
    text-align: center;
    max-width: 36rem;
    margin: 1rem auto 2rem;
}
.hp-empty-landing-glyph {
    font-size: 3.4rem;
    line-height: 1;
    opacity: 0.65;
    margin-bottom: 0.7rem;
}
.hp-empty-landing-glyph svg {
    width: 3.4rem; height: 3.4rem;
    color: var(--brand-primary);
    display: block;
    margin-inline: auto;
}
.hp-empty-landing-title {
    font-size: 1.2rem;
    font-weight: 700;
    margin: 0 0 0.5rem;
}
.hp-empty-landing-body {
    color: var(--rowops-ink-muted);
    margin: 0 0 0.7rem;
    line-height: 1.5;
}
.hp-empty-landing-cta {
    font-size: 0.95rem;
    color: var(--rowops-ink);
    margin: 0;
}

/* Footer for the public landing — sits above the global powered-by
   strip (rendered separately by layout.php). About / Docs links are
   env-gated so self-hosted single-tenant deploys see nothing extra. */
.hp-landing-footer {
    margin-top: 3rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--rowops-border);
    text-align: center;
    color: var(--rowops-ink-muted);
}
.hp-landing-footer-line { margin: 0 0 0.4rem; font-size: 0.92rem; }
.hp-landing-footer-links {
    margin: 0;
    font-size: 0.88rem;
    display: flex;
    gap: 0.5rem;
    justify-content: center;
    flex-wrap: wrap;
}
.hp-landing-footer-sep { color: var(--rowops-ink-muted); }

/* Sun-mode parity for every new colour-bearing element on the landing. */
html.theme-sun .hp-section-title, body.theme-sun .hp-section-title { color: #fff; font-weight: 800; }
html.theme-sun .hp-section-title .pulse,
body.theme-sun .hp-section-title .pulse { background: #fde047; }
html.theme-sun .hp-card:hover, body.theme-sun .hp-card:hover {
    box-shadow: 0 0 0 3px #fde047;
}
html.theme-sun .hp-feature-card, body.theme-sun .hp-feature-card {
    background: #000; border: 2px solid #fff;
}
html.theme-sun .hp-feature-headline, body.theme-sun .hp-feature-headline { color: #fff; }
html.theme-sun .hp-feature-body, body.theme-sun .hp-feature-body { color: #f0f0f0; }
html.theme-sun .hp-landing-hero-title, body.theme-sun .hp-landing-hero-title { color: #fff; }
html.theme-sun .hp-landing-hero-slogan, body.theme-sun .hp-landing-hero-slogan { color: #fde047; }
html.theme-sun .hp-landing-hero-subtext, body.theme-sun .hp-landing-hero-subtext { color: #f0f0f0; }
html.theme-sun .hp-card-kpis, body.theme-sun .hp-card-kpis {
    background: #000; border: 2px solid #fff;
}
html.theme-sun .hp-card-kpi-value, body.theme-sun .hp-card-kpi-value { color: #fff; }
html.theme-sun .hp-card-kpi-accent, body.theme-sun .hp-card-kpi-accent { color: #fde047; }
html.theme-sun .hp-mode-regatta, body.theme-sun .hp-mode-regatta,
html.theme-sun .hp-mode-division, body.theme-sun .hp-mode-division {
    background: #fde047; color: #000; border: 2px solid #fff; font-weight: 800;
}
html.theme-sun .hp-mode-head, body.theme-sun .hp-mode-head,
html.theme-sun .hp-mode-erg, body.theme-sun .hp-mode-erg,
html.theme-sun .hp-mode-ladder, body.theme-sun .hp-mode-ladder {
    background: #000; color: #fde047; border: 2px solid #fde047; font-weight: 800;
}
html.theme-sun .hp-empty-landing, body.theme-sun .hp-empty-landing {
    background: #000; border: 2px solid #fff;
}
html.theme-sun .hp-empty-landing-title, body.theme-sun .hp-empty-landing-title { color: #fff; }
html.theme-sun .hp-empty-landing-body, body.theme-sun .hp-empty-landing-body { color: #f0f0f0; }
html.theme-sun .hp-empty-landing-cta, body.theme-sun .hp-empty-landing-cta { color: #fff; }
html.theme-sun .hp-landing-footer, body.theme-sun .hp-landing-footer { color: #fff; }

/* Dark-mode adjustments — feature strip + KPI strip get the same
   surface-2 tint as elsewhere; mode chips already work via the explicit
   colours above. */
html.theme-dark .hp-feature-card, body.theme-dark .hp-feature-card {
    background: var(--rowops-card);
}
html.theme-dark .hp-card-kpis, body.theme-dark .hp-card-kpis {
    background: var(--rowops-surface-2);
}

/* Print — hide hover transforms + animations on the landing. */
@media print {
    .hp-card { transform: none !important; box-shadow: none !important; }
    .hp-section-title .pulse { animation: none; }
    .hp-landing-hero-ctas, .hp-landing-footer-links { display: none; }
}

/* ------- race_control_settings.php — lane keymap editor ------- */
.keymap-panel { padding: 0.5rem 0 0; }
.keymap-panel .km-row { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 0.5rem; }
.keymap-panel .km-lane { display: inline-flex; align-items: center; gap: 0.3rem; }
.keymap-panel .km-num { font-size: 0.78rem; color: var(--rowops-ink-muted); width: 1.5rem; text-align: right; font-weight: 600; }
.keymap-panel input[data-km-lane] {
    width: 2.4rem; text-align: center; text-transform: uppercase;
    font-family: ui-monospace, Menlo, monospace;
    padding: 0.2rem 0.3rem;
    border: 1px solid var(--rowops-border); border-radius: 0.25rem;
    background: var(--rowops-card); color: var(--rowops-ink);
}
.keymap-panel .km-actions { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.keymap-panel .km-hint { flex: 1 1 100%; margin-top: 0.1rem; }
.keymap-panel .km-status { margin-top: 0.4rem; min-height: 1.1em; }
html.theme-sun .keymap-panel input[data-km-lane],
body.theme-sun .keymap-panel input[data-km-lane] {
    background: #000; color: #fff; border: 2px solid #fff; font-weight: 800;
}
html.theme-sun .keymap-panel .km-num, body.theme-sun .keymap-panel .km-num { color: #fff; }

/* ------- audit_race_resets.php — super-admin reset/recall audit ------- */
.resets-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.5rem 0 1rem; }
.resets-chip {
    padding: 0.3rem 0.7rem; border-radius: 999px; font-size: 0.85rem;
    background: var(--rowops-surface-2); color: var(--rowops-ink);
    border: 1px solid var(--rowops-border); text-decoration: none;
}
.resets-chip.active { background: var(--rowops-accent); color: var(--rowops-accent-fg); border-color: var(--rowops-accent); }
.resets-chip strong { font-family: ui-monospace, Menlo, monospace; }
.resets-table .badge-reset    { background: #fde2e2; color: #7f1d1d; padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
.resets-table .badge-recall   { background: #fef3c7; color: #92400e; padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
.resets-table .badge-restored { background: #d1fae5; color: #065f46; padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
.resets-table .badge-pending  { background: #e0e7ff; color: #3730a3; padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
.resets-table .reason-cell { max-width: 24rem; font-size: 0.85rem; color: var(--rowops-ink-muted); }
.resets-table .race-cell { min-width: 14rem; }
.resets-table .race-cell .race-no { font-family: ui-monospace, Menlo, monospace; font-weight: 700; color: var(--rowops-accent); }
.resets-table .when-cell { font-family: ui-monospace, Menlo, monospace; font-size: 0.85rem; white-space: nowrap; }
.resets-table th { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--rowops-ink-muted); }
html.theme-dark .resets-table .badge-reset, body.theme-dark .resets-table .badge-reset    { background: #4b1d1d; color: #fecaca; }
html.theme-dark .resets-table .badge-recall, body.theme-dark .resets-table .badge-recall  { background: #4a3415; color: #fde68a; }
html.theme-dark .resets-table .badge-restored, body.theme-dark .resets-table .badge-restored { background: #0d3526; color: #a7f3d0; }
html.theme-dark .resets-table .badge-pending, body.theme-dark .resets-table .badge-pending  { background: #1f2459; color: #c7d2fe; }
html.theme-sun .resets-chip, body.theme-sun .resets-chip {
    background: #000; color: #fff; border: 2px solid #fff; font-weight: 700;
}
html.theme-sun .resets-chip.active, body.theme-sun .resets-chip.active {
    background: #fde047; color: #000; border-color: #fde047;
}
html.theme-sun .resets-table .badge-reset, body.theme-sun .resets-table .badge-reset       { background: #000; color: #fde047; border: 2px solid #fde047; }
html.theme-sun .resets-table .badge-recall, body.theme-sun .resets-table .badge-recall     { background: #000; color: #fde047; border: 2px solid #fde047; }
html.theme-sun .resets-table .badge-restored, body.theme-sun .resets-table .badge-restored { background: #000; color: #fff;    border: 2px solid #fff; }
html.theme-sun .resets-table .badge-pending, body.theme-sun .resets-table .badge-pending   { background: #000; color: #fff;    border: 2px solid #fff; }
html.theme-sun .resets-table .race-cell .race-no, body.theme-sun .resets-table .race-cell .race-no { color: #fde047; }

/* ------- erg_dashboard.php — phase chevron + KPI strip + status chips ------- */
.erg-chevron { display: flex; flex-wrap: wrap; gap: 0.25rem; padding: 0; margin: 0 0 1.25rem; list-style: none; }
.erg-chevron-step {
    padding: 0.4rem 0.85rem; border-radius: 1rem;
    font-size: 0.85rem; font-weight: 600;
    background: var(--rowops-surface-2); color: var(--rowops-ink-muted);
}
.erg-chevron-step a, .erg-chevron-step span { color: inherit; text-decoration: none; }
.erg-chevron-step.erg-state-past { background: rgba(25, 135, 84, 0.15); color: var(--rowops-good); }
.erg-chevron-step.erg-state-current {
    background: var(--rowops-accent); color: var(--rowops-accent-fg);
    box-shadow: 0 0 0 2px rgba(var(--brand-primary-rgb, 176, 125, 51), 0.18);
}
.erg-chevron-step.erg-state-future { opacity: 0.55; }
.erg-kpi-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0.75rem; margin-bottom: 1.5rem; }
.erg-kpi {
    background: var(--rowops-card); border: 1px solid var(--rowops-border);
    border-radius: 0.6rem; padding: 0.9rem 1rem;
}
.erg-kpi-label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--rowops-ink-muted); margin-bottom: 0.25rem; }
.erg-kpi-value { font-size: 1.8rem; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.1; }
.erg-kpi-sub { font-size: 0.8rem; color: var(--rowops-ink-muted); margin-top: 0.2rem; }
.erg-panel {
    background: var(--rowops-card); border: 1px solid var(--rowops-border);
    border-radius: 0.6rem; padding: 1rem 1.1rem; margin-bottom: 1.25rem;
}
.erg-panel h2 { font-size: 1.05rem; margin: 0 0 0.6rem; }
.erg-recent-table { width: 100%; border-collapse: collapse; }
.erg-recent-table th, .erg-recent-table td {
    padding: 0.35rem 0.5rem; text-align: left;
    border-bottom: 1px solid var(--rowops-border); font-size: 0.9rem;
}
.erg-recent-table th { font-size: 0.72rem; text-transform: uppercase; color: var(--rowops-ink-muted); font-weight: 600; }
.erg-status-chip { display: inline-block; padding: 0.1rem 0.55rem; border-radius: 0.6rem; font-size: 0.72rem; font-weight: 600; }
.erg-status-pending_payment    { background: #fff3cd; color: #856404; }
.erg-status-paid_pending_review { background: #cfe2ff; color: #084298; }
.erg-status-approved           { background: #d1e7dd; color: #0f5132; }
.erg-status-rejected,
.erg-status-refunded           { background: #f8d7da; color: #842029; }
html.theme-dark .erg-status-pending_payment,
body.theme-dark .erg-status-pending_payment    { background: #4a3a10; color: #ffe7a0; }
html.theme-dark .erg-status-paid_pending_review,
body.theme-dark .erg-status-paid_pending_review { background: #142a4a; color: #cfe2ff; }
html.theme-dark .erg-status-approved,
body.theme-dark .erg-status-approved           { background: #0e3a25; color: #b9eccc; }
html.theme-dark .erg-status-rejected,
html.theme-dark .erg-status-refunded,
body.theme-dark .erg-status-rejected,
body.theme-dark .erg-status-refunded           { background: #421317; color: #f5b1b8; }
html.theme-sun .erg-chevron-step, body.theme-sun .erg-chevron-step {
    background: #000; color: #fff; border: 2px solid #fff; font-weight: 800;
}
html.theme-sun .erg-chevron-step.erg-state-past,
body.theme-sun .erg-chevron-step.erg-state-past   { background: #000; color: #fff; border-color: #fff; }
html.theme-sun .erg-chevron-step.erg-state-current,
body.theme-sun .erg-chevron-step.erg-state-current {
    background: #fde047; color: #000; border-color: #fde047; box-shadow: none;
}
html.theme-sun .erg-status-chip, body.theme-sun .erg-status-chip {
    background: #000; color: #fff; border: 2px solid #fff; font-weight: 800;
}
html.theme-sun .erg-status-approved, body.theme-sun .erg-status-approved {
    background: #000; color: #fde047; border-color: #fde047;
}

/* ------- erg_enter.php — public direct-entry form ------- */
.erg-enter-hero {
    text-align: center; padding: 2.5rem 1rem;
    background: linear-gradient(135deg, var(--rowops-accent) 0%, var(--brand-dark, #1f2a33) 100%);
    color: #fff; border-radius: 1rem; margin-bottom: 1.5rem;
}
.erg-enter-hero h1 { font-size: clamp(1.6rem, 4vw, 2.4rem); margin: 0 0 0.5rem; font-weight: 800; }
.erg-enter-hero .meta { opacity: 0.9; }
.erg-enter-hero .status-band {
    display: inline-block; padding: 0.3rem 1rem; border-radius: 1.2rem;
    background: rgba(40, 200, 100, 0.3);
    font-weight: 600; font-size: 0.85rem; letter-spacing: 0.06em;
    text-transform: uppercase; margin-bottom: 0.8rem;
}
.erg-enter-hero .status-band.is-closed { background: rgba(255, 255, 255, 0.15); }
.erg-form { max-width: 880px; margin: 0 auto; }
.erg-fieldset {
    background: var(--rowops-card); border: 1px solid var(--rowops-border);
    border-radius: 0.6rem; padding: 1.1rem 1.2rem; margin-bottom: 1.1rem;
}
.erg-fieldset legend { font-size: 1.05rem; font-weight: 700; padding: 0; margin: 0 0 0.7rem; float: none; }
.erg-event-grid { display: grid; grid-template-columns: 1fr; gap: 0.6rem; }
.erg-event-card {
    border: 1.5px solid var(--rowops-border); border-radius: 0.5rem;
    padding: 0.8rem 0.9rem; display: grid;
    grid-template-columns: auto 1fr auto auto; gap: 0.9rem; align-items: center;
    transition: border-color 0.12s, background 0.12s; cursor: pointer;
}
.erg-event-card:hover { background: var(--rowops-surface-2); }
.erg-event-card.is-selected { border-color: var(--rowops-accent); background: var(--rowops-accent-tint); }
.erg-event-card.is-full { opacity: 0.5; cursor: not-allowed; }
.erg-event-card input[type="checkbox"] { width: 1.2rem; height: 1.2rem; cursor: pointer; }
.erg-event-card .event-name { font-weight: 600; }
.erg-event-card .event-gender {
    display: inline-block; padding: 0.1rem 0.45rem;
    background: var(--rowops-surface-2); border-radius: 0.4rem;
    font-size: 0.72rem; font-weight: 700; margin-right: 0.35rem;
}
.erg-event-card .event-line { color: var(--rowops-ink-muted); font-size: 0.85rem; }
.erg-event-card .event-cap { font-size: 0.75rem; color: var(--rowops-ink-muted); margin-top: 0.15rem; }
.erg-event-card .event-cap.is-tight { color: var(--rowops-warn); font-weight: 600; }
.erg-event-card .event-cap.is-full  { color: var(--rowops-bad); font-weight: 600; }
.erg-event-card .event-chip {
    padding: 0.25rem 0.7rem; background: var(--rowops-surface-2);
    border-radius: 0.4rem; font-size: 0.85rem; font-weight: 600;
}
.erg-event-card .event-fee { font-weight: 700; color: var(--rowops-accent); font-variant-numeric: tabular-nums; }
.erg-event-card .erg-expected-time {
    padding-left: 1.9rem; padding-top: 0.5rem;
    grid-column: 1 / -1; display: none;
}
.erg-event-card.is-selected .erg-expected-time { display: block; }
.erg-event-card .erg-expected-time input { max-width: 9rem; }
.erg-category-preview {
    display: inline-block; padding: 0.5rem 1rem; border-radius: 0.5rem;
    background: var(--rowops-accent-tint); color: var(--rowops-accent);
    font-weight: 700; margin-top: 0.5rem;
}
.erg-running-total {
    position: sticky; bottom: 0;
    background: var(--rowops-card); border: 1px solid var(--rowops-border);
    border-top: 3px solid var(--rowops-accent);
    border-radius: 0.5rem 0.5rem 0 0; padding: 1rem 1.2rem; margin: 1rem 0 0;
    box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.08);
    display: flex; gap: 1rem; align-items: center; flex-wrap: wrap;
}
.erg-running-total .total-label { font-size: 0.85rem; color: var(--rowops-ink-muted); }
.erg-running-total .total-amount { font-size: 1.5rem; font-weight: 800; font-variant-numeric: tabular-nums; }
.erg-running-total .total-count { color: var(--rowops-ink-muted); font-size: 0.85rem; }
.erg-running-total button { margin-left: auto; }
.erg-club-delegate { display: none; }
.erg-club-delegate.is-active { display: block; }
.erg-closed-state {
    text-align: center; padding: 3rem 1rem;
    background: var(--rowops-card); border-radius: 0.6rem;
}
html.theme-sun .erg-enter-hero, body.theme-sun .erg-enter-hero {
    background: #000; border: 3px solid #fde047; color: #fff;
}
html.theme-sun .erg-enter-hero .status-band,
body.theme-sun .erg-enter-hero .status-band { background: #fde047; color: #000; }
html.theme-sun .erg-enter-hero .status-band.is-closed,
body.theme-sun .erg-enter-hero .status-band.is-closed { background: #000; color: #fff; border: 2px solid #fff; }
html.theme-sun .erg-event-card, body.theme-sun .erg-event-card {
    background: #000; border-color: #fff; color: #fff;
}
html.theme-sun .erg-event-card.is-selected, body.theme-sun .erg-event-card.is-selected {
    background: #000; border-color: #fde047;
}
html.theme-sun .erg-event-card .event-fee, body.theme-sun .erg-event-card .event-fee { color: #fde047; }
html.theme-sun .erg-running-total, body.theme-sun .erg-running-total {
    background: #000; border-color: #fff; border-top-color: #fde047;
}
html.theme-sun .erg-category-preview, body.theme-sun .erg-category-preview {
    background: #000; color: #fde047; border: 2px solid #fde047;
}

/* ------- erg_flights.php — per-event flight block view ------- */
.flight-order-toggle {
    display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;
    padding: 0.9rem 1.1rem; background: var(--rowops-surface-2);
    border-radius: 0.6rem; margin-bottom: 1.5rem;
}
.flight-order-toggle .order-pill {
    display: inline-block; padding: 0.3rem 0.8rem; border-radius: 1.2rem;
    font-weight: 600; font-size: 0.85rem;
    background: var(--rowops-accent-tint); color: var(--rowops-accent);
}
.event-block {
    background: var(--rowops-card); border: 1px solid var(--rowops-border);
    border-radius: 0.6rem; padding: 1rem 1.2rem; margin-bottom: 1.25rem;
}
.event-block h2 {
    font-size: 1.1rem; margin: 0 0 0.6rem;
    display: flex; gap: 0.6rem; align-items: baseline; flex-wrap: wrap;
}
.event-block h2 .flight-count { color: var(--rowops-ink-muted); font-weight: 400; font-size: 0.85rem; margin-left: auto; }
.flight-card {
    background: var(--rowops-surface-2); border-radius: 0.5rem;
    padding: 0.7rem 0.9rem; margin: 0.6rem 0;
}
.flight-card .flight-head { display: flex; gap: 0.6rem; align-items: baseline; font-weight: 600; margin-bottom: 0.5rem; }
.flight-card .flight-head .heat-no { color: var(--rowops-accent); font-size: 1.05rem; }
.flight-card .flight-head .when    { color: var(--rowops-ink-muted); font-weight: 400; font-size: 0.85rem; margin-left: auto; }
.flight-card .flight-fast          { color: var(--rowops-good); font-weight: 600; font-size: 0.75rem; padding: 0.1rem 0.45rem; background: rgba(25, 135, 84, 0.15); border-radius: 0.4rem; }
.lane-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.lane-table th, .lane-table td { padding: 0.25rem 0.5rem; border-bottom: 1px dotted var(--rowops-border); text-align: left; }
.lane-table td.lane     { width: 4rem; font-weight: 700; font-variant-numeric: tabular-nums; text-align: center; }
.lane-table td.expected { font-variant-numeric: tabular-nums; text-align: right; color: var(--rowops-ink-muted); }
.lane-table td.cat      { font-size: 0.78rem; color: var(--rowops-ink-muted); }
.empty-state { padding: 2rem 1rem; text-align: center; color: var(--rowops-ink-muted); }
html.theme-sun .flight-order-toggle, body.theme-sun .flight-order-toggle {
    background: #000; border: 2px solid #fff;
}
html.theme-sun .flight-order-toggle .order-pill,
body.theme-sun .flight-order-toggle .order-pill {
    background: #fde047; color: #000;
}
html.theme-sun .flight-card, body.theme-sun .flight-card {
    background: #000; border: 2px solid #fff;
}
html.theme-sun .flight-card .flight-head .heat-no,
body.theme-sun .flight-card .flight-head .heat-no { color: #fde047; }
html.theme-sun .flight-card .flight-fast,
body.theme-sun .flight-card .flight-fast {
    background: #000; color: #fde047; border: 2px solid #fde047;
}

/* ------- head_finish_audit.php — the finish side's head-mode audit table ------- */
.audit-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.audit-table th, .audit-table td {
    padding: 0.45rem 0.6rem; border-bottom: 1px solid var(--rowops-border);
    vertical-align: top;
}
.audit-table th {
    text-align: left; font-weight: 600; color: var(--rowops-ink-muted);
    font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.02em;
}
.audit-table tr.dup-bright { background: rgba(220, 53, 69, 0.10); }
.audit-table tr.dup-soft   { background: rgba(220, 53, 69, 0.04); }
.audit-table td.ts     { font-variant-numeric: tabular-nums; white-space: nowrap; }
.audit-table td.bow    { font-weight: 700; font-variant-numeric: tabular-nums; text-align: center; min-width: 3em; }
.audit-table td.action { white-space: nowrap; }
.action-pill { display: inline-block; padding: 0.1rem 0.45rem; border-radius: 0.25rem; font-size: 0.78rem; font-weight: 600; }
/* D-race-190 point 1: the pill's TEXT is the board's chip ink (--hb-*, app.css HEAD BOARDS); the tint stays. #157347 on the
   dup-row tints was 4.10:1 and --rowops-warn on the yellow one 3.87:1. Night and Sun keep their own rebases below. */
.action-pill.release { background: rgba(40, 167, 69, 0.18); color: var(--hb-go-bg); }
.action-pill.call    { background: rgba(56, 132, 220, 0.18); color: var(--hb-info-bg); }
.action-pill.recall  { background: rgba(255, 193, 7, 0.22); color: var(--hb-hold-bg); }
.action-pill.finish  { background: rgba(40, 167, 69, 0.22); color: var(--hb-go-bg); }
.action-pill.undo    { background: rgba(120, 120, 120, 0.18); color: var(--hb-muted); }
.audit-help { color: var(--rowops-ink-muted); font-size: 0.88rem; line-height: 1.5; margin-bottom: 0.8rem; }
/* D-race-250 B5: a finish photo on the audit page, as wide as the page but never more than 480px; it links to the full file. */
.bow-photo-img { display: block; width: 100%; max-width: 480px; height: auto; border-radius: 6px; border: 1px solid var(--rowops-border, #ccd); }
html.theme-sun .audit-table tr.dup-bright,
body.theme-sun .audit-table tr.dup-bright { background: #2a0000; }
html.theme-sun .audit-table tr.dup-soft,
body.theme-sun .audit-table tr.dup-soft   { background: #150000; }
html.theme-sun .action-pill, body.theme-sun .action-pill {
    background: #000; color: #fff; border: 2px solid #fff; font-weight: 800;
}
html.theme-sun .action-pill.release, body.theme-sun .action-pill.release { color: #fde047; border-color: #fde047; }
html.theme-sun .action-pill.finish, body.theme-sun .action-pill.finish   { color: #fde047; border-color: #fde047; }
html.theme-sun .action-pill.recall, body.theme-sun .action-pill.recall   { color: #fde047; border-color: #fde047; }
html.theme-sun .action-pill.call, body.theme-sun .action-pill.call       { color: #fff; border-color: #fff; }
html.theme-sun .action-pill.undo, body.theme-sun .action-pill.undo       { color: #fff; border-color: #fff; }

/* =========================================================================
   Print-sheet chrome — 2026-06-02 audit closeout.
   Shared header (regatta logo + name + dates + tenant + sheet subtitle)
   rendered by templates/partials/print_chrome.php on every sheet, plus a
   @page footer (Generated YYYY-MM-DD HH:MM · Page N of M) emitted via
   CSS named strings + counter(page) / counter(pages). Per-sheet @page
   overrides (e.g. landscape mode for draw_rationale) layer on top of
   the @page baseline below.

   Captain-facing sheets (affected_clubs_per_club + availability_windows_per_club)
   use the standalone layout and keep their light-mode body styling; the
   .print-chrome element inherits its surrounding palette so it doesn't
   fight the captain page.
   ========================================================================= */

/* Shared header — always visible on screen as well as in print so the
   operator and the captain see the same identifying chrome on the page
   they're previewing before they hit Print.
   T-race-TODO-341 / D-race-285 (D39) + T-race-TODO-333 / D-race-278: the agreed running-order header (Brian, 20 Oct: "this is how a
   runnign order should print off _race in future") on every sheet: the logo on the left, the sheet's title as a small kicker, the
   regatta's name large, one meta line that names the club once, and a thick rule in the tenant's primary colour. Colours are the
   tenant's brand tokens, never a hex. */
.print-chrome {
    display: flex;
    align-items: center;
    gap: 4mm;
    padding: 0.5rem 0 2.5mm;
    margin-bottom: 1rem;
    border-bottom: 1.2mm solid var(--brand-primary);
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
}
.print-chrome-logo {
    flex: 0 0 auto;
    height: 19mm;
    display: flex;
    align-items: center;
    justify-content: center;
}
.print-chrome-logo img {
    height: 100%;
    width: auto;
    max-width: 40mm;
    object-fit: contain;
    display: block;
}
.print-chrome-text {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
/* T-race-TODO-341 / D-race-285 (D39), the review's fix, the header's colours: nothing in it (the title, the meta line, the sponsors band
   under it) reads a THEME's token (--rowops-ink, -ink-muted, -muted, -border). theme-toggle.js puts theme-dark or theme-sun on /print/
   pages and on the standalone layout too, and the header prints with print-color-adjust: exact, which stops the browser darkening light
   text for paper: Night's ink (#ecedf0) and Sun's (#ffffff) printed white on white, and on a captain's sheet, which pins its own #222 on a
   white page, the name and the date were invisible on screen (T-race-74 again). So the text takes the PAGE's own colour (`inherit`:
   Night's, Sun's, a captain's #222; a muted line is that colour at 70-72% by color-mix on currentColor, with `inherit` for a browser
   without it) and, on paper (the @media print block below), the tenant's dark colour, which no theme changes. */
.print-chrome-sponsors {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    margin: 0.2rem 0 0.4rem;
    padding-top: 0.3rem;
    border-top: 1px solid;
    border-top-color: color-mix(in srgb, currentColor 18%, transparent);
}
.print-chrome-sponsors-label {
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: inherit;
    color: color-mix(in srgb, currentColor 70%, transparent);
}
.print-chrome-sponsors img { max-height: 30px; max-width: 130px; object-fit: contain; }
.print-chrome-kicker {
    font-size: 7.5pt;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--brand-primary);
}
.print-chrome-title {
    font-weight: 800;
    font-size: 18pt;
    line-height: 1.1;
    letter-spacing: -0.01em;
    color: inherit;
}
/* The meta line is the page's colour at 72%, and the club and the facts in it take that. The date is bold; on paper (below) it is the
   tenant's dark colour in full, and on screen it shares the line's colour (inside a muted line the page's own colour cannot be had back
   in full without a theme's token). */
.print-chrome-meta {
    font-size: 9pt;
    color: inherit;
    color: color-mix(in srgb, currentColor 72%, transparent);
}
.print-chrome-meta b { color: inherit; font-weight: 600; }
.print-chrome-dates    { font-variant-numeric: tabular-nums; }

/* The .print-chrome-generated span is the seed for the @page footer.
   Off-screen but kept in flow for the named-string content-pickup. */
.print-chrome-generated {
    position: absolute;
    width: 1px; height: 1px; overflow: hidden;
    clip: rect(1px, 1px, 1px, 1px);
    string-set: print-generated content();
}

@media print {
    /* Pin a single typography baseline + page margin across every print
       sheet. Per-sheet @page named overrides (e.g. dr-page-landscape on
       draw_rationale) still win because they're declared later in the
       sheet template — author-order wins for equal specificity. */
    body { font-size: 9.5pt; }
    @page { margin: 14mm; }
    /* T-race-TODO-341 / D-race-285 (D39): the footer in the page's own type, not the browser's serif. The left side is the string the
       chrome writes into its own @page rule (partials/print_chrome.php), which wins over this one's named string. */
    @page {
        @bottom-left  { content: string(print-generated); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI", Roboto, "Helvetica Neue", system-ui, sans-serif; font-size: 8pt; color: #555; }
        @bottom-right { content: "Page " counter(page) " of " counter(pages); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI", Roboto, "Helvetica Neue", system-ui, sans-serif; font-size: 8pt; color: #555; }
    }

    /* Hide the seed span on print — the named string already carries
       the text into the @bottom-left margin box. */
    .print-chrome-generated { display: none !important; }

    /* T-race-TODO-341 / D-race-285 (D39): the header keeps the screen's sizes on paper (the agreed running-order look); only the
       spacing under it is tighter. The rule is the tenant's primary colour at 1.2 mm. */
    .print-chrome {
        margin-bottom: 3mm;
        padding: 0 0 2.5mm;
    }
    /* The review's fix: on paper the header's text is the tenant's dark colour, whatever theme the screen was in. The page is forced to
       black on white above, but this header prints with print-color-adjust: exact, so a light ink (Night's, Sun's) would print as it
       is; --brand-dark is the tenant's own and no theme changes it. The date in full, the rest of the line at 70%. */
    .print-chrome-title,
    .print-chrome-meta b { color: var(--brand-dark); }
    .print-chrome-meta {
        color: var(--brand-dark);
        color: color-mix(in srgb, var(--brand-dark) 70%, transparent);
    }
}

/* ── Live GPS map (spec 02) — full-bleed map shell + chrome ───────── */
.track-map-wrap { position: fixed; inset: 0; display: flex; flex-direction: column; background: var(--rowops-surface, #fff); z-index: 1; }
.track-map-bar { flex: 0 0 auto; display: flex; align-items: center; gap: 1rem; padding: 0.5rem 1rem; background: #1a1f2e; color: #fff; font-size: 0.95rem; }
.track-map-bar a { color: #fff; }
.track-map-title { font-weight: 700; }
.track-map-status { margin-left: auto; opacity: 0.85; font-variant-numeric: tabular-nums; }
.track-map-canvas { flex: 1 1 auto; width: 100%; min-height: 0; }
.track-map-alert { flex: 0 0 auto; padding: 0.6rem 1rem; background: #dc3545; color: #fff; font-weight: 600; }
.tracker-pwa-card { max-width: 30rem; margin: 1.5rem auto; }
.tracker-pwa-status { font-variant-numeric: tabular-nums; }
[data-bs-theme="sun"] .track-map-bar { background: #000; }
[data-bs-theme="sun"] .track-map-alert { background: #fde047; color: #000; }

/* ── Beach-sprint knockout bracket (spec 11) ───────────────────────── */
.beach-bracket-grid {
  display: flex;
  gap: 1.25rem;
  overflow-x: auto;
  padding-bottom: 0.5rem;
  align-items: flex-start;
}
.beach-bracket-col { min-width: 12rem; flex: 0 0 auto; }
.beach-bracket-stage {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--rowops-muted, #6c757d);
  margin: 0 0 0.5rem;
}
.beach-bracket-race {
  border: 1px solid var(--rowops-border, #dee2e6);
  border-radius: 0.4rem;
  padding: 0.4rem 0.5rem;
  margin-bottom: 0.9rem;
  background: var(--rowops-surface-2, #fff);
}
.beach-bracket-race.is-done { border-color: var(--rowops-success, #198754); }
.beach-bracket-seat {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
  padding: 0.2rem 0;
  font-size: 0.9rem;
}
.beach-bracket-seat.is-winner { font-weight: 700; }
.beach-bracket-seat.is-empty { color: var(--rowops-muted, #6c757d); font-style: italic; }
.beach-seed {
  font-size: 0.72rem;
  color: var(--rowops-muted, #6c757d);
  font-variant-numeric: tabular-nums;
}
.beach-crew { flex: 1 1 auto; }
.beach-club { font-size: 0.74rem; color: var(--rowops-muted, #6c757d); }
.beach-bracket-open { display: inline-block; font-size: 0.74rem; margin-top: 0.2rem; }
[data-bs-theme="sun"] .beach-bracket-race { background: #000; border-color: #fde047; }
[data-bs-theme="sun"] .beach-bracket-stage,
[data-bs-theme="sun"] .beach-seed,
[data-bs-theme="sun"] .beach-club { color: #fde047; }

/* ───────────────────────────────────────────────────────────────────
   Auth / standalone card (login · forgot-password · reset-password ·
   themed 403). Lifted out of templates/auth/login.php's inline <style>
   block 2026-06-17 so EVERY auth page gets the same branded card — the
   forgot/reset pages referenced these classes but never carried the CSS,
   so they rendered unstyled with no brand mark ("poor branding").
   ─────────────────────────────────────────────────────────────────── */
body.standalone-shell {
  background:
    radial-gradient(1200px 600px at 10% 0%, rgba(var(--brand-primary-rgb), 0.10), transparent 60%),
    radial-gradient(900px 500px at 90% 100%, rgba(var(--brand-primary-rgb), 0.05), transparent 60%),
    var(--rowops-surface);
  min-height: 100vh;
}
.standalone-main {
  min-height: 100vh;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: 2rem 1rem;
}
.login-card {
  width: 100%; max-width: 460px; border-radius: 1rem;
  background: var(--rowops-card);
  box-shadow: 0 20px 60px -20px rgba(0, 0, 0, 0.25), 0 8px 24px -8px rgba(0, 0, 0, 0.12);
}
/* Real brand logo (rowOps mark on the apex, club logo on a tenant host). */
.login-logo {
  display: block;
  height: 64px; width: auto;
  max-height: 64px; max-width: 220px;
}
/* Fallback only when a deploy explicitly blanks BRAND_LOGO_URL. */
.login-mark {
  display: block;
  width: 60px; height: 60px; border-radius: 14px;
  background: var(--brand-primary);
  position: relative;
  box-shadow: 0 6px 20px -6px var(--brand-primary);
}
.login-mark::after {
  content: '';
  position: absolute; inset: 14px;
  border-radius: 6px; background: rgba(255, 255, 255, 0.95);
}
.login-title { letter-spacing: -0.025em; font-weight: 800; color: var(--rowops-ink); }
.login-subtitle { letter-spacing: 0.06em; }
.login-label { letter-spacing: 0.06em; }
.login-input {
  background: var(--rowops-surface-2);
  border: 1px solid transparent;
  box-shadow: none;
  font-size: 1.05rem;
  padding: 0.85rem 1rem;
  border-radius: 0.5rem;
}
.login-input:focus {
  background: var(--rowops-card);
  border-color: var(--brand-primary);
  box-shadow: 0 0 0 4px rgba(var(--brand-primary-rgb), 0.15);
}
.login-button {
  padding: 0.85rem 1rem;
  border-radius: 0.5rem;
  font-size: 1.05rem;
  letter-spacing: 0.02em;
  background: var(--brand-primary); border: 0;
}
.login-button:hover { background: var(--brand-dark, #1f2a33); }
.login-card .x-small { font-size: 0.7rem; }
@media (max-width: 480px) {
  .login-card { padding: 1.5rem !important; max-width: 420px; }
}

/* ───────────────────────────────────────────────────────────────────
   Brand glyphs — Support\glyph(). The house replacement for decorative
   emoji (2026-06-17). Inline SVG sized to the surrounding text; colour
   inherits via currentColor. Decorative / race-annotation glyphs take the
   active tenant's brand (var(--brand-primary)); status glyphs use a
   semantic class so meaning-carrying icons keep their colour.
   ─────────────────────────────────────────────────────────────────── */
.glyph {
  width: 1em;
  height: 1em;
  display: inline-block;
  vertical-align: -0.125em;       /* sit on the text baseline */
  flex: 0 0 auto;
  color: var(--brand-primary);    /* decorative default = tenant brand */
}
/* In a button/badge/link, follow the text colour instead of the brand so
   the glyph matches its label (e.g. white on a primary button). */
.btn .glyph, .badge .glyph, a .glyph, .nav-link .glyph, summary .glyph { color: inherit; }
/* Semantic status glyphs — meaning over brand. */
.glyph.is-success { color: var(--brand-success, #198754); }
.glyph.is-warn    { color: var(--brand-warning, #f59e0b); }
.glyph.is-danger  { color: var(--brand-danger,  #dc3545); }
.glyph.is-muted   { color: var(--rowops-muted,  #6c757d); }
.glyph.is-brand   { color: var(--brand-primary); }
.glyph.is-record  { color: #dc3545; }            /* live-record red, theme-independent */
/* Sized variants for hero / empty-state contexts. */
.glyph.glyph-lg   { width: 1.5em; height: 1.5em; }
.glyph.glyph-xl   { width: 2.25em; height: 2.25em; }
/* Sun mode (race-day high-contrast): collapse brand + status hues to the
   jet-yellow glance palette like the rest of the sun theme. */
[data-bs-theme="sun"] .glyph,
[data-bs-theme="sun"] .glyph.is-success,
[data-bs-theme="sun"] .glyph.is-warn,
[data-bs-theme="sun"] .glyph.is-brand { color: #fde047; }
[data-bs-theme="sun"] .glyph.is-danger,
[data-bs-theme="sun"] .glyph.is-record { color: #fde047; }

/* Home empty-state — branded mark + sized glyph fallback (2026-06-17). */
.hp-empty-state .hp-empty-glyph .glyph { width: 3rem; height: 3rem; }
.hp-empty-state .hp-empty-logo { height: 64px; width: auto; max-width: 220px; opacity: 0.9; }

/* ─── Erg Registration + Events + public per-event surfaces (2026-06-18) ───
   New erg-mode operator + spectator chrome. Reuses brand surface tokens so
   day / dark / sun parity flows through; sun overrides for colour-bearing
   bits added explicitly below. */

/* Registration fee/cap table — keep the euro fee + cap forms tidy + aligned. */
.erg-fee-table td { vertical-align: middle; }
.erg-fee-input { width: 9rem; flex: 0 0 auto; }
.input-mw-7rem { max-width: 7rem; }

/* Bulk "one price for all events" card on the events page. */
.erg-bulk-fee-card { border-left: 3px solid var(--brand-primary, #b07d33); }

/* Per-event fee + cap inline cell on the events table (erg mode). Kept
   narrow so the two stacked forms don't widen the row. */
.erg-fee-cell { width: 7rem; flex: 0 0 auto; }
.erg-cap-cell { max-width: 5rem; }

/* Registration danger-zone reset card — mirrors .dash-danger but local to
   the erg surfaces so the typed-confirm + reset CTA reads as destructive. */
.erg-danger {
    margin-top: var(--space-8, 2rem);
    padding-top: var(--space-4, 1rem);
    border-top: 1px solid var(--rowops-border, #e5e5e5);
}
.erg-danger-summary {
    color: var(--rowops-bad, #b02a37);
    font-weight: 700; cursor: pointer; font-size: 0.95rem;
}
.erg-danger-body {
    margin-top: var(--space-3, 0.75rem);
    padding: var(--space-3, 0.75rem) var(--space-4, 1rem);
    background: rgba(176, 42, 55, 0.05);
    border: 1px solid rgba(176, 42, 55, 0.22);
    border-radius: var(--radius-md, 0.5rem);
}
.erg-danger-input { width: auto; max-width: 24rem; }

/* Public per-event flights card link affordance (live_erg_registration). */
.erg-event-card-link { text-decoration: none; color: inherit; display: block; transition: border-color 0.12s, box-shadow 0.12s, transform 0.12s; }
.erg-event-card-link:hover { border-color: var(--brand-primary, #b07d33); box-shadow: 0 4px 14px rgba(0,0,0,0.08); transform: translateY(-1px); }
.erg-event-card-link .erg-event-card-cta { display: block; margin-top: 0.5rem; font-size: 0.82rem; font-weight: 600; color: var(--brand-primary, #b07d33); }

/* Leaderboard event-name link (live_erg). */
.erg-leaderboard-event-link { color: inherit; text-decoration: none; }
.erg-leaderboard-event-link:hover { text-decoration: underline; }

/* Sun-mode parity for the erg danger card + bulk-fee accent. */
html.theme-sun .erg-danger-summary, body.theme-sun .erg-danger-summary { color: #fde047; }
html.theme-sun .erg-danger-body, body.theme-sun .erg-danger-body {
    background: #000; border: 3px solid #fde047; color: #fff;
}
html.theme-sun .erg-bulk-fee-card, body.theme-sun .erg-bulk-fee-card { border-left-color: #fde047; }
html.theme-sun .erg-event-card-link .erg-event-card-cta,
body.theme-sun .erg-event-card-link .erg-event-card-cta { color: #fde047; }
html.theme-sun .erg-event-card-link:hover,
body.theme-sun .erg-event-card-link:hover { border-color: #fde047; }

/* Dark-mode parity — the danger tint reads on the deep-slate surface. */
html.theme-dark .erg-danger-body, body.theme-dark .erg-danger-body {
    background: rgba(176, 42, 55, 0.12);
    border-color: rgba(176, 42, 55, 0.4);
}

/* ─── Digital erg check-in desk (erg_check_in.php) ──────────────────────
   Venue registration operator signs entrants in here; the printable
   sign-in sheet stays the offline backup. Row states read at a glance so
   the desk can scan a queue: signed-in rows go green, pending rows keep
   the primary "Sign in" CTA. Day / dark / sun parity below. */
.checkin-counts .card-body { gap: var(--space-2, 0.5rem); }
.checkin-count-pill {
    display: inline-flex; align-items: center; gap: 0.35rem;
    padding: 0.25rem 0.6rem; border-radius: var(--radius-md, 0.5rem);
    font-size: 0.9rem; font-weight: 600;
    background: var(--rowops-surface-2); color: var(--rowops-ink);
    border: 1px solid var(--rowops-border);
}
.checkin-count-pill.is-pending {
    background: rgba(173, 106, 0, 0.1);
    border-color: rgba(173, 106, 0, 0.3);
    color: var(--rowops-warn);
}
.checkin-count-pill.is-done {
    background: rgba(21, 115, 71, 0.1);
    border-color: rgba(21, 115, 71, 0.3);
    color: var(--rowops-good);
}

.checkin-row {
    display: flex; flex-wrap: wrap; align-items: center;
    justify-content: space-between; gap: var(--space-3, 0.75rem);
}
.checkin-row-info {
    display: flex; flex-wrap: wrap; align-items: baseline;
    gap: 0.35rem 0.75rem; min-width: 0;
}
.checkin-row-name { font-weight: 600; }
.checkin-row-club { font-size: 0.9rem; }
.checkin-row-action { display: flex; align-items: center; gap: 0.5rem; }
.checkin-signed-note {
    display: inline-flex; align-items: center; gap: 0.3rem;
    font-size: 0.85rem; font-weight: 600; color: var(--rowops-good);
}
/* Signed-in rows tint green with a left accent so the desk sees state. */
.checkin-row.is-checked-in {
    background: rgba(21, 115, 71, 0.07);
    box-shadow: inset 3px 0 0 var(--rowops-good);
}
.checkin-event-hidden { display: none; }

/* Per-race expected/not-signed-in badge on erg_finish.php. The is-warning
   / is-ok base classes already carry day/dark/sun parity (defined above);
   this only sizes the inline badge to read in the card header. */
.checkin-race-badge { font-size: 0.78rem; font-weight: 700; }

/* Dark-mode parity — the green tint reads on the deep-slate surface. */
html.theme-dark .checkin-row.is-checked-in,
body.theme-dark .checkin-row.is-checked-in {
    background: rgba(21, 115, 71, 0.18);
}
html.theme-dark .checkin-count-pill,
body.theme-dark .checkin-count-pill {
    background: var(--rowops-surface-2); color: var(--rowops-ink);
    border-color: var(--rowops-border);
}
html.theme-dark .checkin-count-pill.is-done,
body.theme-dark .checkin-count-pill.is-done { background: rgba(21, 115, 71, 0.2); }
html.theme-dark .checkin-count-pill.is-pending,
body.theme-dark .checkin-count-pill.is-pending { background: rgba(173, 106, 0, 0.2); }

/* Sun-mode parity — pure white-on-black, jet-yellow accents, signed-in
   rows get a high-contrast green so the desk reads in direct glare. */
html.theme-sun .checkin-count-pill, body.theme-sun .checkin-count-pill {
    background: #000; color: #fff; border: 2px solid #fff; font-weight: 800;
}
html.theme-sun .checkin-count-pill.is-pending,
body.theme-sun .checkin-count-pill.is-pending {
    background: #000; color: #fde047; border-color: #fde047;
}
html.theme-sun .checkin-count-pill.is-done,
body.theme-sun .checkin-count-pill.is-done {
    background: #000; color: #2ecc71; border-color: #2ecc71;
}
html.theme-sun .checkin-row.is-checked-in,
body.theme-sun .checkin-row.is-checked-in {
    background: rgba(46, 204, 113, 0.18); box-shadow: inset 4px 0 0 #2ecc71;
}
html.theme-sun .checkin-signed-note, body.theme-sun .checkin-signed-note {
    color: #2ecc71; font-weight: 800;
}
html.theme-sun .checkin-row-name, body.theme-sun .checkin-row-name { color: #fff; }
html.theme-sun .checkin-race-badge.is-warning,
body.theme-sun .checkin-race-badge.is-warning {
    background: #fde047; color: #000; border: 2px solid #fff; font-weight: 800;
}

/* Self-updating head pages (D-race-88): public/js/live-region-refresh.js marks
   its status line stale when a refresh failed ("Not updated since HH:MM:SS"). */
[data-live-status] { color: var(--rowops-ink-muted); }
[data-live-status][data-live-stale] { color: var(--rowops-bad); font-weight: 700; }
/* A projector whose session ended or whose link was changed (D-race-97) says so
   in a band a room can read, not a small grey line. Tokens only, no raw hex. */
[data-live-status][data-live-ended] {
    display: inline-block; padding: 0.2em 0.7em; border-radius: var(--radius-md);
    background: var(--rowops-bad); color: var(--rowops-accent-fg);
    font-size: 1.35em; font-weight: 800;
}

/* A head's side-by-side race (D-race-89): templates/partials/side_by_side_race.php.
   Here, not in the partial: on /live, results and the projector the panel sits in a
   [data-live-region] block, and a <style> swapped in with it would carry a nonce the
   page's CSP does not (T-race-52). In em, so the projector's larger root scales it. */
.sbs-race { border: 2px solid var(--rowops-border); border-radius: 0.6em; padding: 0.9em 1.1em; margin: 0 0 1.2em; background: var(--rowops-card); }
.sbs-head { display: flex; flex-wrap: wrap; gap: 0.5em 1em; align-items: baseline; margin-bottom: 0.6em; }
.sbs-event { font-weight: 800; font-size: 1.15em; }
.sbs-when { color: var(--rowops-ink-muted); font-variant-numeric: tabular-nums; }
.sbs-lanes { display: grid; grid-template-columns: 1fr auto 1fr; gap: 0.6em; align-items: center; }
.sbs-lane { border: 1px solid var(--rowops-border); border-radius: 0.45em; padding: 0.55em 0.7em; text-align: center; }
.sbs-lane.is-winner { border-color: var(--brand-primary); border-width: 2px; }
.sbs-lane-no { display: block; font-size: 0.72em; text-transform: uppercase; letter-spacing: 0.05em; color: var(--rowops-ink-muted); }
.sbs-side { display: block; font-weight: 800; font-size: 1.3em; }
.sbs-result { display: block; font-variant-numeric: tabular-nums; min-height: 1.2em; }
.sbs-v { font-weight: 700; color: var(--rowops-ink-muted); }
.sbs-clock { margin-top: 0.6em; text-align: center; font-size: 1.8em; font-weight: 800; font-variant-numeric: tabular-nums; }
.sbs-verdict { margin-top: 0.6em; text-align: center; font-weight: 700; }
@media (max-width: 520px) { .sbs-lanes { grid-template-columns: 1fr; } .sbs-v { text-align: center; } }

/* D-race-188: column sorters (public/js/table-sort.js). The header is a real button, so a keyboard reaches it; it looks like
   the header it replaces, and looks clickable only once the sorter has wired the table (data-table-sort-ready). A row on
   the divisions page whose Division differs from the saved one is marked (public/js/event-divisions.js). */
th[data-sort] > button { background: none; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; text-align: inherit; }
table[data-table-sort-ready] th[data-sort] > button { cursor: pointer; }
table[data-table-sort-ready] th[data-sort] > button:hover { text-decoration: underline; }
th[data-sort] > button:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: 2px; border-radius: 2px; }
.sort-arrow { font-size: 0.8em; }
tr.is-changed > td:first-child { box-shadow: inset 3px 0 0 var(--brand-primary); }

/* D-race-194 — a head's gold medal times page: the time boxes are a few characters wide. */
.gmt-table .gmt-input { max-width: 8rem; }

/* T-race-TODO-268 — the organisation's own logo in a projector header, beside the regatta's name (templates/partials/
   org_logo_header.php). Height-capped to the header. On a dark screen it sits on a chip of the screen's own text colour, which
   is light there, so a dark crest stays visible: the head projector's Night and Sun themes, and the regatta projector, which is
   always dark. The Day theme needs no chip. */
.org-logo-header { flex: none; display: block; width: auto; height: auto; max-height: 1.8em; max-width: 10em; object-fit: contain; border-radius: 0.2em; }
html.theme-dark .org-logo-header, body.theme-dark .org-logo-header,
html.theme-sun .org-logo-header, body.theme-sun .org-logo-header,
.org-logo-header.org-logo-on-dark { background-color: currentColor; padding: 0.1em 0.25em; }

/* T-race-TODO-302 (Brian, 2026-10-19: "a touch slicker looking too along the lines of the other pages", "info needs to track same
   style", and "link buttons arent clear, need to keep a standard style of action button ... not necisarily the same size all the
   time but the same style", which rowops-44 records as D-race-267's amendment): the public landing and /info in the new pages'
   family. The page's one main action is D-race-201's setup-action green (the same green D-race-267 gives the step, whose class
   only the staff slot's partials may name); every other action is outlined in the club's own
   colour (--rowops-accent, the tenant's brand); sizes vary, the style does not. */
.pub-head { margin-bottom: 0.75rem; }
.pub-head .pub-club { margin: 0; font-size: 0.8rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--rowops-accent-text); }
.pub-head h1 { margin: 0.1rem 0 0.45rem; }
.pub-chips { display: flex; flex-wrap: wrap; gap: 0.35rem; list-style: none; padding: 0; margin: 0; }
.pub-chips li { font-size: 0.8rem; font-weight: 600; padding: 0.1rem 0.6rem; border-radius: 999px; background: var(--rowops-accent-tint); color: var(--rowops-accent-text); }
.pub-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; margin: 0 0 0.6rem; }
.pub-actions .pub-pending { color: var(--rowops-muted, #6c757d); font-size: 0.875rem; }
.pub-count-card { border: 1px solid var(--rowops-border, #e0e3e9); border-left: 4px solid var(--rowops-accent); border-radius: 0.5rem; padding: 0.55rem 0.9rem; margin-bottom: 0.9rem; }
.pub-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr)); gap: 0.5rem; margin: 0 0 0.9rem; }
.pub-fact { border: 1px solid var(--rowops-border, #e0e3e9); border-radius: 0.5rem; padding: 0.5rem 0.75rem; min-width: 0; }
.pub-fact dt, .pub-fact .pub-fact-k { font-size: 0.75rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--rowops-muted, #6c757d); font-weight: 600; }
.pub-fact dd, .pub-fact p { margin: 0; font-weight: 600; }
/* The map links on every public landing (partials/course_map_links.php): small buttons in the club's colour, the anchors as
   they were (their text and addresses are pinned by the course map tests). */
.course-map-links { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.course-map-links a { display: inline-flex; align-items: center; min-height: 2rem; padding: 0 0.7rem; border: 1px solid var(--rowops-accent);
    border-radius: 0.375rem; color: var(--rowops-accent-text); font-weight: 600; text-decoration: none; border-bottom-width: 1px; }
/* T-race-TODO-315 (part 2): filled, the page's own colour on the deeper accent (white on the plain bronze is 3.6:1). */
.course-map-links a:hover, .course-map-links a:focus-visible { background: var(--rowops-accent-text); color: var(--rowops-surface); border-color: var(--rowops-accent-text); }
@media (max-width: 575.98px) { .pub-actions .btn, .course-map-links a { min-height: 2.5rem; } }
/* T-race-TODO-342 / D-race-286 (the menu and paths audit's T3; Brian, Wed 21 Oct 2026, "Link line + landing link (Recommended)"): the
   information page's one line to its safety files, right under the map links: "Safety files (4) ↓". A finger high, in the deeper
   shade of the club's colour (--rowops-accent-text, 4.5:1 on the page). The landing pages' link is the map links' fourth button. */
.pub-safety-jump a { display: inline-flex; align-items: center; min-height: 2.75rem; font-weight: 600; color: var(--rowops-accent-text); }

/* T-race-TODO-315 / D-race-271 (Brian, 2026-10-20: "needs to be in phone mode, these live pages are predominently read on
   phones"; "artifact looks good"): the public live pages are phone-first. One column about a phone's width, centred on a laptop
   (never the wide desktop layout); the facts as one strip of pills under the regatta's name; the map first, full width on a
   phone, with "Whole area" and "Start area"; the places in three fold-out groups, every row finger-sized; a tab bar fixed at the
   bottom (Live, Draw, Results, Info), the page padded so it never covers the content, above the phone's home bar. */
.pub-phone { max-width: 30rem; margin-left: auto; margin-right: auto; }
body:has([data-pub-tabs]) { padding-bottom: calc(4rem + env(safe-area-inset-bottom, 0px)); }
/* A browser without :has() (Firefox before 121) pads the column instead: only the privacy line under it can sit behind the bar. */
@supports not selector(:has(*)) { .pub-phone { padding-bottom: calc(4rem + env(safe-area-inset-bottom, 0px)); } }
.pub-chips li .race-countdown { display: inline; margin: 0; font-size: inherit; font-weight: inherit; }
.pub-chips li .race-countdown strong { font-weight: 700; }
.pub-phone .public-course-map { height: 330px; }
@media (max-width: 575.98px) {
    .pub-phone .pub-map-bleed { margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw); }
    .pub-phone .pub-map-bleed .public-course-map { border-radius: 0; border-left: 0; border-right: 0; }
}
.pub-map-tools { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0 0 0.4rem; }
.pub-map-tools .btn { min-height: 2.75rem; }
.pub-map-tools .btn[aria-pressed="true"] { background: var(--rowops-accent-text); color: var(--rowops-surface); border-color: var(--rowops-accent-text); }
.pub-about-map { margin: 0 0 0.75rem; font-size: 0.85rem; color: var(--rowops-muted); }
.pub-about-map > summary { display: flex; align-items: center; gap: 0.5rem; min-height: 2.75rem; cursor: pointer; font-weight: 600; list-style: none; }
/* A summary set to flex loses its triangle: each fold draws its own chevron, turned down while open. */
.pub-about-map > summary::-webkit-details-marker, .place-list.place-folds .place-group > summary::-webkit-details-marker { display: none; }
.pub-about-map > summary::before, .place-list.place-folds .place-group > summary::before { content: ""; flex: none; width: 0.45rem; height: 0.45rem;
    border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(-45deg); transition: transform 0.15s; }
.pub-about-map[open] > summary::before, .place-list.place-folds .place-group[open] > summary::before { transform: rotate(45deg); }
.pub-about-map > p { margin: 0 0 0.25rem; }
.place-list.place-folds .place-groups { display: block; }
.place-list.place-folds .place-group { border-bottom: 1px solid var(--rowops-border); }
.place-list.place-folds .place-group > summary { display: flex; align-items: center; gap: 0.5rem; min-height: 2.75rem; cursor: pointer; list-style: none; }
.place-list.place-folds .place-group > summary .place-group-title { display: inline; margin: 0; padding: 0; border: 0; }
.place-list.place-folds .place-count { margin-left: auto; min-width: 1.75rem; text-align: center; font-size: 0.8rem; font-weight: 700;
    padding: 0 0.5rem; border-radius: 999px; background: var(--rowops-accent-tint); color: var(--rowops-accent-text); }
.place-list.place-folds .place-btn { min-height: 2.75rem; }
.pub-tabs { position: fixed; left: 0; right: 0; bottom: 0; z-index: 1100; display: flex; justify-content: center;
    background: var(--rowops-surface); border-top: 1px solid var(--rowops-border); padding-bottom: env(safe-area-inset-bottom, 0px); }
.pub-tabs ul { display: flex; width: 100%; max-width: 30rem; list-style: none; margin: 0; padding: 0; }
.pub-tabs li { flex: 1 1 0; min-width: 0; }
.pub-tab { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 3.5rem; padding: 0.35rem 0.25rem;
    font-size: 0.9rem; font-weight: 600; line-height: 1.15; text-align: center; color: var(--rowops-ink); text-decoration: none; }
.pub-tab:hover, .pub-tab:focus-visible { color: var(--rowops-accent-text); }
.pub-tab.is-current { color: var(--rowops-accent-text); box-shadow: inset 0 3px 0 var(--rowops-accent); }
.pub-tab.is-off { color: var(--rowops-muted); opacity: 0.65; cursor: default; }
.pub-tab small { font-size: 0.7rem; font-weight: 500; }
@media print { .pub-tabs, .pub-map-tools { display: none; } body:has([data-pub-tabs]) { padding-bottom: 0; } }

/* T-race-TODO-329 (T-race-TODO-315's second part; Brian, 2026-10-20 ~19:25, with /live/{slug}/info on a wide laptop, a phone's
   column in the middle of the screen: "and i thought mobile first would be responsive enough to know when its a laptop or
   desktop ?"): from 992 px the public live pages use the width. Under 992 px the pages stay part 1's (Brian, ~19:20: "screen 1
   is good").
   - .pub-phone grows to 76rem: every page in the column (the landing, information, draw, results and course map pages).
   - A page with a map and the rest (.pub-cols and its blocks .pub-col-info, -card, -map and -rest: the landing pages and the
     information page) takes two columns: the information, the places and the rest on the left, the map on the right, tall and in
     view while the left scrolls (Brian, 2026-10-19, T-race-TODO-293: "on a wide screen the course map should be to the right hand
     side and long as most courses would be. info on the left."; and "Keep the map on race day": "the course map on the right
     all day"). A phone reads the blocks in their order, one column.
   - The tab bar stays at the bottom: a dock centred under the column, no wider than it.
   Screen only: a printed page keeps the one column. */
@media screen and (min-width: 992px) {
    .pub-phone { max-width: 76rem; }
    .pub-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(22rem, 42%); grid-template-rows: auto auto 1fr;
        grid-template-areas: "info map" "card map" "rest map"; column-gap: 2rem; align-items: start; }
    .pub-col-info { grid-area: info; min-width: 0; }
    .pub-col-card { grid-area: card; min-width: 0; }
    .pub-col-rest { grid-area: rest; min-width: 0; }
    /* Its margin is its sticky gap, so it starts where it stays: 1rem under the top bar on every page. */
    .pub-col-map { grid-area: map; min-width: 0; margin-top: 1rem; position: sticky; top: calc(var(--top-bar-h) + 1rem); }
    .pub-col-map > .public-course { margin-bottom: 0 !important; }
    /* Under the top bar and its gap, the two view buttons and "About this map", and above the dock. */
    .pub-phone .pub-col-map .public-course-map { height: calc(100vh - var(--top-bar-h) - 12.5rem); min-height: 20rem; }
    .pub-tabs { max-width: 40rem; margin-left: auto; margin-right: auto; border: 1px solid var(--rowops-border); border-bottom: 0;
        border-radius: var(--radius-lg) var(--radius-lg) 0 0; box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.08); }
    .pub-tabs ul { max-width: none; }
    /* The page's mark (the current tab's top line) follows the dock's rounded corners; no overflow clip, so a focus ring shows whole. */
    .pub-tabs li:first-child .pub-tab { border-top-left-radius: var(--radius-lg); }
    .pub-tabs li:last-child .pub-tab { border-top-right-radius: var(--radius-lg); }
}
/* T-race-TODO-329: on a phone the information page and a regatta's landing keep part 1's look in their blocks (.pub-cols-list-
   under-map): the place list straight under its map, with no heading of its own (a screen reader still hears it) and the map's
   own spacing. From 992 px the list opens the left column, away from the map, so its heading shows. */
@media (max-width: 991.98px) {
    .pub-cols-list-under-map > .pub-col-map > .public-course { margin-bottom: 0.75rem !important; }
    .pub-cols-list-under-map > .pub-col-rest > .place-list > h2 { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden;
        clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
}
/* The jump itself (#safety-files, from the information page's "Safety files (N) ↓" line (.pub-safety-jump, T-race-TODO-342,
   further up) and from the landings' "Safety files" link) must not land under the top bar, which
   sticks at 56 px at every width. From 992 px html's scroll-padding-top (near the top of this file) already clears it, so the margin
   is for below that and the two never add up; 4.5rem is the bar and a gap. Class only: no inline style (CSP).
   POSITION IS LOAD-BEARING: it stays AFTER the T-race-TODO-329 block above, because PublicDesktopLayoutTest reads the FIRST
   max-width 991.98px media block in this file and expects it to be that one. */
@media (max-width: 991.98px) { .safety-files { scroll-margin-top: 4.5rem; } }
/* T-race-TODO-329: every tap target on these pages at least 44 px, the map links and the one main action too (they were 40 px on a
   phone, 32 px wider). And the map keeps Leaflet's layers to itself, so it never paints over the sticky top bar as the page scrolls. */
.pub-phone .course-map-links a { min-height: 2.75rem; }
/* The gap is the space between a button's icon and its words, which a flex box drops from the text ("[icon]Portrait"). */
.pub-phone .pub-actions .btn { display: inline-flex; align-items: center; justify-content: center; gap: 0.35rem; min-height: 2.75rem; }
.pub-phone .public-course-map { isolation: isolate; }
/* T-race-TODO-315 (part 2): the rest of the family's tap targets at 44 px: the map's zoom buttons (Leaflet's touch rule makes them
   30 px; the course, start-area and finish-area maps size theirs in course_area_map.php) and a landing's "See full draw" and
   "See full results" links, text high until now. */
.pub-phone .public-course-map .leaflet-bar a { width: 2.75rem; height: 2.75rem; line-height: 2.75rem; }
.pub-phone .pub-more { display: inline-flex; align-items: center; min-height: 2.75rem; }
/* T-race-TODO-315 (part 2): the family's outlined actions (the draw's print buttons, the map's two views) keep the club's colour
   on their edge and take its deeper shade for their words, so they read at 4.5:1 (--rowops-accent-text); filled, the same. */
.pub-phone .btn-outline-primary { color: var(--rowops-accent-text); }
.pub-phone .btn-outline-primary:hover, .pub-phone .btn-outline-primary:focus-visible { background: var(--rowops-accent-text); color: var(--rowops-surface); border-color: var(--rowops-accent-text); }

/* T-race-TODO-337 / D-race-282 (Brian, 2026-10-20 ~23:05 Irish, with a screenshot of a club's home page: "landing page isnt great, for
   non tech who happen in here over the weekend"): a club's own home page, in the public pages' family (.pub-phone: one column a phone
   wide, 76rem from 992 px). The featured regatta is a card with the club's colour on its edge: the page head (the name, then one strip
   of facts: status, dates, venue, counts) and four big buttons, 2 by 2 on a phone and one row from 992 px. Every other regatta is a
   line of a compact list, which from 992 px is a grid under the card. Tokens only: the club's colours come through --rowops-accent and
   its deeper text shade --rowops-accent-text (4.5:1, T-race-TODO-315 part 2). */
.club-home { padding-top: 1rem; }
.club-home-feature { border: 1px solid var(--rowops-border); border-left: 4px solid var(--rowops-accent); border-radius: var(--radius-md);
    background: var(--rowops-card); padding: 1rem 1rem 1.1rem; margin-bottom: 1.25rem; }
.club-home-feature .pub-head { margin-bottom: 0.9rem; }
.club-home-feature .pub-chips li { font-size: 0.95rem; padding: 0.2rem 0.7rem; }
/* The status line is the strip's first and strongest pill: the theme's own ink filled, the page's surface on it. T-race-TODO-371
   (Brian, Fri 23 Oct 2026, RULINGS row 64: "Neutral fill"): not the club's colour, so a pale or loud club cannot make it look odd; the
   club's colour stays on the card's left edge and the outlined buttons. Ink on surface swaps by theme (Day, Night, Sun), 4.5:1 in all. */
.club-home-feature .pub-chips li[data-club-home-status] { background: var(--rowops-ink); color: var(--rowops-surface); }
/* T-race-TODO-342 / D-race-286 (D88): "Next race 11:42", the pill that matters on race day, a weight heavier than the facts after it. */
.club-home-feature .pub-chips li[data-club-home-next] { font-weight: 700; }
/* Two columns on a phone; the odd one out (one button, or a third) takes the whole row. */
.club-home-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.6rem; }
.club-home-actions > :nth-child(odd):last-child { grid-column: 1 / -1; }
.pub-phone .club-home-btn { display: inline-flex; align-items: center; justify-content: center; min-height: 3rem; padding: 0.5rem 0.75rem;
    font-size: 1.05rem; font-weight: 600; text-align: center; }
/* The button that matters most (Live; Results once it is over) is filled in the theme's ink (T-race-TODO-371), as the card's status pill is. */
.pub-phone .club-home-btn.is-main { background: var(--rowops-ink); color: var(--rowops-surface); border-color: var(--rowops-ink); }
.pub-phone .club-home-btn.is-main:hover, .pub-phone .club-home-btn.is-main:focus-visible { filter: brightness(1.15); }
.club-home-more-title { margin: 0 0 0.5rem; font-size: 0.8rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--rowops-accent-text); }
.club-home-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.club-home-item { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; border: 1px solid var(--rowops-border);
    border-radius: var(--radius-md); background: var(--rowops-card); padding: 0.5rem 0.5rem 0.5rem 0.9rem; }
.club-home-item-text { min-width: 0; }
.club-home-item-name { display: block; overflow-wrap: anywhere; }
.club-home-item-dates { display: block; font-size: 0.85rem; color: var(--rowops-ink-muted); }
.pub-phone .club-home-open { flex: none; display: inline-flex; align-items: center; min-height: 2.75rem; font-weight: 600; white-space: nowrap; }
.club-home-empty { margin: 0.5rem 0 0; font-size: 1.05rem; }
/* The one line for the people who run things: small, last, and still a finger's height to tap. T-race-TODO-342 (the T-race-TODO-337
   review's minor): 0.7rem of padding round a 0.85rem line was 42.8 px, so the link is a 2.75rem (44 px) box. */
.club-home-signin { margin: 1.5rem 0 0.25rem; text-align: center; font-size: 0.85rem; color: var(--rowops-muted); }
.club-home-signin a { display: inline-flex; align-items: center; min-height: 2.75rem; padding: 0.7rem 0.4rem; color: var(--rowops-accent-text); text-decoration: underline; }
@media (min-width: 992px) {
    .club-home-feature { padding: 1.5rem 1.75rem 1.75rem; }
    .club-home-feature .pub-chips li { font-size: 1rem; }
    /* One row, however many buttons answer. */
    .club-home-actions { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); }
    .club-home-actions > :nth-child(odd):last-child { grid-column: auto; }
    .club-home-list { grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr)); }
}
/* T-race-TODO-352 / D-race-297: a regatta's live notices (partials/public_notices.php), a strip at the foot of the public head, newest first.
   An urgent one has its own border and the word "Urgent", never colour alone. Tokens only. Not printed: a printed draw is a document. */
.pub-notices { display: flex; flex-direction: column; gap: 0.4rem; margin: 0.6rem 0 0; }
.pub-notice { border: 1px solid var(--rowops-border); border-left: 4px solid var(--rowops-accent); border-radius: 0.5rem;
    background: var(--rowops-surface); padding: 0.5rem 0.75rem; }
.pub-notice.is-urgent { border-color: var(--rowops-bad); border-left-width: 8px; background: var(--rowops-surface-2); }
.pub-notice-time { margin: 0 0 0.15rem; font-size: 0.75rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
    color: var(--rowops-muted); }
/* White on the bad red in every theme (6.5:1): the page surface was the text colour here, which is 2.5:1 in Night and 3.2:1 in Sun. */
.pub-notice-tag { margin-left: 0.4rem; padding: 0.05rem 0.5rem; border-radius: 999px; background: var(--rowops-bad);
    color: var(--rowops-accent-fg); font-weight: 700; }
.pub-notice-text { margin: 0; overflow-wrap: anywhere; }
.pub-notice.is-urgent .pub-notice-text { font-weight: 600; }
/* The venue projector: large, in the screen's own colours (the lane screen is white on black, the head screen takes the theme). */
.pub-notices.is-screen { gap: 0.4em; margin: 0.4em 0; }
.pub-notices.is-screen .pub-notice { background: transparent; color: inherit; border-color: currentColor; border-left-width: 0.4em; padding: 0.4em 0.8em; }
/* An urgent one on the screen: the bad red all round and a left bar twice the plain one's, so it reads across a room without the tag. */
.pub-notices.is-screen .pub-notice.is-urgent { border-color: var(--rowops-bad); border-width: 0.2em; border-left-width: 0.8em; }
.pub-notices.is-screen .pub-notice-time { font-size: clamp(0.8rem, 0.9vw, 1.2rem); color: inherit; opacity: 0.8; }
.pub-notices.is-screen .pub-notice-text { font-size: clamp(1.25rem, 1.9vw, 2.6rem); line-height: 1.2; font-weight: 600; }
@media print { .pub-notices { display: none; } }

/* T-race-TODO-342 / D-race-286 (the menu and paths audit's T4; Brian, 2026-10-20 23:21: "nav accross these public pages is till bad"):
   the one search box over a head's draw and results, about 510 crews (partials/public_row_search.php, public-row-search.js). The
   page draws it hidden, the script shows it, so with no JavaScript there is no dead control. The field is 44 px (2.75rem) and
   16 px type, so a phone does not zoom the page when it takes the focus; tokens only. From 992 px it stays in reach under the top
   bar while the list scrolls, as the regatta results page's filter bar does. A printed sheet never has it. */
.pub-search { margin: 0 0 0.9rem; }
.pub-search-input { display: block; width: 100%; min-height: 2.75rem; padding: 0.4rem 0.8rem; border: 1px solid var(--rowops-ink-muted);
    border-radius: 0.4rem; background: var(--rowops-surface-2); color: var(--rowops-ink); font-size: 1rem; }
.pub-search-input::placeholder { color: var(--rowops-ink-muted); opacity: 1; }
.pub-search-input:focus { outline: 2px solid var(--rowops-accent); outline-offset: -1px; background: var(--rowops-surface); }
.pub-search-count { margin: 0.25rem 0 0; min-height: 1.3em; font-size: 0.85rem; color: var(--rowops-ink-muted); }
.pub-search-none { margin: 0 0 0.9rem; font-weight: 600; }
@media (min-width: 992px) {
    .pub-search { position: sticky; top: var(--top-bar-h, 56px); z-index: 20; margin-bottom: 0.5rem; padding: 0.5rem 0 0.1rem; background: var(--rowops-surface); }
    .pub-search-input { max-width: 24rem; }
}
@media print { .pub-search, .pub-search-none { display: none !important; } }

/* T-race-TODO-351 / D-race-296 (Brian, 2026-10-21 13:16 Irish: "be good to allow users filter by club here"): the club drop-down on the
   public draw and results (partials/public_club_filter.php, public-club-filter.js). A plain GET form: the select is 44 px (2.75rem)
   with 16 px type so a phone does not zoom the page, the Show button is the form's way to send with no JavaScript (the script hides
   it and sends on change). Tokens only; a printed sheet never has the form, but keeps the note that says "<club> only". */
.pub-club-filter { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem 0.6rem; margin: 0 0 0.6rem; }
.pub-club-label { font-weight: 600; margin: 0; }
.pub-club-select { flex: 1 1 12rem; max-width: 24rem; min-height: 2.75rem; padding: 0.4rem 0.8rem; border: 1px solid var(--rowops-ink-muted);
    border-radius: 0.4rem; background: var(--rowops-surface-2); color: var(--rowops-ink); font-size: 1rem; }
.pub-club-select:focus { outline: 2px solid var(--rowops-accent); outline-offset: -1px; background: var(--rowops-surface); }
.pub-club-go { min-height: 2.75rem; }
.pub-club-note { margin: 0 0 0.9rem; font-size: 0.9rem; color: var(--rowops-ink-muted); }
@media print { .pub-club-filter { display: none !important; } }

/* T-race-TODO-366 / D-race-310 (Brian, Thu 22 Oct 2026, 21:26, of a club's home page: "coudl show scheduled events at any time if not
   information till later"): a scheduled regatta that offers nothing yet says "Information to follow." where its buttons would be. A
   plain line in the club's featured card, a muted word in a compact line, a muted line in the platform's card, which is then a box and
   no link (so it does not lift on hover). A compact line whose regatta offers Live and Course & info carries the two as small buttons
   side by side. Tokens only. */
.club-home-follow { margin: 0; font-size: 1.05rem; color: var(--rowops-ink-muted); }
.club-home-item .club-home-follow { flex: none; font-size: 0.9rem; padding-right: 0.4rem; }
.club-home-item-actions { flex: none; display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0.4rem; }
/* The review of 872d5133c (finding 7): on a 320 px phone the two small buttons took 186 px of the line's 264 and the name was left 55 px, broken
   mid-word ("Invente / d Head"), because the buttons and the words keep their size and the line could not wrap. It wraps now, and the name has a
   basis of 8rem, so a line that fits is as it was (a drawn regatta's "Open →" stays beside its name) and one that does not puts its buttons or
   its words on a row of their own, to the right, under a name that has the whole line. */
.club-home-item { flex-wrap: wrap; }
.club-home-item-text { flex: 1 1 8rem; }
.club-home-item .club-home-follow, .club-home-item-actions { margin-left: auto; }
.hp-card-follow { margin: 0.4rem 0 0; font-size: 0.95rem; color: var(--rowops-ink-muted); }
.hp-card-static:hover { transform: none; box-shadow: none; }
html.theme-sun .hp-card-static:hover, body.theme-sun .hp-card-static:hover { box-shadow: none; }

/* T-race-TODO-366 / D-race-310 (Brian, Thu 22 Oct 2026, 21:25, of the platform's home page: "another to not move when you click on ive
   rgatas."): the hero's "Browse regattas ↓" is the in-page link #hp-regattas, and the list sits on the same short screen, so there is
   nowhere to scroll to and the click looked dead. The list the link names is the :target, and it lights up: a ring of the page's accent
   that fades out over 2.4 s with a tint behind the cards (--rowops-accent-tint). Under prefers-reduced-motion nothing moves or fades: the
   ring stays, static, while the address names the list. Below 992 px the jump leaves the list clear of the sticky top bar (from 992 px
   html's scroll-padding-top already does, and the two would add up). CSS only, no script; the ring is the outline, which takes no room.
   POSITION: after the T-race-TODO-329 block, whose first max-width 991.98px media block PublicDesktopLayoutTest reads. */
.hp-regattas:target { outline: 3px solid transparent; outline-offset: 0.5rem; border-radius: var(--radius-md); animation: hp-regattas-land 2.4s ease-out 1; }
@keyframes hp-regattas-land {
    0%, 55% { outline-color: var(--rowops-accent); background-color: var(--rowops-accent-tint); }
    100%    { outline-color: transparent; background-color: transparent; }
}
@media (prefers-reduced-motion: reduce) { .hp-regattas:target { animation: none; outline-color: var(--rowops-accent); } }
@media (max-width: 991.98px) { .hp-regattas { scroll-margin-top: 4.5rem; } }
@media print { .hp-regattas:target { outline: 0; animation: none; } }
