/* ═══════════════════════════════════════════════════════════════════
   ATHLETE OS  ·  DESIGN SYSTEM  —  refinement layer (Phases 1–4)
   Loaded automatically by Dash (every file in assets/ is auto-served).
   Cascade position:  custom.css  →  design-system.css  →  matchday.css
                      →  positional.css
   Because this file loads AFTER custom.css with equal-or-higher
   specificity, it is the authority for:
     · the token block (:root)      → Phase 2 single source of truth
     · the global navigation dock   → Phase 1 anchoring + z-scale
     · typographic hierarchy        → Phase 2 / type-as-signal
     · surfaces, radius, shadow     → Phase 2 / craft-signals
     · table + list density         → Phase 3
     · per-view layout weight       → Phase 4 / hierarchy-principles
   Every value below resolves from a token. No magic numbers, no
   !important (the single exception is the prefers-reduced-motion
   reset, which is the W3C-recommended pattern, not a specificity fix).
   ═══════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════
   1. TOKENS  (Phase 2 — one source of truth)
   ═══════════════════════════════════════════════════════════════════ */
:root {
    /* ── Spacing — 8px base ramp (spatial-rhythm) ─────────────────── */
    --os-space-1: 4px;
    --os-space-2: 8px;
    --os-space-3: 12px;
    --os-space-4: 16px;
    --os-space-5: 24px;
    --os-space-6: 32px;
    --os-space-7: 48px;
    --os-space-8: 64px;

    /* Legacy aliases consumed across the codebase */
    --os-gap: var(--os-space-5);          /* 24px — between cards        */
    --os-gap-sm: var(--os-space-3);       /* 12px — inside cards         */
    --os-pad-card: var(--os-space-5);     /* 24px — standard card pad    */
    --os-pad-hero: var(--os-space-6);     /* 32px — hero panel pad       */
    --os-gap-section: var(--os-space-7);  /* 48px — between sections     */

    /* ── Radius (craft-signals) ──────────────────────────────────── */
    --os-radius-lg: 12px;
    --os-radius: 12px;
    --os-radius-sm: 8px;
    --os-radius-pill: 999px;

    /* ── Type ramp — 1.25 ratio from a 16px base (type-as-signal) ── */
    --os-text-2xs: 12px;   /* metadata / uppercase labels             */
    --os-text-xs: 13px;    /* secondary + dense table text            */
    --os-text-sm: 14px;    /* compact body                            */
    --os-text-base: 16px;  /* body                                    */
    --os-text-lg: 20px;    /* section titles                          */
    --os-text-xl: 25px;
    --os-text-2xl: 31px;
    --os-text-3xl: 39px;   /* hero metrics                            */
    --os-text-4xl: 49px;

    /* Legacy type aliases */
    --os-micro: var(--os-text-2xs);
    --os-title: var(--os-text-2xs);
    --os-delta: var(--os-text-xs);
    --os-body: 15px;                        /* inside the 14–16 band  */
    --os-section: var(--os-text-lg);
    --os-metric: var(--os-text-2xl);
    --os-metric-sm: var(--os-text-xl);
    --os-hero: var(--os-text-3xl);

    /* ── Weights — exactly three (hierarchy-principles) ──────────── */
    --os-weight-body: 400;      /* body copy, table cells             */
    --os-weight-structure: 600; /* section titles, labels, chips      */
    --os-weight-hero: 700;      /* hero metrics only                  */

    /* ── Canvas & surfaces — Sale 1861 deep navy (elevation by
          lightness; sampled against the club crest) ──────────────── */
    --os-bg: #0A1128;
    --os-bg-alt: #0C1530;
    --os-surface: #121C3A;
    --os-surface-solid: #121C3A;
    --os-surface-elevated: #16224A;
    --os-surface-inset: #0C1530;
    --os-surface-header: rgba(10, 17, 40, 0.88);

    /* ── Borders — hairline navy, never hard rules ───────────────── */
    --os-border-lo: #1B2748;
    --os-border: #263356;
    --os-border-hi: #34426B;
    --os-border-grid: rgba(38, 51, 86, 0.55);

    /* ── Ink — all WCAG AA on the navy surfaces above (no pure
          white as primary) ───────────────────────────────────────── */
    --os-ink: #E6EAF3;
    --os-ink-dim: #C2CADB;
    --os-muted: #8A96B4;
    --os-muted-deep: #6B7796;
    --os-faint: #5A6480;
    --os-ink-faint: var(--os-muted-deep);

    /* ── Accent — gold sampled from the crest. Accent is reserved for
          interactive affordances / signal; it is NEVER a large fill. ─ */
    --os-accent: #C9A227;
    --os-accent-light: #E0C24D;
    --os-accent-deep: #9E7E1C;
    --os-on-accent: #0A1128;

    /* ── Semantic status — always paired with an icon + text label.
          cyan = below band / underloaded · green = in band / on target ·
          red = above band / danger. ───────────────────────────────── */
    --os-emerald: #46A758;        /* IN BAND / AVAILABLE */
    --os-emerald-light: #6FC17C;
    --os-amber: #45B8CC;          /* BELOW BAND / MODIFIED */
    --os-amber-light: #74CFDF;
    --os-rose: #E5484D;           /* ABOVE BAND / DANGER */
    --os-rose-light: #F08A8D;
    --os-blue: #45B8CC;           /* RETURNING (cyan) */
    --os-blue-light: #74CFDF;
    /* Legacy "indigo" aliases now resolve to the gold accent so any
       lingering reference repaints with the rest of the brand. */
    --os-indigo: var(--os-accent);
    --os-indigo-light: var(--os-accent-light);
    --os-indigo-deep: var(--os-accent-deep);

    /* ── RGB CHANNEL TOKENS (space-separated). Translucent tints are
          written as rgb(var(--os-*-rgb) / <alpha>) so every hover /
          border / chip tint resolves from ONE source of truth instead
          of hardcoded literals. ──────────────────────────────────── */
    --os-accent-rgb: 201 162 39;   /* gold      #C9A227 */
    --os-emerald-rgb: 70 167 88;   /* green     #46A758 */
    --os-amber-rgb: 69 184 204;    /* cyan      #45B8CC */
    --os-rose-rgb: 229 72 77;      /* red       #E5484D */
    --os-blue-rgb: 69 184 204;     /* cyan      #45B8CC */
    --os-ink-rgb: 230 234 243;     /* ink       #E6EAF3 */
    --os-surface-rgb: 18 28 58;    /* surface   #121C3A */

    /* ── Elevation — two levels, ambient not hard ────────────────── */
    --os-shadow: 0 1px 2px rgba(0, 0, 0, 0.55), 0 4px 14px rgba(0, 0, 0, 0.32);
    --os-shadow-hi: 0 2px 6px rgba(0, 0, 0, 0.5), 0 16px 40px rgba(0, 0, 0, 0.45);

    /* ── Layout & motion ─────────────────────────────────────────── */
    --os-ghd-bar-h: 56px;
    --os-ghd-toolbar-h: 44px;
    --os-nav-height: calc(var(--os-ghd-bar-h) + var(--os-ghd-toolbar-h));
    --os-header-h: var(--os-nav-height);
    --os-ease: 160ms cubic-bezier(0.4, 0, 0.2, 1);
    --os-row-h: 54px;
    --os-row-pad: var(--os-space-3) var(--os-space-4);

    /* ── Z-index scale — one ladder, overlays above the nav ──────── */
    --os-z-content: 1;
    --os-z-toolbar: 1035;
    --os-z-nav: 1040;
    --os-z-dropdown: 1050;
    --os-z-overlay: 1060;
    --os-z-modal: 1070;
    --os-z-toast: 1090;
    --os-z-tooltip: 1100;
    /* legacy alias referenced by custom.css */
    --os-z-navbar: var(--os-z-nav);
}


/* ═══════════════════════════════════════════════════════════════════
   2. GLOBAL NAVIGATION DOCK  (Phase 1 — emergency anchor fix)
   ─────────────────────────────────────────────────────────────────
   The dock is the first in-flow child of the authenticated root Div
   (see app.py serve_layout). Taking it out of flow with position:fixed
   guarantees it can never be pushed down the document by sibling
   content; .main-wrapper then offsets itself by the dock height so
   nothing renders underneath it. `overflow-x: clip` (not hidden) keeps
   a crowded dock from forcing a page-wide horizontal scrollbar.
   ═══════════════════════════════════════════════════════════════════ */

/* Defensive: a transform / filter / contain / will-change on any of
   these Dash-injected wrappers re-anchors position:fixed to that
   ancestor — which is exactly how a "fixed" header ends up floating
   mid-viewport. Neutralise the whole chain explicitly. */
#react-entry-point,
#_dash-app-content,
#_dash-app-layout,
#_dash-global-error-container {
    transform: none;
    filter: none;
    perspective: none;
    contain: none;
    will-change: auto;
}

.global-header-dock {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    height: var(--os-nav-height);
    z-index: var(--os-z-nav);
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    padding: 0;
    /* Opaque navy — frosted/glass panels are banned, so no backdrop
       filter here. The dock is a solid surface separated by a hairline. */
    background: var(--os-bg);
    border-bottom: 1px solid var(--os-border);
    box-shadow: var(--os-shadow);
    overflow: visible;
}

.ghd-bar {
    position: relative;
    z-index: var(--os-z-nav);
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: space-between;
    height: var(--os-ghd-bar-h);
    padding: 0 var(--os-space-5);
    gap: var(--os-space-4);
    min-width: 0;
    box-sizing: border-box;
}

.ghd-bar .ghd-left {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 0 0 auto;
    min-width: 0;
}

.ghd-bar .ghd-center {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 4px;
    border-radius: var(--os-radius-sm);
    background: rgba(0, 0, 0, 0.28);
    border: 1px solid var(--os-border-lo);
    flex: 0 1 auto;
    overflow-x: auto;
    scrollbar-width: none;
    white-space: nowrap;
    min-width: 0;
}

.ghd-bar .ghd-center::-webkit-scrollbar {
    display: none;
}

.ghd-bar .ghd-right {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 0 0 auto;
    justify-content: flex-end;
    min-width: 0;
}

.ghd-toolbar {
    position: relative;
    z-index: var(--os-z-toolbar);
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: var(--os-space-3);
    height: var(--os-ghd-toolbar-h);
    padding: 0 var(--os-space-5);
    border-top: 1px solid var(--os-border-lo);
    background: rgba(0, 0, 0, 0.18);
    overflow-x: auto;
    white-space: nowrap;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    box-sizing: border-box;
    min-width: 0;
}

.ghd-toolbar::-webkit-scrollbar {
    display: none;
}

.ghd-toolbar .ghd-density {
    margin-left: auto;
    flex-shrink: 0;
}

.ghd-squad-select,
#ghd-squad-select {
    min-width: 140px;
    max-width: 220px;
    flex: 0 0 auto;
    font-size: var(--os-micro);
    position: relative;
    z-index: var(--os-z-dropdown);
}

.ghd-squad-select option,
#ghd-squad-select option {
    background: var(--os-surface);
    color: var(--os-ink);
}

.ghd-squad-select .Select-menu-outer,
.ghd-toolbar .Select-menu-outer,
.ghd-toolbar .dropdown-menu {
    z-index: 1055 !important;
}

.ghd-week-range,
.ghd-presets {
    flex-shrink: 0;
    white-space: nowrap;
}

/* Content offset: the dock is out of flow, so the routed surface starts
   below it. The login route renders .main-wrapper WITHOUT a dock and
   keeps its own compact, centred padding. */
.main-wrapper:not(.login-route-wrapper) {
    padding-top: calc(var(--os-nav-height) + var(--os-space-5));
}

html {
    scroll-padding-top: calc(var(--os-nav-height) + var(--os-space-3));
}

/* Brand mark / avatar: flat accent fill, no gradient, no neon glow. */
.ghd-brand-mark,
.ghd-session-avatar {
    background: var(--os-accent);
    box-shadow: none;
    color: var(--os-on-accent);
}

/* Dock controls: tokenised, 44px-safe at tablet breakpoints. */
.ghd-nav-link,
.ghd-squad-pill,
.ghd-preset,
.ghd-week-select,
.ghd-logout {
    transition: color var(--os-ease), background-color var(--os-ease),
        border-color var(--os-ease);
}

/* The dock owns the top rung of the content ladder; nothing else may
   claim an arbitrary value. */
.cache-status {
    z-index: var(--os-z-content);
}


/* ═══════════════════════════════════════════════════════════════════
   3. DENSITY TOGGLE  (Phase 3 — Comfortable ⇄ Compact)
   ─────────────────────────────────────────────────────────────────
   Pure-CSS: the checkbox is a direct descendant of <body>, so :has()
   re-declares the density tokens for the whole document. No callback,
   no state, no component API change. Default is Comfortable.

   The control is a ``dcc.Checklist`` (id ``density-toggle``) — Dash 4
   removed ``html.Input``. Its rendered input therefore sits one level
   down (``#density-toggle input``), so the :has() predicate selects the
   descendant checkbox.
   ═══════════════════════════════════════════════════════════════════ */
.ghd-density {
    display: inline-flex;
    align-items: center;
}

.ghd-density-checklist {
    display: inline-flex;
    align-items: center;
}

.ghd-density-checklist label {
    display: inline-flex;
    align-items: center;
    gap: var(--os-space-2);
    padding: var(--os-space-1) var(--os-space-2);
    cursor: pointer;
    color: var(--os-muted-deep);
    font-family: var(--os-mono);
    font-size: var(--os-text-2xs);
    font-weight: var(--os-weight-structure);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
    transition: color var(--os-ease);
}

.ghd-density-checklist label:hover {
    color: var(--os-ink-dim);
}

.ghd-density-checklist input {
    width: 15px;
    height: 15px;
    margin: 0;
    accent-color: var(--os-accent);
    cursor: pointer;
}

/* The dcc.Checklist id sits on the wrapper, so the :has() argument matches
   the descendant checkbox input. */
body:has(#density-toggle input:checked) {
    --os-pad-card: var(--os-space-4);
    --os-pad-hero: var(--os-space-5);
    --os-row-h: 42px;
    --os-row-pad: var(--os-space-2) var(--os-space-3);
    --os-body: var(--os-text-sm);
}


/* ═══════════════════════════════════════════════════════════════════
   4. TYPOGRAPHIC HIERARCHY  (Phase 2 — type-as-signal)
   Three weights only: 400 body · 600 structure · 700 hero.
   Accent is reserved for interactive elements, so eyebrows move to
   muted ink and stop competing with actionable chrome.
   ═══════════════════════════════════════════════════════════════════ */

/* Eyebrows / section kickers — quiet metadata, not accent decoration */
.os-eyebrow,
.apex-eyebrow,
.player360-eyebrow,
.roster-eyebrow,
.injury-section-eyebrow,
.module-eyebrow {
    margin: 0 0 var(--os-space-1);
    color: var(--os-muted-deep);
    font-family: var(--os-mono);
    font-size: var(--os-text-2xs);
    font-weight: var(--os-weight-structure);
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

/* Section titles */
.os-section-title,
.apex-section-title,
.player360-section-title,
.roster-section-title,
.injury-section-title,
.injury-table-title,
.module-title,
.clinical-tier-title {
    margin: 0;
    color: var(--os-ink);
    font-size: var(--os-text-lg);
    font-weight: var(--os-weight-structure);
    letter-spacing: -0.015em;
    line-height: 1.25;
}

/* Page titles */
.os-page-title,
.apex-title,
.player360-title,
.matchday-title,
.roster-page-title {
    margin: 0;
    color: var(--os-ink);
    font-size: var(--os-text-2xl);
    font-weight: var(--os-weight-hero);
    letter-spacing: -0.03em;
    line-height: 1.1;
}

/* Body + supporting copy */
.os-subtext,
.apex-description,
.player360-description,
.matchday-description,
.injury-section-description,
.module-description,
.injury-table-caption {
    margin: var(--os-space-2) 0 0;
    color: var(--os-muted);
    font-size: var(--os-text-sm);
    font-weight: var(--os-weight-body);
    line-height: 1.55;
    max-width: 78ch;
}

/* Labels / metadata */
.kpi-tile-label,
.os-kpi-label,
.apex-kpi-label,
.injury-metric-label,
.player360-metric-label,
.apex-positional-label,
.clinical-case-meta-label,
.player360-bio-label,
.med-entry-label,
.injury-form-label,
.roster-move-label,
.roster-section-label {
    color: var(--os-muted-deep);
    font-family: var(--os-mono);
    font-size: var(--os-text-2xs);
    font-weight: var(--os-weight-structure);
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

/* Metric values — monospace, tabular, never ragged */
.kpi-tile-value,
.apex-kpi-value,
.injury-metric-value,
.player360-metric,
.matrix-val,
.apex-positional-value,
.numeric-output,
.matchday-shirt-number {
    font-family: var(--os-mono);
    font-variant-numeric: tabular-nums;
    font-weight: var(--os-weight-hero);
    letter-spacing: -0.02em;
    line-height: 1.1;
    color: var(--os-ink);
}

.kpi-tile-value,
.apex-kpi-value,
.injury-metric-value {
    font-size: var(--os-text-2xl);
    white-space: nowrap;
}

/* Hero metric — the single loudest number on every view */
.os-hero-metric,
.os-kpi-strip > :first-child .apex-kpi-value,
.os-kpi-strip > :first-child .kpi-tile-value,
.os-kpi-strip > :first-child .injury-metric-value {
    font-size: var(--os-hero);
    font-weight: var(--os-weight-hero);
    line-height: 1;
    letter-spacing: -0.035em;
    font-variant-numeric: tabular-nums;
}

.os-kpi-strip > :first-child .apex-kpi-label,
.os-kpi-strip > :first-child .kpi-tile-label,
.os-kpi-strip > :first-child .injury-metric-label {
    color: var(--os-ink-dim);
}

/* Secondary copy under a metric */
.kpi-tile-sub,
.apex-kpi-sublabel,
.player360-metric-sub {
    margin-top: var(--os-space-2);
    color: var(--os-muted);
    font-size: var(--os-text-xs);
    font-weight: var(--os-weight-body);
    line-height: 1.45;
}


/* ═══════════════════════════════════════════════════════════════════
   5. SURFACES  (Phase 2 — craft-signals)
   Hairline borders, ambient elevation, consistent radius. Hover
   elevation is reserved for genuinely interactive surfaces.
   ═══════════════════════════════════════════════════════════════════ */
/* Elevated containers. Two craft signals carry the depth:
     · a LIGHTER hairline border than the card ground (--os-border, not
       --os-border-lo) so the card edge reads as a lit edge, not a seam;
     · the two-level --os-shadow (contact + ambient) instead of a single blur,
       which is what separates a "flat rectangle" from a lifted surface.
   Padding comes from --os-pad-card at rest and steps up on the hero panels
   below, so density preferences keep working. */
.os-card,
.modern-card,
.dash-card,
.apex-kpi-card,
.apex-panel,
.apex-matrix-panel,
.roster-panel,
.player360-panel,
.injury-metric-card,
.injury-table-card,
.med-entry-history-card,
.clinical-tier,
.unavailable-card,
.position-card,
.matchday-starting,
.matchday-reserves,
.matchday-status-panel {
    border: 1px solid var(--os-border);
    border-radius: var(--os-radius);
    background: var(--os-surface);
    box-shadow: var(--os-shadow);
}
/* Inner wells sit one step DOWN in lightness and drop the ambient shadow —
   an inset panel should read as cut into the card, not floating above it. */
.os-well,
.apex-action-card,
.clinical-tier-cases,
.player360-metric-tile,
.matchday-slot,
.apex-positional-card {
    border: 1px solid var(--os-border-grid);
    border-radius: var(--os-radius-sm);
    background: var(--os-surface-inset);
    box-shadow: none;
}

/* Inner wells sit one step DOWN in lightness. */
.os-well,
.apex-action-card,
.clinical-tier-cases,
.player360-metric-tile,
.matchday-slot,
.apex-positional-card {
    border-radius: var(--os-radius-sm);
    background: var(--os-surface-inset);
    border-color: var(--os-border-grid);
}

/* Static panels must not lift on hover — only interactive surfaces do. */
.os-card:hover,
.modern-card:hover,
.dash-card:hover,
.apex-panel:hover,
.roster-panel:hover,
.player360-panel:hover,
.injury-table-card:hover,
.apex-matrix-panel:hover {
    transform: none;
    border-color: var(--os-border);
    box-shadow: var(--os-shadow);
}

/* Interactive surfaces keep the lift. */
.apex-action-card:hover,
.player-card.btn:hover,
.apex-positional-card:hover,
.player360-metric-tile:hover,
.apex-triage-link:hover {
    transform: translateY(-1px);
    border-color: var(--os-border-hi);
    box-shadow: var(--os-shadow-hi);
}

/* Hero panels get the roomier pad and the high ambient shadow — they are the
   one dominant surface per view, so they earn the extra depth. */
.apex-risk-panel,
.apex-matrix-panel,
.matchday-panel,
.player360-load-panel,
.injury-table-card {
    padding: var(--os-pad-hero);
    box-shadow: var(--os-shadow-hi);
}

/* Roster panel is a mid-tier surface: standard pad, hairline top rule so the
   heading separates from the scroll body without a competing border. */
.roster-panel {
    padding: var(--os-pad-card);
    box-shadow: var(--os-shadow);
}

.roster-panel-heading {
    padding-bottom: var(--os-space-3);
    border-bottom: 1px solid var(--os-border-lo);
}

/* Pill / chip surface — flat, no glow. */
.badge,
.os-badge,
.apex-squad-tag,
.apex-position-tag,
.matchday-status-flag,
.player-card-medical-tag,
.unavailable-review-chip,
.apex-positional-count,
.apex-positional-pct,
.clinical-case-tag {
    border-radius: var(--os-radius-pill);
    font-weight: var(--os-weight-structure);
}

/* Status rings lose the neon bloom; colour is reinforced by icon+label. */
.matchday-acwr-dot.green,
.matchday-acwr-dot.amber,
.matchday-acwr-dot.red {
    box-shadow: none;
}

/* Rugby pitch & depth chart: quieter field, no decorative gradients. */
.matchday-pitch,
.depth-chart {
    border: 1px solid var(--os-border-grid);
    border-radius: var(--os-radius);
    background:
        linear-gradient(90deg,
            transparent 49.7%,
            var(--os-border-grid) 50%,
            transparent 50.3%),
        linear-gradient(180deg, var(--os-surface), var(--os-surface-inset));
}

.matchday-starting,
.matchday-reserves {
    border: 1px solid var(--os-border-grid);
    background: var(--os-surface);
}


/* ═══════════════════════════════════════════════════════════════════
   6. TABLES & LISTS  (Phase 3)
   52–56px comfortable rows, no vertical gridlines, faint horizontal
   dividers, soft hover, sticky header, tabular figures.
   ═══════════════════════════════════════════════════════════════════ */
.injury-table,
.premium-table,
.os-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--os-text-xs);
    color: var(--os-ink-dim);
}

.injury-table thead th,
.premium-table thead th,
.os-table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    padding: var(--os-space-3) var(--os-space-4);
    border: 0;
    border-bottom: 1px solid var(--os-border-lo);
    background: var(--os-surface-elevated);
    color: var(--os-muted);
    font-family: var(--os-mono);
    font-size: var(--os-text-2xs);
    font-weight: var(--os-weight-structure);
    letter-spacing: 0.06em;
    text-align: left;
    text-transform: uppercase;
    white-space: nowrap;
}

.injury-table tbody td,
.premium-table tbody td,
.os-table tbody td {
    height: var(--os-row-h);
    padding: var(--os-row-pad);
    border: 0;
    border-bottom: 1px solid var(--os-border-grid);
    font-size: var(--os-text-xs);
    vertical-align: middle;
}

.injury-table tbody tr:last-child td,
.premium-table tbody tr:last-child td,
.os-table tbody tr:last-child td {
    border-bottom: 0;
}

.injury-table tbody tr:hover,
.premium-table tbody tr:hover,
.os-table tbody tr:hover {
    background: rgba(255, 255, 255, 0.03);
}

/* Figures align on the right edge and never jitter between rows. */
.matrix-val,
.os-table .os-num,
.injury-table .os-num,
td.os-num,
th.os-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* Sticky headers only work inside a real vertical scroll container. */
.player360-session-scroll {
    max-height: 460px;
    overflow-y: auto;
}

/* Wide tables scroll inside their own container — never the page. */
.injury-table-scroll,
.injury-table-wrap,
.os-table-scroll {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    max-width: 100%;
}

/* DataTable (dash_table) shares the surface treatment. */
.dash-spreadsheet-container,
.dash-spreadsheet-inner {
    background: transparent;
}

.dash-spreadsheet td,
.dash-spreadsheet th {
    font-variant-numeric: tabular-nums;
}


/* ═══════════════════════════════════════════════════════════════════
   7. CHART CARDS  (Phase 3)
   Consistent frame: tokenised pad, soft ground, no overflow.
   ═══════════════════════════════════════════════════════════════════ */
.os-chart-frame,
.player360-radar,
.player360-load-chart,
.player360-target-gauge,
.apex-panel .dash-graph,
.player360-panel .dash-graph,
.roster-panel .dash-graph {
    width: 100%;
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
    border-radius: var(--os-radius-sm);
}

.os-chart-frame {
    min-height: 260px;
    padding: var(--os-space-2) var(--os-space-1);
}

.js-plotly-plot,
.plotly-graph-div {
    max-width: 100%;
}


/* ═══════════════════════════════════════════════════════════════════
   8. REQUIRED STATES  (Phase 3 — loading / empty / error)
   ═══════════════════════════════════════════════════════════════════ */
@keyframes os-skeleton-pulse {
    0% { opacity: 0.45; }
    50% { opacity: 0.8; }
    100% { opacity: 0.45; }
}

.os-skeleton,
.player360-empty-state[data-state="loading"] {
    border: 1px solid var(--os-border-grid);
    border-radius: var(--os-radius-sm);
    background: linear-gradient(
        90deg,
        var(--os-surface-inset) 0%,
        var(--os-surface-elevated) 50%,
        var(--os-surface-inset) 100%
    );
    animation: os-skeleton-pulse 1.6s ease-in-out infinite;
}

.os-empty,
.injury-empty-state,
.position-empty,
.unavailable-empty,
.matchday-status-empty,
.clinical-tier-empty,
.apex-empty-state {
    padding: var(--os-space-6) var(--os-space-5);
    color: var(--os-muted);
    font-size: var(--os-text-xs);
    line-height: 1.55;
    text-align: center;
}

.os-error,
.injury-error-state,
.player360-data-warning,
.apex-data-warning,
.injury-source-warning,
.stale-physio-alert,
.roster-status {
    color: var(--os-amber-light);
    font-size: var(--os-text-xs);
    font-weight: var(--os-weight-body);
}

.injury-error-state {
    color: var(--os-rose-light);
}


/* ═══════════════════════════════════════════════════════════════════
   9. LAYOUT & RHYTHM  (Phase 4 — hierarchy-principles)
   Each view gets ONE dominant panel; supporting panels are visibly
   smaller. Sections breathe at 48px, cards at 24px.
   ═══════════════════════════════════════════════════════════════════ */

/* KPI strips are asymmetric: the first tile is the hero, the rest
   support it. Uniform four-up grids are the "identical card" smell. */
.os-kpi-strip {
    grid-template-columns: 1.6fr repeat(3, minmax(0, 1fr));
    gap: var(--os-space-5);
    margin-bottom: var(--os-space-6);
    align-items: stretch;
}

.os-kpi-strip > :first-child {
    min-height: 100%;
}

/* Matchday keeps its slim two-up pair (later file, higher intent). */
.matchday-kpi-strip {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* Two-column workloads: dominant left, supporting right. */
.os-grid-2 {
    grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
    gap: var(--os-space-5);
    margin-bottom: var(--os-gap-section);
}

/* Overview: the availability matrix clearly outweighs the triage rail. */
.os-split-40-60,
.apex-tactical-layout {
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 2.15fr);
    gap: var(--os-space-5);
    align-items: start;
}

/* Viewport-locked panels were a clipping hazard under a fixed dock —
   bound them against the dock height instead of a magic 210px. */
.apex-tactical-layout {
    height: min(780px, calc(100vh - var(--os-nav-height) - var(--os-space-7)));
    min-height: 480px;
    margin-bottom: var(--os-gap-section);
}

/* Player 360: the radar leads, the load trend supports it. */
.player360-chart-grid {
    grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
    gap: var(--os-space-5);
    margin-bottom: var(--os-space-7);
}

.player360-detail-grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
    gap: var(--os-space-5);
}

/* Matchday: board dominates, unavailable aside supports. */
.roster-layout {
    grid-template-columns: minmax(0, 2.1fr) minmax(0, 1fr);
    gap: var(--os-space-5);
}

.matchday-builder {
    grid-template-columns: minmax(0, 2.2fr) minmax(0, 1fr);
    gap: var(--os-space-4);
}

/* Positional analysis + medical history: full-width sections. */
.apex-positional-panel,
.med-entry-history-section,
.injury-metric-grid {
    margin-bottom: var(--os-gap-section);
}

/* Section-level vertical rhythm. */
.apex-positional-summary,
.injury-table-wrap {
    margin-bottom: var(--os-space-6);
}

/* Cards inside a grid never collapse. */
.os-card,
.apex-panel,
.roster-panel,
.player360-panel,
.injury-table-card,
.clinical-tier {
    min-width: 0;
}


/* ═══════════════════════════════════════════════════════════════════
   10. RESPONSIVE  (Phase 4)
   Owned here so it always wins the cascade: this file loads after
   custom.css, so equal-specificity rules resolve to this file.
   Every breakpoint that changes a property custom.css also changes
   must re-declare that property here, otherwise the older block would
   silently apply (source order beats media-query proximity).
   ═══════════════════════════════════════════════════════════════════ */

/* No horizontal page scroll — `clip` (unlike `hidden`) never creates a
   scroll container, so it cannot break fixed/sticky positioning. */
html,
body {
    max-width: 100%;
    overflow-x: clip;
}

/* ── 1024px: tablet / pitchside ───────────────────────────────────
   Multi-column grids collapse to one column and touch targets grow to
   44px. Mirrors (and supersedes) the 1024px block in custom.css. */
@media (max-width: 1024px) {
    .apex-content-grid,
    .apex-tactical-layout,
    .os-split-40-60,
    .os-grid-2,
    .os-grid-3,
    .os-grid-4,
    .player360-chart-grid,
    .player360-detail-grid,
    .roster-layout,
    .matchday-builder,
    .clinical-triage-tiers {
        grid-template-columns: minmax(0, 1fr);
        height: auto;
    }

    .apex-tactical-column {
        min-height: 0;
    }

    .apex-hazard-list {
        max-height: 440px;
    }

    .ghd-nav-link,
    .ghd-squad-pill,
    .ghd-preset,
    .ghd-logout,
    .ghd-density-checklist label,
    .btn-pill,
    .week-chip,
    .apex-position-chip,
    .injury-primary-button,
    .roster-move-submit,
    .matchday-save-button,
    .os-button {
        min-height: 44px;
    }

    .matchday-player-select,
    select.form-select,
    .os-select,
    .med-entry-control,
    .roster-move-control {
        min-height: 44px;
    }

    .apex-kpi-value,
    .injury-metric-value,
    .kpi-tile-value {
        font-size: var(--os-text-xl);
    }

    .os-hero-metric,
    .os-kpi-strip > :first-child .apex-kpi-value,
    .os-kpi-strip > :first-child .kpi-tile-value,
    .os-kpi-strip > :first-child .injury-metric-value {
        font-size: var(--os-text-2xl);
    }
}

/* ── 900px: dense panels stack ────────────────────────────────────
   Hero panels drop back to the standard card pad; KPI strips go
   two-up; the Player 360 selector row goes full width. */
@media (max-width: 900px) {
    .os-kpi-strip {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .apex-panel,
    .roster-panel,
    .player360-panel,
    .apex-risk-panel,
    .apex-matrix-panel,
    .matchday-panel,
    .player360-load-panel,
    .injury-table-card {
        padding: var(--os-pad-card);
    }

    .player360-select-wrap {
        min-width: 0;
        width: 100%;
    }

    .clinical-tier-cases {
        max-height: 520px;
    }
}

/* ── 640px: phone ─────────────────────────────────────────────────
   Single column, dock sheds its quietest clusters, and the dock offset
   is re-declared so the old `padding: 12px` shorthand in custom.css
   can never pull content up under the bar. */
@media (max-width: 640px) {
    .os-kpi-strip,
    .matchday-kpi-strip {
        grid-template-columns: minmax(0, 1fr);
    }

    .global-header-dock {
        gap: var(--os-space-2);
        padding: 0 var(--os-space-4);
    }

    .ghd-brand-name,
    .ghd-week-range,
    .ghd-presets,
    .ghd-session-role {
        display: none;
    }

    .ghd-center {
        overflow-x: auto;
        scrollbar-width: none;
    }

    /* The dock is fixed, so the top offset must survive every cascade
       level — re-declare the full shorthand with the offset intact. */
    .main-wrapper {
        padding: var(--os-space-4) var(--os-space-4) var(--os-space-5);
    }

    .main-wrapper:not(.login-route-wrapper) {
        padding-top: calc(var(--os-nav-height) + var(--os-space-4));
    }

    .matchday-reserves-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    .depth-chart-line,
    .depth-chart-line-4,
    .depth-chart-line-5 {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        padding: 0;
    }

    .matchday-pitch-row,
    .matchday-pitch-row-1,
    .matchday-pitch-row-2,
    .matchday-pitch-row-3,
    .matchday-pitch-row-4,
    .matchday-pitch-row-5,
    .matchday-pitch-row-6 {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        padding: 0;
    }

    .module-header {
        flex-direction: column;
    }

    .apex-kpi-grid,
    .injury-metric-grid,
    .med-entry-kpi-grid,
    .injury-form-grid,
    .med-entry-grid,
    .roster-move-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    .injury-heading-row {
        align-items: flex-start;
        flex-direction: column;
    }

    .medical-actions {
        width: 100%;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   11. MOTION & ACCESSIBILITY  (Phase 3)
   ═══════════════════════════════════════════════════════════════════ */
:focus-visible {
    outline: 2px solid var(--os-accent);
    outline-offset: 2px;
    border-radius: var(--os-radius-sm);
}

button:focus:not(:focus-visible),
a:focus:not(:focus-visible) {
    outline: none;
}

/* Status is never colour-alone: every semantic chip carries a glyph or
   a word. This rule guarantees the chip always has room for both. */
.matchday-status-flag,
.player-card-medical-tag,
.unavailable-review-chip,
.badge-position-status {
    gap: var(--os-space-1);
    line-height: 1.4;
}

/* W3C-recommended reduced-motion reset (not a specificity override). */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}







