/* ========================================
   FONT FACES
   ----------------------------------------
   Noto Sans (body) + JetBrains Mono (numbers), latin + latin-ext subsets.
   Variable axes mean one file covers weights 400→800 (Noto) / 400→600
   (JetBrains). Unicode-ranges let the browser skip the larger latin-ext
   file until an athlete name actually needs accented characters.
   ======================================== */
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('../fonts/noto-sans-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
        U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
        U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('../fonts/noto-sans-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
        U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
        U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
        U+A720-A7FF;
}
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 100 800;
    font-display: swap;
    src: url('../fonts/jetbrains-mono-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
        U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
        U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 100 800;
    font-display: swap;
    src: url('../fonts/jetbrains-mono-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
        U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
        U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
        U+A720-A7FF;
}

/* ========================================
   DESIGN TOKENS
   ----------------------------------------
   One source of truth. Change a token here → every page updates.
   Mobile values are the baseline; the @media block at the bottom of this
   section bumps the type scale on desktop in one place rather than per
   selector. Spacing/colour/radius/shadow tokens are viewport-invariant.
   ======================================== */
:root {
    /* ── Type scale ────────────────────────
       Modular-ish 1.18 ratio. Mobile values; desktop scales below. */
    --text-micro:   9px;    /* badge eyebrows, leg-num tags */
    --text-xs:     10px;    /* axis ticks, ap-rd, ap-of */
    --text-sm:     11px;    /* meta, captions */
    --text-base:   12px;    /* body in compact contexts (cards) */
    --text-md:     13px;    /* body, race-row titles */
    --text-lg:     15px;    /* section titles in cards */
    --text-xl:     18px;    /* page subhead, athlete name */
    --text-2xl:    22px;    /* stat val */
    --text-3xl:    28px;    /* big stat */
    --text-display: 36px;   /* hero number (h2h score) */

    /* Uppercase eyebrow used by every "section title" in the app —
       distinguishes structural headings from value labels. */
    --text-eyebrow:  10px;

    --line-tight: 1.25;
    --line-body:  1.5;

    /* ── Type family ───────────────────────
       Noto Sans for body, JetBrains Mono for any tabular number. */
    --font-family: 'Noto Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
    --font-family-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    --font-weight-normal: 400;
    --font-weight-medium: 500;
    --font-weight-semibold: 600;
    --font-weight-bold: 700;
    --font-weight-black: 800;

    /* Legacy aliases kept so old rules still work while we migrate. */

    /* ── Greys (ink + surface) ──────────────
       7-step ink ramp roughly matches Tailwind's slate but named by use. */
    --ink-1: #0f172a;   /* near-black, only for the strongest headings */
    --ink-2: #1e293b;   /* primary body / strong headings */
    --ink-3: #334155;   /* dense body */
    --ink-4: #475569;   /* secondary text, sub-labels */
    --ink-5: #64748b;   /* meta / muted */
    --ink-6: #94a3b8;   /* axis ticks, placeholders */
    --ink-7: #cbd5e1;   /* dividers, disabled */

    --bg-app:        #f4f6f8;  /* page background */
    --bg-surface:    #ffffff;  /* cards, modals, table cells */
    --bg-soft:       #fafbfc;  /* recessed surface, section bg inside cards */
    --bg-hover:      #f8fafc;  /* row hover */
    --bg-tint:       #f1f5f9;  /* zebra / mute tint */

    --line-soft:     var(--bg-tint);  /* hairline */
    --line-base:     #e2e8f0;          /* default border */
    --line-strong:   var(--ink-7);     /* emphasised border */
    /* Underline under a tappable athlete name. Derived from the name's OWN ink so
       it works on every surface and in dark mode, but held well back: a full-ink
       underline under every name in a results list striped the page. */
    --link-underline: color-mix(in srgb, currentColor 30%, transparent);
    /* Rim drawn around every flag image. Deliberately a mid grey, not a tinted
       black: it has to stay visible against a white flag edge on a light surface
       AND a black flag edge on a dark one. */
    --flag-ring:     #94a3b8;

    /* Back-compat aliases for existing rules */
    --surface:           var(--bg-surface);
    --surface-raised:    var(--bg-tint);

    /* ── Brand + semantic ──────────────────
       --primary-color and its variants are NOT set here. tokens.css loads last
       and is the single source for the brand accent; setting a value here would
       shadow-mismatch the live theme. (This note used to point at
       saas-theme.css, which was folded into this file, and at CT.bands for the
       discipline colours, which are now the --seg-* tokens.) */

    --success-color: #047857;
    --danger-color:  #dc2626;
    --warning-color: #f59e0b;

    /* Discipline colours live in tokens.css, which also carries the -ink
       variants and the dark values. Defined here once too, which is how the
       triad ended up with two sources. */

    /* Accent palettes for affordances */
    --amber-fg:     #b45309;
    --amber-strong: #9a3412;
    --amber-bg:     #fff7ed;
    --amber-border: #fdba74;

    --indigo-fg:     #4338ca;
    --indigo-bg:     #eef2ff;
    --indigo-border: #c7d2fe;

    --gold:   #FFD700;
    --silver: #C0C0C0;
    --bronze: #CD7F32;

    /* ── Spacing (px) ──────────────────────
       Geometric-ish ladder. Existing rem-based --spacing-* kept as aliases. */
    --space-1:  2px;
    --space-2:  4px;
    --space-3:  6px;
    --space-4:  8px;
    --space-5: 10px;
    --space-6: 12px;
    --space-7: 14px;
    --space-8: 16px;
    --space-10: 20px;
    --space-12: 24px;
    --space-16: 32px;

    --spacing-lg: var(--space-12);

    /* ── Radius ────────────────────────────  */
    --radius-sm:   4px;
    --radius-md:   6px;
    --radius-lg:   8px;
    --radius-xl:  10px;
    --radius-pill: 999px;

    --border-radius: var(--radius-xl);
    --border-radius-lg: 16px;

    /* ── Chip primitive ──────────────────────
       One geometry for every toggle chip in the app (discipline readout,
       distance filter, country chip, preset chip, gap mode). Padding sized
       so the chip clears the 28px finger-friendly floor without becoming a
       button. A chip NARROWS a list; a group of mutually exclusive lenses is
       a switcher (below), not a row of chips. */
    --chip-py:     4px;
    --chip-px:    10px;
    --chip-radius: var(--radius-pill);
    --chip-text:  var(--text-sm);
    --chip-min-h: 28px;

    /* ── Tab primitive ───────────────────────
       Page-level tabs (race tabs, prediction view). Underline-marked, never
       filled — the fill is the switcher's, and keeping them distinct is what
       tells you whether a control changes the panel or the whole page. */
    --tab-py:      var(--space-3);
    --tab-px:      var(--space-5);
    --tab-gap:     var(--space-2);
    --tab-text:    var(--text-md);
    --tab-min-h:   36px;
    --tab-ink:     var(--ink-5);
    --tab-on-ink:  var(--ink-1);
    --tab-on-line: var(--primary-color);

    /* ── Switcher primitive ──────────────────
       One geometry for every "pick exactly one lens" control: relay Teams /
       Athletes + leg filter, summary and profile units, T100 gender, replay
       Course / Timeline, H2H metric + detail modes, Elite / U23, race-history
       division / distance / trend. These used to be four different looks
       (joined rings at two radii, loose pills, an iOS-style padded track)
       doing the same job — one primitive so a switcher is recognisable
       wherever it appears. Active is the same neutral dark fill as .chip and
       .ctrl-pill, which keeps --accent free for the watch-list selection. */
    --switch-py:      5px;
    --switch-px:      var(--space-5);
    --switch-radius:  var(--radius-md);
    --switch-text:    var(--text-sm);
    --switch-min-h:   32px;
    --switch-bg:      var(--bg-surface);
    --switch-ink:     var(--ink-4);
    --switch-on-bg:   var(--ink-1);
    --switch-on-ink:  var(--surface);

    /* ── Shadow ────────────────────────────  */
    --shadow-1: 0 1px 2px rgba(15, 23, 42, 0.06);
    --shadow-2: 0 2px 6px rgba(15, 23, 42, 0.08), 0 1px 2px rgba(15, 23, 42, 0.04);
    --shadow-3: 0 4px 12px rgba(15, 23, 42, 0.10), 0 2px 4px rgba(15, 23, 42, 0.06);
    --shadow-card: var(--shadow-1);

    /* ── Motion ────────────────────────────  */
    /* ── Breakpoints ─────────────────────────────────────────────────────
       Custom properties do not work inside a media query, so this comment is
       the source of truth and the numbers below are written out by hand. There
       were five accidental secondary widths (480 / 520 / 540 / 620 / 640); the
       ladder is four DELIBERATE steps now, each with a stated job:

         max-width  480px  phone — tighten, stack, drop to one column
         min-width  520px  chart step (chart-theme.css only): tick + label type
         max-width  640px  narrow — hide a column, swap long labels for short
         min-width  769px  THE primary boundary; 77 of the file's 91 queries

       Anything else is drift. Prefer 769px unless the rule is specifically
       about running out of horizontal room before then. */

    --transition-fast:   0.15s ease;
    --transition-normal: 0.25s ease;

    /* ── Layout ────────────────────────────  */
    --container-max-width: 1400px;
    --sidebar-width: 320px;
}

/* Desktop scale-up — bump the whole type ladder at one breakpoint. Spacing,
   colours, radii intentionally don't change here; if a specific layout
   needs to widen on desktop it does so in its own @media block. */
@media (min-width: 769px) {
    :root {
        --text-micro:   10px;
        --text-xs:      11px;
        --text-sm:      12px;
        --text-base:    13px;
        --text-md:      15px;
        --text-lg:      17px;
        --text-xl:      22px;
        --text-2xl:     28px;
        --text-3xl:     34px;
        --text-display: 44px;
        --text-eyebrow: 11px;
    }
}

/* Larger desktops get a final small bump. */
@media (min-width: 1200px) {
    :root {
        --text-lg:      18px;
        --text-xl:      24px;
        --text-2xl:     30px;
    }
}

/* ========================================
   RESET + BODY
   ======================================== */
* { margin: 0; padding: 0; box-sizing: border-box; }

html {
    /* Stop iOS Safari auto-inflating font sizes on some blocks (it made long
       race titles render huge on iPhone, but not in any desktop emulator). */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}
body {
    font-family: var(--font-family);
    font-size: var(--text-md);
    line-height: var(--line-body);
    color: var(--ink-1);
    background: var(--bg-app);
    min-height: 100vh;
    padding: var(--space-4);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

@media (max-width: 768px) {
    body { padding: var(--space-2); }
}

h1 { display: none; }

/* Any node that opts into tabular numbers also opts into the mono face —
   keeps tables (sub-splits, leaderboards, time deltas) aligned cleanly. */
[style*="tabular-nums"],
.podium-time, .podium-paces, .ms-sub, .ms-val, .seg-time, .ap-rtot, .podium-stagger,
.h2h-row-pos, .h2h-row-gap,
[class*="time-cell"], [class*="pace-cell"] {
    font-family: var(--font-family-mono);
}

/* Discipline label tag — colored typography that pairs with chart legends. */
.disc-tag {
    font-weight: var(--font-weight-bold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.92em;
}

/* ========================================
   LAYOUT
   ======================================== */
.container {
    max-width: var(--container-max-width);
    margin: 0 auto;
    background: var(--bg-surface);
    border-radius: var(--border-radius-lg);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
    padding: var(--space-6);
}
@media (max-width: 768px) {
    .container { padding: var(--space-3); border-radius: var(--radius-lg); }
}

/* Top-level content sections share one centred max-width so on wide
   monitors header, tabs, and chart content stay aligned in a readable
   column instead of stretching edge-to-edge. */
#resultsSection,
#athleteProfileSection,
#raceHistorySection,
#predictionSection,
#classFormSection {
    max-width: var(--container-max-width);
    margin-left: auto;
    margin-right: auto;
}

.page-content { padding: var(--space-4); background: var(--bg-surface); border-radius: var(--border-radius); }
@media (max-width: 768px) { .page-content { padding: var(--space-3); } }

/* ========================================
   LOADING / ERROR
   ======================================== */
.loading { display: none; text-align: center; padding: var(--spacing-lg); }
.loading.active { display: block; }
.spinner {
    border: 4px solid var(--bg-tint);
    border-top: 4px solid var(--primary-color);
    border-radius: 50%; width: 40px; height: 40px;
    animation: spin 1s linear infinite; margin: 0 auto;
}
@keyframes spin { 0% { transform: rotate(0); } 100% { transform: rotate(360deg); } }

.error-message {
    color: var(--danger-color);
    margin-top: var(--space-5);
    padding: var(--space-5);
    background: var(--danger-bg);
    border-radius: var(--border-radius);
    display: none;
    font-size: var(--text-md);
}

/* ========================================
   SHARED PRIMITIVES
   ----------------------------------------
   Reused across athlete-profile, race-history, head-to-head, summary.
   Namespace classes below (.ap-*, .rh-*, .h2h-*) inherit these via
   token references so they all look like the same component family.
   ======================================== */

/* ── Athlete name → profile: the app-wide name cue ──
   One rule for every name that opens a profile, wherever it renders (see
   js/ui/athlete-link.js). A thin SOLID underline reads as a link; a dotted one
   read like an <abbr> tooltip cue. The name keeps its surrounding ink, weight
   and size rather than turning into a coloured link, so a results table still
   reads as a table.

   The counterpart rule is the absence of this class: a name that is a selection
   control (rank-chart labels, splits-table rows) carries no underline, because
   tapping it watches the athlete rather than navigating.

   It is an INLINE span, never a button: a button is an atomic inline box, so any
   cell that truncates its own text (the standings name column, the spider name
   cell) would swallow the whole name into a bare "…". See js/ui/athlete-link.js. */
.athlete-link {
    color: inherit; cursor: pointer;
    text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px;
    text-decoration-color: currentColor;                /* fallback */
    text-decoration-color: var(--link-underline);
}
.athlete-link:hover { color: var(--primary-color); }
.athlete-link:focus-visible { outline: 2px solid var(--primary-color); outline-offset: 2px; border-radius: 2px; }
/* SVG chart labels opt into the same cue directly. */
text.athlete-link { cursor: pointer; text-decoration: underline; text-decoration-color: var(--link-underline); }

/* ── Section card: recessed panel grouping related content ── */
.ap-section,
.rh-section {
    margin-bottom: var(--space-7);
    padding: var(--space-5);
    background: var(--bg-soft);
    border-radius: var(--radius-lg);
    border: 1px solid var(--line-soft);
}
@media (min-width: 769px) {
    .ap-section, .rh-section { padding: var(--space-8); }
}

/* ── Section title (eyebrow + optional sub) ── */
.ap-section-title,
.rh-section-title,
.summary-section-title,
.settings-section-title,
.bs-section-title {
    font-size: var(--text-eyebrow);
    font-weight: var(--font-weight-bold);
    color: var(--ink-4);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    margin-bottom: var(--space-4);
    display: flex; align-items: center; gap: var(--space-3);
}
@media (min-width: 769px) {
    .ap-section-title, .rh-section-title,
    .summary-section-title, .settings-section-title { font-size: var(--text-sm); }
}
.ap-section-sub,
.rh-section-sub {
    font-size: var(--text-xs);
    font-weight: var(--font-weight-medium);
    color: var(--ink-6);
    text-transform: none;
    letter-spacing: 0;
}

/* ── Chart host inside a section card ── */
.ap-chart-host,
.rh-chart-host,
.xo-chart-host {
    background: var(--bg-surface);
    border-radius: var(--radius-md);
    padding: var(--space-2);
    border: 1px solid var(--line-soft);
    overflow: hidden;
}
.ap-chart-host svg,
.rh-chart-host svg,
.xo-chart-host svg { display: block; max-width: 100%; }

/* ── Empty state ── */
.ap-empty,
.rh-empty,
.spider-empty {
    padding: var(--space-10);
    text-align: center;
    color: var(--ink-6);
    font-size: var(--text-base);
}
@media (min-width: 769px) {
    .ap-empty, .rh-empty, .spider-empty { font-size: var(--text-md); }
}

/* ── Back control — ONE canonical style, used app-wide (.btn-back) ── */
.btn-back {
    background: transparent;
    border: 1px solid var(--line-base);
    border-radius: var(--radius-md);
    padding: var(--space-3) var(--space-5);
    font-size: var(--text-base);
    color: var(--ink-4);
    cursor: pointer;
    margin-bottom: var(--space-5);
    transition: background var(--transition-fast), border-color var(--transition-fast);
}
.btn-back:hover {
    background: var(--bg-tint);
    border-color: var(--ink-6);
}

/* ── Pill: outlined chip used for tags, filters, status ── */
.ap-pos, .cond-chip, .ab-badge,
.rh-leg, .ap-legend-chip, .rh-rec-year, .ap-readout-count {
    font-variant-numeric: tabular-nums;
}

/* ========================================
   APP HEADER
   ======================================== */
.app-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
    /* Brand + the ⓘ/☰ control pair anchor the top line; the data-source (browse
       buttons + breadcrumb) wraps within its own column between them rather than
       pushing the pair onto a second row. The pair lives in .app-menu-wrap and
       sits flush in the top-right corner, so no right pad is reserved. */
    padding: var(--space-3) 0;
    flex-wrap: nowrap;
    margin-bottom: var(--space-2);
    max-width: var(--container-max-width);
    margin-left: auto;
    margin-right: auto;
    width: 100%;
}
.app-brand {
    font-size: var(--text-lg);
    font-weight: var(--font-weight-bold);
    color: var(--ink-2);
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    gap: 0.38em;
}
.app-brand-mark { height: 0.92em; width: auto; flex: 0 0 auto; }
@media (min-width: 769px) { .app-brand { font-size: var(--text-xl); } }
@media (min-width: 1200px) { .app-brand { font-size: var(--text-2xl); } }

.app-data-source {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
    font-size: var(--text-sm);
}
.app-header.has-data .app-data-source { font-size: var(--text-xs); }
@media (min-width: 769px) {
    .app-data-source { font-size: var(--text-md); }
    .app-header.has-data .app-data-source { font-size: var(--text-sm); }
}

/* ── App menu (☰): always-accessible home for app-level prefs (theme, units)
   and the CSV utility. Race-specific config stays in the Setup tab. ── */
/* The ⓘ help + ☰ menu form one aligned control pair, flush top-right. The ☰
   stays put whether or not the ⓘ is showing (it's per-tab), so the corner
   anchor never shifts. */
.app-menu-wrap { position: relative; margin-left: auto; flex: 0 0 auto; margin-top: var(--space-1);
    display: inline-flex; align-items: center; gap: var(--space-2); }
.app-menu-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px; padding: 0;
    border: 1px solid var(--line-strong); border-radius: var(--radius-md);
    background: var(--bg-surface); color: var(--ink-3); cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}
.app-menu-btn:hover { background: var(--bg-hover); border-color: var(--ink-6); color: var(--ink-1); }
.app-menu-btn[aria-expanded="true"] { background: var(--bg-hover); border-color: var(--primary-color); color: var(--primary-color); }
.app-menu {
    position: absolute; top: calc(100% + var(--space-2)); right: 0; z-index: var(--z-menu);
    min-width: 244px; max-width: min(300px, calc(100vw - 24px));
    background: var(--surface-raised); border: 1px solid var(--line-base);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-3);
    padding: var(--space-5); display: flex; flex-direction: column; gap: var(--space-5);
}
.app-menu[hidden] { display: none; }
.app-menu-sec { display: flex; flex-direction: column; gap: var(--space-3); }
.app-menu-sec + .app-menu-sec { border-top: 1px solid var(--line-soft); padding-top: var(--space-4); }
.app-menu-title {
    font-size: var(--text-xs); font-weight: var(--font-weight-bold);
    text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-5);
}
.app-menu-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); }
.app-menu-label { font-size: var(--text-sm); color: var(--ink-3); font-weight: var(--font-weight-medium); }
.app-menu-item {
    display: block; padding: var(--space-3) var(--space-4);
    border: 1px solid var(--line-base); border-radius: var(--radius-md);
    background: var(--bg-surface); color: var(--ink-2);
    font-size: var(--text-sm); font-weight: var(--font-weight-semibold);
    cursor: pointer; text-align: center;
}
.app-menu-item:hover { background: var(--bg-hover); border-color: var(--ink-6); }
.app-menu-sub {
    background: none; border: none; padding: 0; cursor: pointer;
    font-size: var(--text-xs); color: var(--primary-color); text-align: left;
    font-weight: var(--font-weight-medium);
}
.app-menu-sub:hover { text-decoration: underline; }

/* Quiet surface button: the bg/border/color/weight live in the shared
   .header-btn/.header-file-btn group below; the accent-filled modal-footer
   variant lives at .src-modal-foot .header-btn. Base = geometry only. */
.header-btn {
    padding: var(--space-2) var(--space-5);
    border-radius: var(--radius-md);
    font-size: var(--text-sm);
    cursor: pointer;
    transition: background var(--transition-fast);
}
.header-btn:disabled { opacity: 0.4; cursor: default; }
@media (min-width: 769px) { .header-btn { font-size: var(--text-md); padding: var(--space-3) var(--space-7); } }

.header-sep { color: var(--ink-6); font-size: var(--text-xs); }
@media (min-width: 769px) { .header-sep { font-size: var(--text-md); } }
.header-file-btn.has-file {
    background: var(--success-color);
    color: white;
    border-color: var(--success-color);
}

.header-file-name {
    font-size: var(--text-xs);
    color: var(--ink-5);
}
@media (min-width: 769px) { .header-file-name { font-size: var(--text-md); } }

/* When the loaded race has a history bucket, the filename becomes the entry
   point: link-styled (dotted underline), accent colour, pointer cursor. */
.header-file-name.clickable {
    cursor: pointer;
    color: var(--accent-ink);
    font-weight: var(--font-weight-semibold);
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-md);
    text-decoration: underline dotted;
    text-underline-offset: 3px;
    transition: background var(--transition-fast), color var(--transition-fast);
}
.header-file-name.clickable::after { content: ' ›'; font-weight: 700; text-decoration: none; }
.header-file-name.clickable:hover,
.header-file-name.clickable:focus {
    background: var(--accent-subtle);
    color: var(--accent-hover);
    outline: none;
}
.header-file-name.clickable:focus-visible { box-shadow: 0 0 0 2px var(--focus-ring); }

/* ── Race-tier binding ────────────────────────────────────────────────────
   One class per tier, binding an element to that tier's tokens. This exists
   because a resolved colour copied into a JS record freezes at whatever theme
   it was built under: the profile's tier chips kept their light-mode ink in
   dark, and re-rendering could not fix it because the record still held the old
   hex. main.js says it plainly next to setupThemeRedraw — "colours that CAN
   live in CSS should, and then they need none of this".

     --tier-c   the hue, for a tint or a swatch
     --tier-i   the ink, for text and dots on the app surface
   Both follow the theme with no repaint at all. */
.tier-olympics { --tier-c: var(--tier-olympics); --tier-i: var(--tier-olympics-ink); }
.tier-world-champs { --tier-c: var(--tier-world-champs); --tier-i: var(--tier-world-champs-ink); }
.tier-wtcs-final { --tier-c: var(--tier-wtcs-final); --tier-i: var(--tier-wtcs-final-ink); }
.tier-wtcs { --tier-c: var(--tier-wtcs); --tier-i: var(--tier-wtcs-ink); }
.tier-t100 { --tier-c: var(--tier-t100); --tier-i: var(--tier-t100-ink); }
.tier-world-cup { --tier-c: var(--tier-world-cup); --tier-i: var(--tier-world-cup-ink); }
.tier-olympic-qual { --tier-c: var(--tier-olympic-qual); --tier-i: var(--tier-olympic-qual-ink); }
.tier-conti-champs { --tier-c: var(--tier-conti-champs); --tier-i: var(--tier-conti-champs-ink); }
.tier-conti-cup { --tier-c: var(--tier-conti-cup); --tier-i: var(--tier-conti-cup-ink); }
.tier-multisport { --tier-c: var(--tier-multisport); --tier-i: var(--tier-multisport-ink); }
.tier-regional-champs { --tier-c: var(--tier-regional-champs); --tier-i: var(--tier-regional-champs-ink); }
.tier-games { --tier-c: var(--tier-games); --tier-i: var(--tier-games-ink); }
.tier-junior-u23 { --tier-c: var(--tier-junior-u23); --tier-i: var(--tier-junior-u23-ink); }
.tier-para-series { --tier-c: var(--tier-para-series); --tier-i: var(--tier-para-series-ink); }
.tier-para-cup { --tier-c: var(--tier-para-cup); --tier-i: var(--tier-para-cup-ink); }
.tier-para-conti-cup { --tier-c: var(--tier-para-conti-cup); --tier-i: var(--tier-para-conti-cup-ink); }
.tier-ncaa-nationals { --tier-c: var(--tier-ncaa-nationals); --tier-i: var(--tier-ncaa-nationals-ink); }
.tier-ncaa-regionals { --tier-c: var(--tier-ncaa-regionals); --tier-i: var(--tier-ncaa-regionals-ink); }
.tier-ncaa-cup { --tier-c: var(--tier-ncaa-cup); --tier-i: var(--tier-ncaa-cup-ink); }
.tier-other { --tier-c: var(--tier-other); --tier-i: var(--tier-other-ink); }

/* Marks that carry a tier: the dot in the career-arc legend, and the dots and
   rung labels drawn into SVG. */
.tier-dot { background: var(--tier-i); }
.tier-fill { fill: var(--tier-i); }
.tier-stroke { stroke: var(--tier-i); }

/* ── Icon button: ONE box for every square/circular glyph control ──────────
   These were three independent definitions — a 22px ghost circle, a 28px
   bordered square and a 30px bordered square — so the same affordance read
   differently depending on which screen you were on. The geometry lives here
   once; an instance sets only what makes it itself, via the two custom
   properties. Bare glyph buttons (no box at all) are the group below. */
.btn--icon, .share-btn, .h2h-detail-close {
    display: inline-flex; align-items: center; justify-content: center;
    width: var(--icon-btn-size); height: var(--icon-btn-size);
    padding: 0; flex-shrink: 0;
    border: 1px solid transparent; border-radius: var(--icon-btn-radius);
    background: transparent; color: var(--ink-6);
    font-size: var(--text-sm); line-height: 1; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}
.btn--icon { --icon-btn-size: 22px; --icon-btn-radius: 50%; }
.btn--icon:hover, .h2h-detail-close:hover { background: var(--bg-hover); color: var(--ink-3); }

/* ── Bare glyph button: a control with no box, only the mark ──────────────
   Three of these existed independently (modal dismiss, share-sheet dismiss,
   remove-from-comparison). Size, placement and hover intent differ; nothing
   else does. */
.src-modal-close, .sc-close, .spider-remove-btn {
    background: none; border: 0; padding: 0; line-height: 1; cursor: pointer;
    color: var(--ink-5);
    transition: background var(--transition-fast), color var(--transition-fast);
}
/* ── CSV-format help (ⓘ next to "Upload CSV") ── */
.csv-help-btn.active {
    background: var(--indigo-bg); border-color: var(--primary-color); color: var(--primary-color);
}
.csv-help-tip {
    position: fixed; z-index: var(--z-tooltip);
    background: var(--bg-surface); border: 1px solid var(--line-strong);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-3);
    padding: var(--space-6) var(--space-7);
    max-width: 380px; font-size: var(--text-base); color: var(--ink-3);
}
.csv-help-title { font-weight: var(--font-weight-bold); font-size: var(--text-md); color: var(--ink-2); margin-bottom: var(--space-3); }
.csv-help-body p { margin: var(--space-3) 0; }
.csv-help-body code {
    font-family: var(--font-family-mono);
    font-size: var(--text-sm); background: var(--bg-tint);
    padding: 1px var(--space-2); border-radius: var(--radius-sm);
}
.csv-help-body ul { margin: var(--space-3) 0 0 18px; padding: 0; }
.csv-help-body li { margin: var(--space-2) 0; }
.csv-help-note { color: var(--ink-5); }

/* ── App-level info button: the ⓘ half of the top-right control pair, sat
   just left of the ☰ menu inside .app-menu-wrap (not fixed) ── */
.app-info-btn {
    width: 34px; height: 34px; font-size: var(--text-md);
}
.app-info-btn:hover { border-color: var(--ink-6); }
.app-info-btn.active { background: var(--primary-color); color: var(--on-accent); border-color: var(--primary-color); }
.app-info-tip {
    position: fixed; z-index: var(--z-tooltip);
    background: var(--surface-inverse); color: var(--on-surface-inverse);
    padding: var(--space-6) var(--space-7); border-radius: var(--radius-lg);
    font-size: var(--text-base); line-height: 1.6;
    max-width: 320px; min-width: 220px;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
    max-height: 80vh; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
}
.app-info-tip strong { color: var(--warning-color); display: block; margin-bottom: var(--space-2); }
.app-info-tip ul { margin: var(--space-2) 0 0; padding-left: var(--space-8); }
.app-info-tip li { margin-bottom: var(--space-2); }
@media (min-width: 769px) {
    .app-info-tip { font-size: var(--text-md); max-width: 380px; }
}

/* ========================================
   TAB NAVIGATION
   ========================================
   `.tab-btn` is the PAGE-LEVEL tab: it switches the whole panel below it and
   there is at most one bar of them on screen (the race tabs, the prediction
   view's `.pvx-tabs`). Underline-marked, no fill — deliberately a different
   shape from `.switcher`, which picks a lens WITHIN one panel. Don't reach for
   `.tab-btn` for an in-panel toggle; that's what `.switcher` is for. */
.tab-navigation {
    display: flex;
    gap: var(--tab-gap);
    border-bottom: 1px solid var(--line-base);
    padding: 0;
    margin-bottom: var(--space-3);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}
.tab-navigation::-webkit-scrollbar { display: none; }
.tab-btn {
    flex-shrink: 0;
    padding: var(--tab-py) var(--tab-px);
    background: transparent;
    color: var(--tab-ink);
    border: none;
    border-radius: 0;
    border-bottom: 2px solid transparent;
    cursor: pointer;
    font-family: inherit;
    font-size: var(--tab-text);
    font-weight: var(--font-weight-medium);
    transition: color var(--transition-fast), border-color var(--transition-fast);
    white-space: nowrap;
    -webkit-tap-highlight-color: transparent;
    min-height: var(--tab-min-h);
}
.tab-btn:hover { background: transparent; color: var(--ink-2); }
.tab-btn.active {
    background: transparent;
    color: var(--tab-on-ink);
    font-weight: var(--font-weight-semibold);
    border-bottom-color: var(--tab-on-line);
}
/* Tabs gated by state (e.g. Head-to-Head needs exactly 2 athletes selected). */
.tab-btn.hidden { display: none; }
/* A gated tab appears MID-SESSION, in a bar that scrolls on a phone — so it can
   arrive off-screen, or on-screen and simply unnoticed. Three indigo pulses say
   "this is new" without moving anything or stealing the tap. Applied by
   _revealTab in js/main.js and removed when the animation ends, so it re-fires
   every time the tab comes back. */
.tab-btn--flash {
    animation: tab-flash 1.2s ease-in-out 1;
    border-radius: var(--radius-sm);
}
/* The whole tab fills, rather than just the label changing colour — a filled
   block is what catches the eye in peripheral vision on a phone. The label flips
   to --on-accent so it stays readable at the peak. ONE pulse: it announces the
   tab without turning into a blinking thing beside the content. */
@keyframes tab-flash {
    0%, 100% { background: transparent; color: var(--tab-ink); }
    22%, 55% { background: var(--accent); color: var(--on-accent); }
}
@media (prefers-reduced-motion: reduce) {
    /* Still say it, just don't blink: hold the filled state. */
    .tab-btn--flash { animation: none; background: var(--accent); color: var(--on-accent); }
}
@media (min-width: 769px) {
    .tab-btn { font-size: var(--text-lg); padding: var(--space-5) var(--space-10); min-height: 42px; }
}
@media (max-width: 480px) {
    /* Tighten so the 7-tab bar fits a 390px viewport without crowding the edge;
       the 8th (Head-to-Head) tab still scrolls, with trailing room to hint it. */
    .tab-navigation { gap: 1px; padding-right: var(--space-3); scroll-padding-right: var(--space-3); }
    .tab-btn { padding: var(--space-2); font-size: var(--text-sm); min-height: 44px; }
}

/* ========================================
   CHART SECTIONS
   ======================================== */
.chart-section { display: none; }
.chart-section.active { display: block; }
.chart-container {
    margin: var(--space-2) 0; padding: var(--space-3);
    background: var(--bg-tint); border-radius: var(--border-radius);
    display: none; width: 100%; overflow: hidden;
}
.chart-container.active { display: block; }
.chart-container svg { display: block; margin: 0 auto; max-width: 100%; height: auto; }
.chart-title {
    font-size: var(--text-md);
    color: var(--ink-1);
    padding: var(--space-3) var(--space-5);
    text-align: center;
    font-weight: var(--font-weight-semibold);
    background: var(--bg-surface);
    border-bottom: 1px solid var(--line-base);
}
@media (min-width: 769px) { .chart-title { font-size: var(--text-xl); padding: var(--space-4) var(--space-6); } }
@media (min-width: 1200px) { .chart-title { font-size: var(--text-2xl); } }
@media (max-width: 768px) {
    .chart-title { display: none; }
    .chart-container { padding: var(--space-1); margin: 0; }
}

/* Rank chart owns its scroll container — let it overflow its parent. */
#rankChart svg { max-width: none !important; }
#rankChart .scroll-container { -webkit-overflow-scrolling: touch; }
#rankChart foreignObject div { font-family: var(--font-family); }

/* SVG chart primitives (.axis/.grid/.athlete-*/.radar-*/spotlight) live in
   chart-theme.css — the single source. Don't re-add them here. */
.sel-dot { transition: background var(--transition-fast); }

/* ========================================
   TOOLTIP
   ======================================== */
.tooltip {
    position: absolute; text-align: left;
    padding: var(--space-5); font-size: var(--text-xs);
    background: var(--scrim-tooltip); color: var(--on-surface-inverse);
    border-radius: var(--radius-md);
    pointer-events: none; opacity: 0; transition: opacity var(--transition-normal);
    z-index: var(--z-tooltip); max-width: 250px;
}
@media (min-width: 769px) {
    .tooltip { font-size: var(--text-md); padding: var(--space-6) var(--space-7); max-width: 320px; }
}
@media (max-width: 768px) {
    .tooltip { position: fixed !important; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); border: 1px solid rgba(255, 255, 255, 0.15); }
}

/* ========================================
   GENERIC BUTTON
   ======================================== */
.btn {
    padding: var(--space-3) var(--space-5);
    background: var(--primary-color);
    color: white;
    border: none;
    border-radius: var(--border-radius);
    cursor: pointer;
    font-size: var(--text-md);
    font-weight: var(--font-weight-medium);
    transition: all var(--transition-normal);
}
.btn:hover { background: var(--primary-dark); }
.btn.toggle-all { background: var(--success-color); }
/* Start-list → prediction CTA: shared .btn appearance, full-width block. */
.ap-sl-pred-cta { display: block; width: 100%; margin-top: var(--space-8); font-weight: var(--font-weight-bold); }

/* ========================================
   INLINE FILTERS / CHIP STRIP / SEARCH
   ======================================== */
.inline-filters {
    display: flex; flex-direction: column; gap: var(--space-2);
    margin-bottom: var(--space-3); padding: var(--space-2) var(--space-3);
    background: var(--bg-soft); border-radius: var(--radius-md);
}
@media (min-width: 769px) {
    .inline-filters { gap: var(--space-3); padding: var(--space-3) var(--space-4); }
}

.filter-chip-row {
    display: flex; gap: var(--space-2); align-items: center;
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
}
.filter-chip-row::-webkit-scrollbar { display: none; }
/* The filter bar is a flex COLUMN, so a control dropped straight into it would
   stretch to the full bar width. Controls size to their content instead — this
   is the bar's job, not an inline `alignSelf` at each call site. */
.inline-filters > .switcher,
.inline-filters > .ctrl-pill { align-self: flex-start; }

/* ── Switcher: the shared segmented control ──────────────────────────────────
   `.switcher` wraps 2+ `.switcher-btn`s and marks the current one `.on`. Reach
   for it whenever a control picks ONE of a small set of lenses; use `.chip` for
   filters that narrow a list and `.tab-btn` for page-level tabs. One shared
   border ring, so the per-button radius collapses inside the group.

   Variants:
     .switcher--scroll  the group may outgrow a phone (5+ options) and scrolls.
     .switcher--soft    quieter active fill, for a switcher sitting inside an
                        already-dark or already-tinted surface.
     .switcher--fill    segments share the width evenly; for a switcher that
                        owns its own full-width row. */
.switcher {
    display: inline-flex; flex: none; align-items: stretch;
    max-width: 100%;
    border: 1px solid var(--line-strong); border-radius: var(--switch-radius);
    overflow: hidden; min-height: var(--switch-min-h);
    background: var(--switch-bg);
}
.switcher-btn {
    /* Segments size to their own label. Growing them to fill the row was tried
       and reads as a heavy full-width bar next to the rest of the controls. */
    flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
    padding: var(--switch-py) var(--switch-px);
    border: 0; background: transparent; color: var(--switch-ink);
    font-family: inherit; font-size: var(--switch-text);
    font-weight: var(--font-weight-semibold);
    min-height: var(--switch-min-h); white-space: nowrap; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background var(--transition-fast), color var(--transition-fast);
}
.switcher-btn + .switcher-btn { border-left: 1px solid var(--line-base); }
.switcher-btn:hover:not(.on):not([disabled]) { background: var(--bg-tint); color: var(--ink-2); }
.switcher-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.switcher-btn.on { background: var(--switch-on-bg); color: var(--switch-on-ink); }
.switcher-btn[disabled] { opacity: 0.4; cursor: not-allowed; }
/* A count / unit that rides inside a segment (leg counts, race counts). */
.switcher-btn .switcher-n {
    font-size: var(--text-micro); font-weight: var(--font-weight-bold);
    opacity: 0.7; font-variant-numeric: tabular-nums;
}
.switcher-btn.on .switcher-n { opacity: 0.85; }
/* Every switcher's active segment is the same inverse fill (--switch-on-bg).
   Segments keyed to a chart series once took that series' colour instead, which
   put two active-states on one page (race history's header vs its sections) —
   the series colour lives on the line and its legend, not on the control. */

.switcher--scroll { overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.switcher--scroll::-webkit-scrollbar { display: none; }
.switcher--soft .switcher-btn.on { background: var(--ink-2); }
.switcher--fill .switcher-btn { flex: 1 1 0; }
/* Several switchers are hosts filled from JS (race-history division rotation,
   the H2H metric group) and are emptied when the race has nothing to switch
   between — an empty group must not leave a bare border ring behind. */
.switcher:empty { display: none; }

/* Filter/toggle chip — one geometry source (outline-quiet, dark-filled active). */
.chip {
    flex-shrink: 0;
    display: inline-flex; align-items: center; gap: var(--space-2);
    padding: var(--chip-py) var(--chip-px);
    border: 1px solid var(--line-base); border-radius: var(--chip-radius);
    background: var(--bg-surface); color: var(--ink-4);
    font-size: var(--chip-text); font-weight: 500;
    min-height: var(--chip-min-h); cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
/* The dark-fill ON state. Every pick-one/filter control shares it — a second
   copy of these four declarations lived down in the surfaces section and had
   to be kept in step by hand. */
.chip.active, .chip:active { transform: scale(0.95); }

/* ⚙ Options button — collapses Quick-picks + Countries/Teams behind a panel.
   `.ctrl-pill` is the SHARED chart-control pill: any chart toggle (Race median,
   Pacing impact, …) uses it so every control reads as one component. Do not
   hand-roll button styling — add `.ctrl-pill` (+ `.on` when it's a toggle).
   It carries a <select> too, for a pick-one with too many options to sit in a
   `.switcher` (a championship's dozen-plus age bands). */
.filter-options-btn, .ctrl-pill {
    flex-shrink: 0; display: inline-flex; align-items: center; gap: var(--space-2);
    padding: var(--space-2) var(--space-5);
    border: 1px solid var(--line-strong); border-radius: var(--radius-xl);
    background: var(--bg-surface); cursor: pointer;
    font-size: var(--text-sm); font-weight: var(--font-weight-semibold);
    color: var(--ink-3); -webkit-tap-highlight-color: transparent;
    transition: all var(--transition-fast);
}
.filter-options-btn .fo-icon { font-size: var(--text-md); line-height: 1; }
.filter-options-btn .fo-text { white-space: nowrap; }
.filter-options-btn .fo-summary {
    color: var(--primary-color); font-weight: var(--font-weight-bold);
    max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
select.ctrl-pill {
    /* The platform caret stays: it is the only thing that says "menu" rather
       than "button", and on a phone the native picker is the right control for
       a dozen-plus options. Only the box is restyled to match the pills. */
    padding-right: var(--space-3);
    max-width: 11rem;
}
.filter-options-btn:hover, .ctrl-pill:hover { border-color: var(--ink-6); background: var(--bg-tint); }
.filter-options-btn.active {
    background: var(--primary-color); color: white; border-color: var(--primary-color);
}
.filter-options-btn.active .fo-summary { color: var(--surface); }
.filter-options-btn.has-selection:not(.active) { border-color: var(--primary-color); }

.filter-options-panel {
    display: none; flex-direction: column; gap: var(--space-3);
    margin-top: var(--space-3); padding: var(--space-4) var(--space-5);
    background: var(--bg-surface); border: 1px solid var(--line-base);
    border-radius: var(--radius-lg);
}
.filter-options-panel.open { display: flex; }
.fop-section { display: flex; flex-direction: column; gap: var(--space-2); }
.fop-section-title {
    font-size: var(--text-eyebrow); text-transform: uppercase; letter-spacing: 0.5px;
    font-weight: var(--font-weight-bold); color: var(--ink-5);
}
.fop-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.fop-chips.country-chip-row { display: flex; max-height: 120px; overflow-y: auto; }

/* .country-chip geometry from .chip; keeps nowrap + transition + hover, and a
   bold + ✓ active (the dark fill itself comes from .chip.active). */
.country-chip { white-space: nowrap; transition: all var(--transition-fast); }
.country-chip:hover { border-color: var(--ink-6); background: var(--bg-tint); }
.country-chip.active { font-weight: var(--font-weight-bold); }
.country-chip.active::before { content: '✓ '; font-weight: 900; }
/* flag / team-logo inside a filter chip (replaces the abbreviation text) */
.country-chip img, .country-chip .flag-img {
    width: 22px; height: 15px; object-fit: contain; border-radius: 2px; display: block;
}

/* Inline search (lives inside the Options panel) */
.fop-search-wrap { position: relative; }
.inline-search-input {
    width: 100%; padding: var(--space-2) var(--space-4);
    border: 1px solid var(--line-strong); border-radius: var(--radius-md);
    font-size: var(--text-md); background: var(--bg-surface);
    -webkit-appearance: none; box-sizing: border-box;
}
.inline-search-input:focus { outline: none; border-color: var(--primary-color); }
.inline-search-results {
    position: absolute; top: 100%; left: 0; right: 0; z-index: var(--z-dropdown);
    background: var(--bg-surface); border: 1px solid var(--line-strong);
    border-radius: 0 0 var(--radius-md) var(--radius-md);
    box-shadow: var(--shadow-3); max-height: 180px; overflow-y: auto; display: none;
}
.inline-search-results.open { display: block; }
@media (min-width: 769px) {
    .inline-search-input { font-size: var(--text-lg); padding: var(--space-4) var(--space-6); }
}

.sr-row {
    display: flex; align-items: center; gap: var(--space-3);
    padding: var(--space-3) var(--space-4); cursor: pointer;
    border-bottom: 1px solid var(--bg-tint); font-size: var(--text-sm);
}
.sr-row:active { background: var(--accent-subtle); }
.sr-rank { font-weight: var(--font-weight-bold); color: var(--primary-color); min-width: 22px; }
.sr-name { flex: 1; }
.sr-toggle { font-weight: var(--font-weight-bold); font-size: var(--text-md); }
.sr-toggle.on { color: var(--success-color); }
.sr-toggle.off { color: var(--ink-7); }
.sr-no-results { padding: var(--space-5); color: var(--ink-6); text-align: center; font-size: var(--text-sm); }
@media (min-width: 769px) {
    .sr-row { font-size: var(--text-md); padding: var(--space-5) var(--space-7); }
    .sr-rank { font-size: var(--text-md); min-width: 30px; }
}
.chart-controls-row .label { font-weight: var(--font-weight-semibold); color: var(--ink-4); white-space: nowrap; }

/* Mobile collapsible controls (dev chart header reset button) */
.dev-header-row, .rank-header-row { position: relative; }
.dev-header-row .chart-header,
.rank-header-row .chart-header { display: block; }
.chart-reset {
    display: none;                     /* hidden until a segment is zoomed */
    position: absolute; left: 0; top: 4px;
    height: 26px; min-width: 30px; padding: 0 var(--space-2);
    background: var(--bg-hover); border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    font-size: var(--text-xs); font-weight: var(--font-weight-bold);
    color: var(--ink-4); cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    white-space: nowrap; line-height: 1;
    transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
    z-index: 1;
}
.chart-reset.show {
    display: block; background: var(--ink-2); color: var(--bg-app); border-color: var(--ink-2);
}
.chart-reset.show:hover { background: var(--ink-1); border-color: var(--ink-1); }
@media (min-width: 769px) {
    .chart-reset { height: 30px; padding: 0 var(--space-5); font-size: var(--text-md); min-width: 40px; }
}
.chart-controls-panel.open { display: block; }

/* ========================================
   CHIP STRIP — global watched-athletes bar
   ======================================== */
.chip-strip {
    display: flex; align-items: center;
    gap: var(--space-2); padding: var(--space-2) var(--space-3);
    background: linear-gradient(135deg, var(--accent-subtle), var(--surface-tint));
    border: 1px solid var(--line-base); border-radius: var(--radius-md);
    margin-bottom: var(--space-2);
    min-height: 30px;
}
@media (min-width: 769px) {
    .chip-strip { padding: var(--space-3) var(--space-5); min-height: 36px; }
}
.cs-label {
    font-size: var(--text-micro);
    font-weight: var(--font-weight-bold); color: var(--ink-6);
    white-space: nowrap; text-transform: uppercase; letter-spacing: 0.5px;
    flex-shrink: 0;
}
@media (min-width: 769px) { .cs-label { font-size: var(--text-sm); } }
.cs-scroll {
    display: flex; gap: var(--space-2); overflow-x: auto; flex: 1;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.cs-scroll::-webkit-scrollbar { display: none; }
.cs-chip {
    display: inline-flex; align-items: center; gap: var(--space-2);
    padding: var(--space-1) var(--space-3) var(--space-1) var(--space-2);
    border: 1.5px solid var(--chip-color, var(--primary-color));
    border-radius: 12px;
    background: var(--bg-surface);
    font-size: var(--text-xs); font-weight: var(--font-weight-semibold);
    color: var(--chip-color, var(--ink-2));
    cursor: pointer; white-space: nowrap; flex-shrink: 0;
    -webkit-tap-highlight-color: transparent;
    transition: background var(--transition-fast);
    line-height: 1.4;
}
.cs-chip:hover { background: var(--bg-hover); }
/* The name is the tap target for the athlete's profile — see .athlete-link, the
   app-wide rule. Colour identity is already carried by the name + pill here, so
   the chip needs no separate dot/glyph. */
.cs-name {
    text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px;
    text-decoration-color: currentColor;                /* fallback */
    text-decoration-color: var(--link-underline);
}
.cs-x {
    font-size: var(--text-md); font-weight: var(--font-weight-normal);
    margin-left: var(--space-3); color: var(--ink-6); cursor: pointer; line-height: 1;
}
.cs-x:hover { color: var(--danger); }
/* Share sits before the flag (leading the chip) so its hit target is clear of the
   trailing × — margin-right spaces it off the flag rather than off the name. */
.cs-share {
    display: inline-flex; align-items: center; margin-right: var(--space-1);
    color: var(--ink-5); cursor: pointer; line-height: 1;
}
.cs-share:hover { color: var(--primary-color); }
.cs-clear {
    flex-shrink: 0; width: 22px; height: 22px; border-radius: 50%;
    border: 1px solid var(--line-base); background: var(--bg-surface);
    color: var(--ink-6); font-size: var(--text-xs); cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    -webkit-tap-highlight-color: transparent;
}
.cs-clear:hover { background: var(--danger-bg); color: var(--danger); border-color: var(--danger); }
@media (min-width: 769px) {
    .cs-chip { font-size: var(--text-md); padding: var(--space-2) var(--space-5) var(--space-2) var(--space-3); }
    .cs-x { font-size: var(--text-lg); }
    .cs-clear { width: 28px; height: 28px; font-size: var(--text-md); }
}

/* ========================================
   MODALS — source picker, dialog, athlete browser
   ======================================== */
.src-overlay {
    position: fixed; inset: 0; background: var(--scrim); z-index: var(--z-modal);
    display: flex; align-items: center; justify-content: center; padding: var(--space-8);
}
.src-modal {
    background: var(--bg-surface); border-radius: var(--radius-lg);
    width: min(420px, 100%); max-height: 90vh;
    display: flex; flex-direction: column; overflow: hidden;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
}
.src-modal-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: var(--space-5) var(--space-7);
    border-bottom: 1px solid var(--line-base);
    background: var(--bg-hover);
}
.src-modal-title { font-weight: var(--font-weight-bold); font-size: var(--text-md); color: var(--ink-2); }
/* Clickable variant — the start-list modal uses this on its title so the
   user can jump from "who's racing" to "how has this race evolved". */
.src-modal-title-link {
    background: none; border: 0; padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-md);
    cursor: pointer; color: var(--amber-fg);
    font-weight: var(--font-weight-bold); font-size: var(--text-md);
    font-family: inherit;
    transition: background var(--transition-fast), color var(--transition-fast);
}
.src-modal-title-link:hover,
.src-modal-title-link:focus-visible {
    background: var(--amber-bg); color: var(--amber-strong); outline: none;
}
.src-modal-sub { font-size: var(--text-sm); color: var(--ink-5); margin-left: var(--space-2); }
.src-modal-close { font-size: var(--text-xl); padding: 0 var(--space-2); }
.src-modal-close:hover { color: var(--ink-2); }
.src-modal-body {
    padding: var(--space-7);
    display: flex; flex-direction: column; gap: var(--space-5);
    overflow-y: auto;
}
.src-field { display: flex; flex-direction: column; gap: var(--space-2); }
.src-field > span { font-size: var(--text-sm); font-weight: var(--font-weight-semibold); color: var(--ink-4); }
.src-field select {
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--line-strong); border-radius: var(--radius-md);
    background: var(--bg-surface); font-size: var(--text-md);
}
.src-field select:disabled { background: var(--bg-tint); color: var(--ink-6); }
.src-status { font-size: var(--text-sm); color: var(--ink-5); min-height: 14px; }
.src-modal-foot {
    padding: var(--space-5) var(--space-7); border-top: 1px solid var(--line-base);
    background: var(--bg-hover);
    display: flex; justify-content: flex-end;
}

/* Generic dialog (used by chart configuration etc.) */
.dialog-overlay { position: fixed; inset: 0; background: var(--scrim-strong); z-index: var(--z-modal); display: flex; align-items: center; justify-content: center; }

.summary-podium { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-4); }
.podium-card {
    display: flex; align-items: flex-start; gap: var(--space-4);
    padding: var(--space-4) var(--space-6);
    background: var(--bg-surface); border-radius: var(--radius-md);
    border-left: 3px solid var(--line-strong);
    font-size: var(--text-base); line-height: 1.4;
}
.podium-1 { border-color: var(--gold); background: linear-gradient(to right, var(--warning-bg), var(--surface)); }
.podium-2 { border-color: var(--silver); }
.podium-3 { border-color: var(--bronze); }
.podium-medal { font-size: var(--text-md); margin-top: 1px; }
.podium-body { flex: 1; display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.podium-row { display: flex; align-items: baseline; gap: var(--space-3); }
.podium-name { flex: 1; font-weight: var(--font-weight-semibold); }
.podium-time { font-weight: var(--font-weight-bold); color: var(--primary-color); font-size: var(--text-md); }
.podium-paces { font-size: var(--text-micro); color: var(--ink-5); }
@media (min-width: 769px) {
    .podium-card { font-size: var(--text-md); padding: var(--space-5) var(--space-7); gap: var(--space-5); }
    .podium-medal { font-size: var(--text-2xl); }
    .podium-time { font-size: var(--text-lg); }
}

.summary-share-row { display: flex; justify-content: flex-end; gap: var(--space-3); margin-bottom: var(--space-3); flex-wrap: wrap; }
.summary-share-all {
    display: inline-flex; align-items: center; gap: var(--space-3);
    padding: var(--space-4) var(--space-7); border: 1px solid var(--line); border-radius: var(--radius-pill);
    background: transparent; color: var(--ink-3); cursor: pointer;
    font-size: var(--text-sm); font-weight: var(--font-weight-bold);
    transition: color var(--transition-fast), border-color var(--transition-fast), background var(--transition-fast);
}
.summary-share-all:hover { color: var(--primary-color); border-color: var(--primary-color); background: var(--bg-surface); }
.summary-share-all svg { width: 14px; height: 14px; }

.share-btn {
    --icon-btn-size: 30px; --icon-btn-radius: var(--radius-md);
    align-self: center; border-color: var(--line); color: var(--ink-5);
}
.share-btn:hover { color: var(--primary-color); border-color: var(--primary-color); background: var(--bg-surface); }
.share-btn-sm { --icon-btn-size: 24px; border-color: transparent; }
.seg-entry:hover .share-btn-sm { border-color: var(--line); }

.share-fab {
    position: fixed; right: 16px; bottom: 16px; z-index: var(--z-fab);
    display: inline-flex; align-items: center; gap: var(--space-3);
    padding: var(--space-5) var(--space-8); border: 0; border-radius: var(--radius-pill);
    background: var(--primary-color); color: var(--on-accent); cursor: pointer;
    font-size: var(--text-md); font-weight: var(--font-weight-bold);
    box-shadow: 0 6px 20px rgba(0,0,0,.28);
}
.share-fab:hover { filter: brightness(1.06); }
.share-fab[hidden] { display: none; }

.summary-stats {
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--space-2); margin-bottom: var(--space-4);
}
.mini-stat { text-align: center; padding: var(--space-2) var(--space-1); background: var(--bg-surface); border-radius: var(--radius-md); }
.ms-val { display: block; font-weight: var(--font-weight-bold); font-size: var(--text-md); color: var(--primary-color); }
.ms-label { display: block; font-size: var(--text-micro); color: var(--ink-5); text-transform: uppercase; letter-spacing: 0.4px; }
.ms-sub { display: block; font-size: var(--text-micro); color: var(--ink-5); margin-top: 1px; }
@media (min-width: 769px) {
    .ms-val { font-size: var(--text-2xl); }
    .ms-label { font-size: var(--text-sm); letter-spacing: 0.6px; }
    .mini-stat { padding: var(--space-4) var(--space-1); }
}
@media (min-width: 1200px) {
    .ms-val { font-size: var(--text-3xl); }
}

/* A fixed `repeat(3, 1fr)` was the bug behind the vanishing Run column: a `1fr`
   track keeps an `auto` MINIMUM, so each column refused to shrink below the
   longest name in it, three of them overflowed the container, and the third was
   clipped out of sight at desktop widths.
   auto-fit + a real floor instead: three columns when they fit, two in the
   middle band, one on a phone. `min(100%, …)` keeps the floor from overflowing
   a container narrower than the floor itself. */
.summary-segments {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
    gap: var(--space-2);
}
.seg-col { background: var(--bg-surface); border-radius: var(--radius-md); padding: var(--space-4) var(--space-5); }
.seg-header {
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
    font-weight: var(--font-weight-bold); font-size: var(--text-sm); color: var(--ink-2);
    margin-bottom: var(--space-2); padding-bottom: var(--space-1); border-bottom: 1px solid var(--bg-tint);
}
/* Discipline-podium share button lives in the header (no row-hover to reveal it) */
.seg-header .seg-podium-share { border-color: var(--line); color: var(--ink-5); }
.seg-header .seg-podium-share:hover { color: var(--primary-color); border-color: var(--primary-color); background: var(--bg-surface); }
/* ── Para: one block per sport class ──────────────────────
   A para race is several races sharing a course, scored within class, so the
   summary repeats podium + discipline leaders per class instead of showing one
   field-wide ranking. Same surface/radius vocabulary as .seg-col. */
.para-class-block { background: var(--bg-surface); border-radius: var(--radius-md); padding: var(--space-4) var(--space-5); margin-bottom: var(--space-3); }
.para-class-head {
    display: flex; align-items: center; gap: var(--space-3);
    font-weight: var(--font-weight-bold); font-size: var(--text-sm); color: var(--ink-2);
    margin-bottom: var(--space-3); padding-bottom: var(--space-1); border-bottom: 1px solid var(--bg-tint);
}
.para-class-name { color: var(--ink-1); }
/* PTVI guide, named under the athlete: they race the whole race together. */
.podium-guide { color: var(--ink-4); font-size: var(--text-sm); }
/* Staggered start: elapsed + the head start conceded, so the finish time above
   reconciles with splits that deliberately sum to less than it. */
.podium-stagger { color: var(--ink-4); font-size: var(--text-sm); }
.para-class-count { color: var(--ink-5); font-weight: var(--font-weight-normal); margin-right: auto; }
.para-class-head .share-btn { border-color: var(--line); color: var(--ink-5); }
.para-class-head .share-btn:hover { color: var(--primary-color); border-color: var(--primary-color); background: var(--bg-surface); }
@media (min-width: 769px) {
    .para-class-block { padding: var(--space-5); }
    .para-class-head { font-size: var(--text-md); }
}

/* The para form-spread box plot (.para-spread / .ps-*) used to live here. It was
   the cross-course normalisation experiment, which measured as a coin flip and
   was replaced by the per-race class chart (see scripts/para/EXPERIMENTS.md and
   js/charts/para-class-chart.js). Nothing has rendered those classes since. */

/* Gender group heading inside a fastest-sub-splits column (mixed relays) */
.seg-gender-label { font-weight: var(--font-weight-bold); color: var(--ink-4); font-size: var(--text-sm); line-height: 1.7; margin: var(--space-2) 0 1px; }
.seg-gender-label:first-of-type { margin-top: 1px; }
@media (min-width: 769px) {
    .seg-col { padding: var(--space-5); }
    .seg-header { font-size: var(--text-md); }
}

/* Single-row per-rank entry inside .seg-col */
.seg-entry {
    display: flex; flex-wrap: nowrap; align-items: baseline;
    gap: var(--space-2); padding: var(--space-2) 0;
    border-bottom: 1px solid var(--bg-tint);
    font-size: var(--text-sm);
}
.seg-entry .seg-rank { font-weight: var(--font-weight-bold); color: var(--primary-color); min-width: 14px; flex-shrink: 0; }
.seg-entry .seg-time { font-weight: var(--font-weight-semibold); flex-shrink: 0; }
.seg-entry .seg-pace { color: var(--ink-5); font-size: var(--text-micro); flex-shrink: 0; }
/* Wraps rather than ellipsing: three columns share the row, so at narrow desktop
   widths the name gets little space and an ellipsis ate most of it. A second
   line costs a few pixels of height and keeps the name whole. */
.seg-entry .seg-name {
    flex: 1; min-width: 0; overflow-wrap: break-word;
    font-weight: var(--font-weight-semibold);
}
@media (min-width: 769px) {
    .seg-entry { font-size: var(--text-md); gap: var(--space-2); padding: var(--space-2) 0; }
    .seg-entry .seg-rank { min-width: 18px; }
    .seg-entry .seg-pace { font-size: var(--text-sm); }
}
@media (min-width: 1200px) { .seg-entry { font-size: var(--text-lg); } }

.summary-section-title {
    margin: var(--space-5) 0 var(--space-2);
    font-size: var(--text-sm);
}
@media (min-width: 769px) {
    .summary-section-title { font-size: var(--text-md); margin: var(--space-7) 0 var(--space-3); }
}

/* Relay member breakdown inside podium card */
.relay-members {
    display: flex; flex-direction: column; gap: 1px;
    margin-top: var(--space-2); padding: var(--space-2) 0 0 var(--space-3);
    border-top: 1px dashed rgba(0, 0, 0, 0.08);
}
.relay-member {
    display: flex; align-items: baseline; gap: var(--space-3);
    font-size: var(--text-sm); color: var(--ink-4); padding: 1px 0;
}
.relay-leg-num { font-weight: var(--font-weight-bold); color: var(--ink-5); min-width: 18px; font-size: var(--text-micro); text-transform: uppercase; }
.relay-gender { color: var(--ink-6); font-size: var(--text-xs); min-width: 10px; }
.relay-flag { font-size: var(--text-sm); line-height: 1; min-width: 14px; }
.relay-member-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.relay-member-time { font-size: var(--text-xs); color: var(--ink-5); }
@media (min-width: 769px) {
    .relay-member { font-size: var(--text-md); padding: var(--space-1) 0; gap: var(--space-4); }
    .relay-leg-num { font-size: var(--text-base); min-width: 22px; }
    .relay-gender { font-size: var(--text-md); min-width: 12px; }
    .relay-flag { font-size: var(--text-md); min-width: 16px; }
    .relay-member-time { font-size: var(--text-md); }
}

.summary-legs {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: var(--space-2);
}
@media (min-width: 769px) { .summary-legs { gap: var(--space-3); } }

.summary-headbar { display: flex; align-items: center; justify-content: space-between; gap: var(--space-5); flex-wrap: wrap; margin-bottom: var(--space-3); }
.summary-distances { font-size: var(--text-base); color: var(--ink-4); }
.summary-distances .dist-seg { white-space: nowrap; }
.summary-distances .dist-leg { color: var(--ink-6); font-weight: var(--font-weight-normal); }
.summary-distances .dist-km { color: var(--ink-3); font-weight: var(--font-weight-semibold); }
.summary-distances .dist-sep { color: var(--ink-6); margin: 0 var(--space-2); }
.summary-dist-note { color: var(--ink-6); font-weight: var(--font-weight-normal); }
/* Theme / units pickers in the app menu use the shared `.switcher`. */
.summary-conditions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin: var(--space-2) 0 var(--space-5); }
.cond-chip { font-size: var(--text-sm); background: var(--bg-tint); border: 1px solid var(--line-base); border-radius: var(--radius-xl); padding: 3px var(--space-4); color: var(--ink-3); }
.cond-chip small { color: var(--ink-6); }
.cond-chip.cond-duathlon { background: var(--amber-bg); border-color: var(--amber-border); color: var(--amber-strong); }

/* ========================================
   SETTINGS
   ======================================== */
.settings-card { max-width: 560px; margin: 0 auto; padding: var(--space-5); }
.settings-section { margin-bottom: var(--space-8); }
.settings-section-title {
    letter-spacing: 0.08em;
    color: var(--ink-6);
    margin-bottom: var(--space-3); padding-left: var(--space-1);
}
.settings-inline .settings-label { font-size: var(--text-md); font-weight: 600; color: var(--ink-3); }
.settings-presets {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4);
}
.settings-preset {
    display: flex; flex-direction: column; align-items: center; gap: 1px;
    padding: var(--space-3) var(--space-3);
    border: 1px solid var(--line-base); border-radius: var(--radius-md);
    background: var(--bg-surface); cursor: pointer; text-align: center;
    transition: all var(--transition-fast);
}
.settings-preset:hover { border-color: var(--line-strong); }
.settings-preset.active { border-color: var(--primary-color); background: var(--indigo-bg); }
.settings-preset-label { font-size: var(--text-md); font-weight: var(--font-weight-semibold); color: var(--ink-3); }
.settings-preset-sub { font-size: var(--text-xs); color: var(--ink-6); }
.settings-preset.active .settings-preset-label { color: var(--primary-darker); }

.settings-row { margin-bottom: var(--space-4); }
.settings-row-head {
    display: flex; justify-content: space-between; align-items: baseline;
    margin-bottom: var(--space-2);
}
.settings-label { font-size: var(--text-md); font-weight: var(--font-weight-semibold); color: var(--ink-3); }
.settings-value { font-size: var(--text-md); font-weight: var(--font-weight-bold); color: var(--primary-color); }
.settings-num {
    width: 4.2em; text-align: right; font: inherit; color: var(--primary-color);
    border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
    padding: 1px var(--space-2); margin-right: var(--space-1); background: var(--bg-surface);
}
.settings-num:focus { outline: none; border-color: var(--primary-color); }
.settings-row-foot {
    display: flex; justify-content: space-between;
    font-size: var(--text-xs); color: var(--ink-6); margin-top: var(--space-2);
}
/* ── Unified range slider (settings, prediction, replay — one look) ──
   The track is drawn by the runnable-track pseudo at a fixed 6px, so an input
   can keep a taller element height for a bigger touch target (e.g. the replay
   scrubber) without thickening the visible track. Accent = primary in both
   themes. Any per-component accent-color is a no-op once appearance is none. */
input[type="range"] {
    -webkit-appearance: none; appearance: none;
    width: 100%; margin: 0; background: transparent;
    cursor: pointer; outline: none; touch-action: pan-y;
}
input[type="range"]::-webkit-slider-runnable-track {
    height: 6px; border-radius: var(--radius-pill); background: var(--line-base);
}
input[type="range"]::-moz-range-track {
    height: 6px; border-radius: var(--radius-pill); background: var(--line-base);
}
input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none; margin-top: -5px;
    width: 16px; height: 16px; border-radius: 50%;
    background: var(--primary-color); border: 2px solid var(--bg-surface);
    box-shadow: 0 1px 3px rgba(15, 23, 42, 0.28); cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
    width: 16px; height: 16px; border-radius: 50%;
    background: var(--primary-color); border: 2px solid var(--bg-surface);
    box-shadow: 0 1px 3px rgba(15, 23, 42, 0.28); cursor: pointer;
}
input[type="range"]:focus-visible { outline: 2px solid var(--primary-color); outline-offset: 3px; }
.settings-save {
    width: 100%; padding: var(--space-6);
    font-size: var(--text-md); font-weight: var(--font-weight-semibold);
    margin-top: var(--space-4);
}
.settings-save.saved { background: var(--success); }
.settings-note { margin: var(--space-6) 0 0; font-size: var(--text-sm); color: var(--ink-6); text-align: center; }

/* ========================================
   TEAM RANKINGS
   ======================================== */
.team-rankings, .country-rankings { display: flex; flex-direction: column; gap: var(--space-2); }
.team-division-group { margin-bottom: var(--space-5); }
.team-division-title {
    margin: var(--space-4) 0 var(--space-2);
    font-size: var(--text-sm); font-weight: var(--font-weight-bold);
    text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-4);
    padding-bottom: var(--space-1); border-bottom: 2px solid var(--indigo-border);
}
.team-row { background: var(--bg-surface); border-radius: var(--radius-sm); overflow: hidden; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); }
.team-row.podium { background: linear-gradient(to right, var(--warning-bg), var(--surface)); }
/* Relay: a country's squads grouped under one heading */
.relay-country-group { background: var(--bg-surface); border-radius: var(--radius-sm); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); overflow: hidden; }
.relay-country-group .relay-country-header { font-weight: var(--font-weight-bold); font-size: var(--text-md); padding: var(--space-3) var(--space-5) 0; color: var(--ink-1); }
.relay-country-group .team-row { box-shadow: none; border-radius: 0; background: transparent; }
.relay-country-group .team-row + .team-row { border-top: 1px solid var(--bg-tint); }
.team-status-badge { font-size: var(--text-xs); font-weight: var(--font-weight-bold); color: var(--ink-4); letter-spacing: 0.03em; }
/* The summary is a grid, not a flow of flex items: a spelled-out school name is
   long enough that it needs a cell of its own, and the grid lets the points sit
   beside the places (rather than on a line of their own) without either of them
   moving in the markup. Areas are re-mapped per breakpoint below. */
.team-summary { display: grid; align-items: center; column-gap: var(--space-4); row-gap: var(--space-1); padding: var(--space-2) var(--space-5); cursor: pointer; user-select: none; }
.team-summary:hover { background: var(--bg-hover); }
.team-info { display: contents; }   /* its two children are the real grid items */
.team-rank { grid-area: rank; font-size: var(--text-md); font-weight: var(--font-weight-bold); min-width: 28px; color: var(--ink-2); }
.team-name { grid-area: name; min-width: 0; font-weight: var(--font-weight-bold); font-size: var(--text-md); display: flex; align-items: center; gap: var(--space-2); }
/* Standings spell the team out, so the name wraps instead of squeezing the
   figures beside it. */
.team-name-text { overflow-wrap: anywhere; line-height: 1.2; }
.team-score { grid-area: score; justify-self: start; font-size: var(--text-sm); color: var(--ink-5); font-weight: var(--font-weight-medium); }
/* Right-hand stack: scoring places, and the team's average paces under them.
   Points sit on the left of the row, these on the right. */
.team-meta { grid-area: meta; justify-self: end; display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-1); min-width: 0; }
/* Bare figures, no per-discipline tag — the tags ate the width the numbers
   need, most visibly for an NCAA squad with 5+ scoring places. Order is always
   swim, bike, run; the units tell them apart. */
.team-averages { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: baseline; gap: var(--space-1) var(--space-8); font-size: var(--text-xs); color: var(--ink-4); }
/* Fixed slot per discipline so swim/bike/run line up as columns down the
   standings instead of drifting with each row's digits. */
.avg { font-family: var(--font-family-mono); font-weight: var(--font-weight-semibold); white-space: nowrap; min-width: 5.6em; text-align: right; }
.avg:last-child { min-width: 0; }
.scoring-positions { display: flex; gap: var(--space-1); flex-wrap: wrap; justify-content: flex-end; }
.pos { padding: 1px var(--space-2); border-radius: var(--radius-sm); font-size: var(--text-micro); font-weight: var(--font-weight-semibold); }
.pos.scoring, .pos.counting { color: white; }
.pos.displacer { background: var(--ink-7); color: var(--ink-4); }
.expand-icon { grid-area: chev; color: var(--ink-6); font-size: var(--text-xs); transition: transform var(--transition-normal); }
.team-row.expanded .expand-icon { transform: rotate(180deg); }
.team-details { display: none; padding: var(--space-3) var(--space-5) var(--space-5); background: var(--bg-soft); border-top: 1px solid var(--bg-tint); }
.team-row.expanded .team-details { display: block; }
@media (min-width: 769px) {
    /* Desktop keeps the name/points column on the left and the whole stack on
       the right, side by side. */
    .team-summary {
        padding: var(--space-4) var(--space-6);
        column-gap: var(--space-5);
        grid-template-columns: auto minmax(0, 1fr) auto auto;
        grid-template-areas:
            "rank name  meta chev"
            "rank score meta chev";
    }
    .team-rank { font-size: var(--text-lg); min-width: 34px; }
    .team-name { font-size: var(--text-lg); gap: var(--space-3); }
    .team-score { font-size: var(--text-md); }
    .team-averages { font-size: var(--text-base); gap: var(--space-1) var(--space-8); }
    .pos { font-size: var(--text-base); padding: 3px var(--space-4); }
    .expand-icon { font-size: var(--text-md); }
}
@media (min-width: 1200px) { .team-name { font-size: var(--text-xl); } }
@media (max-width: 768px) {
    /* Phone: the name gets the full width of its own line (schools spell out
       long), and the points move down to sit at the left of the places/paces
       stack instead of taking a line of their own — one line saved per row
       without crowding the name. */
    .team-summary {
        padding: var(--space-2) var(--space-4);
        grid-template-columns: auto auto minmax(0, 1fr) auto;
        grid-template-areas:
            "rank name  name chev"
            "rank score meta chev";
    }
    .team-meta { align-self: start; }
    .team-score { align-self: end; }   /* points level with the paces line */
}

.athletes-table { width: 100%; border-collapse: collapse; font-size: var(--text-xs); }
.athletes-table th { text-align: left; padding: 3px var(--space-3); font-weight: var(--font-weight-semibold); color: var(--ink-5); border-bottom: 1px solid var(--line-base); font-size: var(--text-micro); text-transform: uppercase; }
.athletes-table td { padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--bg-tint); }
/* Cap the name column on phones so a long name wraps to ~2 lines instead of
   stealing width from the swim/bike/run pace columns (#6). overflow-wrap keeps
   even a long single token from forcing the cell wider. Lifted on desktop. */
.athletes-table .athlete-name-cell { font-weight: var(--font-weight-semibold); max-width: 8.5em; overflow-wrap: anywhere; line-height: 1.25; }
.athletes-table .time-cell { font-family: var(--font-family-mono); font-size: var(--text-xs); }
.athletes-table .pace-cell { font-size: var(--text-micro); color: var(--ink-5); }
.athletes-table tr.displacer { opacity: 0.7; background: var(--bg-tint); }
.divider-row td { font-size: var(--text-micro); font-weight: var(--font-weight-semibold); color: var(--ink-6); text-transform: uppercase; padding: var(--space-3); border-bottom: 1px dashed var(--line-base) !important; background: var(--bg-tint); }
.scoring-note { font-size: var(--text-xs); color: var(--ink-5); margin-top: var(--space-4); padding: var(--space-3) var(--space-5); background: var(--warning-bg); border-left: 3px solid var(--warning); border-radius: var(--radius-sm); }
.scoring-rules { margin-top: var(--space-4); padding: var(--space-4) var(--space-5); background: var(--bg-hover); border-radius: var(--radius-md); font-size: var(--text-xs); color: var(--ink-4); border: 1px solid var(--indigo-border); }
.scoring-rules strong { color: var(--primary-color); }
@media (min-width: 769px) {
    .athletes-table { font-size: var(--text-md); }
    .athletes-table th { font-size: var(--text-base); padding: var(--space-3) var(--space-5); }
    .athletes-table td { padding: var(--space-3) var(--space-5); }
    .athletes-table .athlete-name-cell { max-width: none; }
    .athletes-table .time-cell { font-size: var(--text-md); }
    .athletes-table .pace-cell { font-size: var(--text-base); }
    .divider-row td { font-size: var(--text-base); padding: var(--space-4); }
    .scoring-note { font-size: var(--text-md); padding: var(--space-4) var(--space-7); }
    .scoring-rules { font-size: var(--text-md); padding: var(--space-5) var(--space-7); }
}

.spider-table-wrap {
    overflow-x: auto; overflow-y: auto; -webkit-overflow-scrolling: touch;
    border: 1px solid var(--line-base); border-radius: var(--radius-md);
    background: var(--bg-surface); min-width: 0;
}
.spider-tbl { width: 100%; border-collapse: collapse; font-size: var(--text-sm); min-width: 400px; }
.spider-tbl thead th {
    padding: var(--space-3) var(--space-2); font-size: var(--text-xs); font-weight: var(--font-weight-bold); color: var(--ink-4);
    background: var(--bg-hover); border-bottom: 2px solid var(--line-base); text-align: center;
    position: sticky; top: 0; z-index: 5; text-transform: uppercase; letter-spacing: 0.3px;
}
.spider-tbl thead th:first-child { text-align: left; z-index: 10; }
/* Opaque base so the sticky first column (background: inherit) always masks the
   scrolling value columns behind it — odd/unstriped rows were transparent, so
   text bled through the name on horizontal scroll. */
.spider-tbl tbody tr { border-bottom: 1px solid var(--bg-tint); background: var(--bg-surface); }
.spider-tbl tbody tr:nth-child(even) { background: var(--bg-soft); }
.spider-tbl tbody tr:hover { background: var(--bg-hover); }
/* 44px minimum cell height on mobile so each row is a finger-friendly
   target (the per-row ✕ button and athlete cell both inherit this). */
.spider-tbl td { padding: var(--space-4) var(--space-2); vertical-align: middle; text-align: center; }
.spider-tbl td:first-child { text-align: left; position: sticky; left: 0; background: inherit; z-index: 2; min-width: 110px; }
.spider-tbl .ath-cell { display: flex; align-items: center; gap: var(--space-2); }
.spider-tbl .cdot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
/* Names wrap rather than truncate: an ellipsis here hid text while the cell still
   had ~40px of spare width, and a half-name is worth less than a second line.
   The max-width is a brake on the column stealing room from the split columns
   (which start wrapping "42.3 km/h"), NOT a clip: anything past it wraps. */
.spider-tbl .aname {
    font-weight: var(--font-weight-semibold); font-size: var(--text-sm);
    white-space: normal; overflow-wrap: anywhere; line-height: 1.2;
    max-width: 124px;
}
.spider-tbl .acountry { font-size: var(--text-micro); color: var(--ink-5); }
/* Pace under each split. Was inline 9px, which was too small to read at a glance
   for the number people most want off this table; --text-sm matches the grid's
   own body size and scales up again on mobile. Also carries the relay member
   name in the Teams lens. */
.spider-tbl .tpace { font-size: var(--text-sm); color: var(--text-muted); line-height: 1.2; }
/* The unit, stated once per column under its name (cells hold bare numbers). */
.spider-tbl thead th .tunit {
    display: block; margin-top: 1px; font-size: var(--text-micro); font-weight: var(--font-weight-normal);
    color: var(--ink-5); text-transform: none; letter-spacing: 0;
}
/* White tile + hairline ring: most NCAA logos ship on a solid white background,
   so a bare <img> shows a white rectangle on coloured rows / dark mode. The tile
   normalises every logo (white-bg blends in, transparent gets a legible backing)
   into one cohesive badge. --logo-tile stays white in both themes (see tokens). */
/* The tile is a FIXED shape and every mark is contained inside it, so a row of
   badges reads as one set. `aspect-ratio` rather than a hard width, so the
   handful of rules that override only `height` still get a proportional tile.
   6/5 is the median shape of the trimmed logos.

   This has to be a fixed box. The source files used to be a uniform 800x640
   canvas, which normalised every badge for free — but the mark inside was
   floating in dead space, which is what made crests unreadable. Trimming the
   canvas to the mark (scripts/trim_team_logos.py) fixed the size but exposed a
   5.3x spread in aspect ratio, so `width: auto` then rendered badges anywhere
   from 0.66em to 2.75em wide. Trimmed source + fixed tile gives both: one tile
   shape, and each mark scaled up to fill as much of it as its shape allows. */
.team-logo {
    height: 1.3em; aspect-ratio: 6 / 5; width: auto;
    object-fit: contain; object-position: center; vertical-align: middle;
    background: var(--logo-tile); padding: 1px; border-radius: var(--radius-sm);
    box-shadow: 0 0 0 1px var(--logo-tile-ring);
}
/* Transfer athlete: one crest per school, sitting together with a hair of gap. */
.team-logos { display: inline-flex; align-items: center; gap: var(--space-1); vertical-align: middle; }
.spider-tbl .tval { font-weight: var(--font-weight-medium); }
.spider-tbl .rbadge {
    display: inline-block; padding: 1px var(--space-2); border-radius: var(--radius-lg);
    font-size: var(--text-micro); font-weight: var(--font-weight-bold); margin-top: 1px;
}
/* .rbadge tier colors live in chart-theme.css (single source). */
.spider-tbl .tpace { font-size: 8px; color: var(--accent-ink); margin-top: 1px; }
.spider-tbl .spider-remove-col { width: 24px; padding: 0; }

/* ── Splits grid (WT-style sortable results table) ───────────────────────── */
.spl-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; border: 1px solid var(--line-base); border-radius: var(--radius-lg); }
.spl-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); font-variant-numeric: tabular-nums; --spl-freeze: 116px; }

/* Header scrolls away with the table (no top-sticky) — on a phone a pinned
   header row otherwise floats over the app chrome above as you scroll. */
.spl-table thead th {
    padding: var(--space-3) var(--space-2); font-size: var(--text-xs); font-weight: var(--font-weight-bold);
    color: var(--text-2); background: var(--bg-hover); border-bottom: 2px solid var(--line-base);
    text-align: center; white-space: nowrap; user-select: none;
}
.spl-th[data-key] { cursor: pointer; }
.spl-th[data-key]:hover { filter: brightness(0.97); }
/* Sorted-column underline + arrow colour are set inline per column (discipline
   colour for swim/bike/run, neutral otherwise) — never purple. */
.spl-arrow { font-size: var(--text-micro); margin-left: var(--space-1); vertical-align: middle; }
.spl-th-name { text-align: left !important; }

/* The name is the only frozen column. At REST it auto-sizes to the full names
   (nothing truncated). Once you scroll sideways, .spl-x clamps it to the frozen
   width and the name ellipsises — so the splits get the room only when you're
   actually looking at them. The divider marks the frozen edge. */
/* `1%` is the shrink-to-fit width in a table that is itself width:100% — the cell
   takes what its content needs and no more. It matters because SOMETHING has to
   absorb the leftover pixels on a wide desktop, and while this column was `auto`
   it was the only candidate: it grew into a band of dead space with the splits
   shoved against the right edge. The split columns share the slack instead (see
   .spl-splitcol). --spl-rest is then the column's real content width, measured
   after layout, which is what makes the rest⇄scrolled transition interpolable. */
.spl-th-name, .spl-name {
    position: sticky; left: 0; z-index: 6;
    width: var(--spl-rest, 1%); min-width: var(--spl-freeze);
    /* A table cell's `width` is only a MINIMUM under auto layout — the column can
       still be handed slack — so the measured content width has to be a max-width
       as well, or the name column silently re-absorbs the leftover space. */
    max-width: var(--spl-rest, none);
    white-space: nowrap;
    border-right: 1px solid var(--line-base);
    transition: width var(--transition-fast), max-width var(--transition-fast);
}
.spl-th-name { z-index: 15; }
.spl-scroll.spl-x .spl-th-name, .spl-scroll.spl-x .spl-name {
    width: var(--spl-freeze); min-width: var(--spl-freeze); max-width: var(--spl-freeze);
}
@media (prefers-reduced-motion: reduce) {
    .spl-th-name, .spl-name { transition: none; }
}

/* Row backgrounds MUST be opaque — the sticky columns inherit them, and any
   translucency lets scrolled-under cells show through the pinned columns. */
.spl-table tbody tr { background: var(--bg-surface); border-bottom: 1px solid var(--line-soft); }
.spl-table tbody tr:nth-child(even) { background: var(--bg-soft); }
.spl-table tbody tr:hover { background: var(--bg-hover); }
.spl-row { cursor: pointer; }
.spl-row-sel, .spl-row-sel:hover { background: var(--surface-selected) !important; }
.spl-row-dnf { color: var(--text-faint); }

.spl-cell { padding: var(--space-3) var(--space-2); text-align: center; vertical-align: middle; white-space: nowrap; }
/* Frozen name: opaque (inherits row bg) so scrolling splits pass cleanly beneath.
   One line, flag first with a little gap. Full at rest; ellipsis only when scrolled
   (.spl-x) — full name always in the title for a long-press. */
.spl-name { background: inherit; text-align: left; font-weight: var(--font-weight-semibold); color: var(--text); vertical-align: middle; }
.spl-name .spl-nm { display: inline-block; margin-left: var(--space-2); white-space: nowrap; vertical-align: bottom; }
.spl-scroll.spl-x .spl-name .spl-nm { max-width: calc(var(--spl-freeze) - 30px); overflow: hidden; text-overflow: ellipsis; }
.spl-pos { width: 40px; min-width: 40px; color: var(--text-muted); font-weight: var(--font-weight-semibold); }
/* Every split column shares one width so the grid reads with even spacing (T1/T2
   would otherwise sit narrower than swim/bike/run and stagger the gaps). `auto`
   over a floor rather than a fixed width, so on a wide desktop the leftover space
   is shared out across the numbers instead of pooling in one dead column. */
.spl-th.spl-splitcol, .spl-cell.spl-splitcol { width: auto; min-width: 3.6em; }
/* Pace lens: the unit is stated once under the column name, so the cells hold a
   bare number ("3:52") and the columns need only a little more room than a time. */
.spl-scroll.spl-pace .spl-th.spl-splitcol,
.spl-scroll.spl-pace .spl-cell.spl-splitcol { min-width: 4.2em; }
.spl-unit {
    display: block; margin-top: 1px; font-size: var(--text-micro); line-height: 1;
    font-weight: var(--font-weight-normal); color: var(--text-faint); text-transform: none;
    letter-spacing: 0;
}
/* Start (bib) number — a normal, sortable scrolling column; muted + tabular.
   It needs an explicit width like every other column: the table is width:100%,
   so any column left to size itself becomes the sole sink for slack space and
   balloons the moment another column gives some up (the frozen name clamping
   to --spl-freeze once used to hand it 500px). Wide enough for a 3-digit bib
   plus the sort arrow. */
.spl-th-bib, .spl-bibcell { width: 2.8em; min-width: 2.8em; }
.spl-bibcell { color: var(--text-muted); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
/* Selection colour bar rides the frozen name cell (left edge). The cell is sticky
   → a valid positioning context. */
.spl-accent { position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: transparent; z-index: 1; }
.spl-status { font-size: var(--text-micro); font-weight: var(--font-weight-bold); color: var(--text-faint); }

.spl-time { display: block; font-weight: var(--font-weight-medium); line-height: 1.15; }
.spl-rk { display: block; margin-top: 1px; font-size: var(--text-micro); line-height: 1; color: var(--text-faint); }
/* Sorted-column tint + fastest-split colour are applied inline per column
   (discipline colour), so there are no purple .spl-best / .spl-cell-active rules. */
.spl-empty { color: var(--text-faint); }
/* ── Relay lens toggle + leg filter — both are shared `.switcher`s ────────── */
.spl-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-bottom: var(--space-3); }
/* The leg filter can run to 5+ options, so it scrolls rather than clipping. */
.spl-legfilter { margin-left: auto; }

/* Leg column (Athletes mode): narrow, muted, sortable — groups the grid by leg. */
.spl-th-leg, .spl-legcell { width: 2.6em; min-width: 2.6em; }
.spl-legcell { color: var(--text-muted); font-size: var(--text-xs); font-weight: var(--font-weight-semibold); }

/* Athlete row in a relay: the team name rides under the runner's name so the
   frozen column still answers "whose leg was that?" when sorted by bike. */
.spl-name .spl-nm { line-height: 1.15; }
.spl-team { display: block; font-size: var(--text-micro); font-weight: var(--font-weight-normal); color: var(--text-muted); }
.spl-scroll.spl-x .spl-name .spl-team { overflow: hidden; text-overflow: ellipsis; }

/* What the timer logged against the athlete: why a race ended, or a penalty
   served during it. Rides under the name like the relay team line. */
.spl-note { display: block; font-size: var(--text-micro); font-weight: var(--font-weight-normal); color: var(--danger-color); white-space: normal; }
.spl-scroll.spl-x .spl-name .spl-note { overflow: hidden; text-overflow: ellipsis; }

@media (max-width: 480px) {
    /* Phone: the leg filter drops to its own row under the lens toggle rather
       than fighting it for the same line. */
    .spl-legfilter { margin-left: 0; }
}
.spider-tbl .spider-remove-cell { padding: var(--space-1); width: 24px; }
.spider-remove-btn {
    padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm);
    color: var(--ink-7); font-size: var(--text-sm);
}
.spider-remove-btn:hover { background: var(--danger-bg); color: var(--danger); }
@media (min-width: 769px) {
    .spider-tbl { font-size: var(--text-md); }
    .spider-tbl thead th { font-size: var(--text-base); padding: var(--space-4); }
    .spider-tbl td { padding: var(--space-3); }
    .spider-tbl .aname { font-size: var(--text-md); max-width: 200px; }
    .spider-tbl .acountry { font-size: var(--text-base); }
    .spider-tbl .tval { font-size: var(--text-md); }
    .spider-tbl .rbadge { font-size: var(--text-sm); padding: 3px var(--space-4); }
    .spider-tbl .tpace { font-size: var(--text-sm); }
    .spider-tbl td:first-child { min-width: 140px; }
    .spider-tbl .spider-remove-col, .spider-tbl .spider-remove-cell { width: 32px; }
    .spider-remove-btn { font-size: var(--text-lg); padding: var(--space-3) var(--space-5); }
}
@media (min-width: 1200px) {
    .spider-tbl { font-size: var(--text-lg); }
    .spider-tbl .aname { font-size: var(--text-lg); }
}

/* ========================================
   CHECKPOINT NAV
   ======================================== */
.checkpoint-navigation { display: flex; align-items: center; justify-content: center; gap: var(--space-6); padding: var(--space-6); background: linear-gradient(135deg, var(--primary-color), var(--primary-darker)); border-radius: var(--radius-xl); margin-bottom: var(--space-5); }
.checkpoint-nav-btn { width: 36px; height: 36px; border-radius: 50%; background: var(--bg-surface); border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.checkpoint-nav-btn.disabled { opacity: 0.3; cursor: not-allowed; }
.checkpoint-nav-btn .arrow { font-size: var(--text-lg); color: var(--primary-color); font-weight: bold; }
.checkpoint-info { text-align: center; color: white; min-width: 160px; }
.checkpoint-label { font-size: var(--text-xs); opacity: 0.9; text-transform: uppercase; letter-spacing: 1px; }
.checkpoint-name { font-size: var(--text-xl); font-weight: var(--font-weight-bold); margin: var(--space-2) 0; }
.checkpoint-indicator { display: flex; gap: var(--space-3); justify-content: center; margin-top: var(--space-3); }
.checkpoint-indicator .dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(255, 255, 255, 0.3); }
.checkpoint-indicator .dot.active { background: white; transform: scale(1.5); }
.checkpoint-indicator .dot.passed { background: rgba(255, 255, 255, 0.6); }
@media (min-width: 769px) {
    .checkpoint-label { font-size: var(--text-md); letter-spacing: 1.2px; }
    .checkpoint-name { font-size: var(--text-2xl); }
    .checkpoint-info { min-width: 200px; }
}
@media (max-width: 768px) {
    .checkpoint-navigation { padding: var(--space-5); gap: var(--space-4); }
    .checkpoint-name { font-size: var(--text-lg); }
    .checkpoint-info { min-width: 120px; }
}

/* ========================================
   SIDEBAR (legacy, mostly hidden)
   ======================================== */
.sidebar {
    position: fixed; left: calc(-1 * var(--sidebar-width)); top: 0;
    width: var(--sidebar-width); height: 100vh; background: var(--bg-surface);
    border-right: 1px solid var(--line-base); box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
    transition: left var(--transition-normal); z-index: var(--z-drawer); overflow-y: auto;
}
.sidebar.open { left: 0; }

/* ========================================
   ATHLETE BROWSER (.ab-*)
   ======================================== */
.ab-results { display: flex; flex-direction: column; gap: var(--space-2); max-height: 50vh; overflow-y: auto; }
.ab-section-title { font-size: var(--text-eyebrow); font-weight: var(--font-weight-bold); color: var(--ink-6); text-transform: uppercase; letter-spacing: 0.5px; padding: var(--space-3) var(--space-2) var(--space-1); }
.ab-row {
    display: flex; align-items: center; gap: var(--space-3); padding: var(--space-4) var(--space-5);
    background: var(--bg-surface); border: 1px solid var(--line-base); border-radius: var(--radius-md);
    font-size: var(--text-md); text-align: left; cursor: pointer; min-height: 44px;
    -webkit-tap-highlight-color: transparent;
    position: relative;
}
.ab-row:active { background: var(--indigo-bg); }
.ab-name { flex: 1; font-weight: var(--font-weight-semibold); color: var(--ink-2); }
.ab-sub { font-size: var(--text-sm); color: var(--ink-5); }
.ab-badge.wt { background: var(--badge-blue-bg); color: var(--badge-blue-ink); }
.ab-badge.ncaa { background: var(--badge-amber-bg); color: var(--badge-amber-ink); }
.ab-races-head { font-size: var(--text-md); padding: var(--space-1) 0 var(--space-3); color: var(--ink-3); }

.ab-star {
    font-size: var(--text-lg); color: var(--ink-7);
    padding: var(--space-2) var(--space-3); cursor: pointer; line-height: 1;
    flex-shrink: 0; border-radius: var(--radius-sm);
    transition: color var(--transition-fast), background var(--transition-fast);
    -webkit-tap-highlight-color: transparent;
}
.ab-star:hover { color: var(--warning-color); background: var(--bg-hover); }
.ab-star.on { color: var(--major-accent); }
.ab-icon {
    flex-shrink: 0; min-width: 22px;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: var(--text-lg);
    text-align: center;
}
/* Shape + fit come from .team-logo; only the scale is local here. */
.ab-icon img.team-logo { height: 1.4em; }
.ab-yob { font-size: var(--text-sm); color: var(--ink-6); font-weight: var(--font-weight-normal); }
/* Start-list race number. Tabular + fixed width so the names stay in a column. */
.ab-bib {
    flex: none; min-width: 2.2em; text-align: right;
    font-family: var(--font-family-mono); font-variant-numeric: tabular-nums;
    font-size: var(--text-sm); color: var(--ink-5);
}

/* ========================================
   ATHLETE PROFILE (.ap-*)
   ======================================== */
#athleteProfileSection { display: none; }
#athleteProfileSection[style*="block"] { display: block !important; }
#athleteProfileContent { background: var(--bg-surface); border-radius: var(--radius-xl); padding: var(--space-4); }

.ap-header {
    display: flex; gap: var(--space-6); align-items: flex-start; padding: var(--space-5);
    background: linear-gradient(135deg, var(--accent-subtle), var(--surface-tint));
    border-radius: var(--radius-xl); margin-bottom: var(--space-6);
}
.ap-photo {
    width: 80px; height: 80px; border-radius: 50%;
    object-fit: cover; border: 3px solid var(--bg-surface);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); flex-shrink: 0;
}
.ap-photo-blank {
    display: flex; align-items: center; justify-content: center;
    background: var(--line-base); font-size: 32px;
}
.ap-header-body { flex: 1; min-width: 0; }
.ap-name {
    font-size: var(--text-xl); font-weight: var(--font-weight-bold); color: var(--ink-2);
    margin: 0 0 var(--space-2); display: flex; align-items: center; gap: var(--space-4);
    flex-wrap: wrap;
}
.ap-name-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.ap-flag { width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0; }
.ap-meta { font-size: var(--text-sm); color: var(--ink-5); margin-bottom: var(--space-4); }
.ap-meta span + span { margin-left: var(--space-1); }
.ap-ranks { display: flex; gap: var(--space-5); flex-wrap: wrap; }
.ap-rank {
    display: flex; flex-direction: column; align-items: center;
    background: var(--bg-surface); border-radius: var(--radius-md);
    padding: var(--space-2) var(--space-5);
    border: 1px solid var(--line-base);
}
.ap-rank-val { font-size: var(--text-lg); font-weight: var(--font-weight-bold); color: var(--primary-color); }
.ap-rank-lbl { font-size: var(--text-micro); color: var(--ink-6); text-transform: uppercase; letter-spacing: 0.5px; }
@media (min-width: 769px) {
    .ap-photo { width: 120px; height: 120px; }
    .ap-name { font-size: var(--text-3xl); }
    .ap-meta { font-size: var(--text-md); }
    .ap-rank-val { font-size: var(--text-2xl); }
    .ap-rank-lbl { font-size: var(--text-base); }
}

.ap-fav-btn {
    background: transparent; border: none; cursor: pointer;
    font-size: var(--text-2xl); color: var(--ink-7); padding: 0 var(--space-2);
    line-height: 1; flex-shrink: 0;
    transition: color var(--transition-fast), transform var(--transition-fast);
    -webkit-tap-highlight-color: transparent;
}
.ap-fav-btn:hover { color: var(--warning-color); }
.ap-fav-btn:active { transform: scale(0.9); }
.ap-fav-btn.on { color: var(--major-accent); text-shadow: 0 0 6px rgba(245, 158, 11, 0.25); }

.ap-stats {
    display: grid; grid-template-columns: repeat(3, 1fr);
    gap: var(--space-3); margin-bottom: var(--space-6);
}
.ap-stat {
    background: var(--bg-surface); border: 1px solid var(--line-base);
    border-radius: var(--radius-lg);
    padding: var(--space-4) var(--space-2); text-align: center;
}
.ap-stat-val { display: block; font-size: var(--text-xl); font-weight: var(--font-weight-bold); color: var(--ink-3); }
.ap-stat-lbl { display: block; font-size: var(--text-micro); color: var(--ink-6); text-transform: uppercase; letter-spacing: 0.4px; margin-top: var(--space-1); }
@media (min-width: 769px) {
    .ap-stat-val { font-size: var(--text-3xl); }
    .ap-stat-lbl { font-size: var(--text-base); }
}

.ap-upcoming { display: flex; gap: var(--space-4); overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.ap-upcoming::-webkit-scrollbar { display: none; }
.ap-upcoming-card {
    flex: 0 0 auto; min-width: 180px; padding: var(--space-4) var(--space-5);
    background: var(--bg-surface); border: 1px solid var(--line-base); border-radius: var(--radius-md);
}
.ap-up-date { font-size: var(--text-xs); color: var(--ink-5); }
.ap-up-title { font-size: var(--text-base); font-weight: var(--font-weight-semibold); color: var(--ink-2); margin: var(--space-1) 0; }
.ap-up-prog { font-size: var(--text-xs); color: var(--ink-6); }
.ap-up-startlist { font-size: var(--text-xs); color: var(--primary-color); font-weight: var(--font-weight-bold); margin-top: var(--space-2); }
.ap-upcoming-card.startlist-ready { border-color: var(--primary-color); cursor: pointer; }
.ap-upcoming-card.startlist-ready:hover { background: var(--bg-hover); }

.ap-pulse {
    display: inline-block; width: 8px; height: 8px; border-radius: 50%;
    background: var(--primary-color);
    animation: ap-pulse-anim 1.2s infinite ease-in-out;
}
@keyframes ap-pulse-anim { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } }

.ap-legend { display: flex; gap: var(--space-3); flex-wrap: wrap; margin-top: var(--space-3); padding: 0 var(--space-2); }
.ap-legend-chip {
    font-size: var(--text-xs); padding: var(--space-1) var(--space-4);
    background: var(--bg-surface); border: 1px solid var(--line-base);
    border-radius: var(--radius-lg); color: var(--ink-4);
    display: inline-flex; align-items: center; gap: var(--space-2);
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ap-legend-dot { width: 7px; height: 7px; border-radius: 50%; }
.ap-legend-count { color: var(--ink-6); font-weight: var(--font-weight-bold); }

/* .ap-dist-btn geometry + active come from the shared `.switcher`; the race
   count inside each segment uses the shared `.switcher-n`. */
.ap-dist-filter { margin-bottom: var(--space-3); }

/* Trend controls (chip-shaped colour-keyed readouts) */
.ap-trend-controls { display: inline-flex; gap: var(--space-5); align-items: center; margin-left: var(--space-4); flex-wrap: wrap; }
/* The trend unit picker (abs / % / time / △ pace) is a shared `.switcher`. */

.ap-readout { display: flex; gap: var(--space-4); flex-wrap: wrap; margin-top: var(--space-5); }
.ap-readout-item {
    user-select: none; display: inline-flex; gap: var(--space-2); align-items: center;
    padding: var(--chip-py) var(--chip-px);
    border: 1px solid var(--line-base); border-radius: var(--chip-radius);
    cursor: pointer; transition: background var(--transition-fast), border-color var(--transition-fast), opacity var(--transition-fast), box-shadow var(--transition-fast);
    font-size: var(--chip-text); min-height: var(--chip-min-h);
}
.ap-readout-item:hover { background: var(--bg-tint); border-color: var(--line-strong); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); }
.ap-readout-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ap-readout-item.off { opacity: 0.4; }
.ap-readout-item.off:hover { opacity: 0.65; }
.ap-readout-item.off .ap-readout-val,
.ap-readout-item.off .ap-readout-lbl { text-decoration: line-through; }
.ap-readout-tgl { width: 8px; height: 8px; border-radius: 50%; border: 1.5px solid; align-self: center; }
.ap-readout-icon { font-size: var(--text-md); }
.ap-readout-val { font-weight: var(--font-weight-bold); color: var(--ink-3); }
.ap-readout-unit { font-size: var(--text-xs); }
.ap-readout-count { font-size: var(--text-xs); color: var(--ink-6); }
.ap-readout-warn { font-size: var(--text-sm); }

.ap-readout-radio .ap-readout-tgl.radio { border-radius: 50%; }
.ap-readout-radio .ap-readout-item.off { opacity: 0.55; }
.ap-readout-radio .ap-readout-item.on { border-color: currentColor; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); }

/* Best-of cards */
.ap-bests { display: flex; flex-direction: column; gap: var(--space-2); }
.ap-best-card {
    display: grid; grid-template-columns: auto auto 1fr; gap: var(--space-4);
    align-items: center; padding: var(--space-4) var(--space-5);
    background: var(--bg-surface); border: 1px solid var(--line-base);
    border-radius: var(--radius-md); cursor: pointer;
    text-align: left; min-height: 44px; min-width: 0;
}
.ap-best-card:hover, .ap-best-card:active { background: var(--bg-hover); }
.ap-best-tier {
    font-size: var(--text-micro); font-weight: var(--font-weight-bold);
    /* A tinted pill with coloured ink, like .ab-badge and .ap-pos.pod. It was a
       SOLID tier colour under white text, which cannot clear 4.5:1 at 9px
       without going almost black: Olympics amber sat at 2.15:1. The tint keeps
       the hue legible as identity and lets the ink carry the contrast. */
    background: color-mix(in srgb, var(--tier-c, var(--ink-5)) 14%, var(--surface));
    color: var(--tier-i, var(--ink-2));
    padding: 3px var(--space-4);
    border-radius: var(--radius-lg); text-transform: uppercase; letter-spacing: 0.5px;
    max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    box-sizing: border-box;
}
.ap-best-pos { font-size: var(--text-lg); font-weight: var(--font-weight-bold); color: var(--ink-4); min-width: 36px; }
.ap-best-pos.pod { color: var(--amber-fg); }
.ap-best-info { display: flex; flex-direction: column; min-width: 0; }
/* Match .ap-race-title sizing so the same race appears at the same font
   weight + size whether it's in the best-of-tier card or the race list. */
/* ── Name label: a semibold name on one line, ellipsised if it must be ─────
   Five selectors carried byte-identical copies of this. Name styling is the
   thing that gets revisited most (surname convention, wrap-vs-truncate, tie
   ranking), and five copies meant five edits or five surfaces disagreeing.
   .t100-sr-name sits in a denser table, so it takes the smaller step. */
.ap-best-title, .ap-race-title, .rh-pr-name, .t100-r-venue, .t100-sr-name {
    font-weight: var(--font-weight-semibold); color: var(--ink-2);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ap-best-title, .ap-race-title, .rh-pr-name, .t100-r-venue { font-size: var(--text-base); }
.t100-sr-name { font-size: var(--text-md); }
.ap-best-sub { font-size: var(--text-xs); color: var(--ink-6); }
@media (min-width: 769px) {
    .ap-best-title { font-size: var(--text-md); }
}

/* Race list (groups + rows) */
.ap-race-list { display: flex; flex-direction: column; gap: var(--space-2); }
.ap-race-group { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.ap-race-group + .ap-race-group { margin-top: var(--space-5); }
.ap-race-group-head { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-1) var(--space-2); margin-bottom: var(--space-1); min-width: 0; }
.ap-race-group-bar { width: 3px; height: 14px; border-radius: 2px; flex-shrink: 0; }
.ap-race-group-label {
    font-size: var(--text-sm); font-weight: var(--font-weight-bold);
    text-transform: uppercase; letter-spacing: 0.4px;
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ap-race-group-count { font-size: var(--text-xs); color: var(--ink-6); font-weight: var(--font-weight-bold); margin-left: auto; }

.ap-race-row {
    display: flex; flex-direction: column;
    background: var(--bg-surface); border: 1px solid var(--line-base); border-radius: var(--radius-md);
    overflow: hidden;
}
.ap-race-row:hover, .ap-race-row:active { background: var(--bg-hover); border-color: var(--line-strong); }
.ap-race-strip { display: flex; align-items: stretch; }
.ap-race-main {
    flex: 1; min-width: 0;
    display: flex; flex-direction: column; gap: var(--space-2);
    padding: var(--space-5) var(--space-6);
    background: transparent; border: 0; text-align: left; cursor: pointer;
    -webkit-tap-highlight-color: transparent; font: inherit; color: inherit;
    line-height: 1.35;
}
.ap-race-toggle {
    flex-shrink: 0; padding: 0 var(--space-6);
    background: transparent; border: 0; border-left: 1px solid var(--line-base);
    font-size: var(--text-md); color: var(--ink-6); cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: transform var(--transition-fast), color var(--transition-fast);
}
.ap-race-toggle:hover { color: var(--ink-4); }
.ap-race-toggle[aria-expanded="true"] { color: var(--ink-3); transform: rotate(180deg); }
.ap-race-line1 { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; font-size: var(--text-xs); min-width: 0; }
.ap-rd { color: var(--ink-5); }
.ap-pos {
    font-size: var(--text-sm); font-weight: var(--font-weight-bold); color: var(--ink-4);
    padding: 1px var(--space-2); border-radius: var(--radius-sm); background: var(--bg-tint);
}
.ap-pos.pod { background: var(--badge-amber-bg); color: var(--badge-amber-ink); }
.ap-pos-dnf { font-size: var(--text-xs); font-weight: var(--font-weight-bold); color: var(--badge-red-ink); padding: 1px var(--space-2); background: var(--badge-red-bg); border-radius: var(--radius-sm); }
.ap-of { font-size: var(--text-xs); color: var(--ink-6); }
.ap-rtot { margin-left: auto; font-weight: var(--font-weight-semibold); color: var(--ink-3); }
@media (min-width: 769px) { .ap-race-title { font-size: var(--text-md); } }
.ap-relay-tog {
    margin-left: auto; font-size: 11px; font-weight: 400; color: var(--ink-6);
    display: inline-flex; align-items: center; gap: var(--space-2); cursor: pointer;
}
.ap-relay-tog input { margin: 0; }
.ap-rp { color: var(--ink-6); font-weight: var(--font-weight-normal); }
/* Race title reads differently by width: mobile shows only the venue (the group
   heading already names the tier), desktop shows the full cleaned title. */
.ap-rtt-place { display: none; }
@media (max-width: 768px) {
    .ap-rtt-full { display: none; }
    .ap-rtt-place { display: inline; }
}

.ap-race-segs {
    display: none; gap: var(--space-3); flex-wrap: wrap;
    padding: var(--space-3) var(--space-5) var(--space-4); border-top: 1px solid var(--bg-tint);
}
.ap-race-row.expanded .ap-race-segs { display: flex; }
.ap-seg { display: inline-flex; align-items: baseline; gap: var(--space-2); font-size: var(--text-xs); }
.ap-si { font-size: var(--text-sm); }
.ap-st { color: var(--ink-3); font-weight: var(--font-weight-semibold); }
.ap-sg { font-size: var(--text-micro); font-weight: var(--font-weight-bold); padding: 1px var(--space-2); border-radius: var(--radius-sm); }
.ap-sg.good { background: var(--badge-green-bg); color: var(--badge-green-ink); }
.ap-sg.mid  { background: var(--badge-amber-bg); color: var(--badge-amber-ink); }
.ap-sg.low  { background: var(--badge-amber-bg); color: var(--badge-amber-ink); }
.ap-sg.verylow { background: var(--badge-red-bg); color: var(--badge-red-ink); }
.ap-dash { color: var(--line-strong); }
@media (min-width: 769px) {
    .ap-st { font-size: var(--text-md); }
    .ap-sg { font-size: var(--text-sm); }
}

.ap-load-banner {
    padding: var(--space-4) var(--space-6); background: var(--indigo-bg); border: 1px solid var(--indigo-border);
    border-radius: var(--radius-md); font-size: var(--text-base); color: var(--indigo-fg); margin-bottom: var(--space-5);
}
.ap-load-banner.error { background: var(--danger-bg); border-color: var(--danger); color: var(--danger); }

.ap-tooltip {
    position: absolute; z-index: var(--z-tooltip); background: var(--scrim-tooltip);
    color: white; border-radius: var(--radius-md); padding: var(--space-4) var(--space-5);
    font-size: var(--text-sm);
    pointer-events: none; box-shadow: var(--shadow-3); max-width: 240px;
}
.ap-tt-date { font-size: var(--text-xs); color: var(--ink-6); }
.ap-tt-title { font-weight: var(--font-weight-bold); margin: var(--space-1) 0; }
.ap-tt-prog { font-size: var(--text-xs); color: var(--ink-7); }
.ap-tt-pos { font-size: var(--text-base); font-weight: var(--font-weight-bold); margin-top: var(--space-2); color: var(--warning-color); }
.ap-tt-hint { font-size: var(--text-micro); color: var(--ink-6); font-style: italic; margin-top: var(--space-2); }

.ap-toggle { margin-left: auto; font-size: var(--text-xs); font-weight: var(--font-weight-medium); color: var(--ink-5); text-transform: none; letter-spacing: 0; display: inline-flex; align-items: center; gap: var(--space-2); cursor: pointer; }
.ap-toggle input { margin: 0; }

@media (max-width: 480px) {
    .ap-best-tier { max-width: 100px; font-size: 8.5px; letter-spacing: 0.3px; padding: var(--space-1) var(--space-3); }
    .ap-race-group-label { font-size: var(--text-xs); letter-spacing: 0.3px; }
}

/* ========================================
   RACE HISTORY (.rh-*)
   ======================================== */
#raceHistorySection { display: none; }
#raceHistorySection[style*="block"] { display: block !important; }
#raceHistoryContent { background: var(--bg-surface); border-radius: var(--radius-xl); padding: var(--space-4); }

.rh-header {
    padding: var(--space-6);
    background: linear-gradient(135deg, var(--amber-bg), var(--surface));
    border-radius: var(--radius-xl); margin-bottom: var(--space-6);
}
.rh-title { font-size: var(--text-xl); font-weight: var(--font-weight-black); color: var(--ink-2); margin: 0 0 var(--space-1); }
.rh-sub { font-size: var(--text-base); color: var(--amber-fg); font-weight: var(--font-weight-semibold); margin-bottom: var(--space-1); }
.rh-meta { font-size: var(--text-sm); color: var(--ink-5); }
/* .rh-divpill geometry + active come from the shared `.switcher`; only the
   spacing around the group is local. */
.rh-divfilter { margin-top: var(--space-4); }
/* Metric pills inside the trend section sit between title and chart. */
.rh-trendmodes { margin-top: 0; margin-bottom: var(--space-3); }
@media (min-width: 769px) {
    .rh-title { font-size: var(--text-3xl); }
    .rh-sub { font-size: var(--text-md); }
    .rh-meta { font-size: var(--text-md); }
}

/* Course-record cards. Discipline is signalled by:
   • a 3px left stripe in the discipline colour (.rh-record-card border-left)
   • the eyebrow label in the same colour (inline style on .rh-rec-label)
   No emoji — the time + colour + label do the work. */
.rh-records {
    /* 2×2 on phones so the four records don't stack into a tall column
       (punch-list #9); one row of four from tablet up. */
    display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-3); margin-bottom: var(--space-6);
}
.rh-record-card {
    padding: var(--space-3) var(--space-4);
    background: var(--bg-surface);
    border: 1px solid var(--line-base);
    border-left: 3px solid var(--line-strong);
    border-radius: var(--radius-lg);
}
.rh-rec-body { display: flex; flex-direction: column; min-width: 0; }
.rh-rec-time { font-size: var(--text-lg); font-weight: var(--font-weight-black); color: var(--ink-2); }
.rh-rec-label {
    font-size: var(--text-micro); color: var(--ink-6);
    text-transform: uppercase; letter-spacing: 0.5px; margin-top: 1px;
    font-weight: var(--font-weight-bold);
}
.rh-rec-who { font-size: var(--text-sm); color: var(--ink-4); margin-top: var(--space-1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rh-rec-year { color: var(--ink-6); margin-left: var(--space-2); }
@media (min-width: 769px) {
    .rh-records { grid-template-columns: repeat(4, 1fr); }
    .rh-rec-time { font-size: var(--text-2xl); }
    .rh-rec-label { font-size: var(--text-sm); }
    .rh-rec-who { font-size: var(--text-md); }
}

/* Timeline drilldown card */
.rh-drilldown { margin-top: var(--space-4); }
.rh-dd-card {
    padding: var(--space-5) var(--space-6); background: var(--bg-surface);
    border: 1px solid var(--line-base); border-radius: var(--radius-lg);
}
.rh-dd-head { display: flex; align-items: baseline; gap: var(--space-4); margin-bottom: var(--space-3); }
.rh-dd-year { font-size: var(--text-lg); font-weight: var(--font-weight-black); color: var(--danger-color); }
.rh-dd-venue { font-size: var(--text-base); color: var(--ink-4); }
.rh-dd-stats { display: flex; gap: var(--space-6); font-size: var(--text-sm); color: var(--ink-5); margin-bottom: var(--space-3); flex-wrap: wrap; }
.rh-dd-stats strong { color: var(--ink-2); }
/* The drilldown podium/splits reuse the .rh-yr-podium / .rh-yr-splits display
   so they always match the year-by-year expansion. */

/* .rh-load-btn = .btn + a smaller load pill (hover from .btn:hover). */
.rh-load-btn { padding: var(--space-3) var(--space-6); font-size: var(--text-sm); }

/* Legends — shared visual language with ap-legend */
.rh-legend,
.rh-ladder-legend {
    display: flex; gap: var(--space-6); flex-wrap: wrap; margin-top: var(--space-3);
    padding: var(--space-2) var(--space-4);
    font-size: var(--text-xs); color: var(--ink-5);
}
.rh-leg { display: inline-flex; align-items: center; gap: var(--space-2); }
.rh-leg-line { width: 18px; height: 3px; border-radius: 2px; display: inline-block; }
.rh-leg-line.rh-dashed {
    /* Dash colour comes from the swatch's own --lc (set inline) so each dashed
       legend entry matches its line — currentColor made every dashed swatch
       identical, so Top 10 vs Median couldn't be told apart. */
    background-image: linear-gradient(to right, var(--lc, currentColor) 50%, transparent 50%);
    background-size: 6px 100%;
    background-color: transparent;
}
.rh-leg-box { width: 12px; height: 10px; border-radius: 2px; display: inline-block; }
.rh-dot-selected { stroke: var(--warning-color) !important; stroke-width: 2.5 !important; }

/* Podium-regulars rows */
.rh-podium-row {
    display: grid; grid-template-columns: auto 1fr auto;
    gap: var(--space-5); align-items: center; padding: var(--space-3) var(--space-5);
    background: var(--bg-surface); border: 1px solid var(--line-base); border-radius: var(--radius-md);
    margin-bottom: var(--space-2);
}
.rh-pr-medals { font-size: var(--text-md); line-height: 1; }

.rh-pr-years { font-size: var(--text-xs); color: var(--ink-6); }
@media (min-width: 769px) {
    .rh-pr-name { font-size: var(--text-md); }
    .rh-pr-years { font-size: var(--text-base); }
}

/* Career-placings / team / repeat-starters grids.
   These ARE the season-standings matrix — competitors down, events across, a
   placing in the cell — so they wear `.t100-mx` (see the standings block) and
   only layer on the heat fill and the click affordance. The host supplies the
   panel and the height cap; `.t100-mx-scroll` handles sideways scroll and must
   not keep its edge-to-edge bleed once it's inside that bordered panel. */
/* No horizontal padding. A sticky column pins to the scrollport's PADDING edge
   while the table scrolls out to the BORDER edge, so any side padding leaves
   the pinned name column offset from the cells it's meant to sit against, with
   a sliver of the scrolled row showing past it. */
.rh-grid-host {
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    background: var(--bg-surface); border-radius: var(--radius-md);
    padding: 0; border: 1px solid var(--line-soft);
}
/* One scroll container, not two. `.t100-mx-scroll` is its own horizontal
   scroller for the standings page, but nested inside the host — which already
   scrolls vertically for the long list — it left two overlapping horizontal
   scrollers and a swipe could stall partway along the year columns. The host
   takes both axes; the inner wrapper keeps its bleed margin off in here too. */
.rh-grid-host .t100-mx-scroll { margin: 0; overflow: visible; }
.rh-cell {
    cursor: pointer;
    font-size: var(--text-xs); font-weight: var(--font-weight-bold); color: var(--ink-2);
}
.rh-cell:hover { outline: 2px solid var(--primary-color); }
.rh-cell-empty { cursor: default; background: transparent; }
.rh-cell-empty:hover { outline: none; }
/* Heat tiers — bg+ink pairs from tokens (see --heat-* in tokens.css) so both
   grids read in light AND dark; never inline hexes on cells. */
.rh-heat-gold   { background: var(--heat-gold-bg);   color: var(--heat-gold-ink); }
.rh-heat-silver { background: var(--heat-silver-bg); color: var(--heat-silver-ink); }
.rh-heat-bronze { background: var(--heat-bronze-bg); color: var(--heat-bronze-ink); }
.rh-heat-b2     { background: var(--heat-blue-2-bg); color: var(--heat-blue-2-ink); }
.rh-heat-b1     { background: var(--heat-blue-1-bg); color: var(--heat-blue-1-ink); }
.rh-heat-b0     { background: var(--heat-blue-0-bg); color: var(--heat-blue-0-ink); }
.rh-heat-pct0   { background: var(--heat-pct-0-bg);  color: var(--heat-pct-0-ink); }
.rh-heat-pct1   { background: var(--heat-pct-1-bg);  color: var(--heat-pct-1-ink); }
.rh-heat-pct2   { background: var(--heat-pct-2-bg);  color: var(--heat-pct-2-ink); }
.rh-heat-pct3   { background: var(--heat-pct-3-bg);  color: var(--heat-pct-3-ink); }
.rh-heat-pct4   { background: var(--heat-pct-4-bg);  color: var(--heat-pct-4-ink); }
.rh-heat-pct5   { background: var(--heat-pct-5-bg);  color: var(--heat-pct-5-ink); }
@media (min-width: 769px) {
    .rh-cell { width: 30px; height: 26px; font-size: var(--text-base); }
}

/* Year-by-year accordion */
.rh-year-row {
    background: var(--bg-surface); border: 1px solid var(--line-base);
    border-radius: var(--radius-md); margin-bottom: var(--space-2); overflow: hidden;
}
/* Flex, not a fixed grid: the row carries a variable set of children (an
   optional distance tag, an optional major badge, one or two winner cells), and
   a fixed column count made the extras overflow — the chevron wrapped to a
   second row on the left and the stats got crushed into a sliver (punch-list
   #9). Flex lets the winner absorb the slack and pins the chevron on the right. */
.rh-yr-head {
    display: flex; align-items: center; gap: var(--space-4);
    padding: var(--space-4) var(--space-6);
    cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.rh-yr-head:hover { background: var(--bg-hover); }
.rh-yr-year { flex: none; font-size: var(--text-md); font-weight: var(--font-weight-black); color: var(--ink-2); min-width: 36px; }
.rh-yr-winner { flex: 1 1 0; min-width: 0; font-size: var(--text-sm); color: var(--ink-4); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rh-yr-stats { flex: none; margin-left: auto; font-size: var(--text-xs); color: var(--ink-6); white-space: nowrap; }
.rh-yr-chev { flex: none; font-size: var(--text-sm); color: var(--ink-6); }
.rh-yr-detail {
    display: none; padding: var(--space-4) var(--space-6) var(--space-6);
    border-top: 1px solid var(--bg-tint); background: var(--bg-soft);
}
.rh-year-row.expanded .rh-yr-detail { display: block; }
.rh-yr-podium { width: 100%; font-size: var(--text-base); border-collapse: collapse; margin-bottom: var(--space-3); }
.rh-yr-podium td { padding: 3px var(--space-2); }
/* NCAA: an extra winner cell for the champion team (flexes alongside the
   individual winner) + a team-standings table in the detail. */
.rh-yr-team { font-weight: var(--font-weight-semibold); color: var(--ink-3); }
.rh-yr-team-pts { color: var(--ink-6); font-weight: var(--font-weight-normal); font-variant-numeric: tabular-nums; }
.rh-yr-teams { width: 100%; font-size: var(--text-sm); border-collapse: collapse; margin-bottom: var(--space-3); }
.rh-yr-teams td { padding: 3px var(--space-2); }
.rh-yr-teams-cap { color: var(--ink-6); font-size: var(--text-xs); padding-bottom: var(--space-2); font-weight: var(--font-weight-semibold); }
.rh-yr-team-score { text-align: right; font-variant-numeric: tabular-nums; font-weight: var(--font-weight-bold); color: var(--ink-3); }
.rh-yr-div-tag { display: inline-block; padding: 0 var(--space-2); border-radius: var(--radius-sm); background: var(--bg-hover); font-size: var(--text-xs); font-weight: var(--font-weight-bold); color: var(--ink-5); }
.rh-yr-incomplete { color: var(--warning-color); font-weight: var(--font-weight-bold); }
@media (max-width: 640px) {
    .rh-yr-team { display: none; }   /* shown in the expanded team-standings table */
}
.rh-yr-splits { display: flex; gap: var(--space-5); flex-wrap: wrap; margin-bottom: var(--space-3); }
.rh-yr-split {
    font-size: var(--text-sm); color: var(--ink-3);
    display: inline-flex; align-items: baseline; gap: var(--space-2);
}
.rh-yr-split-who { color: var(--ink-6); font-weight: var(--font-weight-normal); }
@media (min-width: 769px) {
    .rh-yr-winner { font-size: var(--text-md); }
    .rh-yr-stats { font-size: var(--text-base); }
}

/* ── Tier ladder ("where this race sits") ── */
.rh-ladder-section { overflow: hidden; }
.rh-ladder { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.rh-ladder svg { display: block; }
/* Legend geometry is .rh-legend above; only the swatch shapes are local. */
.rh-leg-diamond {
    width: 9px; height: 9px; display: inline-block;
    transform: rotate(45deg); border: 1px solid var(--chart-halo);
}
.rh-leg-ring {
    width: 12px; height: 12px; display: inline-block; border-radius: 50%;
    border: 2px dashed var(--major-accent);
}

/* ── Ordered-category tints ──────────────────────────────────────────────
   A division or distance class sets --rh-c; the consumer decides whether that
   lands as text, a swatch or an SVG mark.

   These MUST resolve in CSS. Colours read through cssVar() in JS and written
   as inline style freeze at whichever theme was live when the section rendered,
   and switching theme does not re-render this page — which left DI wearing the
   light ramp's near-black purple on the dark ground, at about 1.2:1.

   The discipline colours alongside these (swim/bike/run on the record labels)
   stay inline on purpose: they're identical in both themes, so they cannot go
   stale the way a ramp step can. */
.rh-c-di   { --rh-c: var(--rh-div-3); }
.rh-c-dii  { --rh-c: var(--rh-div-2); }
.rh-c-diii { --rh-c: var(--rh-div-1); }
.rh-c-super-sprint { --rh-c: var(--rh-dist-1); }
.rh-c-sprint       { --rh-c: var(--rh-dist-2); }
.rh-c-standard     { --rh-c: var(--rh-dist-3); }
.rh-c-middle       { --rh-c: var(--rh-dist-4); }
.rh-c-long         { --rh-c: var(--rh-dist-5); }
.rh-c-none, .rh-c-unknown { --rh-c: var(--ink-6); }
/* Discipline tints use the INK role: these land on the records captions as
   small bold text, where the mark hues sit under 4.5:1 on white. */
.rh-c-overall { --rh-c: var(--warning-color); }
.rh-c-swim    { --rh-c: var(--seg-swim-ink); }
.rh-c-bike    { --rh-c: var(--seg-bike-ink); }
.rh-c-run     { --rh-c: var(--seg-run-ink); }

.rh-ink    { color: var(--rh-c); }
.rh-swatch { background: var(--rh-c); }
.rh-line   { stroke: var(--rh-c); }
.rh-mark   { fill: var(--rh-c); }

/* ── NCAA records as a division matrix ──
   Same signal as the single .rh-record-card (colour + label + time), laid out
   one column per division because each division crowns its own champion. The
   host is the bordered panel; the discipline colour arrives inline on the
   eyebrow, exactly as it does on the cards. */
.rh-records--table {
    display: block;
    background: var(--bg-surface);
    border: 1px solid var(--line-base);
    border-radius: var(--radius-lg);
    overflow: hidden;
}
/* Equal thirds so the columns don't resize as names change length. */
.rh-rec-table { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: var(--text-base); }
.rh-rec-table th, .rh-rec-table td {
    padding: var(--space-3); text-align: left;
    vertical-align: top; overflow-wrap: anywhere;
}
/* Each metric is a full-width caption ABOVE its row of divisions rather than a
   left-hand column. As a column it took a third of a 390px table, leaving ~90px
   per name — enough to wrap almost every surname and push the crest onto its
   own line, so some cells ran three lines deep and others one. */
.rh-rect-group th {
    padding-top: var(--space-6); padding-bottom: var(--space-1);
    border-top: 1px solid var(--line-soft);
}
.rh-rec-table tbody tr:first-child th { border-top: 0; padding-top: var(--space-2); }
/* Division headers + metric captions are eyebrows, matching .rh-rec-label. */
.rh-rec-table thead th,
.rh-rect-label {
    font-size: var(--text-micro); font-weight: var(--font-weight-bold);
    text-transform: uppercase; letter-spacing: 0.5px; white-space: nowrap;
}
.rh-rec-table thead th { padding-bottom: var(--space-2); }
.rh-rect-time {
    font-variant-numeric: tabular-nums;
    font-size: var(--text-md); font-weight: var(--font-weight-black); color: var(--ink-2);
}
.rh-rect-who { font-size: var(--text-sm); color: var(--ink-4); margin-top: var(--space-1); }
.rh-rect-meta {
    display: flex; align-items: center; gap: var(--space-2);
    margin-top: var(--space-1); min-height: 1.3em;
}
.rh-rect-meta .rh-rec-year { margin-left: 0; }
.rh-rect-empty { color: var(--ink-6); }
@media (min-width: 769px) {
    .rh-rec-table th, .rh-rec-table td { padding: var(--space-4) var(--space-5); }
    .rh-rect-label, .rh-rec-table thead th { font-size: var(--text-sm); }
    .rh-rect-who { overflow-wrap: normal; }
    .rh-rect-time { font-size: var(--text-lg); }
    .rh-rect-who { font-size: var(--text-base); }
}

/* ── Heat / category strip in the loaded race view ──
   The buttons inside are the shared `.switcher`; only the strip is local. */
.heat-strip {
    display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap;
    margin: 0 0 var(--space-5); padding: var(--space-4) var(--space-5);
    background: var(--bg-soft); border: 1px solid var(--line-base); border-radius: var(--radius-lg);
}
.heat-strip-label {
    font-size: var(--text-eyebrow); font-weight: var(--font-weight-bold);
    letter-spacing: 0.5px; text-transform: uppercase; color: var(--ink-4);
}

/* ── Achievement / team place grids ──
   Ranked rows, cells = finishing place. Capped height + scroll so the leaders
   sit in view and the long tail is a scroll away; the header row stays pinned.
   Panel chrome comes from .rh-grid-host — only the cap is added here. */
.rh-heatdiv { margin-bottom: var(--space-4); }
.rh-grid-block { margin-bottom: var(--space-8); }
.rh-grid-block .rh-grid-host,
.rh-grid-host--capped { max-height: 320px; overflow: auto; }
/* Header pins while the list scrolls. z-index 4 clears the matrix's own sticky
   name/total columns (2), but the two header cells that are sticky in BOTH
   directions have to sit above their own row or the scrolling year headers
   slide over them and blank out "Won". */
.rh-grid-block thead th,
.rh-grid-host--capped thead th { position: sticky; top: 0; z-index: 4; }
.rh-grid-host .t100-mx thead .mx-name { z-index: 5; }
/* Cells here are the tap target (they load that year's race), unlike standings
   where the whole row is. Scrolling one into view must leave it clear of the
   pinned name/total columns — without this, a cell scrolled to the edge sits
   BEHIND the pinned column and swallows the tap. Width mirrors the <colgroup>
   in _renderPlaceGrid. */
.rh-grid-host .t100-mx { --mx-name-w: 168px; }
.rh-grid-host .t100-mx .mx-cell {
    scroll-margin-left: calc(var(--mx-name-w) + var(--space-3));
}
/* The athlete count sits where the standings say "Athlete" — same slot, so it
   keeps the eyebrow treatment rather than reading as a column name. */
.rh-grid-host .t100-mx thead .mx-name {
    font-weight: var(--font-weight-semibold); color: var(--ink-5);
}
/* Division caption above each grid — eyebrow, colour inline per division. */
.rh-grid-head {
    font-size: var(--text-eyebrow); font-weight: var(--font-weight-bold);
    text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: var(--space-2);
}
.rh-grid--ach { font-variant-numeric: tabular-nums; }
.rh-cell-win { font-weight: var(--font-weight-black); box-shadow: inset 0 0 0 2px var(--major-accent); }

/* ── Major-championship badge on a year row ── */
.rh-major-badge {
    display: inline-flex; align-items: center; gap: var(--space-2);
    font-size: var(--text-micro); font-weight: var(--font-weight-bold);
    border-radius: var(--radius-pill);
    padding: 1px var(--space-4); margin-left: var(--space-3); vertical-align: middle;
}
.rh-major-badge.rh-major-olympic { background: var(--badge-amber-bg);  color: var(--badge-amber-ink); }
.rh-major-badge.rh-major-gf      { background: var(--badge-violet-bg); color: var(--badge-violet-ink); }
.rh-major-badge.rh-major-worlds  { background: var(--badge-blue-bg);   color: var(--badge-blue-ink); }
.rh-major-badge.rh-major-olyqual { background: var(--badge-cyan-bg);   color: var(--badge-cyan-ink); }

/* ========================================
   HEAD-TO-HEAD (.h2h-*)
   ======================================== */
.h2h-page { padding: var(--space-4) var(--space-5) var(--space-16); display: flex; flex-direction: column; gap: var(--space-7); }
.h2h-flag .flag-img { width: 34px; height: 25px; border-radius: var(--radius-sm); vertical-align: 0; }
.h2h-duel-relay .h2h-flag .flag-img { width: 44px; height: 33px; }

.h2h-pos-toggle { margin-bottom: var(--space-4); }
.h2h-win { font-weight: var(--font-weight-bold); }
.h2h-shared-row-wrap.expanded .h2h-sr-detail { display: block; }

.h2h-sr-splits-tbl { width: 100%; border-collapse: collapse; font-size: var(--text-base); }
.h2h-sr-splits-tbl th { font-size: var(--text-sm); font-weight: var(--font-weight-bold); padding: var(--space-2); text-align: center; }
.h2h-sr-splits-tbl td { padding: 3px var(--space-2); text-align: center; }
.h2h-srd-seg { text-align: left !important; font-weight: var(--font-weight-semibold); }
.h2h-srd-total td { border-top: 1px solid var(--line-base); font-weight: var(--font-weight-bold); }
.h2h-row-chev.empty { visibility: hidden; }
.h2h-row-wrap.expanded .h2h-row-detail { display: block; }


.h2h-never-met, .h2h-empty { text-align: center; padding: var(--space-16) var(--space-10); }
.h2h-never-icon, .h2h-empty-icon { font-size: 40px; margin-bottom: var(--space-4); }
.h2h-empty h2 { font-size: var(--text-xl); color: var(--ink-3); margin: var(--space-4) 0; }
.h2h-empty p { font-size: var(--text-md); color: var(--ink-5); margin: var(--space-2) 0; }
.h2h-hint { font-size: var(--text-base); color: var(--ink-6); font-style: italic; }
.h2h-muted { color: var(--ink-6); font-size: var(--text-base); padding: var(--space-4) 0; text-align: center; }
.h2h-pulse { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); animation: h2h-pulse-k 1s ease infinite; }
@keyframes h2h-pulse-k { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } }

.h2h-relay-details { margin-top: var(--space-6); }
.h2h-leg-panel {
    border: 1px solid var(--line-base); border-radius: var(--radius-lg);
    margin-bottom: var(--space-3); overflow: hidden;
}
.h2h-leg-head {
    display: flex; align-items: center; gap: var(--space-4);
    padding: var(--space-4) var(--space-6); cursor: pointer;
    background: var(--bg-hover); transition: background var(--transition-fast);
}
.h2h-leg-head:hover { background: var(--bg-tint); }
.h2h-leg-label { font-weight: var(--font-weight-bold); font-size: var(--text-md); min-width: 80px; }
.h2h-leg-athletes { flex: 1; font-size: var(--text-base); }
.h2h-leg-vs-txt { color: var(--ink-6); margin: 0 var(--space-2); font-size: var(--text-xs); }
.h2h-leg-delta { font-weight: var(--font-weight-bold); font-size: var(--text-base); }
.h2h-leg-chev { font-size: var(--text-md); color: var(--ink-6); transition: transform var(--transition-fast); user-select: none; }
.h2h-leg-body { display: none; padding: var(--space-4) var(--space-6) var(--space-6); }
.h2h-leg-panel.expanded .h2h-leg-body { display: block; }

.h2h-relay-rosters { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-6); margin-bottom: var(--space-6); }
@media (max-width: 480px) { .h2h-relay-rosters { grid-template-columns: 1fr; } }
.h2h-relay-roster { padding: var(--space-4) var(--space-5); background: var(--bg-hover); border-radius: var(--radius-md); }
.h2h-relay-roster-title { font-weight: var(--font-weight-bold); font-size: var(--text-md); margin-bottom: var(--space-2); }
.h2h-relay-member { font-size: var(--text-base); padding: var(--space-1) 0; }
.h2h-relay-leg-num {
    display: inline-block; font-weight: var(--font-weight-bold); font-size: var(--text-micro);
    background: var(--surface-tint); border-radius: var(--radius-sm);
    padding: 0 var(--space-2); margin-right: var(--space-2); letter-spacing: 0.04em;
}

.h2h-history-footer {
    display: flex; flex-wrap: wrap; gap: var(--space-4); align-items: center;
    margin-top: var(--space-6); padding-top: var(--space-5);
    border-top: 1px solid var(--bg-tint);
}
.h2h-more-btn {
    padding: var(--space-3) var(--space-6); font-size: var(--text-base);
    font-weight: var(--font-weight-semibold);
    color: var(--ink-4); background: var(--bg-hover);
    border: 1px solid var(--line-base); border-radius: var(--radius-md);
    cursor: pointer; transition: all var(--transition-fast);
}
.h2h-more-btn:hover:not(:disabled) { background: var(--bg-tint); border-color: var(--line-strong); }
.h2h-more-btn:disabled { opacity: 0.55; cursor: default; }
.h2h-history-summary { margin-left: auto; font-size: var(--text-sm); color: var(--ink-6); }

/* Cross-source link suggestions inside the Shared-Races section. Shown
   per athlete when a same-name candidate exists in the other registry
   but no confirmed link is on file yet. */
.h2h-link-suggestions {
    display: flex; flex-direction: column; gap: var(--space-2);
    margin-bottom: var(--space-5);
}
.h2h-link-suggest {
    display: flex; align-items: center; gap: var(--space-4);
    padding: var(--space-3) var(--space-5);
    background: var(--indigo-bg);
    border: 1px dashed var(--indigo-border);
    border-radius: var(--radius-lg);
    font-size: var(--text-sm); color: var(--ink-3);
}
.h2h-link-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.h2h-link-text { flex: 1; min-width: 0; }
.h2h-link-text strong { color: var(--ink-2); }
.h2h-link-text em { font-style: normal; font-weight: var(--font-weight-semibold); color: var(--indigo-fg); }
/* .h2h-link-confirm = .btn + compact inline pill; hover unified to accent-dark. */
.h2h-link-confirm {
    flex-shrink: 0;
    padding: var(--space-2) var(--space-5);
    font-size: var(--text-sm);
    transition: background var(--transition-fast);
}
.h2h-link-confirm:hover:not(:disabled) { background: var(--primary-dark); }
.h2h-link-confirm:disabled { opacity: 0.6; cursor: default; }
@media (min-width: 769px) {
    .h2h-link-suggest { font-size: var(--text-md); padding: var(--space-4) var(--space-6); }
    .h2h-link-confirm { font-size: var(--text-md); padding: var(--space-3) var(--space-6); }
}
@media (min-width: 769px) {
    .h2h-history-summary { font-size: var(--text-base); }
}

/* ════════════════════════════════════════════════════════════
   HEAD-TO-HEAD REDESIGN (one chart → drill-down)
   ------------------------------------------------------------
   Single shared-race timeline with a hero record above and a
   click-to-open detail drawer below. Reuses the chip, leg-panel,
   roster, split-table and link-suggestion primitives above; only
   the layout shell is new. All values come from design tokens so
   dark mode falls out for free.
   ════════════════════════════════════════════════════════════ */

.h2h-empty-icon { color: var(--ink-6); line-height: 0; }

/* ── Hero: two athlete columns flanking the win-loss record ── */
.h2h-hero {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--space-4);
}
.h2h-hero-side {
    display: flex; flex-direction: column; align-items: center;
    text-align: center; gap: var(--space-1); min-width: 0;
}
.h2h-hero-flag { line-height: 0; }
.h2h-hero-flag .flag-img { width: 34px; height: 25px; border-radius: var(--radius-sm); }
.h2h-hero-name {
    font-size: var(--text-md); font-weight: var(--font-weight-black);
    line-height: var(--line-tight); overflow-wrap: anywhere;
}
.h2h-hero-noc {
    display: inline-flex; align-items: center; gap: var(--space-2);
    font-size: var(--text-sm); color: var(--ink-5);
}
.h2h-hero-mid { text-align: center; padding: 0 var(--space-3); }
.h2h-hero-record {
    display: flex; align-items: baseline; justify-content: center; gap: var(--space-2);
    font-size: var(--text-display); font-weight: var(--font-weight-black);
    font-variant-numeric: tabular-nums; line-height: 1;
}
.h2h-hero-dash { color: var(--ink-7); font-weight: var(--font-weight-normal); }
.h2h-hero-sub { font-size: var(--text-sm); color: var(--ink-5); margin-top: var(--space-2); }
@media (min-width: 769px) {
    .h2h-hero-name { font-size: var(--text-lg); }
    .h2h-hero-flag .flag-img { width: 42px; height: 31px; }
}

/* ── Chart card: header (metric chips + status) over a scrollable plot.
   min-width:0 + overflow-x on the scroller keep a wide SVG (many shared
   relay races) scrolling INSIDE the card instead of stretching the page
   — the flexbox blow-out that made relays impossible to scroll. ── */
.h2h-chart-card {
    background: var(--bg-surface);
    border: 1px solid var(--line-base);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
    min-width: 0; overflow: hidden;
}
.h2h-chart-head {
    display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap;
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--line-soft);
}
.h2h-metric-chips { flex-shrink: 1; }
.h2h-chart-status {
    display: inline-flex; align-items: center; gap: var(--space-2);
    margin-left: auto; font-size: var(--text-sm); color: var(--ink-5);
}
.h2h-chart-status:empty { display: none; }
/* Frozen y-axis gutter + horizontally scrolling plot. The gutter (.h2h-chart-axis)
   sits outside the scroll area so the scale stays visible at any scroll offset. */
.h2h-chart-scroll {
    position: relative;
    display: flex; align-items: flex-start; overflow: hidden;
    padding: var(--space-3) 0 var(--space-4) var(--space-2);
}
/* Right-edge fade hints that the timeline scrolls (toggled from JS on scroll
   state). Only the right side — the left is the frozen axis, not a scroll edge. */
.h2h-chart-scroll::after {
    content: ''; position: absolute; top: 0; bottom: 0; right: 0; width: 24px;
    pointer-events: none; opacity: 0; transition: opacity var(--transition-fast); z-index: 1;
    background: linear-gradient(to right, transparent, var(--bg-surface));
}
.h2h-chart-scroll.more-right::after { opacity: 1; }
.h2h-chart-axis { flex: 0 0 auto; }
.h2h-chart-plot {
    flex: 1 1 auto; min-width: 0;
    overflow-x: auto; overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    /* Let a horizontal drag scroll the plot reliably (default touch-action
       biases the page's vertical scroll and makes horizontal panning fiddly);
       contain stops the scroll chaining into back-nav / page bounce. */
    touch-action: pan-x;
    overscroll-behavior-x: contain;
    padding-right: var(--space-4);
}

/* ── Detail drawer — opens under the chart when a race is tapped ── */
.h2h-detail-host { min-width: 0; }
.h2h-detail-hint {
    text-align: center; color: var(--ink-6);
    font-size: var(--text-base); font-style: italic;
    padding: var(--space-6) var(--space-5);
    border: 1px dashed var(--line-base); border-radius: var(--radius-lg);
}
.h2h-detail-card {
    background: var(--bg-surface);
    border: 1px solid var(--line-base);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
    overflow: hidden;
}
.h2h-detail-head {
    display: flex; align-items: flex-start; gap: var(--space-4); flex-wrap: wrap;
    padding: var(--space-5) var(--space-5) var(--space-4);
    border-bottom: 1px solid var(--line-soft);
}
.h2h-detail-title { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; flex: 1; }
.h2h-detail-title strong { font-size: var(--text-md); font-weight: var(--font-weight-bold); color: var(--ink-2); }
/* Loadable race title doubles as the "load this race" link (no separate button). */
.h2h-title-link {
    align-self: flex-start; max-width: 100%;
    background: none; border: 0; padding: 0;
    font: inherit; font-size: var(--text-md); font-weight: var(--font-weight-bold);
    text-align: left; white-space: normal;
    color: var(--accent); cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: color var(--transition-fast);
}
.h2h-title-link .h2h-title-txt {
    text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px;
}
/* open-in glyph signals the title loads the whole race (not just a link) */
.h2h-title-open {
    width: 0.82em; height: 0.82em; flex: none;
    margin-left: 0.35em; vertical-align: -0.1em;
    fill: none; stroke: currentColor; stroke-width: 2;
    stroke-linecap: round; stroke-linejoin: round;
}
.h2h-title-link:hover, .h2h-title-link:focus-visible { color: var(--primary-dark); outline: none; }
.h2h-detail-date { font-size: var(--text-sm); font-weight: var(--font-weight-bold); color: var(--ink-5); }
.h2h-detail-prog { font-size: var(--text-sm); color: var(--ink-5); }
.h2h-chip-now {
    align-self: flex-start;
    font-size: var(--text-micro); font-weight: var(--font-weight-bold);
    text-transform: uppercase; letter-spacing: 0.05em;
    color: var(--accent); background: var(--surface-tint);
    padding: 1px var(--space-3); border-radius: var(--radius-pill);
}
/* Result sits on its own line below the title (order:3 + full-width basis) so a
   long race title keeps the whole width and doesn't wrap early.
   Two rows — places above, times below — around a centred "v": each side hugs
   the divider, so a place never reads as part of the neighbouring time. */
.h2h-detail-res {
    order: 3; flex: 1 0 100%;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    grid-template-areas:
        "posA  vs  posB"
        "timeA gap timeB";
    align-items: baseline;
    column-gap: var(--space-8); row-gap: var(--space-2);
    margin-top: var(--space-2);
    font-variant-numeric: tabular-nums;
}
.h2h-res-pos { font-size: var(--text-md); font-weight: var(--font-weight-black); }
.h2h-res-time { font-size: var(--text-base); font-weight: var(--font-weight-semibold); }
.h2h-res-pos-a, .h2h-res-time-a { text-align: right; }
.h2h-res-pos-b, .h2h-res-time-b { text-align: left; }
.h2h-res-pos-a { grid-area: posA; }
.h2h-res-pos-b { grid-area: posB; }
.h2h-res-time-a { grid-area: timeA; }
.h2h-res-time-b { grid-area: timeB; }
.h2h-detail-vs {
    grid-area: vs; text-align: center;
    font-size: var(--text-base); font-weight: var(--font-weight-normal);
    color: var(--ink-6);
}
.h2h-detail-gap {
    grid-area: gap; text-align: center; white-space: nowrap;
    font-size: var(--text-sm); font-weight: var(--font-weight-bold);
    color: var(--ink-4);
}
.h2h-detail-actions { display: flex; align-items: center; gap: var(--space-3); margin-left: auto; }
.h2h-detail-close {
    --icon-btn-size: 28px; --icon-btn-radius: var(--radius-md);
    border-color: var(--line-base); background: var(--bg-surface);
    color: var(--ink-5); font-size: var(--text-md);
}
.h2h-detail-modes { display: flex; gap: var(--space-2); flex-wrap: wrap; padding: var(--space-4) var(--space-5) 0; }
.h2h-detail-body { padding: var(--space-5); }
.h2h-pos-chart-host { min-width: 0; overflow-x: auto; }

/* ========================================
   NCAA → WT CROSSOVER (.xo-*)
   ======================================== */
.xo-banner {
    display: flex; align-items: center; gap: var(--space-4);
    padding: var(--space-5) var(--space-6); border-radius: var(--radius-lg);
    font-size: var(--text-base); margin-bottom: var(--space-5); line-height: 1.5;
}
.xo-suggested {
    background: var(--warning-bg);
    border: 1px solid var(--warning); color: var(--warning);
}
.xo-confirmed {
    background: var(--success-bg);
    border: 1px solid var(--success); color: var(--success);
}
.xo-icon { font-size: var(--text-xl); flex-shrink: 0; }
.xo-team { color: inherit; opacity: 0.7; }
.xo-yob { font-size: var(--text-sm); color: inherit; opacity: 0.6; }
.xo-banner .team-logo { height: 16px; vertical-align: text-bottom; }
@media (min-width: 769px) { .xo-banner { font-size: var(--text-md); padding: var(--space-6) var(--space-8); } }

.xo-stats { display: flex; gap: var(--space-7); margin-bottom: var(--space-4); flex-wrap: wrap; }
.xo-stat { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--ink-4); }
.xo-stat-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.xo-stat-val { font-weight: var(--font-weight-bold); font-size: var(--text-base); }
.xo-stat-lbl { color: var(--ink-6); }
@media (min-width: 769px) {
    .xo-stat { font-size: var(--text-md); }
    .xo-stat-val { font-size: var(--text-lg); }
}

.xo-actions { display: flex; gap: var(--space-4); flex-wrap: wrap; }
.xo-btn {
    padding: var(--space-4) var(--space-8); border-radius: var(--radius-lg);
    border: 1px solid; font-size: var(--text-base); font-weight: var(--font-weight-semibold);
    cursor: pointer; min-height: 40px;
    -webkit-tap-highlight-color: transparent;
    transition: background var(--transition-fast), border-color var(--transition-fast);
}
.xo-confirm { background: var(--success); color: var(--on-accent); border-color: var(--success); }
.xo-confirm:hover { filter: brightness(0.92); }
.xo-confirm:active { filter: brightness(0.84); }
.xo-dismiss { background: var(--bg-surface); color: var(--ink-5); border-color: var(--line-strong); }
.xo-dismiss:hover { background: var(--bg-hover); border-color: var(--ink-6); }
.xo-unlink {
    background: var(--bg-surface); color: var(--ink-6); border-color: var(--line-base);
    font-size: var(--text-sm); padding: var(--space-2) var(--space-6); min-height: 32px;
}
.xo-unlink:hover { color: var(--danger); border-color: var(--danger); background: var(--danger-bg); }
/* A dismissed suggestion, kept as one quiet line so the decision stays visible
   and reversible. It used to hide the whole section, which made "not this
   person" indistinguishable from "no match found" and undoable only from the
   console. Deliberately the quietest thing in the panel — it is a footnote,
   not an offer. */
.xo-dismissed {
    display: flex; align-items: center; gap: var(--space-5); flex-wrap: wrap;
    font-size: var(--text-sm); color: var(--ink-6);
}
.xo-dismissed strong { color: var(--ink-4); font-weight: var(--font-weight-semibold); }
.xo-restore {
    background: transparent; color: var(--ink-5); border-color: var(--line-base);
    font-size: var(--text-sm); padding: var(--space-2) var(--space-6); min-height: 32px;
    margin-left: auto;
}
.xo-restore:hover { color: var(--primary-color); border-color: var(--primary-color); }
@media (min-width: 769px) { .xo-btn { font-size: var(--text-md); padding: var(--space-5) var(--space-10); } }

.xo-legend {
    display: flex; gap: var(--space-7); align-items: center;
    padding: var(--space-3) var(--space-4); font-size: var(--text-xs);
    color: var(--ink-5); flex-wrap: wrap;
}
.xo-leg { display: inline-flex; align-items: center; gap: var(--space-2); font-weight: var(--font-weight-semibold); }
.xo-leg-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.xo-leg-note { margin-left: auto; font-weight: var(--font-weight-normal); font-style: italic; color: var(--ink-6); }
@media (min-width: 769px) { .xo-legend { font-size: var(--text-base); } }

/* ========================================
   REDUCED MOTION
   ======================================== */
@media (prefers-reduced-motion: reduce) {
    .athlete-path, .athlete-circle { transition: none !important; }
}

/* ========================================
   T100 STANDINGS (.t100-*)
   ----------------------------------------
   Purple/violet season-story theme. The page is visualization-first:
   the only table-shaped element is the calendar. Everything else is a
   d3 chart or a click-to-drill card.
   ======================================== */
/* Plain title block (no card) for the T100 season page. */
.t100-title { margin: 0 0 var(--space-5); }
.t100-title-sub { color: var(--primary-dark); }
.t100-venue-link { margin-top: var(--space-3); }

/* ── Gender toggle — a shared `.switcher`; only the spacing is local. ── */
.t100-gender-toggle { margin-bottom: var(--space-5); }

/* Hero charts: bleed to the section edges and drop the inner card chrome so
   they read as full-width focal elements rather than boxed thumbnails. */
.t100-season-host, .t100-pc-host {
    margin-left: calc(-1 * var(--space-5));
    margin-right: calc(-1 * var(--space-5));
    padding: var(--space-2) 0 0;
    border-left: none; border-right: none; border-radius: 0;
    background: transparent;
}
@media (min-width: 769px) {
    .t100-season-host, .t100-pc-host {
        margin-left: calc(-1 * var(--space-8));
        margin-right: calc(-1 * var(--space-8));
    }
}

/* ── Season matrix: an app-styled table with a pinned name column (left) and a
   pinned total (right); the round columns scroll between them. ── */
.t100-mx-scroll {
    overflow-x: auto; overflow-y: hidden;
    margin: 0 calc(-1 * var(--space-5));
}
@media (min-width: 769px) { .t100-mx-scroll { margin: 0 calc(-1 * var(--space-8)); } }
.t100-mx {
    border-collapse: collapse; width: 100%;
    font-size: var(--text-sm); color: var(--ink-2);
}
.t100-mx th, .t100-mx td {
    padding: var(--space-3) var(--space-2);
    text-align: center; vertical-align: middle;
}
.t100-mx thead th {
    background: var(--bg-soft); color: var(--ink-5);
    font-size: var(--text-xs); font-weight: var(--font-weight-bold);
    border-bottom: 1px solid var(--line-base); line-height: 1.15;
}
.t100-mx tbody tr { border-bottom: 1px solid var(--line-soft); cursor: pointer; }
.t100-mx tbody tr:last-child { border-bottom: none; }
/* Column widths come from the <colgroup> (equal round columns); table-layout is
   fixed so long venue headers can't stretch their column. */
/* Pinned name (left) + total (right); opaque so scrolled cells pass behind. */
.t100-mx .mx-name {
    position: sticky; left: 0; z-index: 2;
    text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    background: var(--bg-surface); box-shadow: 1px 0 0 var(--line-base);
}
.t100-mx .mx-total {
    position: sticky; right: 0; z-index: 2;
    background: var(--bg-surface); box-shadow: -1px 0 0 var(--line-base);
}
.t100-mx thead .mx-name, .t100-mx thead .mx-total { z-index: 3; background: var(--bg-soft); }
.t100-mx tbody tr:hover .mx-name, .t100-mx tbody tr:hover .mx-total { background: var(--bg-hover); }
.t100-mx tbody tr.sel .mx-name, .t100-mx tbody tr.sel .mx-total { background: var(--surface-selected); }
.mx-rank {
    display: inline-block; min-width: 1.4em; margin-right: var(--space-2);
    color: var(--ink-5); font-weight: var(--font-weight-bold);
    font-variant-numeric: tabular-nums;
}
.t100-mx .mx-round, .t100-mx .mx-cell { padding-left: var(--space-1); padding-right: var(--space-1); }
.mx-cell { font-weight: var(--font-weight-bold); font-variant-numeric: tabular-nums; }
.mx-dns { color: var(--ink-6); }
.mx-drop { opacity: 0.55; }
.mx-round { cursor: pointer; overflow: hidden; }
.mx-vn { display: block; white-space: nowrap; line-height: 1.1; }
.mx-round.mx-gf { color: var(--amber-strong); }
.mx-round.sel { background: var(--accent-subtle); }
.mx-final { display: block; font-size: var(--text-micro); color: var(--major-accent); font-weight: var(--font-weight-bold); }
/* Inline expanded detail row (race-by-race points) under the selected athlete. */
.t100-mx .mx-detail-row td { padding: 0; background: var(--bg-soft); border-bottom: 1px solid var(--line-base); }
.t100-mx .mx-detail-row .t100-drill-races { padding: var(--space-4) var(--space-5); }
.mx-total b { color: var(--primary-dark); font-variant-numeric: tabular-nums; font-size: var(--text-md); }
/* Split total bar: violet counting points + gold Grand Final points. */
.mx-bar-track {
    display: block; height: 7px; margin: 4px auto 0; width: calc(100% - 8px);
    background: var(--bg-tint); border-radius: var(--radius-pill); overflow: hidden;
}
.mx-bar { display: flex; height: 100%; border-radius: var(--radius-pill); overflow: hidden; min-width: 4px; }
.mx-bar-reg { background: var(--primary-color); }
.mx-bar-gf { background: var(--major-accent); }

/* Scoring chart legend (Grand Final / Regular), below the plot. */
.t100-pc-legend {
    display: flex; justify-content: center; flex-wrap: wrap;
    gap: var(--space-6); margin-top: var(--space-3);
    font-size: var(--text-sm); color: var(--ink-3);
}
.t100-pc-leg-item { display: inline-flex; align-items: center; gap: var(--space-2); font-weight: var(--font-weight-semibold); }
.t100-pc-swatch { width: 14px; height: 3px; border-radius: var(--radius-pill); }
/* Fills come from CSS, not from a colour resolved in JS: an inline background
   is captured at render time and this legend is not rebuilt on a theme change. */
.t100-pc-swatch.t100-pc-gf  { background: var(--major-accent); }
.t100-pc-swatch.t100-pc-reg { background: var(--primary-dark); }

/* ── Athlete drill-down panel ── */
.t100-drill { margin-top: var(--space-5); }
.t100-drill-card {
    background: var(--bg-surface); border: 1px solid var(--line-base);
    border-radius: var(--radius-lg); overflow: hidden;
}
.t100-drill-close {
    position: absolute; top: var(--space-3); right: var(--space-4);
    background: transparent; border: 0; font-size: var(--text-xl);
    color: var(--ink-5); cursor: pointer; line-height: 1;
    padding: var(--space-2);
}
.t100-drill-close:hover { color: var(--ink-2); }

.t100-drill-races {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
    gap: var(--space-3); padding: var(--space-5) var(--space-6);
}
.t100-drill-race {
    background: var(--bg-surface);
    border: 1px solid var(--line-base); border-left: 3px solid var(--ink-6);
    border-radius: var(--radius-md);
    padding: var(--space-3) var(--space-5);
    cursor: pointer; text-align: left;
    transition: background var(--transition-fast), border-color var(--transition-fast);
    -webkit-tap-highlight-color: transparent;
    display: flex; flex-direction: column; gap: var(--space-1);
    font: inherit; color: inherit;
}
.t100-drill-race:hover { background: var(--bg-hover); }
.t100-r-counting { border-left-color: var(--primary-color); }
.t100-r-counting:hover { background: var(--accent-subtle); }
.t100-r-gf       { border-left-color: var(--warning-color); background: var(--warning-bg); }
.t100-r-gf:hover { background: var(--badge-amber-bg); }
.t100-r-dropped  { border-left-color: var(--ink-7); opacity: 0.75; }
.t100-r-line {
    display: flex; justify-content: space-between; align-items: baseline;
    gap: var(--space-3);
}
.t100-r-date {
    font-family: var(--font-family-mono);
    font-size: var(--text-xs); color: var(--ink-5);
}
.t100-r-tag {
    font-size: var(--text-micro); color: var(--ink-6);
    text-transform: uppercase; letter-spacing: 0.4px;
    font-weight: var(--font-weight-bold);
}
.t100-r-gf       .t100-r-tag { color: var(--amber-strong); }
.t100-r-counting .t100-r-tag { color: var(--primary-dark); }
.t100-r-pos {
    font-family: var(--font-family-mono);
    font-size: var(--text-md); font-weight: var(--font-weight-bold);
    color: var(--ink-3);
}
.t100-r-pts {
    font-family: var(--font-family-mono);
    font-size: var(--text-sm); color: var(--primary-color);
    font-weight: var(--font-weight-semibold);
}

.t100-drill-footer {
    display: flex; gap: var(--space-3); flex-wrap: wrap;
    align-items: center; padding: var(--space-4) var(--space-7);
    border-top: 1px solid var(--bg-tint); background: var(--bg-soft);
    font-size: var(--text-xs);
}
.t100-drill-hint { margin-left: auto; color: var(--ink-6); font-style: italic; }

/* ── Calendar ── */
.t100-calendar { display: flex; flex-direction: column; gap: var(--space-2); }
.t100-race-row {
    display: grid;
    grid-template-columns: auto 1fr auto;
    gap: var(--space-5); align-items: center;
    padding: var(--space-4) var(--space-5);
    background: var(--bg-surface); border: 1px solid var(--line-base);
    border-radius: var(--radius-md);
    transition: background var(--transition-fast);
}
.t100-race-row.complete { cursor: pointer; }
.t100-race-row.complete:hover { background: var(--bg-hover); border-color: var(--line-strong); }
.t100-race-row.cancelled { opacity: 0.55; text-decoration: line-through; }
.t100-race-row.grand-final { border-left: 4px solid var(--warning-color); background: var(--warning-bg); }
.t100-race-date {
    font-family: var(--font-family-mono); font-size: var(--text-sm);
    color: var(--ink-5); white-space: nowrap;
}
.t100-race-info { min-width: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.t100-race-venue { font-weight: var(--font-weight-semibold); color: var(--ink-2); font-size: var(--text-md); }
.t100-gf-tag {
    margin-left: var(--space-3); font-size: var(--text-micro); font-weight: var(--font-weight-bold);
    color: var(--amber-strong); background: var(--badge-amber-bg);
    padding: 1px var(--space-3); border-radius: var(--radius-sm);
    letter-spacing: 0.5px;
}
.t100-race-winners {
    font-size: var(--text-sm); color: var(--ink-4);
    display: flex; gap: var(--space-5); flex-wrap: wrap;
}
.t100-race-status {
    font-size: var(--text-xs); font-weight: var(--font-weight-semibold);
    color: var(--ink-5); white-space: nowrap;
}
.t100-race-row.complete  .t100-race-status { color: var(--success-color); }
.t100-race-row.pending   .t100-race-status { color: var(--warning-color); }
.t100-race-row.upcoming  .t100-race-status { color: var(--ink-6); }
.t100-race-row.cancelled .t100-race-status { color: var(--danger-color); }

/* ── Points-curve viz ── */
.t100-pc-host { position: relative; }
.t100-pc-tip {
    position: absolute; display: none;
    background: var(--scrim-tooltip); color: var(--on-surface-inverse);
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-sm); font-size: var(--text-xs);
    pointer-events: none; z-index: var(--z-tooltip);
    white-space: nowrap;
}

/* ── Tie-breakers ── */
.t100-tiebreak {
    margin: 0; padding-left: var(--space-10);
    color: var(--ink-3); font-size: var(--text-base); line-height: 1.7;
}
.t100-tiebreak li { margin-bottom: var(--space-1); }

/* ── Race-band drill-down (tap a completed band on the chart) ── */
.t100-rdrill-head {
    display: flex; align-items: baseline; gap: var(--space-4);
    flex-wrap: wrap; padding: var(--space-5) var(--space-7);
    background: linear-gradient(135deg, var(--accent-subtle), var(--surface-tint));
    border-bottom: 1px solid var(--line-base);
    position: relative;
}
.t100-rdrill-venue { font-size: var(--text-lg); font-weight: var(--font-weight-bold); color: var(--ink-2); }
.t100-rdrill-date {
    font-family: var(--font-family-mono); font-size: var(--text-sm); color: var(--ink-5);
}
.t100-rdrill-results { padding: var(--space-3) var(--space-6); }
.t100-rdrill-row {
    display: grid; grid-template-columns: auto auto 1fr auto;
    gap: var(--space-4); align-items: center;
    padding: var(--space-3) var(--space-2);
    border-bottom: 1px solid var(--bg-tint);
}
.t100-rdrill-row:last-child { border-bottom: 0; }
.t100-rdrill-pos {
    font-family: var(--font-family-mono); font-weight: var(--font-weight-bold);
    font-size: var(--text-md); color: var(--ink-5);
    width: 1.6em; text-align: right;
}
.t100-rdrill-pos.pod { color: var(--badge-violet-ink); }
.t100-rdrill-name { font-weight: var(--font-weight-semibold); color: var(--ink-2); font-size: var(--text-md); min-width: 0; }
.t100-rdrill-pts {
    font-family: var(--font-family-mono); font-weight: var(--font-weight-bold);
    font-size: var(--text-md); color: var(--primary-dark);
}
.t100-rdrill-pts.gf { color: var(--amber-strong); }
/* .t100-rdrill-load = .btn + a smaller load pill (hover from .btn:hover). */
.t100-rdrill-load {
    padding: var(--space-3) var(--space-6); font-size: var(--text-sm);
    -webkit-tap-highlight-color: transparent;
    transition: background var(--transition-fast);
}

/* ── Full standings (split reg/GF bars, expandable) ── */
#t100Standings { display: flex; flex-direction: column; gap: var(--space-2); }
.t100-stand-row {
    display: grid; grid-template-columns: auto auto 1fr auto;
    gap: var(--space-4); align-items: center;
    padding: var(--space-3) var(--space-5);
    background: var(--bg-surface); border: 1px solid var(--line-base);
    border-radius: var(--radius-md); cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background var(--transition-fast), border-color var(--transition-fast);
}
.t100-stand-row:hover { background: var(--bg-hover); border-color: var(--line-strong); }
.t100-stand-row.podium { border-left: 3px solid var(--primary-color); }
.t100-sr-rank {
    font-family: var(--font-family-mono); font-size: var(--text-md);
    font-weight: var(--font-weight-bold); color: var(--ink-5);
    width: 1.8em; text-align: center;
}
.t100-stand-row.podium .t100-sr-rank { color: var(--primary-dark); }
.t100-sr-body { min-width: 0; }
.t100-sr-head {
    display: flex; justify-content: space-between; align-items: baseline;
    gap: var(--space-4); margin-bottom: var(--space-2);
}
.t100-sr-total {
    font-family: var(--font-family-mono); font-weight: var(--font-weight-bold);
    font-size: var(--text-lg); color: var(--badge-violet-ink);
}
.t100-sr-bar-track { background: var(--bg-tint); border-radius: var(--radius-pill); height: 8px; overflow: hidden; }
.t100-sr-bar { display: flex; height: 100%; border-radius: var(--radius-pill); overflow: hidden; min-width: 4px; }
.t100-bar-reg { background: var(--primary-color); }
.t100-sr-chev { color: var(--ink-6); font-size: var(--text-sm); }
.t100-sr-detail {
    display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4);
    margin-top: var(--space-3); padding-top: var(--space-3);
    border-top: 1px dashed var(--line-base);
}
/* Author `display:flex` outranks the UA [hidden]{display:none}, so collapse
   needs its own (higher-specificity) author rule to actually hide the row. */
.t100-sr-detail[hidden] { display: none; }
.t100-det-row {
    display: flex; align-items: baseline; gap: var(--space-3);
    font-size: var(--text-xs); color: var(--ink-4);
}
.t100-det-row.dropped { opacity: 0.6; }
.t100-det-venue { color: var(--ink-3); font-weight: var(--font-weight-semibold); }
.t100-det-pts { font-family: var(--font-family-mono); font-weight: var(--font-weight-bold); color: var(--ink-4); }
.t100-det-pts.counting { color: var(--badge-violet-ink); }
.t100-det-pts.gf { color: var(--amber-strong); }
.t100-det-drop { font-size: var(--text-micro); text-transform: uppercase; letter-spacing: 0.4px; color: var(--ink-6); }

/* ── Scoring reference table (behind a <details>) ── */
.t100-scoring-details { margin-top: var(--space-4); }
.t100-scoring-summary {
    cursor: pointer; font-size: var(--text-sm); font-weight: var(--font-weight-semibold);
    color: var(--badge-violet-ink); padding: var(--space-2) 0;
}
.t100-rules-blurb { font-size: var(--text-sm); color: var(--ink-3); line-height: 1.5; margin: var(--space-3) 0; }

/* ── NCAA power rankings ──
   Rides the .t100-stand-row chrome above; these are only the extras that row
   doesn't already carry (a school/starts subline, a division tag, and the
   races-and-weights reference table). */
/* .t100-stand-row's four columns assume a standings row's avatar. These rows
   are rank / body / chevron, so without an explicit template the body would be
   sized to its content and the chevron would absorb the 1fr — leaving every
   row a different width and the bars unreadable against each other. */
.np-row { grid-template-columns: auto 1fr auto; }
.np-sr-sub {
    display: flex; align-items: baseline; gap: var(--space-3);
    margin-top: var(--space-2); font-size: var(--text-xs); color: var(--ink-5);
    min-width: 0;
}
.np-sr-school { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-4); }
.np-noschool { color: var(--ink-6); font-style: italic; }
.np-starts { margin-left: auto; flex: none; font-variant-numeric: tabular-nums; }
.np-div {
    font-size: var(--text-micro); font-weight: var(--font-weight-bold);
    color: var(--ink-6); letter-spacing: 0.4px;
}
/* A race name inside a drill row is a load control, but it sits in a line of
   running text, so it reads as a link rather than wearing a button's chrome. */
.np-race-link {
    background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
    color: var(--ink-3); text-decoration: underline; text-underline-offset: 2px;
}
.np-race-link:hover { color: var(--primary-dark); }
.np-racetable td, .np-racetable th { text-align: left; padding: var(--space-2) var(--space-4); white-space: nowrap; }
.np-racetable .np-rt-w, .np-racetable .np-rt-n { font-variant-numeric: tabular-nums; text-align: right; }
.np-racetable .np-rt-tier, .np-racetable .np-rt-n { color: var(--ink-5); }
.np-info p { margin: 0 0 var(--space-3); line-height: 1.55; }
.np-info p:last-child { margin-bottom: 0; color: var(--ink-5); }
.t100-points-tbl {
    border-collapse: collapse; font-size: var(--text-sm); margin-top: var(--space-3);
}
.t100-points-tbl th, .t100-points-tbl td {
    padding: var(--space-1) var(--space-5); text-align: right; font-variant-numeric: tabular-nums;
}
.t100-points-tbl th { color: var(--ink-5); font-weight: var(--font-weight-bold); border-bottom: 1px solid var(--line-base); }
.t100-points-tbl td { color: var(--ink-3); }
.t100-points-tbl .t100-gf-cell { color: var(--amber-strong); font-weight: var(--font-weight-semibold); }
.t100-points-tbl .t100-overflow td { border-top: 1px solid var(--line-base); color: var(--ink-5); }
/* ── Athlete headshots + flags ──
   Circular photo with a monogram underneath; the <img> removes itself on a
   404 (see _avatar) so the monogram shows through. */
.t100-avatar {
    position: relative; display: inline-flex; flex: none;
    width: 36px; height: 36px; border-radius: 50%; overflow: hidden;
    background: linear-gradient(135deg, var(--accent-subtle), var(--surface-tint));
    align-items: center; justify-content: center;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.06);
}
.t100-avatar img {
    position: absolute; inset: 0; width: 100%; height: 100%;
    object-fit: cover; object-position: top center;
}
.t100-avatar-mono {
    font-size: 13px; font-weight: var(--font-weight-bold);
    color: var(--primary-dark); letter-spacing: 0.5px; user-select: none;
}
.t100-flag {
    width: 18px; height: 18px; border-radius: 50%; object-fit: cover;
    vertical-align: -3px; flex: none;
}
.t100-drill-avatar .t100-avatar-mono { font-size: 16px; }
.t100-sr-avatar    .t100-avatar-mono { font-size: 11px; }
.t100-rdrill-avatar { width: 28px; height: 28px; }
.t100-rdrill-avatar .t100-avatar-mono { font-size: 11px; }

/* Ability development tracks (career strength-of-field rating per discipline) */
.abt-bar { display:flex; gap: var(--space-5); align-items:center; flex-wrap:wrap; margin-bottom: var(--space-5); }
.abt-tog { font-size:12px; color:var(--ink-3); display:inline-flex; align-items:center; gap: var(--space-2); cursor:pointer; }
.abt-reset { font:inherit; font-size:12px; padding: var(--space-2) var(--space-5); border:1px solid var(--line-base); border-radius: 8px; background:var(--bg-surface); color:var(--ink-3); cursor:pointer; }
.abt-reset:hover { border-color:var(--border-strong); }
.abt-hint { font-size:11px; color:var(--ink-6); }
/* Hover readout on the ability tracks. Deliberately INVERSE — a dark ground
   with light ink in both themes, unlike the surface-coloured .tooltip — so it
   reads over the lane fills without inheriting their tint. It was built from
   eleven inline .style() calls in ability-tracks.js, which put its background
   and shadow outside every search of css/. */
.abt-tip {
    position: absolute; z-index: var(--z-tooltip); pointer-events: none;
    max-width: 190px; padding: var(--space-2) var(--space-4);
    background: var(--scrim-tooltip); color: var(--on-surface-inverse);
    border-radius: var(--radius-md); box-shadow: var(--shadow-2);
    font-size: var(--text-sm); line-height: 1.35;
}
/* No horizontal padding so the chart SVG spans the full card width and its year
   ticks line up with the career arc; the header keeps its own inset instead. */
.abt-lane { background:var(--bg-surface); border:1px solid var(--line-base); border-radius: 12px; padding: var(--space-4) 0 var(--space-2); margin-bottom: var(--space-5); }
.abt-lane-head { display:flex; align-items:baseline; justify-content:space-between; gap: var(--space-4); padding: 0 var(--space-5); }
.abt-lane-name { font-weight:800; font-size:13px; letter-spacing:.02em; }
.abt-lane-read { font-size:12px; color:var(--ink-5); text-align:right; }
.abt-lane-read b { color:var(--ink-1); font-variant-numeric:tabular-nums; }
.abt-lanes svg { display:block; width:100%; height:auto; }
/* Dual short/long athletes: two side-by-side columns on wide screens. */
.abt-lanes.abt-split { display:flex; gap: var(--space-8); align-items:flex-start; }
.abt-lanes.abt-split .abt-col { flex:1 1 0; min-width:0; }
.abt-col-head { font-weight:800; font-size:12px; color:var(--ink-4); letter-spacing:.04em; text-transform:uppercase; margin: 0 var(--space-1) var(--space-3); }
/* Phones: stack the Short / Long sections instead of side-by-side (too narrow). */
@media (max-width: 640px) { .abt-lanes.abt-split { flex-direction:column; gap: var(--space-2); } .abt-lanes.abt-split .abt-col { width:100%; } }
.abt-brush .selection { fill:var(--accent); fill-opacity:.08; stroke:var(--seg-swim); stroke-opacity:.5; }
.abt-legend { color:var(--ink-5); font-size:11px; margin-top: var(--space-4); line-height:1.9; }
.abt-legend .abt-leg-item { display:inline-flex; align-items:center; white-space:nowrap; margin-left: var(--space-6); }
.abt-legend .abt-chip { display:inline-block; width:22px; border-top:3px solid; margin-right: var(--space-2); }
.abt-legend .abt-band { display:inline-block; width:14px; height:11px; background:var(--badge-amber-bg); border:1px solid var(--badge-amber-ink); margin-right: var(--space-2); }
.abt-legend .abt-shade { display:inline-block; width:14px; height:11px; background:rgba(37,99,235,.18); margin-right: var(--space-2); }
.abt-legend .abt-xk { display:inline-block; margin-right: var(--space-2); color:var(--ink-5); font-weight:800; }
.abt-legend .abt-dnfk { display:inline-block; margin-right: var(--space-2); color:var(--ink-5); font-weight:600; }

/* ============================================================
   SaaS chrome layer (folded in from the former saas-theme.css).
   Loads after the base rules above, so it keeps overriding them —
   same cascade as when it was a separate file. Dedupe incrementally.
   ============================================================ */
/* ============================================================
   SaaS theme layer — chrome only.
   Athlete/team identity colors are inline (colorScale) and are
   deliberately untouched: comparison-by-color is preserved.
   ============================================================ */

body { background: var(--bg-app); }

/* Page shell: hairline border instead of floating card */
.container {
    box-shadow: none;
    border: 1px solid var(--line-base);
    border-radius: var(--border-radius-lg);
}

/* ── Header: quiet toolbar ─────────────────────────────── */
.app-brand { font-weight: 700; letter-spacing: -0.01em; }
.header-sep { display: none; }
.app-data-source { gap: var(--space-3); }

.header-btn,
.header-file-btn {
    background: var(--bg-surface);
    color: var(--ink-3);
    border: 1px solid var(--line-base);
    font-weight: 500;
    box-shadow: var(--shadow-1);
}
/* Action labels: full text by default, compact on phones (no hamburger —
   the buttons stay one tap away, just shorter). */
.lbl-short { display: none; }
@media (max-width: 640px) {
    .header-btn .lbl-full, .header-file-btn .lbl-full { display: none; }
    .header-btn .lbl-short, .header-file-btn .lbl-short { display: inline; }
}
.header-btn:hover:not(:disabled),
.header-file-btn:hover {
    background: var(--bg-hover);
    border-color: var(--line-strong);
    color: var(--ink-1);
}
/* Modal-footer "Load race" stays the one primary action */
.src-modal-foot .header-btn {
    background: var(--primary-color);
    border-color: var(--primary-color);
    color: var(--on-accent);
}
.src-modal-foot .header-btn:hover:not(:disabled) { background: var(--primary-dark); }
.header-file-btn.has-file { background: var(--badge-green-bg); color: var(--badge-green-ink); border-color: var(--badge-green-ink); }

/* Tabs are defined once, up in TAB NAVIGATION — the underline style used to be
   patched in here as a second layer, which meant a tab's real appearance was
   two rules 2700 lines apart. It's one rule now; change it there. */

/* ── Surfaces: flatten, border-define ──────────────────── */
/* .chart-container visual lives in chart-theme.css (single source). */
.ap-section, .rh-section {
    background: var(--bg-surface);
    border: 1px solid var(--line-base);
}

/* Kill the candy gradients — neutral panels with an accent rule */
.ap-header, .rh-header,
.t100-drill-head, .t100-rdrill-head {
    background: var(--bg-soft);
    border: 1px solid var(--line-base);
    border-left: 3px solid var(--primary-color);
}
.chip-strip {
    background: var(--bg-soft);
    border: 1px solid var(--line-base);
}

/* Checkpoint nav (Teams): gradient hero → toolbar. Always-dark chrome so its
   white-text children stay legible in both themes (--ink-2 would flip light). */
.checkpoint-navigation {
    background: var(--surface-inverse);
    border-radius: var(--radius-lg);
}

/* ── Chips & filters: outline-quiet, filled-active ─────── */
.filter-options-btn, .ctrl-pill {
    border-color: var(--line-base);
    color: var(--ink-4);
    font-weight: 500;
}
.filter-options-btn.active, .ctrl-pill.on {
    background: var(--ink-1);            /* dark-fill active = neutral, lets data colors pop */
    border-color: var(--ink-1);
    color: var(--surface);               /* inverse of --ink-1 so it flips in dark */
    box-shadow: none;
}
/* `.switcher-btn.on` uses the same --ink-1 fill (see the switcher primitive);
   don't add a --primary-color active anywhere — it drifts from this family and
   collides with the accent that marks a watched athlete. */

/* Watched-athlete chips: keep --chip-color (the data color!) but quiet the frame */
.cs-chip { border-width: 1px; background: var(--bg-surface); }
.cs-label { color: var(--ink-5); }

/* ── Buttons ───────────────────────────────────────────── */
.btn {
    background: var(--primary-color);
    border-radius: var(--radius-md);
    font-weight: 600;
}
.xo-confirm { background: var(--primary-color); border-color: var(--primary-color); }
.xo-confirm:hover { background: var(--primary-dark); }

/* ── Modals: crisper ───────────────────────────────────── */
.src-overlay { background: rgba(15, 23, 42, 0.4); backdrop-filter: blur(2px); }
.src-modal { border-radius: var(--radius-xl); box-shadow: var(--shadow-3); border: 1px solid var(--line-base); }
.src-modal-head, .src-modal-foot { background: var(--bg-surface); }

/* ── Podium: number badges, not gold bars ──────────────── */
.podium-1, .team-row.podium { background: var(--bg-surface); }
.podium-card { border-left-width: 2px; }
.podium-1 { border-color: var(--gold); }   /* keep medal hue only as a thin rule */

/* T100: fold the purple sub-theme into the app accent */
.t100-title-sub { color: var(--primary-dark); }
.t100-bar-reg { background: var(--primary-color); }
.t100-sr-total, .t100-drill-rank, .t100-rdrill-pos.pod,
.t100-det-pts.counting, .t100-scoring-summary { color: var(--primary-dark); }

/* ── Country flags: real images, not emoji ─────────────── */
.flag-img {
    width: 16px; height: 12px;
    border-radius: 2px;
    object-fit: cover;
    vertical-align: -2px;
    flex: none;
}
/* Every flag image — rectangular (.flag-img) or the circular ones from the WT /
   T100 APIs — carries a hairline rim so a flag whose edge matches the surface
   (JP/CA white on white, DE/EE black on dark) still reads as a flag.
   NOTE: this must be an OUTER shadow. An inset shadow paints under an <img>'s
   replaced content, so the old inset rim here was invisible on exactly the
   flags that needed it. */
.flag-img, .country-chip .flag-img, .ap-flag, .t100-flag, .lf-flag {
    box-shadow: 0 0 0 1px var(--flag-ring);
}
/* Flags fill their box so the rim hugs the flag (team logos in the same chip
   stay `contain` — see the .country-chip img rule above). */
.country-chip .flag-img { object-fit: cover; }
/* Fallback chip for codes with no national flag (clubs, mixed relay…) */
.flag-code {
    display: inline-block;
    padding: 1px var(--space-2);
    border-radius: var(--radius-sm);
    background: var(--bg-tint);
    color: var(--ink-5);
    font-size: 0.72em;
    font-weight: 700;
    letter-spacing: 0.02em;
    vertical-align: 1px;
    font-variant-numeric: tabular-nums;
}
/* Initials crest for an NCAA team with no logo (replaces 🎓) */
.team-crest {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px; height: 18px;
    border-radius: var(--radius-sm);
    background: var(--ink-1);
    color: var(--surface);
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.02em;
    vertical-align: -4px;
    flex: none;
}

/* ── Rank badges: numeric, not medals ──────────────────── */
.rank-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.5em;
    height: 1.5em;
    padding: 0 0.42em;
    border-radius: var(--radius-sm);
    background: var(--bg-tint);
    color: var(--ink-2);
    border: 1px solid var(--line-base);
    font-weight: 700;
    font-size: 0.82em;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}
/* Medal hues kept only as thin chips so the podium still reads at a glance */
.rank-badge-1 { background: var(--badge-amber-bg); color: var(--badge-amber-ink); border-color: var(--badge-amber-ink); }
.rank-badge-2 { background: var(--badge-neutral-bg); color: var(--badge-neutral-ink); border-color: var(--badge-neutral-ink); }
.rank-badge-3 { background: var(--badge-red-bg); color: var(--badge-red-ink); border-color: var(--badge-red-ink); }

/* Right-size the containers that used to hold emoji (badges bring own size) */
.podium-medal { font-size: var(--text-base); margin-top: 0; }
@media (min-width: 769px) { .podium-medal { font-size: var(--text-md); } }
.rh-pr-medals { display: inline-flex; gap: var(--space-2); align-items: center; font-size: var(--text-sm); }
.xo-icon { display: inline-flex; align-items: center; }
.h2h-empty-icon, .h2h-never-icon { color: var(--ink-6); line-height: 0; }
/* ── Landing / welcome (no race loaded) ────────────────── */
.landing { padding: clamp(var(--space-8), 8vh, var(--space-16)) var(--space-4); }
.landing-hero { max-width: 660px; margin: 0 auto; text-align: center; }
.landing-title {
    display: block;                    /* override global `h1 { display:none }` */
    font-size: var(--text-display);
    font-weight: 800;
    letter-spacing: -0.025em;
    color: var(--ink-1);
    margin: 0 0 var(--space-3);
}
.landing-tagline {
    font-size: var(--text-lg);
    line-height: 1.5;
    color: var(--ink-4);
    margin: 0 auto var(--space-8);
    max-width: 34em;
}
.landing-cta { margin-bottom: var(--space-10); }
.landing-primary {
    display: inline-block;
    padding: var(--space-4) var(--space-8);
    background: var(--primary-color);
    color: var(--on-accent);
    border: none;
    border-radius: var(--radius-md);
    font-size: var(--text-lg);
    font-weight: 700;
    cursor: pointer;
    transition: background var(--transition-fast), transform .05s;
}
.landing-primary:hover { background: var(--primary-dark); }
.landing-primary:active { transform: translateY(1px); }

.landing-foot { border-top: 1px solid var(--line-base); margin-top: var(--space-16); padding-top: var(--space-8); text-align: center; }
.privacy-note { display: inline-block; text-align: left; max-width: 34em; }
.privacy-note > summary { cursor: pointer; list-style: none; color: var(--ink-6); font-size: var(--text-eyebrow); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 700; }
.privacy-note > summary::-webkit-details-marker { display: none; }
.privacy-note > summary:hover { color: var(--ink-4); }
.privacy-note p { margin-top: var(--space-4); color: var(--ink-4); font-size: var(--text-sm); line-height: 1.5; }

.landing-featured { border-top: 1px solid var(--line-base); padding-top: var(--space-6); text-align: left; }
.lf-label {
    font-size: var(--text-eyebrow);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--ink-6);
    font-weight: 700;
    margin-bottom: var(--space-3);
}
/* Sub-heading inside the races list (e.g. "Juniors & U23") — like .lf-label but
   spaced above so it separates one band of tiles from the one before it. */
.lf-sublabel {
    font-size: var(--text-eyebrow);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--ink-6);
    font-weight: 700;
    margin: var(--space-5) 0 var(--space-3);
    padding-top: var(--space-4);
    border-top: 1px solid var(--line);
}
/* The first band has nothing above it to separate from: it sits straight under
   the section title, so it drops the rule and the leading space. */
.lf-sublabel:first-child {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
}
/* Reserve the loaded height so async content fills it instead of pushing the
   page down on slow loads (CLS). Sized to the rendered 5-row lists. */
.lf-list { display: flex; flex-direction: column; gap: var(--space-2); min-height: 220px; }
.lf-item {
    display: flex;
    align-items: center;
    min-height: 2.5rem;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: var(--bg-surface);
    border: 1px solid var(--line-base);
    border-radius: var(--radius-md);
    cursor: pointer;
    text-align: left;
    transition: border-color var(--transition-fast), background var(--transition-fast);
}
.lf-item:hover { border-color: var(--primary-color); background: var(--primary-subtle); }
.lf-name { font-weight: 600; color: var(--ink-2); }
/* Date + host city. Shown at every width: on a phone it's the only thing that
   says where a race was, since the tile is one line of chrome either way. */
.lf-meta { font-size: var(--text-sm); color: var(--ink-6); }
.lf-item::after { content: '→'; margin-left: auto; align-self: center; color: var(--primary-color); font-weight: 700; }
/* Latest races: the race name on its own line (full width, no truncation), with
   the Women + Men buttons stacked underneath it. */
.lf-race {
    display: flex; flex-direction: column; align-items: stretch; gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    background: var(--bg-surface); border: 1px solid var(--line-base); border-radius: var(--radius-md);
}
.lf-race-info { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.lf-race-genders { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.lf-race-gender, .lf-race-round {
    flex: 1 1 calc(50% - var(--space-2)); text-align: center;
    font-size: var(--text-sm); font-weight: 600; color: var(--primary-color);
    padding: var(--space-5) var(--space-6); border: 1px solid var(--primary-color); border-radius: var(--radius-md);
    background: var(--bg-surface); cursor: pointer; white-space: nowrap;
    transition: background var(--transition-fast), color var(--transition-fast);
}
.lf-race-gender:hover { background: var(--primary-color); color: var(--on-accent); }
.lf-race-gender.lf-loading, .lf-race-round.lf-loading { opacity: .5; pointer-events: none; }
/* Mixed relay: a team race, not a gender split → full-width row of its own. */
.lf-race-relay { flex-basis: 100%; }
/* Ghost: the gender is scheduled but not yet scored. Muted + dashed so it reads
   as a placeholder holding its half-width slot, not a clickable button. */
.lf-race-gender.lf-ghost {
    color: var(--ink-6); border: 1px dashed var(--line-base);
    background: transparent; cursor: default;
}
.lf-race-gender.lf-ghost:hover { background: transparent; color: var(--ink-6); }
/* Dropdown variant (multi-round / para-class genders): a caret hints it opens,
   and text-align-last centres the closed-state gender label like the buttons. */
.lf-race-round {
    -webkit-appearance: none; appearance: none; text-align-last: center;
    background-image: linear-gradient(45deg, transparent 50%, var(--primary-color) 50%),
                      linear-gradient(135deg, var(--primary-color) 50%, transparent 50%);
    background-position: calc(100% - 14px) 1.05em, calc(100% - 9px) 1.05em;
    background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
    padding-right: var(--space-12);
}
.lf-race-round:hover { background-color: var(--primary-color); color: var(--on-accent); }
.lf-race-round option { color: var(--text); background: var(--bg-surface); }

/* ── Landing dynamic sections: rankings grid + tiles ───── */
.lf-empty { padding: var(--space-2) var(--space-1); color: var(--ink-6); font-size: var(--text-sm); }
.lf-loading { opacity: 0.55; pointer-events: none; }
/* Shared-rank rows: rank · man · woman — rank on the left, both names left-aligned. */
.lf-rankings { display: flex; flex-direction: column; gap: var(--space-1); min-height: 240px; }
.lf-rank-headrow, .lf-rank-row {
    display: grid; grid-template-columns: 1.5em 1fr 1fr; align-items: center; gap: var(--space-2);
}
.lf-rank-headrow { margin-bottom: var(--space-1); padding: 0 var(--space-1); }
.lf-rank-headrow span {
    font-size: var(--text-eyebrow); text-transform: uppercase; letter-spacing: 0.06em;
    font-weight: 700; color: var(--ink-6);
}
.lf-rank { align-items: center; gap: var(--space-2); min-width: 0; justify-content: flex-start; text-align: left; }
.lf-rank::after { content: none; }
.lf-rk {
    font-variant-numeric: tabular-nums; font-weight: 700;
    color: var(--ink-5); text-align: left; min-width: 1.2em;
}
.lf-flag {
    width: 18px; height: 18px; border-radius: 50%; object-fit: cover; flex: none;
}
.lf-rank .lf-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ============================================================
   PREDICTION VIEW (prediction-view.js) — .pvx-*
   ============================================================ */
.pvx { margin:0 auto; padding: 0 var(--space-6) 40px; font-family:var(--font-family); position:relative; color:var(--ink-1); }
.pvx-title { font-size:var(--text-xl); font-weight:var(--font-weight-black); line-height:var(--line-tight); color:var(--ink-1); }
.pvx-meta { font-size:var(--text-base); color:var(--ink-5); margin: var(--space-2) 0; }
.pvx-asof { font-size:var(--text-sm); color:var(--ink-6); margin: var(--space-1) 0 var(--space-3); }
.pvx-asof b { color:var(--ink-5); font-weight:var(--font-weight-semibold); }
.pvx-row { display:flex; gap:var(--space-5); flex-wrap:wrap; align-items:center; margin-bottom:var(--space-3); }
.pvx select { padding:var(--space-3) var(--space-4); border:1px solid var(--line-base); border-radius:var(--radius-md); background:var(--bg-surface); font-size:var(--text-base); color:var(--ink-2); }
/* The prediction view's tabs ARE `.tab-navigation` — it carries both classes,
   so the bar geometry has one source and only the margin is local. */
.pvx-tabs { margin:var(--space-3) 0 var(--space-2); }
.pvx-cap { font-size:var(--text-sm); color:var(--ink-5); margin:var(--space-2) 0 0; }
.pvx-adj { display:none; }
.pvx-adj.show { display:block; }
.pvx-legend { display:flex; flex-wrap:wrap; gap: var(--space-3) var(--space-7); margin-top:var(--space-3); font-size:var(--text-sm); color:var(--ink-5); }
.pvx-legend .lg { display:inline-flex; align-items:center; gap: var(--space-2); }
.pvx-legend .lg-sw { width:14px; height:8px; border-radius:2px; display:inline-block; flex:none; }
.pvx-legend .lg-dot { width:8px; height:8px; border-radius:50%; display:inline-block; flex:none; background:var(--ink-4); }
.pvx-legend .lg-bar { width:16px; height:5px; border-radius: var(--radius-sm); display:inline-block; flex:none; background:var(--ink-4); }
.pvx-legend .lg-whisk { width:16px; height:2px; border-radius:2px; display:inline-block; flex:none; background:var(--chart-ref); }
.pvx-stage { min-height:320px; position:relative; }
/* Long charts scroll inside a fixed box so the frozen axis above them stays put. */
.pvx-scroll { max-height:500px; overflow-y:auto; -webkit-overflow-scrolling:touch; border-radius:var(--radius-lg); }
.pvx-scroll--short { max-height:440px; }
.pvx-note { display:flex; gap:var(--space-7); flex-wrap:wrap; font-size:var(--text-sm); color:var(--ink-5); margin-top:var(--space-3); }
.pvx-note i { display:inline-block; width:14px; height:3px; border-radius:2px; vertical-align:middle; margin-right:var(--space-2); }
.pvx-alts { margin-top:var(--space-5); font-size:var(--text-base); }
.pvx-alts-title { font-weight:var(--font-weight-bold); color:var(--text-2); margin-bottom: var(--space-2); }
.pvx-alt { display:flex; justify-content:space-between; max-width:520px; padding:4px var(--space-3); border-bottom:1px solid var(--line-soft); }
.pvx-alt.foc { background:var(--primary-subtle); font-weight:var(--font-weight-bold); border-radius:var(--radius-sm); }
.pvx-alt span:last-child { font-family:var(--font-family-mono); }
.pvx-alt-rank { flex:none; min-width:2.1em; text-align:right; font-variant-numeric:tabular-nums; color:var(--ink-5); margin-right:var(--space-2); }
.pvx-alt .pvx-alt-bib { font-family:var(--font-family-mono); font-variant-numeric:tabular-nums; color:var(--ink-6); min-width:2.2em; text-align:right; margin-left:auto; margin-right:var(--space-4); }
/* Names in this view use the shared .athlete-link cue; .pvx-name only marks the
   SVG labels so they keep their chart sizing alongside it. */
.pvx details { font-size:var(--text-base); margin-top:var(--space-4); }
.pvx details > summary { cursor:pointer; color:var(--primary-color); font-weight:var(--font-weight-bold); padding:var(--space-2) 0; }
.pvx-tip { position:absolute; pointer-events:none; background:var(--bg-surface); color:var(--ink-2); border:1px solid var(--line-base); box-shadow:0 4px 16px rgba(15,23,42,.08), 0 1px 3px rgba(15,23,42,.04); font-size:var(--text-base); padding: var(--space-3) var(--space-5); border-radius:var(--radius-lg); white-space:nowrap; opacity:0; transition:opacity var(--transition-fast); z-index:20; }
.pvx svg text { font-family:var(--font-family); }
.pvx svg text.pvx-name:hover { fill:var(--primary-color); }
.pvx-field-note { font-size:var(--text-sm); color:var(--ink-6); margin-top:var(--space-4); text-align:center; font-style:italic; }
@media (max-width: 480px) {
  .pvx { padding:0 var(--space-5) var(--space-16); }
  .pvx-title { font-size:var(--text-lg); }
}

/* ============================================================
   DEVELOPMENT-CHART WHAT-IF PANEL (development-chart.js) — .whatif-*
   ============================================================ */
.whatif-wrap { margin: var(--space-2) 0 var(--space-1); }
/* Pacing-impact toggle uses the shared `.ctrl-pill` (+ `.on`) — no bespoke style. */
/* The shared adjustment sliders (js/ui/adjust-panel.js): the prediction form
   guide and the development chart's pacing what-if both render this. */
.adjust { display:flex; flex-wrap:wrap; gap: var(--space-5) var(--space-7); align-items:center; justify-content:center; padding: var(--space-4) var(--space-1); margin: var(--space-1) 0; }
.adjust[hidden] { display:none; }
.adjust-item { display:flex; flex-direction:column; gap: var(--space-2); flex:1 1 40%; max-width:220px; min-width:126px; }
.adjust-top { display:flex; align-items:baseline; justify-content:space-between; gap: var(--space-3); line-height:1; }
.adjust-lbl { font-size:9px; font-weight:var(--font-weight-bold); letter-spacing:.06em; color:var(--ink-6); }
.adjust-val { font-family:var(--font-family-mono); font-size:12px; font-weight:var(--font-weight-bold); color:var(--ink-5); white-space:nowrap; }
.adjust-val.changed { color:var(--primary-color); }
.adjust-val.faster { color:var(--success); }
.adjust-val.slower { color:var(--danger); }
.adjust-range { width:100%; margin: 0; cursor:pointer; accent-color:var(--primary-color); }
.adjust-sub { font-family:var(--font-family-mono); font-size:10px; color:var(--ink-6); letter-spacing:-.01em; white-space:nowrap; margin-top: 1px; }
.adjust-reset { flex:none; }
.whatif-pack { flex:1 1 100%; display:flex; align-items:baseline; justify-content:space-between; gap: var(--space-5); font-size:11px; color:var(--ink-5,#64748b); padding-top: var(--space-1); border-top:1px solid var(--line-base,#e2e8f0); margin-top: var(--space-1); }
.whatif-pack-l { text-align:left; }
.whatif-pack-r { text-align:right; margin-left:auto; }
.whatif-pack b { font-weight:700; color:var(--ink-4); }

/* ========================================
   SHARE SHEET (.sc-*)
   ----------------------------------------
   The preview modal for share cards. Always dark, whatever the app theme, so a
   card is judged against the dark ground it will be posted onto. Colours come
   from the --sheet-* tokens, which are deliberately not re-defined in dark.

   This lived as a string in share-card.js and was injected into <head>. It is
   here so it can be found, diffed and audited like every other rule.
   ======================================== */
.sc-modal {
    position: fixed; inset: 0; z-index: var(--z-modal);
    background: var(--sheet-scrim);
    display: none; align-items: center; justify-content: center;
    padding: var(--space-8); backdrop-filter: blur(3px);
}
.sc-modal.open { display: flex; }
.sc-sheet {
    background: var(--sheet-bg); border: 1px solid var(--sheet-line);
    border-radius: 18px; width: min(440px, 100%); padding: var(--space-8);
    box-shadow: 0 24px 60px rgba(0, 0, 0, .6);
    position: relative; color: var(--sheet-ink);
}
.sc-close {
    position: absolute; top: 12px; right: 14px;
    /* the share sheet is always-dark, so its dismiss takes the sheet's ink */
    color: var(--sheet-ink-dim); font-size: var(--text-2xl);
}
.sc-title { font-weight: var(--font-weight-black); font-size: var(--text-lg); margin: 0 0 var(--space-6); }
.sc-preview {
    display: flex; justify-content: center; background: var(--sheet-well);
    border-radius: 12px; padding: var(--space-7); margin-bottom: var(--space-6);
}
.sc-preview canvas {
    border-radius: 10px; box-shadow: 0 8px 24px rgba(0, 0, 0, .5);
    max-width: 100%; max-height: min(60vh, 540px);
    width: auto; height: auto; object-fit: contain;
}
/* A card exported with transparency previews on a checkerboard, so "no
   background" is visibly different from "white background". */
.sc-preview canvas.sc-alpha {
    background-color: var(--sheet-alpha-base);
    background-image:
        linear-gradient(45deg, var(--sheet-alpha-check) 25%, transparent 25%, transparent 75%, var(--sheet-alpha-check) 75%),
        linear-gradient(45deg, var(--sheet-alpha-check) 25%, transparent 25%, transparent 75%, var(--sheet-alpha-check) 75%);
    background-size: 22px 22px; background-position: 0 0, 11px 11px;
}
.sc-row { display: flex; gap: var(--space-4); align-items: center; margin-top: var(--space-5); }
.sc-modes button {
    flex: 1; background: var(--sheet-raised); border: 1px solid var(--sheet-line);
    color: var(--sheet-ink-dim); border-radius: 9px; padding: var(--space-4);
    font-size: var(--text-md); font-weight: var(--font-weight-semibold); cursor: pointer;
}
.sc-modes button.on { border-color: var(--sheet-accent); color: var(--sheet-ink); background: var(--sheet-raised-on); }
/* Which CARD to make (results list vs progression chart), above the row that
   picks how it's rendered. Same control as .sc-modes on purpose: they are the
   same kind of choice, one level apart. */
.sc-cards button {
    flex: 1; background: var(--sheet-raised); border: 1px solid var(--sheet-line);
    color: var(--sheet-ink-dim); border-radius: 9px; padding: var(--space-4);
    font-size: var(--text-md); font-weight: var(--font-weight-semibold); cursor: pointer;
}
.sc-cards button.on { border-color: var(--sheet-accent); color: var(--sheet-ink); background: var(--sheet-raised-on); }
.sc-cards[hidden] { display: none; }
.sc-styles { display: none; align-items: center; justify-content: center; gap: var(--space-6); margin-top: var(--space-3); }
.sc-styles .sc-ghost { padding: var(--space-2) 11px; font-size: var(--text-lg); line-height: 1; }
.sc-style-dots { display: flex; gap: var(--space-3); }
.sc-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--sheet-line); }
.sc-dot.on { background: var(--sheet-accent); }
.sc-hint { color: var(--sheet-ink-dim); font-size: var(--text-base); margin-top: var(--space-4); min-height: 30px; }
.sc-spacer { flex: 1; }
.sc-ghost {
    background: var(--sheet-raised); border: 1px solid var(--sheet-line);
    color: var(--sheet-ink); border-radius: 9px; padding: var(--space-4) var(--space-6);
    font-size: var(--text-md); font-weight: var(--font-weight-semibold); cursor: pointer;
}
.sc-primary {
    background: var(--sheet-accent); border: 0; color: var(--on-accent); border-radius: 9px;
    padding: var(--space-4) var(--space-8); font-size: var(--text-md); font-weight: var(--font-weight-bold); cursor: pointer;
}
.sc-toast {
    position: absolute; left: 50%; bottom: 14px;
    transform: translateX(-50%) translateY(8px);
    background: var(--sheet-line); color: var(--sheet-ink);
    font-size: var(--text-base); padding: var(--space-3) var(--space-6); border-radius: var(--radius-pill);
    opacity: 0; transition: opacity var(--transition-fast), transform var(--transition-fast); pointer-events: none;
}
.sc-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.sc-busy .sc-preview { position: relative; }
.sc-busy .sc-preview::after {
    content: "Generating…"; position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    color: var(--sheet-ink-dim); font-size: var(--text-md);
    background: rgba(6, 10, 18, .6); border-radius: 12px;
}

/* Para class-form view: its own page, so it carries its own heading. */
.cf-head { margin: var(--space-3) 0 var(--space-4); display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2); }
.cf-head .cf-sub { margin-right: auto; }
.cf-title { font-size: var(--text-lg); font-weight: var(--font-weight-bold); color: var(--ink-1); margin: 0; }
.cf-sub { color: var(--ink-5); font-size: var(--text-sm); margin-top: var(--space-1); }

/* The para class chart's own styles (.pc-*) sit with every other chart's in
   css/chart-theme.css, so they read from the same --chart-* chrome tokens. */

/* The class name IS the door to the class view: same thin underline the app
   uses everywhere for "this name goes somewhere" (see athlete-link.js). */
.para-class-open { cursor: pointer; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }
.para-class-open:hover, .para-class-open:focus-visible { color: var(--primary-color); }
/* ============================================================
   CHART THEME OVERRIDES
   ============================================================ */

/* ── Axes: token-driven so they adapt to dark and aren't silently overriding
   the charts' own .attr() calls. Domain lines shown (the axis rule), tokenized. */
.axis path.domain { stroke: var(--chart-axis-line); }
.axis line { stroke: var(--chart-axis-line); }
/* One size for every large chart's tick labels (rank, development, …). Charts
   must NOT re-set font-size inline on axis text — let this own it so they stay
   standardized across charts and adapt at the mobile breakpoint. */
.axis text {
    font-family: var(--font-family);
    fill: var(--chart-axis);
    font-size: 9px;
    font-weight: 400;
    font-variant-numeric: tabular-nums;
}
@media (min-width: 769px) {
    .axis text { font-size: 12px; }
}
.axis-label {
    font-family: var(--font-family);
    fill: var(--chart-label);
    font-size: 12px;
    font-weight: 500;
}
/* Development chart end-of-line name labels. Sizes live here (not inline JS) so
   watched names track the rank chart's name size; fill stays inline (watched =
   the athlete's own colour, field = muted). */
.dev-end-label { font-size: 10px; font-weight: 400; }
.dev-end-label.spot { font-size: 12px; font-weight: 700; }
@media (min-width: 769px) {
    .dev-end-label { font-size: 11px; }
    .dev-end-label.spot { font-size: 13px; }
}

/* Prediction view SVG text. Sizes and weights live here rather than in
   .attr('font-size', …) so they follow the breakpoint like every other chart's
   labels. Dynamic fills (an athlete's own colour) stay inline, as with
   .dev-end-label above. The 520px switch is prediction-view.js's own `mob`
   layout breakpoint, so text and row geometry change together. */
.pvx-band-label { font-size: 10px; font-weight: 600; letter-spacing: .04em; }
.pvx-flag { border-radius: 1px; }
/* Race numbers off the start list. Mono + tabular so a 2-digit number does not
   shove the column it sits in. */
.pvx-row-bib, .pvx-podium-bib {
    font-family: var(--font-family-mono); font-variant-numeric: tabular-nums;
    fill: var(--chart-axis); font-size: 9px;
}
.pvx-podium-bib { font-size: 10px; }
.pvx-axis-title { font-size: 10px; fill: var(--chart-axis); }
.pvx-medal { font-size: 18px; }
.pvx-podium-name { font-size: 14px; font-weight: 700; }
.pvx-podium-name.is-focus { font-weight: 900; }
.pvx-podium-mark { font-size: 11px; }
.pvx-podium-pct { font-size: 13px; font-weight: 800; font-family: var(--font-family-mono); }
.pvx-podium-cap { font-size: 10px; }
.pvx-podium-win { font-size: 13px; font-weight: 700; font-family: var(--font-family-mono); fill: var(--text-2); }
.pvx-row-name { font-size: 11px; font-weight: 500; }
.pvx-row-name.is-focus { font-weight: 800; }
.pvx-row-val { font-size: 9px; font-family: var(--font-family-mono); }
.pvx-lane-name { font-size: 11px; font-weight: 400; }
.pvx-lane-name.is-focus { font-size: 12px; font-weight: 800; }
.pvx-lane-val { font-size: 10px; font-family: var(--font-family-mono); }
.pvx-leg-label { font-size: 8px; font-weight: 400; }
.pvx-leg-label.is-pick { font-size: 9px; font-weight: 700; }
.pvx-leg-label.is-focus { font-size: 10px; font-weight: 800; }
@media (min-width: 520px) {
    .pvx-band-label { font-size: 13px; }
    .pvx-axis-title { font-size: 12px; }
    .pvx-medal { font-size: 24px; }
    .pvx-podium-name { font-size: 17px; }
    .pvx-podium-mark { font-size: 13px; }
    .pvx-podium-pct { font-size: 16px; }
    .pvx-podium-cap { font-size: 12px; }
    .pvx-podium-win { font-size: 16px; }
    .pvx-row-name { font-size: 13px; }
    .pvx-row-val { font-size: 10px; }
    .pvx-lane-name { font-size: 12px; }
    .pvx-lane-name.is-focus { font-size: 13px; }
    .pvx-lane-val { font-size: 11px; }
    .pvx-leg-label { font-size: 9px; }
    .pvx-leg-label.is-pick { font-size: 11px; }
    .pvx-leg-label.is-focus { font-size: 12px; }
    .pvx-row-bib { font-size: 10px; }
    .pvx-podium-bib { font-size: 12px; }
}

/* ── Grid lines: whisper-quiet (the faintest layer) ── */
.grid line {
    stroke: var(--chart-grid);
    stroke-opacity: 1;
    stroke-width: 0.5;
    shape-rendering: crispEdges;
}
.grid path.domain { display: none; }

/* ── Athlete paths ── */
/* Base opacity comes from --data-line-op (CSS wins over the SVG opacity
   *attribute*, so this is the real control). The token is boosted in dark
   (tokens.css) so the data doesn't go faint against the dark surface. */
.athlete-path {
    fill: none;
    stroke-width: 1.5;
    opacity: var(--data-line-op);
    stroke-linecap: round;
    stroke-linejoin: round;
    pointer-events: none;
    transition: opacity 0.2s, stroke-width 0.2s;
}
.athlete-path.hidden { display: none; }
.athlete-path-hitarea { pointer-events: stroke; stroke: transparent; stroke-width: 12; fill: none; }
.athlete-path-hitarea.hidden { display: none; pointer-events: none; }
.athlete-path.spotlighted {
    opacity: 1 !important;
    stroke-width: 2.8 !important;
}
.athlete-path.spotlight-dimmed { opacity: 0.06 !important; }
/* On the dark surface 0.06 is invisible — keep a faint trace of the field. */
:root[data-theme="dark"] .athlete-path.spotlight-dimmed { opacity: 0.14 !important; }
.athlete-path.filtered-out { opacity: 0.05 !important; stroke-width: 0.75 !important; }

.athlete-circle {
    opacity: 0.65;
    stroke: var(--chart-halo);
    stroke-width: 1.2;
    transition: all 0.2s;
}
.athlete-circle.hidden { display: none; }
.athlete-circle.spotlighted { opacity: 1 !important; }
.athlete-circle.spotlight-dimmed { opacity: 0.04 !important; }
.athlete-circle.filtered-out { opacity: 0.03 !important; }

/* ── Radar / Spider ── */
.radar-area {
    fill-opacity: 0.08;
    stroke-width: 1.8;
    transition: all 0.2s;
}
.radar-area:hover {
    fill-opacity: 0.18;
    stroke-width: 2.2;
}
.radar-area.hidden { display: none; }
.radar-dots { fill-opacity: 0.8; }
.radar-dots.hidden { display: none; }

/* ── Tooltip card (theme-aware) ── */
.tooltip {
    background: var(--surface-raised);
    color: var(--text);
    border: 1px solid var(--line-base);
    border-radius: 8px;
    box-shadow: var(--shadow-2);
    padding: var(--space-5) var(--space-6);
    font-size: 12px;
    line-height: 1.5;
}
@media (min-width: 769px) {
    .tooltip { padding: var(--space-6) var(--space-7); font-size: 13px; }
}

/* ── Chart containers: no tinted background ── */
.chart-container {
    background: var(--bg-surface);
    border: 1px solid var(--line-soft);
    border-radius: var(--radius-lg);
}
.chart-container.active { border-color: var(--line-base); }

.chart-title {
    background: transparent;
    border-bottom: 1px solid var(--line-soft);
    font-weight: 600;
    color: var(--ink-2);
}

/* ── Segment header bands (rank + dev charts) ── */
.x-section-rect { rx: 2; ry: 2; transition: fill var(--transition-fast); -webkit-tap-highlight-color: transparent; }
.x-section-rect:hover { filter: brightness(0.94); }
.x-section-rect.active {
    stroke: var(--ink-3);
    stroke-width: 1.5;
}

/* ── Inline filter bar ── */
.inline-filters {
    background: var(--bg-surface);
    border: 1px solid var(--line-soft);
    border-radius: var(--radius-md);
}

/* ── Dev chart scroll container ── */
.scroll-container {
    border-color: var(--line-soft) !important;
    background: var(--bg-surface) !important;
}

/* Spotlight labels */
.spotlight-name, .spotlight-label, .wlabel {
    pointer-events: none;
    text-shadow: 0 0 4px var(--chart-halo), 0 0 8px var(--chart-halo), 0 0 12px var(--chart-halo);
    font-weight: 600;
}

.rbadge {
    font-size: 10px;
    font-weight: 600;
    padding: 1px var(--space-3);
    border-radius: var(--radius-sm);
}
.rbadge.top { background: var(--badge-green-bg); color: var(--badge-green-ink); }
.rbadge.mid { background: var(--badge-amber-bg); color: var(--badge-amber-ink); }
.rbadge.low { background: var(--badge-red-bg); color: var(--badge-red-ink); }

/* ── Replay controls ── */
.replay-controls {
    display: flex; flex-direction: column; gap: var(--space-3);
    padding: var(--space-3) var(--space-5);
    background: var(--bg-surface);
    border: 1px solid var(--line-base);
    border-radius: var(--radius-lg);
    margin-bottom: var(--space-2);
}
.replay-filter { margin-bottom: var(--space-1); }
/* Row 1: Course/Timeline segmented toggle + Focus, speed stepper at the right
   edge — keeping it out of the transport row saves a wrap line on phones. */
.rc-toolbar { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; }
.rc-toolbar .rc-speed { margin-left: auto; }
/* Course ⇄ Timeline is the shared `.switcher`; Focus is a boolean chart toggle,
   so it's the shared `.ctrl-pill` (+ `.on`) — it used to carry an --accent fill,
   which is the watch-list colour and made it read as a third kind of control
   sitting between two switcher segments. Only the steppers stay bespoke: they
   are ± nudges, not a state. */
.rc-step-btn {
    font-size: 12px; padding: var(--space-2) var(--space-5);
    border: 1px solid var(--line-base); border-radius: var(--radius-md);
    background: var(--bg-surface); color: var(--ink-3);
    cursor: pointer; font-weight: 600; white-space: nowrap;
}
.rc-focus-btn { font-size: 12px; padding: var(--space-2) var(--space-5); }

/* Row 2: one media bar — play · restart · ±30s · scrubber · time. */
.rc-transport { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; }
.rc-play-btn, .rc-pause-btn {
    width: 38px; height: 38px; border: none; border-radius: 50%; flex: none;
    background: var(--accent); color: var(--on-accent); cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .25);
}
.rc-reset-btn {
    width: 34px; height: 34px; flex: none; cursor: pointer;
    border: 1px solid var(--line-base); border-radius: var(--radius-md);
    background: var(--bg-surface); color: var(--ink-3);
    display: inline-flex; align-items: center; justify-content: center;
}
.rc-step-btn { height: 34px; }
.rc-time {
    font-size: 14px; font-weight: 700; color: var(--ink-2);
    font-variant-numeric: tabular-nums; white-space: nowrap;
}
.rc-time .rc-sep { color: var(--ink-7); font-weight: 500; }
/* Scrubber = a real media slider: rounded track, accent-filled elapsed portion
   (--rc-pct set from JS), clean thumb. touch-action: pan-y so a vertical scroll
   gesture starting on it scrolls the page instead of scrubbing. */
.rc-timeline {
    -webkit-appearance: none; appearance: none;
    flex: 1 1 200px; min-width: 120px; height: 6px; border-radius: 999px;
    background: linear-gradient(to right, var(--accent) 0 var(--rc-pct, 0%), var(--line-strong) var(--rc-pct, 0%) 100%);
    cursor: pointer; touch-action: pan-y;
}
.rc-timeline::-webkit-slider-thumb {
    -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%;
    background: var(--accent); border: 2px solid var(--bg-surface);
    box-shadow: 0 1px 3px rgba(0, 0, 0, .35); cursor: pointer;
}
.rc-timeline::-moz-range-thumb {
    width: 16px; height: 16px; border-radius: 50%;
    background: var(--accent); border: 2px solid var(--bg-surface);
    box-shadow: 0 1px 3px rgba(0, 0, 0, .35); cursor: pointer;
}
/* Speed = a compact stepper through preset multipliers (no raw slider). */
.rc-speed {
    display: inline-flex; align-items: center; gap: 2px; flex: none; padding: 2px;
    border: 1px solid var(--line-base); border-radius: var(--radius-md); background: var(--bg-surface);
}
.rc-speed-dn, .rc-speed-up {
    width: 26px; height: 28px; border: none; background: transparent; color: var(--ink-3);
    font-size: 16px; font-weight: 700; line-height: 1; cursor: pointer; border-radius: var(--radius-sm, 5px);
}
.rc-speed-dn:hover, .rc-speed-up:hover { background: var(--bg-tint); }
.rc-speed-value {
    font-size: 13px; font-weight: 800; color: var(--accent-ink);
    min-width: 40px; text-align: center; font-variant-numeric: tabular-nums;
}

/* Replay map labels (leg names, START/FINISH, leg-leaders) sit over the course
   route, so they were unreadable where they crossed a leg line. A white halo
   behind the text keeps them legible without moving them; non-scaling-stroke so
   the halo stays a crisp constant thickness as the map zooms. */
.map-annot {
    paint-order: stroke;
    stroke: var(--chart-halo);
    stroke-width: 3.5px;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}
@media (min-width: 769px) {
    .rc-focus-btn, .rc-step-btn { font-size: 13px; }
    .rc-time { font-size: 15px; }
}
/* Phone: the transport must hold one line (toolbar + transport = 2 rows total),
   so buttons tighten and the time readout stacks current-over-total instead of
   spending ~110px on the inline "cur / max" form. */
@media (max-width: 768px) {
    .rc-transport { gap: var(--space-2); }
    .rc-play-btn, .rc-pause-btn { width: 34px; height: 34px; }
    .rc-reset-btn { width: 30px; height: 30px; }
    .rc-step-btn { padding: var(--space-2) var(--space-3); height: 30px; }
    .rc-timeline { min-width: 90px; flex-basis: 120px; }
    .rc-time { display: inline-flex; flex-direction: column; align-items: flex-end; line-height: 1.15; font-size: 12px; }
    .rc-time .rc-sep { display: none; }
    .rc-time .rc-max { font-size: 10px; font-weight: 600; color: var(--ink-5); }
}

/* ── Replay ticker ── */
.replay-ticker {
    background: var(--bg-surface) !important;
    border-color: var(--line-soft) !important;
    font-size: 12px;
}
.replay-ticker-header {
    font-size: 11px !important;
    color: var(--ink-5) !important;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border-bottom-color: var(--line-soft) !important;
}
/* ── Rank chart: position-trajectory overview strip (additive) ──
   The whole field's rank-over-race compressed into a short band so a dramatic
   mover's full arc shows without scrolling the per-row chart. */
.rank-overview-host { width: 100%; }
.rank-overview {
    background: var(--bg-surface); border: 1px solid var(--line-base); border-radius: var(--radius-md);
    padding: 5px var(--space-4) var(--space-2); margin-bottom: var(--space-3);
}
.rank-overview-title {
    font-size: 10.5px; font-weight: 600; color: var(--chart-label);
    letter-spacing: .02em; margin-bottom: 1px;
}
.rank-overview-svg { display: block; overflow: visible; width: 100%; height: auto; }
.rank-ov-field { fill: none; stroke: var(--chart-ref); stroke-width: 1; opacity: .3; }
.rank-ov-sel { fill: none; stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.rank-ov-guide { stroke: var(--chart-grid); stroke-width: 1; }
.rank-ov-axis { font-size: 9px; fill: var(--chart-axis); }
.rank-overview-caps { display: flex; flex-wrap: wrap; gap: 3px var(--space-6); margin-top: var(--space-1); }
.rank-ov-cap { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 600; color: var(--ink-3); }
.rank-ov-dot { width: 9px; height: 9px; border-radius: 2px; display: inline-block; flex: none; }
/* Minimap "you are here" viewport band — marks the slice of the field the
   scrollable chart is currently showing; the strip is draggable to scroll. */
.rank-ov-viewport { fill: rgba(99,102,241,0.10); stroke: rgba(99,102,241,0.55); stroke-width: 1; pointer-events: none; }
.rank-overview-svg { touch-action: none; }

/* ── Para class chart ────────────────────────────────────────────────────
   Every race in a sport class as a column carrying its whole field. Frozen
   y-gutter + scrolling plot (the head-to-head pattern): ~29 columns never fit a
   phone, and an axis that scrolls away with the data is useless.
   Chrome comes from the --chart-* family, same as every chart above; the JS
   also writes each colour as a paint ATTRIBUTE (resolved through CT) because
   the share card serialises this SVG out of the document, where custom
   properties no longer resolve. Keep the two in step. */
.pc-controls { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-3); }
.pc-body { display: flex; align-items: flex-start; }
.pc-gutter { flex: 0 0 auto; }
.pc-scroll { flex: 1 1 auto; overflow-x: auto; overflow-y: hidden; }
.pc-plot { display: block; }
.pc-empty { color: var(--ink-5); font-size: var(--text-sm); padding: var(--space-4) 0; }
.pc-grid { stroke: var(--chart-grid); stroke-width: 1; }
/* Colour and size are NOT set here. A CSS declaration beats a presentation
   attribute on screen, but html-to-image serialises this SVG on its attributes
   — so anything painted from this block wins in the app and loses on the share
   card, and the two drift. They had: every watched athlete drew in the same
   primary blue while their chip showed their real palette hue, and the
   discipline tint on the field never appeared at all. para-class-chart.js sets
   both as attributes, off the same CT tokens this block would have used. What
   stays here is state and typeface. */
.pc-tick { text-anchor: end; font-family: var(--font-family-mono); }
/* The two editions of the race you came from, bracketing the window. */
.pc-anchor { fill: var(--bg-tint); opacity: 0.7; }
.pc-venue { text-anchor: middle; }
.pc-venue.on { font-weight: var(--font-weight-bold); }
.pc-date { text-anchor: middle; font-family: var(--font-family-mono); }
.pc-col-label { cursor: pointer; }
.pc-col-label:hover .pc-venue { fill: var(--primary-color); text-decoration: underline; }
.pc-dot { opacity: 0.55; cursor: pointer; }
.pc-dot:hover { opacity: 1; }
.pc-dot.on { opacity: 1; }
/* Past the 95th-percentile cap: on the floor, still tappable, real value in the tip. */
.pc-over { opacity: 0.65; cursor: pointer; }
.pc-over.on { opacity: 1; }

/* ── Live board ──────────────────────────────────────────────────────────
   A live race arrives in beats, so the board is built around the wait: a
   clock and a filling bar to the next split, then the state of the race as
   packs, then the chase arithmetic for whoever is being watched. Everything
   is tokenized so it follows the theme. */
.lv-board { display: flex; flex-direction: column; gap: var(--space-4); }

/* Following a real feed there is no transport, because there is nothing to
   scrub to. This strip takes its place: live, how old the data is, and a way
   into the poll log. The AGE is the load-bearing part — a board that has
   silently stopped updating is indistinguishable from a quiet race. */
.lv-livebar {
    display: flex; align-items: center; gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: var(--bg-surface); border: 1px solid var(--line-base);
    border-radius: var(--radius-lg); font-size: var(--text-xs);
}
.lv-live-dot {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--danger-color); flex: none;
    animation: lv-pulse 2s ease-in-out infinite;
}
@keyframes lv-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) { .lv-live-dot { animation: none; } }
.lv-live-tag { font-weight: 700; letter-spacing: 0.04em; color: var(--ink-2); }
.lv-live-sim {
    font-weight: 700; color: var(--surface); background: var(--ink-4);
    border-radius: var(--radius-sm); padding: 1px 4px; margin-left: 4px;
}
.lv-live-age { color: var(--ink-4); }
.lv-live-err { color: var(--danger-color); margin-left: auto; }
.lv-livebar .lv-diag-btn { margin-left: auto; }
.lv-live-err:not(:empty) + .lv-diag-btn { margin-left: var(--space-3); }
/* The feed has gone quiet: the dot stops pretending everything is fine. */
.lv-livebar.stale .lv-live-dot { background: var(--ink-4); animation: none; }

.lv-waiting {
    text-align: center; padding: var(--space-6) var(--space-4);
    color: var(--ink-4); font-size: var(--text-sm);
}
.lv-waiting-title { font-size: var(--text-lg); font-weight: 700; color: var(--ink-1); }
.lv-waiting-sub { margin-top: var(--space-2); }

/* Poll log. Deliberately plain: read once a year, under pressure. */
.lv-diag-panel {
    margin-top: var(--space-3); padding: var(--space-3);
    background: var(--bg-surface); border: 1px solid var(--line-base);
    border-radius: var(--radius-lg);
    font-family: var(--font-family-mono); font-size: var(--text-xs);
    overflow-x: auto;
}
.lv-diag-head {
    display: flex; flex-wrap: wrap; gap: var(--space-3);
    color: var(--ink-4); padding-bottom: var(--space-2);
    border-bottom: 1px solid var(--line-base); margin-bottom: var(--space-2);
}
.lv-diag-log { width: 100%; border-collapse: collapse; }
.lv-diag-log td { padding: 2px var(--space-2); color: var(--ink-3); white-space: nowrap; }
.lv-diag-log tr.bad td { color: var(--danger-color); }

.lv-beat {
    background: var(--bg-surface); border: 1px solid var(--line-base);
    border-radius: var(--radius-lg); padding: var(--space-4) var(--space-5);
}
.lv-beat-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4); }
.lv-clock {
    font-family: var(--font-family-mono); font-variant-numeric: tabular-nums;
    font-size: var(--text-xl); font-weight: 800; color: var(--ink-1);
}
.lv-next { text-align: right; display: flex; flex-direction: column; }
.lv-next-label { font-size: var(--text-sm); font-weight: 700; color: var(--ink-2); }
.lv-next-eta { font-family: var(--font-family-mono); font-size: var(--text-xs); color: var(--ink-4); }
.lv-fill { height: 4px; border-radius: 2px; background: var(--bg-tint); margin: var(--space-3) 0 var(--space-2); overflow: hidden; }
.lv-fill span { display: block; height: 100%; background: var(--accent); transition: width .3s linear; }
.lv-through { font-size: var(--text-xs); color: var(--ink-4); font-variant-numeric: tabular-nums; }
.lv-beat-done .lv-beat-label { margin-left: var(--space-4); font-size: var(--text-sm); color: var(--ink-4); }
.lv-card {
    background: var(--bg-surface); border: 1px solid var(--line-base);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-lg); padding: var(--space-3) var(--space-4);
}

.lv-road { display: flex; flex-direction: column; }
.lv-road-head { font-size: var(--text-xs); font-weight: 700; color: var(--ink-4); letter-spacing: .04em; margin-bottom: var(--space-2); }
.lv-group { display: flex; flex-direction: column; }
/* A pack is one thing on the road, so it is one row carrying one number: how
   far behind the pack in front. Opening it lists who is in it, in the order
   they crossed the mat, and each name is a watch toggle. */
.lv-pack {
    display: flex; align-items: baseline; gap: var(--space-3); width: 100%;
    background: var(--bg-surface); border: 1px solid var(--line-base);
    border-radius: var(--radius-md); padding: var(--space-3) var(--space-4);
    cursor: pointer; text-align: left; font: inherit; color: inherit;
}
.lv-pack:hover { background: var(--bg-tint); }
.lv-caret { color: var(--ink-4); font-size: var(--text-xs); }
.lv-members {
    display: flex; flex-direction: column;
    margin: 2px 0 var(--space-2) var(--space-5);
    border-left: 2px solid var(--line-base); padding-left: var(--space-3);
}
.lv-member {
    display: flex; align-items: baseline; gap: var(--space-3);
    background: none; border: 0; padding: var(--space-2) var(--space-3);
    cursor: pointer; text-align: left; font: inherit; color: var(--ink-2);
    border-radius: var(--radius-sm);
}
.lv-member:hover { background: var(--bg-tint); }
.lv-member.on { color: var(--accent); font-weight: 700; }
.lv-mk { font-family: var(--font-family-mono); color: var(--ink-4); font-size: var(--text-xs); min-width: 2ch; }
.lv-mn { font-size: var(--text-sm); }
.lv-mc { font-size: var(--text-xs); color: var(--ink-4); }
.lv-mg { margin-left: auto; font-family: var(--font-family-mono); font-variant-numeric: tabular-nums; font-size: var(--text-xs); color: var(--ink-3); }
/* A pack carrying a watched athlete keeps the watch-list accent, so it never
   disappears into the field however far down the road it is. */
.lv-pack-watched { border-color: var(--accent); box-shadow: inset 3px 0 0 var(--accent); }
.lv-size {
    font-family: var(--font-family-mono); font-weight: 800; color: var(--ink-2);
    min-width: 2.5ch; text-align: right; font-variant-numeric: tabular-nums;
}
.lv-names { color: var(--ink-2); font-size: var(--text-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lv-pgap { margin-left: auto; font-family: var(--font-family-mono); font-variant-numeric: tabular-nums; font-weight: 700; color: var(--ink-1); }
/* The separation between two packs, drawn where it is on the road. The strip
   grows with the gap, so the shape of the race reads before any number does. */
.lv-between {
    display: flex; align-items: center; gap: var(--space-3);
    min-height: var(--gap-h, 10px);
    padding-left: var(--space-5); font-size: var(--text-xs);
    color: var(--ink-4); font-variant-numeric: tabular-nums;
    border-left: 2px dotted var(--line-base); margin-left: var(--space-5);
}
.lv-drop::before { content: '↓ '; }
.lv-findwrap { position: relative; }
.lv-hit {
    display: flex; align-items: baseline; gap: var(--space-3); width: 100%;
    background: none; border: 0; padding: var(--space-3) var(--space-4);
    cursor: pointer; text-align: left; font: inherit; color: var(--ink-2);
}
.lv-hit:hover { background: var(--bg-tint); }
.lv-hit.on { color: var(--accent); font-weight: 700; }
.lv-empty { font-size: var(--text-sm); color: var(--ink-4); padding: var(--space-3) 0; }
.lv-tail { font-size: var(--text-xs); color: var(--ink-4); padding: var(--space-3) 0 0 var(--space-5); }

/* Watched athletes ride on the pack row rather than in a panel of their own. */
.lv-watched-name { color: var(--accent); font-weight: 700; }
.lv-lens { display: flex; justify-content: flex-end; }
.lv-lens .switcher-btn { font-size: var(--text-xs); }

/* A watched athlete opened out to the same segment table the charts show. */
.lv-segments {
    margin: 2px 0 var(--space-3) var(--space-6);
    padding: var(--space-3) var(--space-4);
    background: var(--bg-tint); border-radius: var(--radius-md);
}
.lv-segments table { width: 100%; }
/* ============================================================================
   SplitLab Design System — "Lab Slate"  (see design/SYSTEM.md)
   Single source of truth for design tokens, light AND dark.
   Loaded LAST so it consolidates the drifted per-file tokens: the new semantic
   roles are defined here, and every legacy token name is re-pointed at them as
   a live `var()` alias — so the existing CSS (3.9k lines) adapts to the system
   and to dark mode without rewriting each rule. Only hardcoded hex values (and
   d3-inlined chart chrome) still need hand-migration.
   Type scale, spacing, weights, chip/discipline/medal tokens are left to their
   original files (unchanged by the redesign).
   ============================================================================ */

:root {
    color-scheme: light;

    /* ── Semantic surfaces ───────────────────────────────────────────────── */
    --bg-app: #f8fafc;
    --surface: #ffffff;
    --surface-raised: #ffffff;   /* tooltips, dropdowns, popovers, sticky bars */
    --surface-sunken: #fafbfc;   /* recessed panels inside cards */
    --surface-hover: #f1f5f9;
    --surface-tint: #eef2f6;     /* zebra, muted chips, rank-badge bg */
    --surface-selected: #f1f0fd; /* OPAQUE accent tint for a selected row — safe under sticky columns (accent-subtle is translucent and leaks when scrolled) */
    /* Always-dark chrome (hero toolbars, dark tooltips): NOT re-defined in dark,
       so it stays dark in both themes. Pair with white/near-white text. */
    --surface-inverse: #1e293b;
    --on-surface-inverse: #f8fafc;
    /* Share sheet: an always-dark preview modal, so a share card is judged on
       the dark ground it will be posted onto rather than on the app's theme.
       NOT re-defined in dark, same rule as --surface-inverse above. These were
       27 literals inside a stylesheet built from a JS string in share-card.js,
       which put them outside every search of css/. */
    --sheet-scrim: rgba(2, 6, 15, .74);
    --sheet-bg: #0b1220;
    --sheet-well: #060a12;      /* recessed frame the card previews inside */
    --sheet-raised: #0f172a;    /* buttons on the sheet */
    --sheet-line: #1e293b;
    --sheet-ink: #e2e8f0;
    --sheet-ink-dim: #94a3b8;
    --sheet-raised-on: #171c34;     /* a selected button on the sheet */
    --sheet-accent: #6366f1;
    --sheet-alpha-base: #ffffff;    /* the 'white background' half of the preview */
    --sheet-alpha-check: #d1d5db;   /* transparency checkerboard */

    /* Always-white tile behind NCAA team logos: most logos are authored on a
       solid white background, so they need a white backing in BOTH themes to sit
       cleanly on coloured rows / selection tints / dark mode. NOT re-defined in
       dark. The ring is a translucent hairline so it reads on light backgrounds
       and disappears against dark ones (where the white tile already contrasts). */
    --logo-tile: #ffffff;
    --logo-tile-ring: rgba(15, 23, 42, .12);

    /* ── Borders ─────────────────────────────────────────────────────────── */
    --border: #e2e8f0;
    --border-soft: #eef2f6;
    --border-strong: #cbd5e1;

    /* ── Text ────────────────────────────────────────────────────────────── */
    --text: #0f172a;
    --text-2: #334155;
    --text-muted: #64748b;
    --text-faint: #94a3b8;

    /* ── Accent (one brand: indigo) ──────────────────────────────────────── */
    --accent: #4f46e5;
    --accent-hover: #4338ca;
    --accent-ink: #4f46e5;       /* accent-colored TEXT/links on a surface */
    --accent-subtle: rgba(79, 70, 229, .08);
    --on-accent: #ffffff;

    /* ── State ───────────────────────────────────────────────────────────── */
    --success: #047857;
    --success-bg: #ecfdf5;
    --danger: #dc2626;
    --danger-bg: #fee2e2;
    --warning: #b45309;
    --warning-bg: #fffbeb;
    --focus-ring: rgba(79, 70, 229, .30);

    /* ── Soft status badges (one canonical tint per hue; adapts to dark) ──────
       Replaces a scatter of one-off pastel hexes on rank/position/ability/T100
       pills so they share a look and don't glare on dark. */
    --badge-green-bg:   rgba(16, 185, 129, .12);  --badge-green-ink:  #047857;
    --badge-amber-bg:   rgba(245, 158, 11, .15);  --badge-amber-ink:  #b45309;
    --badge-red-bg:     rgba(239, 68, 68, .12);   --badge-red-ink:    #b91c1c;
    --badge-blue-bg:    rgba(59, 130, 246, .12);  --badge-blue-ink:   #1d4ed8;
    --badge-violet-bg:  rgba(139, 92, 246, .12);  --badge-violet-ink: #6d28d9;
    --badge-cyan-bg:    rgba(6, 182, 212, .12);   --badge-cyan-ink:   #0e7490;
    --badge-neutral-bg: rgba(100, 116, 139, .14); --badge-neutral-ink: #475569;

    /* ── Discipline triad (swim / bike / run) ────────────────────────────────
       One source for the three colours the whole app reads a race by. They were
       duplicated as literal hex in chart-theme.js and in a SEG_COLOR map in
       several views, which is why nothing could reason about them: the NCAA
       division palette was allowed to collide with them, and the values had
       never been checked for contrast.

       Two roles, because one value cannot serve both: --seg-* is the MARK (a
       chart line, a swatch, a fill) where the hue matters and the contrast bar
       is 3:1 non-text; --seg-*-ink is TEXT (a caption, a label) and clears
       4.5:1 on its own ground. The mark values are the app's established hues
       and are unchanged; only the ink is adjusted, and only where it had to be. */
    --seg-swim: #3b82f6;   --seg-swim-ink: #196cf4;
    --seg-bike: #16a34a;   --seg-bike-ink: #12863d;
    --seg-run:  #dc2626;   --seg-run-ink:  #dc2626;

    /* ── Ordered-category ramps (race history) ───────────────────────────────
       NCAA divisions and WT race distances are ORDERED sets, not categorical
       ones, so each is a single-hue ramp rather than a colour triad. A ramp
       reads as "one family, N levels", which is what they are — and it can
       never be mistaken for the categorical discipline hues (swim blue / bike
       green / run red) the way the old per-division triad was: it gave DIII
       bike's exact green, DII a near-swim blue and DI a near-run red.

       Higher step = higher rank (DI, longer race) = more contrast against the
       ground, which is why dark mode is not a flip of these values. Divisions
       are drawn as small bold label text so every step clears 4.5:1; distances
       are only dots and legend swatches, so they clear 3:1 non-text. Purple and
       teal both sit well clear of the discipline hues and of --accent. */
    --rh-div-1: #b046d3;   /* DIII */
    --rh-div-2: #81259f;   /* DII  */
    --rh-div-3: #49155b;   /* DI   */
    --rh-dist-1: #0fa3b7;  /* super-sprint */
    --rh-dist-2: #0c899a;  /* sprint       */
    --rh-dist-3: #0a6f7d;  /* standard     */
    --rh-dist-4: #08555f;  /* middle       */
    --rh-dist-5: #053b42;  /* long         */

    /* ── Prediction tiers and data-source identity ───────────────────────────
       The tier bands on the prediction view and the NCAA / World Triathlon dots
       on the profile. Both were inline literals on legend swatches, so neither
       could follow the theme. */
    --tier-rest: #64748b;
    --src-ncaa: #f97316;
    --src-wt: #3b82f6;

    /* Podium card grounds and the prediction row tints. These were a DARK()
       branch inside prediction-view.js holding both themes' hexes in JS, so
       they sat outside the token system and only followed a theme switch if
       something happened to redraw. */
    --medal-1-bg: #fef3c7;
    --medal-2-bg: #f1f5f9;
    --medal-3-bg: #fff1f2;
    --medal-1-line: #f59e0b;
    --medal-2-line: #94a3b8;
    --medal-3-line: #f97316;
    --row-focus-bg: #eff6ff;
    --row-rival-bg: #f0f9ff;

    /* ── Replay course map ───────────────────────────────────────────────────
       The course is drawn straight onto --surface-sunken, with no map tile of
       its own, so every annotation has to clear contrast on BOTH grounds. They
       were hardcoded in replay-chart.js and never did: FINISH sat at 2.25:1 and
       START at 2.15:1 in dark mode, and START was under AA on light too.

       These are read through cssVar() at draw time and the replay redraws on a
       theme change, so a token here is enough. A literal in the chart is not:
       redrawing re-runs the same hardcoded value. */
    --map-start: #196cf4;          /* START label */
    --map-finish: #991b1b;         /* FINISH label */
    --map-transition: #d97706;     /* transition-zone ring */
    --map-transition-fill: rgba(251, 191, 36, .12);
    --map-transition-ink: #b45309; /* the "T" inside the zone */
    --map-handover-ink: #92400e;   /* relay HANDOVER label */
    --map-marker: #eb6c05;
    --map-flag-edge: #bbbbbb;      /* checkered finish flag hairline */

    /* ── Heat cells (race-history place grids; the number sits ON the fill) ──
       Two scales: finish POSITION (medals + blue depth) and PLACE-IN-FIELD
       percentile (green→red). Dark-mode values are selected tints over the
       dark surface, not flips of these — light pastels glare there and the
       light ink vanishes on them. */
    --heat-gold-bg: #FFD700;      --heat-gold-ink: #1e293b;
    --heat-silver-bg: #C0C0C0;    --heat-silver-ink: #1e293b;
    --heat-bronze-bg: #CD7F32;    --heat-bronze-ink: #1e293b;
    --heat-blue-2-bg: #bfdbfe;    --heat-blue-2-ink: #1e293b;
    --heat-blue-1-bg: #dbeafe;    --heat-blue-1-ink: #1e293b;
    --heat-blue-0-bg: #eff6ff;    --heat-blue-0-ink: #334155;
    --heat-pct-0-bg: #15803d;     --heat-pct-0-ink: #ffffff;
    --heat-pct-1-bg: #22c55e;     --heat-pct-1-ink: #1e293b;
    --heat-pct-2-bg: #86efac;     --heat-pct-2-ink: #1e293b;
    --heat-pct-3-bg: #fde68a;     --heat-pct-3-ink: #1e293b;
    --heat-pct-4-bg: #fdba74;     --heat-pct-4-ink: #1e293b;
    --heat-pct-5-bg: #fecaca;     --heat-pct-5-ink: #1e293b;

    /* ── Chart chrome (frames only; series/discipline colors untouched) ───── */
    --major-accent: #f59e0b;     /* major-championship / Grand-Final marker (rings, win cells, FINAL tags) */
    --chart-axis: #94a3b8;       /* tick text */
    --chart-axis-line: #e2e8f0;  /* tick marks / axis rules */
    --chart-grid: #f1f5f9;       /* gridlines */
    --chart-label: #64748b;      /* axis titles, in-SVG captions */
    --chart-halo: #ffffff;       /* label halos, dot outlines, "white" strokes */
    --chart-ref: #cbd5e1;        /* neutral reference lines (VS midline, medians) */
    --chart-band: #3b82f6;       /* statistical range band, drawn at low opacity */
    --data-line-op: 0.5;         /* unselected athlete-line opacity (boosted in dark) */

    /* ── Race-tier identity ──────────────────────────────────────────────
       What KIND of race a result was: a WTCS round, a World Cup, an NCAA
       Regional. Categorical identity, closer to a flag or a medal than to
       chart chrome. They lived as literal hex in three JS tables that had
       drifted apart — the same WTCS race drew teal on a venue history and red
       on a profile. js/config/tier-colors.js reads these and is the one source.

       THREE ROLES, because the same tier lands on three different grounds and
       no single value can clear all of them:

         --tier-*          the HUE. The identity itself. Used as a tint source
                           (a chip is `color-mix` of this with the surface) and
                           as a swatch. Never used as text.
         --tier-*-ink      TEXT and DOTS on the app surface, AND the text of a
                           tier chip, whose ground is a 14% tint of the hue. The
                           tint is the harder of the two — it pulls the ground
                           toward the ink's own hue and costs about 0.7 — so the
                           value is tuned against the tint and clears the plain
                           surface comfortably.
         --tier-*-on-dark  TEXT on always-dark chrome — the career-arc tooltip
                           is `--scrim-tooltip` in BOTH themes, so its tier name
                           needs a light value even in light mode. Defined once
                           and never re-declared; dark's ink simply aliases it.

       RAMP SPACING follows --rh-div above, which runs 4.50 / 7.78 / 13.62 with
       ~1.74 between steps. The two ordered families here (the WT red family and
       the NCAA levels) are staged at 4.5 / 6.9 / 10.6, ~1.53 apart. Raising a
       ramp to a shared 4.5 floor instead would have collapsed it: all three
       NCAA oranges landed within 1.00 of each other, one indistinguishable
       colour where there had been three. A ramp needs separated levels, not a
       common minimum. */
    --tier-olympics:        #f59e0b;
    --tier-olympics-ink:        #976106;
    --tier-olympics-on-dark:        #d38809;
    --tier-world-champs:    #dc2626;
    --tier-world-champs-ink:    #951818;
    --tier-world-champs-on-dark:    #ee9494;
    --tier-wtcs-final:      #b91c1c;
    --tier-wtcs-final-ink:      #5f0e0e;
    --tier-wtcs-final-on-dark:      #f6c6c6;
    --tier-wtcs:            #ef4444;
    --tier-wtcs-ink:            #d01212;
    --tier-wtcs-on-dark:            #f15f5f;
    --tier-t100:            #7c3aed;
    --tier-t100-ink:            #7b39ed;
    --tier-t100-on-dark:            #a374f2;
    --tier-world-cup:       #2563eb;
    --tier-world-cup-ink:       #1a5bea;
    --tier-world-cup-on-dark:       #5f8df0;
    --tier-olympic-qual:    #3b82f6;
    --tier-olympic-qual-ink:    #0b60eb;
    --tier-olympic-qual-on-dark:    #5492f7;
    --tier-conti-champs:    #0891b2;
    --tier-conti-champs-ink:    #06738e;
    --tier-conti-champs-on-dark:    #099fc3;
    --tier-conti-cup:       #059669;
    --tier-conti-cup-ink:       #047854;
    --tier-conti-cup-on-dark:       #06a674;
    --tier-multisport:      #7c2d12;
    --tier-multisport-ink:      #af4019;
    --tier-multisport-on-dark:      #e26034;
    --tier-regional-champs: #9333ea;
    --tier-regional-champs-ink: #8e2ae9;
    --tier-regional-champs-on-dark: #b26df0;
    --tier-games:           #0e7490;
    --tier-games-ink:           #0e708b;
    --tier-games-on-dark:           #139bc0;
    --tier-junior-u23:      #0d9488;
    --tier-junior-u23-ink:      #0a766d;
    --tier-junior-u23-on-dark:      #0ea496;
    --tier-para-series:     #0284c7;
    --tier-para-series-ink:     #026ea6;
    --tier-para-series-on-dark:     #0299e6;
    --tier-para-cup:        #0d9488;
    --tier-para-cup-ink:        #0a766d;
    --tier-para-cup-on-dark:        #0ea496;
    --tier-para-conti-cup:  #059669;
    --tier-para-conti-cup-ink:  #047854;
    --tier-para-conti-cup-on-dark:  #06a674;
    --tier-ncaa-nationals:  #ea580c;
    --tier-ncaa-nationals-ink:  #5a2205;
    --tier-ncaa-nationals-on-dark:  #fcd2bc;
    --tier-ncaa-regionals:  #f97316;
    --tier-ncaa-regionals-ink:  #863903;
    --tier-ncaa-regionals-on-dark:  #fba061;
    --tier-ncaa-cup:        #fb923c;
    --tier-ncaa-cup-ink:        #b05104;
    --tier-ncaa-cup-on-dark:        #f47105;
    --tier-other:           #94a3b8;
    --tier-other-ink:           #5b6e89;
    --tier-other-on-dark:           #8899b0;

    /* ── Scrims ──────────────────────────────────────────────────────────
       The wash behind a modal, and the ground under a dark tooltip. These were
       four hardcoded near-blacks (rgba(0,0,0,.7), rgba(0,0,0,.9),
       rgba(15,23,42,.55), rgba(15,23,42,.96)) picked one modal at a time. A
       fixed near-black over an already-dark app reads as a dead panel, so the
       dark values lean on opacity rather than on more black. */
    --scrim:         rgba(15, 23, 42, .55);
    --scrim-strong:  rgba(15, 23, 42, .72);
    --scrim-tooltip: rgba(15, 23, 42, .96);

    /* ── Stacking order ──────────────────────────────────────────────────
       Everything that floats above the page reads from here. There were 9
       improvised values (900 / 1000 / 1100 / 1300 / 2000 / 9999 / 10000) with
       no scale, which is how .app-info-tip ended up at 1100 — under both modal
       scrims — while its two sibling tooltips sat at 10000. The order below is
       the order these things must stack; a raw z-index above 20 is a bug now.
       Values 1-20 elsewhere in this file are LOCAL: sticky table headers and
       frozen columns layering inside their own stacking context, which is a
       different problem and correctly stays local. */
    --z-dropdown: 200;   /* anchored panel: inline search results */
    --z-fab:      300;   /* floating action button over content */
    --z-drawer:   400;   /* off-canvas sidebar */
    --z-menu:     500;   /* app menu, over the drawer */
    --z-modal:    600;   /* modal scrims + the share sheet */
    --z-tooltip:  700;   /* always on top, including over a modal */

    /* ── Radii (consolidated) ────────────────────────────────────────────── */
    --radius-sm: 4px;
    --radius-md: 6px;
    --radius-lg: 10px;           /* cards, sections, chart frames */
    --radius-xl: 14px;           /* modals, page shell */
    --radius-pill: 999px;

    /* ── Elevation (borders first; shadows mean "floats") ────────────────── */
    --shadow-1: 0 1px 2px rgba(2, 6, 23, .06);
    --shadow-2: 0 4px 12px rgba(2, 6, 23, .10);
    --shadow-3: 0 12px 32px rgba(2, 6, 23, .16);

    /* ── Legacy aliases → new roles (live refs; adapt to dark automatically) ─ */
    --bg-surface: var(--surface);
    --bg-soft: var(--surface-sunken);
    --bg-hover: var(--surface-hover);
    --bg-tint: var(--surface-tint);
    --line: var(--border);
    --line-soft: var(--border-soft);
    --line-base: var(--border);
    --line-strong: var(--border-strong);
    --ink-1: var(--text);
    --ink-2: var(--text-2);
    --ink-3: var(--text-2);
    --ink-4: var(--text-muted);
    --ink-5: var(--text-muted);
    --ink-6: var(--text-faint);
    --ink-7: var(--border-strong);
    --primary-color: var(--accent);
    --primary-dark: var(--accent-hover);
    --primary-darker: var(--accent-hover);
    --primary-subtle: var(--accent-subtle);
    --indigo-fg: var(--accent-ink);
    --indigo-bg: var(--accent-subtle);
    --indigo-border: var(--accent);
    --amber-fg: var(--warning);
    --amber-strong: var(--warning);
    --amber-bg: var(--warning-bg);
    --amber-border: var(--warning);
    --success-color: var(--success);
    --danger-color: var(--danger);
    --warning-color: var(--warning);
    --shadow-card: none;
    --border-radius: var(--radius-lg);
    --border-radius-lg: var(--radius-xl);
}

/* ── Dark theme ──────────────────────────────────────────────────────────
   data-theme is resolved to "light" | "dark" by the FOUC-free head script
   (auto → the OS preference, kept live via a matchMedia listener), so only
   the semantic roles need dark values here; every alias above follows. ──── */
:root[data-theme="dark"] {
    color-scheme: dark;

    --bg-app: #0b1220;
    --surface: #131c2e;
    --surface-raised: #1b2740;
    --surface-sunken: #0e1626;
    --surface-hover: #1a2539;
    --surface-tint: #162135;
    --surface-selected: #20284d;

    --border: #243349;
    --border-soft: #1c2940;
    --border-strong: #33445f;

    --text: #e8edf6;
    --text-2: #c3cee0;
    --text-muted: #8ea0b8;
    --text-faint: #5d7089;

    --accent: #6366f1;
    --accent-hover: #818cf8;
    --accent-ink: #a5b4fc;
    --accent-subtle: rgba(99, 102, 241, .16);
    --on-accent: #ffffff;

    --success: #34d399;
    --success-bg: rgba(52, 211, 153, .12);
    --danger: #f87171;
    --danger-bg: rgba(248, 113, 113, .12);
    --warning: #fbbf24;
    --warning-bg: rgba(251, 191, 36, .12);
    --focus-ring: rgba(129, 140, 248, .40);

    /* Discipline triad: same hues, ink lifted to clear 4.5:1 on the dark ground. */
    --seg-swim: #3b82f6;   --seg-swim-ink: #3b82f6;
    --seg-bike: #16a34a;   --seg-bike-ink: #16a34a;
    --seg-run:  #dc2626;   --seg-run-ink:  #e45656;

    /* Ordered-category ramps — selected for the dark ground, not flipped:
       higher step still means higher rank, so it runs toward the light end. */
    --rh-div-1: #be55e1;   /* DIII */
    --rh-div-2: #d38beb;   /* DII  */
    --rh-div-3: #e8c2f4;   /* DI   */
    --rh-dist-1: #1d717c;  /* super-sprint */
    --rh-dist-2: #2ba7b8;  /* sprint       */
    --rh-dist-3: #55c8d7;  /* standard     */
    --rh-dist-4: #91dce5;  /* middle       */
    --rh-dist-5: #cdeff3;  /* long         */

    --badge-green-bg:   rgba(16, 185, 129, .18);  --badge-green-ink:  #6ee7b7;
    --badge-amber-bg:   rgba(245, 158, 11, .18);  --badge-amber-ink:  #fcd34d;
    --badge-red-bg:     rgba(239, 68, 68, .20);   --badge-red-ink:    #fca5a5;
    --badge-blue-bg:    rgba(59, 130, 246, .20);  --badge-blue-ink:   #93c5fd;
    --badge-violet-bg:  rgba(139, 92, 246, .20);  --badge-violet-ink: #c4b5fd;
    --badge-cyan-bg:    rgba(34, 211, 238, .16);  --badge-cyan-ink:   #67e8f9;
    --badge-neutral-bg: rgba(148, 163, 184, .18); --badge-neutral-ink: #cbd5e1;

    /* Tiers and source identity: lifted for the dark ground. */
    --tier-rest: #94a3b8;
    --src-ncaa: #fb923c;
    --src-wt: #60a5fa;

    /* Podium grounds and row tints: translucent so the dark surface shows through. */
    --medal-1-bg: rgba(250, 204, 21, .15);
    --medal-2-bg: rgba(148, 163, 184, .15);
    --medal-3-bg: rgba(251, 146, 60, .15);
    --medal-1-line: #fbbf24;
    --medal-2-line: #cbd5e1;
    --medal-3-line: #fb923c;
    --row-focus-bg: rgba(99, 102, 241, .20);
    --row-rival-bg: rgba(99, 102, 241, .11);

    /* Replay course map: same hues, lifted to clear the dark map ground. */
    --map-start: #3b82f6;
    --map-finish: #df4c4c;
    --map-transition: #d97706;
    --map-transition-fill: rgba(251, 191, 36, .14);
    --map-transition-ink: #f4791b;
    --map-handover-ink: #d15c14;
    --map-marker: #fb923c;
    --map-flag-edge: #bbbbbb;

    /* Heat cells — same-hue tints selected for the dark surface (ink:bg ≥ 6:1,
       checked over --surface). Faintest tiers stay deliberately recessive. */
    --heat-gold-bg: rgba(250, 204, 21, .25);    --heat-gold-ink: #fcd34d;
    --heat-silver-bg: rgba(203, 213, 225, .22); --heat-silver-ink: #e2e8f0;
    --heat-bronze-bg: rgba(205, 127, 50, .30);  --heat-bronze-ink: #fdba74;
    --heat-blue-2-bg: rgba(96, 165, 250, .32);  --heat-blue-2-ink: #dbeafe;
    --heat-blue-1-bg: rgba(96, 165, 250, .18);  --heat-blue-1-ink: #bfdbfe;
    --heat-blue-0-bg: rgba(96, 165, 250, .09);  --heat-blue-0-ink: #93c5fd;
    --heat-pct-0-bg: #15803d;                   --heat-pct-0-ink: #ffffff;
    --heat-pct-1-bg: rgba(34, 197, 94, .42);    --heat-pct-1-ink: #dcfce7;
    --heat-pct-2-bg: rgba(34, 197, 94, .20);    --heat-pct-2-ink: #86efac;
    --heat-pct-3-bg: rgba(250, 204, 21, .18);   --heat-pct-3-ink: #fde047;
    --heat-pct-4-bg: rgba(249, 115, 22, .20);   --heat-pct-4-ink: #fdba74;
    --heat-pct-5-bg: rgba(239, 68, 68, .18);    --heat-pct-5-ink: #fca5a5;

    --chart-axis: #7c90ac;       /* tick text — clearly readable */
    --chart-axis-line: #3a5279;  /* axis rules / transitions — a clear step above grid */
    --chart-grid: #182339;       /* gridlines — the faintest layer */
    --chart-label: #93a5be;
    --chart-halo: #131c2e;
    --chart-ref: #33445f;
    --chart-band: #60a5fa;
    --data-line-op: 0.8;

    /* Race-tier ink on the dark ground IS the always-dark value — the tooltip
       and the dark app surface are close enough that a second set of numbers
       would be two things to keep in step for no gain. The hue and the on-dark
       value are not re-declared. */
    --tier-olympics-ink: var(--tier-olympics-on-dark);
    --tier-world-champs-ink: var(--tier-world-champs-on-dark);
    --tier-wtcs-final-ink: var(--tier-wtcs-final-on-dark);
    --tier-wtcs-ink: var(--tier-wtcs-on-dark);
    --tier-t100-ink: var(--tier-t100-on-dark);
    --tier-world-cup-ink: var(--tier-world-cup-on-dark);
    --tier-olympic-qual-ink: var(--tier-olympic-qual-on-dark);
    --tier-conti-champs-ink: var(--tier-conti-champs-on-dark);
    --tier-conti-cup-ink: var(--tier-conti-cup-on-dark);
    --tier-multisport-ink: var(--tier-multisport-on-dark);
    --tier-regional-champs-ink: var(--tier-regional-champs-on-dark);
    --tier-games-ink: var(--tier-games-on-dark);
    --tier-junior-u23-ink: var(--tier-junior-u23-on-dark);
    --tier-para-series-ink: var(--tier-para-series-on-dark);
    --tier-para-cup-ink: var(--tier-para-cup-on-dark);
    --tier-para-conti-cup-ink: var(--tier-para-conti-cup-on-dark);
    --tier-ncaa-nationals-ink: var(--tier-ncaa-nationals-on-dark);
    --tier-ncaa-regionals-ink: var(--tier-ncaa-regionals-on-dark);
    --tier-ncaa-cup-ink: var(--tier-ncaa-cup-on-dark);
    --tier-other-ink: var(--tier-other-on-dark);

    --scrim:         rgba(2, 6, 15, .68);
    --scrim-strong:  rgba(2, 6, 15, .80);
    --scrim-tooltip: rgba(2, 6, 15, .97);

    --shadow-1: 0 1px 2px rgba(0, 0, 0, .35);
    --shadow-2: 0 4px 14px rgba(0, 0, 0, .5);
    --shadow-3: 0 12px 36px rgba(0, 0, 0, .6);
}
