/* ══════════════════════════════════════════════════════
   Signal Tennis Design System Tokens (v2 — UX2, 2026-07-11)
   ══════════════════════════════════════════════════════
   WHAT LIVES IN THIS FILE:
   - Design tokens (:root custom properties): colors, semantic
     layer, spacing, typography, radii, shadows, z-index,
     breakpoints (informational), animation.
   - Canonical shared rules: .filter-btn / .filter-label pills,
     .tag-input-* widget, chart CLS-prevention rules, and the
     ≤575.98px mobile baseline.

   WHAT DOES NOT LIVE HERE (component rules referenced by the
   design system but declared elsewhere):
   - .signal-card             → templates/base.html (~line 203)
   - .signal-pills .nav-link  → local style blocks in the big-3
     dashboards (player_home / squad_analytics / group_analytics)
   - .player-section-header   → same big-3 local style blocks
   These extract to a shared dashboard shell in UX4.

   Component pattern quick reference (per D-06):
   PRIMARY BTN:     .btn-signal-primary — var(--signal-blue) bg, #fff text, 8px radius
   SECONDARY BTN:   .btn-outline-primary — border var(--signal-blue), transparent bg
   STATUS BADGE:    .badge — bg-success/bg-danger/bg-warning, rounded-pill
   EMPTY STATE:     portal/includes/_empty_state.html — bi icon, heading, body, CTA
   LOADING:         portal/includes/_spinner.html — .spinner-border
   ══════════════════════════════════════════════════════ */

:root {
  /* -- Colors ---------------------------------------- */
  --signal-blue:        #1a5fa8;
  --signal-blue-dark:   #154d8a;
  --signal-green:       #8dc63f;
  --signal-green-dark:  #72a32e;
  --signal-light:       #f4f7fb;
  --signal-border:      #dce5f0;
  --signal-navy:        #0f3a6b;
  --signal-ink:         #1e2a3a;
  --signal-muted:       #5a6a7e;
  --signal-destructive: #dc3545;
  --signal-success:     #2e7d32;
  --signal-amber:       #f59e0b;

  /* Color consolidation decisions (UX2, 2026-07-11) — applied
     mechanically in the UX3 sweeps, documented here as truth:
     REDS  — two canonical reds only:
       --signal-destructive (#dc3545) for UI error/danger states;
       --v3-red (#d04438) for data-viz negative values.
       #dc2626, #ef4444, #c0392b → map to one of the two above.
     GRAYS — #6b7a90 unifies to --signal-muted (#5a6a7e);
       #374151 gets its own token --v3-ink-mid (see V3 block). */

  /* -- Spacing --------------------------------------- */
  --sp-xs:  4px;
  --sp-sm:  8px;
  --sp-md:  16px;
  --sp-lg:  24px;
  --sp-xl:  32px;
  --sp-2xl: 48px;
  --sp-3xl: 64px;

  /* -- Typography - canonical roles ------------------ */
  --fs-body:    1rem;
  --fs-label:   0.82rem;
  --fs-heading: 1.25rem;
  --fs-display: 2rem;

  --fw-regular: 400;
  --fw-bold:    700;

  /* -- Typography - legacy (sunset v1.1) ------------- */
  --fs-xs:      0.68rem;
  --fs-sm:      0.75rem;
  --fs-md-sm:   0.88rem;

  --fw-medium:    500;
  --fw-semibold:  600;
  --fw-extrabold: 800;

  --lh-body:    1.5;
  --lh-heading: 1.2;

  /* -- Radii ----------------------------------------- */
  --radius-sm:  6px;
  --radius-md:  8px;
  --radius-lg:  12px;
  --radius-xl:  14px;
  --radius-2xl: 16px;

  /* -- Shadows --------------------------------------- */
  --shadow-sm:   0 1px 4px rgba(0,0,0,.06);
  --shadow-md:   0 2px 8px rgba(0,0,0,.05);
  --shadow-lg:   0 4px 24px rgba(26,95,168,.1);
  --shadow-hover: 0 8px 24px rgba(26,95,168,.12);

  /* -- Z-index scale (UX2) ---------------------------
     Aligned with Bootstrap 5.3 defaults so custom layers
     slot in predictably. Use these, never magic numbers. */
  --z-dropdown:       1000;
  --z-sticky:         1020;
  --z-fixed:          1030;
  --z-modal-backdrop: 1050;
  --z-modal:          1055;
  --z-popover:        1070;
  --z-tooltip:        1080;
  --z-toast:          1090;
  --z-palette:        2000;  /* command palette — above everything */

  /* -- Breakpoints (UX2, informational) --------------
     CSS media queries CANNOT consume var() — these exist
     for JS (getComputedStyle) and as the documented scale.
     Media queries hardcode the same values (Bootstrap 5). */
  --bp-sm: 576px;
  --bp-md: 768px;
  --bp-lg: 992px;
  --bp-xl: 1200px;

  /* -- Animation (UX2) -------------------------------- */
  --dur-fast: .15s;   /* hovers, pills, small transitions */
  --dur-med:  .2s;    /* buttons, cards */
  --dur-slow: .3s;    /* panels, drawers, collapse */
  --ease-standard: ease;
  --ease-out: cubic-bezier(.16, 1, .3, 1);
}

/* ── Semantic layer (UX2 — dark-ready, NOT dark-built) ──
   Roles, not colors. New code should prefer these over raw
   primitives so a future dark mode only needs to override
   this block (e.g. under [data-theme="dark"]). Values map
   1:1 to existing primitives — zero visual change. */
:root {
  --surface-page:    var(--v3-bg);          /* app page background */
  --surface-card:    var(--v3-surface);     /* cards, panels */
  --surface-subtle:  var(--v3-surface-alt); /* alt rows, wells */
  --text-primary:    var(--v3-ink);
  --text-secondary:  var(--v3-ink-soft);
  --text-faint:      var(--v3-ink-faint);
  --border-default:  var(--v3-border);
  --border-accent:   var(--signal-border);
  --accent:          var(--signal-blue);
  --accent-strong:   var(--signal-blue-dark);
  --accent-contrast: #ffffff;               /* text on --accent */
  --state-success:   var(--signal-success);
  --state-warning:   var(--signal-amber);
  --state-danger:    var(--signal-destructive);
  --state-info:      var(--signal-blue);
}

/* ── V3 surface palette ──────────────────────────────
   Consolidated from player_home.html:318 and squad_analytics.html:319.
   These were previously defined inline in each template. */
:root {
  --v3-bg:           #f0f2f7;
  --v3-surface:      #ffffff;
  --v3-surface-alt:  #f8fafc;
  --v3-ink:          #0f1626;
  --v3-ink-soft:     #4a5268;
  --v3-ink-mid:      #374151;  /* mid-tone ink (UX2) — heavily used in newer templates */
  --v3-ink-faint:    #5f6b80;  /* darkened for WCAG AA 4.5:1 on --v3-bg (UX11) */
  --v3-border:       #dce1eb;
  --v3-blue:         var(--signal-blue);
  --v3-blue-light:   #e8f0fb;
  --v3-green:        var(--signal-green);
  --v3-green-dark:   #5f8c22;
  --v3-gold:         #b8841f;  /* darkened from #d89b2b for WCAG AA 3:1 on white */
  --v3-red:          #d04438;
  --v3-navy:         #0f2648;

  /* Elevated shadow (D-05) */
  --shadow-elevated: 0 8px 18px rgba(15, 23, 36, .12);

  /* Removed in UX2 (2026-07-11), zero references at removal:
     --v3-navy-mid, --clr-primary, --lh-tight */
}

/* -- CLS prevention for dynamic chart images -------- */
.chart-card img {
  aspect-ratio: 4 / 3;
  width: 100%;
  height: auto;
}
.chart-iframe-wrap {
  aspect-ratio: 1 / 1;
  width: 100%;
}
.chart-iframe-interactive {
  aspect-ratio: 1 / 1;
}

/* -- Filter pills (canonical — reuse, don't redeclare) */
.filter-btn {
  font-size: var(--fs-label, .82rem);
  font-weight: 600;
  padding: .25rem .7rem;
  border: 1.5px solid var(--signal-border);
  border-radius: var(--radius-sm, 6px);
  background: transparent;
  color: var(--signal-blue);
  cursor: pointer;
  transition: all .15s;
}
.filter-btn:hover {
  background: var(--signal-light);
  border-color: var(--signal-blue);
}
.filter-btn.active {
  background: var(--signal-blue);
  color: #fff;
  border-color: var(--signal-blue);
}
/* Tri-state filter pill, third state: exclude. Neutral is the bare pill,
   include is .active above, exclude is this. Struck through as well as red so
   the state survives a colour-blind read and a greyscale screenshot. Any pill
   row that offers a NOT reuses these two classes rather than inventing a
   third colour of its own. */
.filter-btn.is-exclude {
  background: var(--signal-destructive);
  color: #fff;
  border-color: var(--signal-destructive);
  text-decoration: line-through;
}
.filter-btn.is-exclude:hover {
  background: var(--signal-destructive);
  border-color: var(--signal-destructive);
}
.filter-label {
  font-size: var(--fs-xs, .68rem);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--signal-muted);
}

/* -- Tag input widget (reusable JSONField multi-select) */
.tag-input-container {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-xs, 4px);
  padding: var(--sp-xs, 4px) var(--sp-sm, 8px);
  border: 1.5px solid var(--signal-border);
  border-radius: var(--radius-sm, 6px);
  background: #fff;
  min-height: 38px;
  position: relative;
  cursor: text;
  transition: border-color .15s;
}
.tag-input-container:focus-within {
  border-color: var(--signal-blue);
  box-shadow: 0 0 0 2px rgba(26,95,168,.12);
}
.tag-input-pill {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: var(--fs-label, .82rem);
  font-weight: 600;
  padding: .15rem .5rem;
  border-radius: var(--radius-sm, 6px);
  background: var(--signal-blue);
  color: #fff;
  white-space: nowrap;
  line-height: 1.4;
}
/* A pill carrying a stage picker can outgrow a phone-width container, and
   nowrap then pushes it past the card edge. Let it wrap on small screens;
   desktop keeps the single-line pill it has always had. */
@media (max-width: 575.98px) {
  .tag-input-pill {
    white-space: normal;
    max-width: 100%;
  }
}
/* Per-pill stage picker, rendered only when a TagInput is given
   stageOptions (drill stage scoping). Inherits the pill's own colours so a
   tagged item still reads as one unit. */
.tag-input-pill-stage {
  border: none;
  border-radius: var(--radius-sm, 6px);
  background: rgba(255,255,255,.55);
  color: inherit;
  font-size: .7rem;
  font-weight: 600;
  line-height: 1.3;
  padding: 0 .15rem;
  max-width: 9rem;
  cursor: pointer;
}
.tag-input-pill-stage:focus-visible {
  outline: 2px solid var(--signal-blue);
  outline-offset: 1px;
}
.tag-input-pill-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  color: rgba(255,255,255,.7);
  font-size: .9rem;
  line-height: 1;
  padding: 0 0 0 2px;
  cursor: pointer;
}
.tag-input-pill-close:hover {
  color: #fff;
}
.tag-input-field {
  border: none;
  outline: none;
  font-size: var(--fs-label, .82rem);
  flex: 1 1 60px;
  min-width: 60px;
  padding: .15rem 0;
  background: transparent;
  color: var(--signal-ink, #1e2a3a);
}
.tag-input-field::placeholder {
  color: var(--signal-muted, #5a6a7e);
}
.tag-input-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 1050;
  background: #fff;
  border: 1px solid var(--signal-border);
  border-radius: var(--radius-sm, 6px);
  box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,.05));
  max-height: 200px;
  overflow-y: auto;
  margin-top: 2px;
}
.tag-input-dropdown-item {
  padding: .35rem .7rem;
  font-size: var(--fs-label, .82rem);
  cursor: pointer;
  color: var(--signal-ink, #1e2a3a);
}
.tag-input-dropdown-item:hover,
.tag-input-dropdown-item.highlighted {
  background: var(--signal-light, #f4f7fb);
  color: var(--signal-blue);
}

/* -- Tables scroll in wrappers, never the page (UX6) --
   Approach chosen: (1) .table-responsive keeps overflow-x:auto
   globally (belt-and-braces over Bootstrap's own rule) with iOS
   momentum scrolling; (2) at phone widths, any BARE table.table
   inside the portal shell that is NOT already wrapped in a
   .table-responsive becomes its own block-level scroll container
   (display:block + overflow-x:auto), so wide tables scroll within
   themselves instead of forcing horizontal document overflow.
   Wrapped tables are excluded via :not() and keep table layout.
   Deliberately NOT using main { overflow-x:hidden } — that would
   break position:sticky descendants. */
.table-responsive {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* -- Mobile responsive: 375px safe ------------------- */
@media (max-width: 575.98px) {
  /* Touch-friendly tap targets */
  .btn-sm {
    min-height: 44px;
    padding: 10px 16px;
  }
  .filter-btn {
    min-height: 44px;
    padding: .5rem .9rem;
  }

  /* Bare (unwrapped) tables become their own scroll container —
     see the UX6 table-overflow comment above the mobile block */
  main.signal-main :not(.table-responsive) > table.table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  /* Scrollable tab strips */
  .nav-tabs,
  .v3-tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .nav-tabs::-webkit-scrollbar,
  .v3-tabs::-webkit-scrollbar {
    display: none;
  }
  .nav-tabs .nav-link,
  .v3-tab-group {
    white-space: nowrap;
    flex-shrink: 0;
  }

  /* Ensure images never cause horizontal scroll */
  img {
    max-width: 100%;
    height: auto;
  }

  /* Full-width form inputs on mobile */
  .form-control,
  .form-select {
    width: 100%;
  }
}

/* -- Reduced motion (UX11 — WCAG 2.3.3) --------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* -- Inferred-content provenance marker --------------------------------
   Classified lesson blocks store AI-inferred bullets as li.inferred.
   The marker renders as a ::after pseudo-element so the bullet DOM is
   byte-identical for every viewer; templates emit the .show-inferred
   scope class on a wrapping container for staff viewers only, so
   players and parents never receive any badge markup or styling hook
   they can observe beyond the stored class name. */
/* -- Classified-block source label as a pill ---------------------------
   Model-written lesson content is appended inside
   div.lesson-classified-block, whose first paragraph is the source label
   ("Extracted from voice notes" etc.) stored as <p><strong>label</strong></p>.
   Pure CSS over that stored markup, so existing DB rows restyle too:
   the bold prose line becomes a small muted pill matching the inferred
   marker below. Plain-text fields render escaped and keep their label
   line as-is. */
.lesson-classified-block > p:first-child {
  margin-bottom: .25rem;
}
.lesson-classified-block > p:first-child > strong {
  display: inline-block;
  padding: .05rem .3rem;
  font-size: .6rem;
  font-weight: 600;
  line-height: 1.3;
  text-transform: uppercase;
  letter-spacing: .04em;
  white-space: nowrap;
  color: var(--signal-muted);
  background: var(--signal-light);
  border: 1px solid var(--signal-border);
  border-radius: var(--radius-sm, 6px);
}

/* Staff extraction-marker toggle scope: templates emit show-inferred when
   the viewer wants markers (default) and hide-extraction-markers when the
   stored pref turns them off — hiding the source-label pills; the
   li.inferred::after pills are already off because show-inferred is
   withheld. */
.hide-extraction-markers .lesson-classified-block > p:first-child {
  display: none;
}

.show-inferred li.inferred::after {
  content: "inferred";
  display: inline-block;
  margin-left: .4rem;
  padding: .05rem .3rem;
  font-size: .6rem;
  font-weight: 600;
  line-height: 1.3;
  text-transform: uppercase;
  letter-spacing: .04em;
  vertical-align: 15%;
  white-space: nowrap;
  color: var(--signal-muted);
  background: var(--signal-light);
  border: 1px solid var(--signal-border);
  border-radius: var(--radius-sm, 6px);
}

/* Full-width documents on desktop: add st-full-desktop to any width-capped
   document/template wrapper (max-width prose columns, capped containers) to
   lift the cap at the lg breakpoint and up. Mobile/tablet layout is
   untouched; inner padding on the wrapper still provides the gutters.
   !important is deliberate so it wins over inline style="max-width:..."
   and template-scoped wrapper rules. */
@media (min-width: 992px) {
  .st-full-desktop { max-width: none !important; }
}

/* ── ED33: rich-editor table gridlines on rendered pages ──────────
   The Tiptap editor writes data-gridlines="on|off" on every table it
   saves (on by default). Legacy tables stored before ED33 carry no
   attribute and no class, so they are treated as "on" too. Specificity
   is pinned at (0,0,2) via :where() so Bootstrap's reboot (td border
   0) loses but any class-scoped table styling (.table, .outline-table,
   .rkp-tblwrap, popups) still wins. */
table:where([data-gridlines], :not([class]):not(.md-shortcuts-popup *):not(.rkp-tblwrap *):not(.rich-editor-surface *)) {
  width: 100%;
  border-collapse: collapse;
  margin: .75rem 0;
  font-size: .85rem;
}

table:where([data-gridlines="on"], :not([class]):not([data-gridlines]):not(.md-shortcuts-popup *):not(.rkp-tblwrap *):not(.rich-editor-surface *)) :is(th, td) {
  border: 1px solid var(--signal-border, #dce5f0);
  padding: .45rem .55rem;
  vertical-align: top;
}

table:where([data-gridlines="on"], :not([class]):not([data-gridlines]):not(.md-shortcuts-popup *):not(.rkp-tblwrap *):not(.rich-editor-surface *)) th {
  background: var(--signal-light, #f4f7fb);
  color: var(--signal-blue-dark, #154d8a);
  font-weight: 700;
}

table:where([data-gridlines="off"]) :is(th, td) {
  border: 0;
  padding: .3rem .55rem;
  vertical-align: top;
}
