/**
 * JerryGM Theme Foundation v4
 * ================================
 * Single source of truth for all theme-aware color variables.
 * Load this file BEFORE all other CSS files in index.html.
 *
 * USAGE:
 *   - Dark mode (default): No attribute needed, or data-theme="dark" on <html>
 *   - Light mode: data-theme="light" on <html>
 *   - Toggle in JS: document.documentElement.setAttribute('data-theme', 'light'|'dark')
 *
 * MIGRATION STRATEGY:
 *   Component files already use patterns like var(--text-primary, #fallback).
 *   Once this file loads first, those fallbacks become inert — the variable is always defined.
 *   Hardcoded hex values can be migrated file-by-file to use these vars.
 *
 * NAMING CONVENTION:
 *   --jgm-*  = App-level semantic tokens (surfaces, text, accents)
 *   Component files can create scoped aliases:
 *     --rm-bg-primary: var(--jgm-bg-primary);  (Roster Management)
 *     --cb-t0: #4f4a8a;                        (Clipboard team colors — not themed)
 */

/* ================================================================
   DARK THEME (Default)
   Based on Tailwind Slate palette — the app's primary dark surface system.
   ================================================================ */
:root,
[data-theme="dark"] {

    /* ── Surfaces ────────────────────────────────────────────────
       Ordered from deepest background to most elevated.
       Use --jgm-bg-primary for page/app bg, --jgm-bg-elevated for popovers.
    ────────────────────────────────────────────────────────────── */
    --jgm-bg-deep:      #0f172a;  /* slate-900  — deepest bg, modals overlay */
    --jgm-bg-primary:   #1e293b;  /* slate-800  — main app/header bg */
    --jgm-bg-secondary: #1f2937;  /* gray-800   — card/panel bg */
    --jgm-bg-tertiary:  #334155;  /* slate-700  — input fields, hover states */
    --jgm-bg-elevated:  #475569;  /* slate-600  — raised elements, active states */
    --jgm-bg-hover:     rgba(255, 255, 255, 0.05); /* universal subtle hover */

    /* ── Borders ─────────────────────────────────────────────── */
    --jgm-border-primary:   #334155;  /* slate-700  — main dividers */
    --jgm-border-secondary: #475569;  /* slate-600  — subtle separators */
    --jgm-border-muted:     rgba(255, 255, 255, 0.06); /* faintest divider */
    --jgm-border-focus:     #3b82f6;  /* blue-500   — focus rings */

    /* ── Text ────────────────────────────────────────────────── */
    --jgm-text-primary:   #e2e8f0;  /* slate-200  — body text, headings */
    --jgm-text-secondary: #94a3b8;  /* slate-400  — labels, descriptions */
    /* Between slate-500 and slate-400. The shipped slate-500 (#64748b) measured
       3.75 on --jgm-bg-deep, 3.07 on --jgm-bg-primary and 3.08 on
       --jgm-bg-secondary — failing AA on every surface the app paints text on,
       app-wide, and worst on the interactive things (the Trade Finder's "Why
       this trade?" / "Partner Intel" toggles read as disabled). This clears
       4.5 on all three (5.81 / 4.76 / 4.78) while staying a visible step below
       --jgm-text-secondary. Fixed at the token, not per-file, or it just moves.
       NOTE: still 3.37 on --jgm-bg-tertiary — muted text does not belong on an
       input/hover fill; use --jgm-text-secondary there. */
    --jgm-text-muted:     #8794a9;  /* slate-450  — timestamps, hints */
    --jgm-text-disabled:  #475569;  /* slate-600  — disabled controls */
    /* Companion to --jgm-text-disabled. ALWAYS a neutral gray, never follows
       the user's palette customization (text-base color picker). Use for
       state indicators where "dimmed" should mean "neutral gray" regardless
       of the user's palette — e.g. rostered/drafted players in the depth
       chart. Default value matches --jgm-text-disabled so visual behavior is
       identical until the user customizes their palette; once customized,
       --jgm-text-disabled shifts to follow the new hue but this token stays
       neutral. */
    --jgm-text-disabled-grey: #475569;  /* slate-600 — palette-independent */
    --jgm-text-inverse:   #111827;  /* gray-900   — text on light surfaces */

    /* ── Semantic Accent Colors ──────────────────────────────── */
    --jgm-accent-blue:    #3b82f6;  /* blue-500   — primary action, links */
    --jgm-accent-blue-hover: #60a5fa; /* blue-400 — hover/active */
    --jgm-accent-green:   #10b981;  /* emerald-500— success, positive delta */
    --jgm-accent-green-hover: #34d399; /* emerald-400 */
    --jgm-accent-red:     #ef4444;  /* red-500    — error, negative delta */
    --jgm-accent-red-hover: #f87171; /* red-400 */
    --jgm-accent-amber:   #f59e0b;  /* amber-500  — warning, caution */
    --jgm-accent-amber-hover: #fbbf24; /* amber-400 */
    /* FILL ONLY. Alone among the accents this base is a -600, not a -500: it is
       the Elite-tier fill, tuned so white ink reads on it (5.70). As TEXT on a
       dark surface it measures 3.13 / 2.57 / 2.58 / 1.82 on deep / primary /
       secondary / tertiary — it failed AA on every one of them, across 53 call
       sites. Don't brighten it to fix that: violet-500 drops white-on-purple to
       4.23 and violet-400 to 2.72, breaking the fills instead. */
    --jgm-accent-purple:  #7c3aed;  /* violet-600 — Elite tier fill, premium */
    /* violet-400 — purple TEXT on every dark surface (5.38 / 5.39 / 6.56), and
       violet-600 in light, which clears AA there too (5.70 white, 5.20 deep,
       4.60 elevated). Same arrangement as --jgm-accent-indigo-hover: the -hover
       shade is the hue's text token in BOTH themes while the base stays the
       fill. Purple needs no light-mode pin the way indigo did. */
    --jgm-accent-purple-hover: #a78bfa; /* violet-400 */
    /* Mixed from the BASE, like every other tint token below — it was a fixed
       violet-400 rgba that ignored the theme, which is what the tint block
       exists to stop. Every call site's own fallback already spells
       rgba(124, 58, 237, …), i.e. the base, so this is what they expected.
       It also carries the purple chips over AA: the violet-400 wash lifted the
       chip to #333858 and left violet-400 ink at 4.18, where 14% of the base
       reads 4.90 in dark and 4.53 in light. */
    --jgm-accent-purple-hover-bg: color-mix(in srgb, var(--jgm-accent-purple) 14%, transparent);
    --jgm-accent-purple-hover-border: rgba(167, 139, 250, 0.3);
    --jgm-accent-indigo:  #6366f1;  /* indigo-500 — secondary accent */
    /* indigo-400 — indigo TEXT on every dark surface. The -500 base measures
       2.96-4.00 as text here (deep 4.00, secondary 3.29, primary 3.27) so it
       fails AA on ALL of them, not just the trade card. Fills keep the base. */
    --jgm-accent-indigo-hover: #818cf8;
    /* CYAN, not teal. Teal sat ΔE 21.9 from green as a solid fill and only
       13.4 as a tinted chip's ink — below the ~15 where two colours stop
       reading as distinct. They are sibling variants in real components:
       .te-equity-banner--green/--teal, .te-rt mine-vs-in, .wib-bye-ok vs
       .wib-bye-plug. Cyan takes that to 50.1 dark / 39.0 light at no cost to
       contrast (6.05 vs 5.90 as text), and stays 60+ from blue. The
       combined/aggregate meaning is unchanged — cyan is the next hue over.
       NOTE the --jgm-pos-* teal values are a different system (position
       identity) and deliberately stay teal. */
    --jgm-accent-teal:    #06b6d4;  /* cyan-500   — combined/aggregate accent */
    --jgm-accent-teal-hover: #67e8f9; /* cyan-300 */

    /* ── Accent TEXT inks ────────────────────────────────────────
       One token per hue for accent-coloured TEXT, so `-hover` goes back to
       meaning hover. 167 steady-state rules used to borrow
       --jgm-accent-{hue}-hover as their text colour; that shade is a LIGHTER
       step tuned for fills and hover states, so in light theme those rules
       measured 1.73–4.43. Amber on --jgm-bg-elevated (1.73) was the worst
       contrast in the app.

       Dark values ARE the -hover shades those sites already used, so dark
       rendering is unchanged by the migration. Light values (in the light
       block) go a step past the base — the same "text needs a deeper step
       than the fill" rule the tint sites follow.

       Pinning -hover itself, the way indigo and purple do, is not available to
       red/amber/green/blue: 14 rules consume their -hover as a BACKGROUND
       (black-on-amber would drop 9.78 → 4.18, taking the injury-Q chip with
       it) and 18 more are genuine :hover text states that would stop
       answering the pointer. Indigo and purple have neither, which is why
       they can — their -text tokens alias what they already use, so all six
       hues read the same way at the call site.

       Use -text for text, the base for fills, -hover for hover. */
    /* red-300, NOT the -400 the other hues alias. Red is the darkest of the
       -400 shades, so on a RED tint the background climbs toward the ink and
       the gap collapses: red-400 measured 3.79 on .tng-chip.tng-critical, 3.86
       on .wpl-pos.position-qb, 4.26 on .fir-inj / .tng-phase-rebuild and 4.38
       on .mwv-bye-badge / .sr-inj-ir — all small badge text, so 4.5 applies
       with no large-text exemption. red-300 carries all six to 5.53–6.38 and
       also lifts red text on --jgm-bg-tertiary from 3.74 to 5.46. Amber, green
       and blue don't need this; their -400 shades are far lighter and already
       clear their own tints. Nothing consumes -text as a fill, so lightening
       it breaks nothing. (--jgm-bg-elevated stays short at 3.99 — that surface
       is a known ceiling, not an ink problem.) */
    --jgm-accent-red-text:    #fca5a5;                       /* red-300 */
    --jgm-accent-amber-text:  var(--jgm-accent-amber-hover);  /* amber-400 */
    --jgm-accent-green-text:  var(--jgm-accent-green-hover);  /* emerald-400 */
    --jgm-accent-blue-text:   var(--jgm-accent-blue-hover);   /* blue-400 */
    --jgm-accent-indigo-text: var(--jgm-accent-indigo-hover); /* indigo-400 */
    --jgm-accent-purple-text: var(--jgm-accent-purple-hover); /* violet-400 */
    --jgm-accent-teal-text:   var(--jgm-accent-teal-hover);   /* cyan-300 */

    /* ── Accent tints (low-alpha fills / borders) ────────────────
       Components kept reaching for --jgm-accent-*-bg and -*-subtle with
       hardcoded rgba fallbacks; the tokens never existed, so every one of them
       rendered a FIXED tint that ignored the theme. Declared here once, mixed
       from the themed accent above, so they follow it in both themes — which
       also means they need no [data-theme="light"] override. One name per
       accent: the old "-subtle" spelling was the same idea at a slightly
       different alpha and now points here. */
    --jgm-accent-purple-bg:        color-mix(in srgb, var(--jgm-accent-purple) 10%, transparent);
    --jgm-accent-purple-bg-hover:  color-mix(in srgb, var(--jgm-accent-purple) 20%, transparent);
    --jgm-accent-purple-border:    color-mix(in srgb, var(--jgm-accent-purple) 20%, transparent);
    --jgm-accent-blue-bg:          color-mix(in srgb, var(--jgm-accent-blue) 10%, transparent);
    --jgm-accent-green-bg:         color-mix(in srgb, var(--jgm-accent-green) 10%, transparent);
    --jgm-accent-amber-bg:         color-mix(in srgb, var(--jgm-accent-amber) 10%, transparent);
    --jgm-accent-red-bg:           color-mix(in srgb, var(--jgm-accent-red) 10%, transparent);
    /* Border siblings, so the tinted-chip recipe is one name per accent the way
       the comment above intends. 20% matches -purple-border, which predates
       these. Chip recipe: -bg fill + -border edge + -text ink; measured
       4.64–7.39 in both themes at 10%. Don't push the fill to 15% — purple
       drops to 4.28 in light. */
    --jgm-accent-teal-bg:          color-mix(in srgb, var(--jgm-accent-teal) 10%, transparent);
    --jgm-accent-teal-border:      color-mix(in srgb, var(--jgm-accent-teal) 20%, transparent);
    --jgm-accent-blue-border:      color-mix(in srgb, var(--jgm-accent-blue) 20%, transparent);
    --jgm-accent-green-border:     color-mix(in srgb, var(--jgm-accent-green) 20%, transparent);
    --jgm-accent-amber-border:     color-mix(in srgb, var(--jgm-accent-amber) 20%, transparent);
    --jgm-accent-red-border:       color-mix(in srgb, var(--jgm-accent-red) 20%, transparent);

    /* ── Filled-CTA brand ramp ───────────────────────────────────
       Deliberately IDENTICAL in both themes, unlike every accent above. A
       filled primary button carries white ink at a fixed weight, so a themed
       fill would flip to the light -500 shade in dark mode and drop the label
       to ~2.3:1. These are the two shades that clear 4.5:1 against #fff on
       their own: cyan-700 measures 4.76, blue-700 measures 7.04. Use them ONLY
       under white text — as ink or on a light surface they are far too dark. */
    --jgm-cta-teal: #0e7490;   /* cyan-700 */
    --jgm-cta-blue: #1d4ed8;   /* blue-700 */

    /* Typography stack — referenced by snapshot_viewer, never declared. */
    --jgm-font-sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

    /* Combined-target trade packages (Trade Finder) — semantic trio so any
       "aggregate / lands-everything" UI can reuse the same theming. Dark mode
       IS the on-dark teal, so point at it rather than restating the values;
       the light block below overrides this trio and not the on-dark one. */
    --jgm-combined-fg:     var(--jgm-on-dark-teal-text);
    --jgm-combined-bg:     var(--jgm-on-dark-teal-bg);
    --jgm-combined-border: var(--jgm-on-dark-teal-border);

    /* ── Brand Gradients ─────────────────────────────────────── */
    /* Primary is a TEXT-BEARING surface (buttons, panel headers) and does not
       flip, so #fff sits on it in BOTH themes. The old 600-level ramp measured
       3.68–3.77:1 end to end — near-flat, and the cyan END was the worst point,
       not the green start. So darkening one stop or re-angling fixed nothing;
       the whole ramp had to move. Now one Tailwind step down (emerald/teal/
       cyan-700): 5.36–5.48:1 across, AA at any size. Keep every stop under
       0.183 relative luminance or #fff drops below 4.5:1 again. */
    --jgm-gradient-primary: linear-gradient(135deg, #047857 0%, #0f766e 50%, #0e7490 100%);
    --jgm-gradient-elite:   linear-gradient(135deg, #7c3aed, #9333ea);
    --jgm-gradient-pro:     linear-gradient(135deg, #0369a1, #1d4ed8);
    --jgm-gradient-success: linear-gradient(135deg, #047857, #065f46);
    /* Warning/danger keep their 500-level stops: darkening amber far enough to
       carry #fff turns it brown and kills the signal. Their surfaces use
       --jgm-on-light-text instead (5.57:1). Danger has no text pairing today. */
    --jgm-gradient-warning: linear-gradient(135deg, #f59e0b, #d97706);
    --jgm-gradient-danger:  linear-gradient(135deg, #ef4444, #dc2626);
    --jgm-gradient-indigo:  linear-gradient(135deg, #4f46e5, #7c3aed);
    /* Avatar circles carried three different ramps across four rules, two of
       them starting from the FLIPPING --primary-color under fixed white
       initials — 2.54:1 in dark. Fixed stops, one source. */
    --jgm-gradient-avatar:  linear-gradient(135deg, #047857, #6b21a8);

    /* ── Modal / Overlay ─────────────────────────────────────── */
    --jgm-overlay-bg:       rgba(0, 0, 0, 0.65);
    --jgm-overlay-blur:     blur(2px);
    --jgm-modal-bg:         #1e293b;
    --jgm-modal-border:     #334155;
    --jgm-modal-shadow:     0 24px 64px rgba(0, 0, 0, 0.55);

    /* ── Tooltip (JS overlay — comment icons, 🤝 marks, name tips) ── */
    --jgm-tooltip-bg:       #2d3748;   /* elevated slate — original fixed value */
    --jgm-tooltip-text:     #ffffff;
    --jgm-tooltip-border:   transparent;
    --jgm-tooltip-shadow:   0 4px 12px rgba(0, 0, 0, 0.3);

    /* ── Scrollbar ───────────────────────────────────────────── */
    --jgm-scrollbar-track:  transparent;
    --jgm-scrollbar-thumb:  rgba(255, 255, 255, 0.15);
    --jgm-scrollbar-hover:  rgba(255, 255, 255, 0.25);
    --jgm-scrollbar-width:  6px;

    /* ── Status Colors (unchanged across themes) ─────────────── */
    --jgm-status-live:     #22c55e;
    --jgm-status-complete: #6b7280;
    --jgm-status-warning:  #f59e0b;
    --jgm-status-error:    #ef4444;

    /* ── Injury Badge Colors (unchanged across themes) ───────── */
    --jgm-injury-q-bg: #fbbf24;  --jgm-injury-q-text: #000;
    --jgm-injury-d-bg: #f97316;  --jgm-injury-d-text: #fff;
    --jgm-injury-o-bg: #ef4444;  --jgm-injury-o-text: #fff;
    --jgm-injury-i-bg: #dc2626;  --jgm-injury-i-text: #fff;
    --jgm-injury-p-bg: #6b7280;  --jgm-injury-p-text: #fff;
    --jgm-injury-s-bg: #4b5563;  --jgm-injury-s-text: #fff;

    /* ── Position Colors — badge/slot fills ──────────────────────
       OPAQUE on purpose. These were rgba(…, 0.85) so the surface texture
       showed through; the side effect was that the RENDERED fill depended on
       the card underneath, so white text on a chip measured 6.5:1 on a dark
       card and 4.3:1 on a light one — the same component failing AA on one
       theme only. Alpha is the bug, not the ink. Opaque fills render the same
       on every card, which is what lets one ink token below be correct
       everywhere.
       Same in both themes — positions are identity, not surface.
       Each fill is paired with --jgm-pos-*-on below; never put your own text
       colour on one of these. */
    --jgm-pos-qb:   #ff3b5e;
    --jgm-pos-tqb:  #be185d;   /* pink-700 — red family, ≠ QB */
    --jgm-pos-rb:   #218721;   /* nudged 3% darker — #228b22 carried neither ink */
    --jgm-pos-rbwr: #f59e0b;   /* amber — flex */
    --jgm-pos-wr:   #0066cc;
    --jgm-pos-wrte: #14b8a6;   /* teal — flex */
    --jgm-pos-te:   #ff8c00;
    --jgm-pos-flex: #f59e0b;   /* amber — flex */
    --jgm-pos-op:   #4338ca;   /* indigo — super flex */
    /* Fallback fill for a slot with no position-specific colour. Fixed in
       both themes like the rest of this family (these are badge fills, not
       surfaces). Was an undeclared --ldm-pos-default. */
    --jgm-pos-default: #1289d2;  /* nudged 3% lighter — #1185cc carried neither ink */
    --jgm-pos-k:    #8b5a2b;   /* sienna — was purple, too close to DEF indigo */
    /* P follows K (see below) — the two stay a dark/light pair, as the purples were. */
    --jgm-pos-p:    #a87a42;   /* tan — lighter sibling of K, as the purples were */
    --jgm-pos-def:  #4b0082;
    --jgm-pos-dt:   #ec4899;   /* pink-500 — DL family */
    --jgm-pos-de:   #d92776;   /* pink-600 — DL family */
    --jgm-pos-dl:   #f06595;
    --jgm-pos-lb:   #ff922b;
    --jgm-pos-cb:   #0891b2;   /* cyan — DB family */
    --jgm-pos-s:    #0d9488;   /* teal-600 — DB family */
    --jgm-pos-db:   #20c997;
    --jgm-pos-dp:   #475569;   /* slate — defensive utility */
    --jgm-pos-er:   #9f1239;   /* rose-800 — edge rusher */
    --jgm-pos-hc:   #ca8a04;   /* yellow-600 — head coach */

    /* ── Ink ON the fills above ──────────────────────────────────
       One token per position, holding the ONE ink its hue can carry at
       ≥4.5:1. Whether that is white or near-black is a property of the hue,
       not of the theme — the same split the injury badges already make
       (--jgm-injury-q-text is #000 while its siblings are #fff). Forcing all
       23 onto white would mean darkening te/lb/db/dt/dl/hc so far that the
       warm group (te/lb/k/p) and the DL pinks collapse into each other, and
       those separations are deliberate — see the notes on the fills.
       ALWAYS say `background: var(--jgm-pos-X); color: var(--jgm-pos-X-on)`.
       Pure #fff, not --jgm-on-dark-text (0.88 alpha) — the margins here are
       thin enough that the 12% dilution matters. */
    --jgm-pos-ink-light: #fff;
    --jgm-pos-ink-dark:  #111827;   /* gray-900 */

    --jgm-pos-qb-on:      var(--jgm-pos-ink-dark);    /* 5.10 */
    --jgm-pos-tqb-on:     var(--jgm-pos-ink-light);   /* 6.04 */
    --jgm-pos-rb-on:      var(--jgm-pos-ink-light);   /* 4.62 */
    --jgm-pos-rbwr-on:    var(--jgm-pos-ink-dark);    /* 8.26 */
    --jgm-pos-wr-on:      var(--jgm-pos-ink-light);   /* 5.57 */
    --jgm-pos-wrte-on:    var(--jgm-pos-ink-dark);    /* 7.13 */
    --jgm-pos-te-on:      var(--jgm-pos-ink-dark);    /* 7.61 */
    --jgm-pos-flex-on:    var(--jgm-pos-ink-dark);    /* 8.26 */
    --jgm-pos-op-on:      var(--jgm-pos-ink-light);   /* 7.90 */
    --jgm-pos-default-on: var(--jgm-pos-ink-dark);    /* 4.68 */
    --jgm-pos-k-on:       var(--jgm-pos-ink-light);   /* 5.84 */
    --jgm-pos-p-on:       var(--jgm-pos-ink-dark);    /* 4.67 — K/P split ink, as they split lightness */
    --jgm-pos-def-on:     var(--jgm-pos-ink-light);   /* 12.95 */
    --jgm-pos-dt-on:      var(--jgm-pos-ink-dark);    /* 5.03 */
    --jgm-pos-de-on:      var(--jgm-pos-ink-light);   /* 4.68 — the one dark pink of the three */
    --jgm-pos-dl-on:      var(--jgm-pos-ink-dark);    /* 5.91 */
    --jgm-pos-lb-on:      var(--jgm-pos-ink-dark);    /* 7.94 */
    --jgm-pos-cb-on:      var(--jgm-pos-ink-dark);    /* 4.82 */
    --jgm-pos-s-on:       var(--jgm-pos-ink-dark);    /* 4.74 */
    --jgm-pos-db-on:      var(--jgm-pos-ink-dark);    /* 8.33 */
    --jgm-pos-dp-on:      var(--jgm-pos-ink-light);   /* 7.58 */
    --jgm-pos-er-on:      var(--jgm-pos-ink-light);   /* 8.02 */
    --jgm-pos-hc-on:      var(--jgm-pos-ink-dark);    /* 6.04 */
    /* ── Position Colors — INK on a white chip ───────────────── */
    /* Same hue as the --jgm-pos-* fill above, darkened only as far as it     */
    /* takes to clear 4.5:1 as TEXT on white. Used by the draft board's      */
    /* injury badge so each chip echoes the slot it sits on instead of one   */
    /* fixed red. The raw fills can NOT be used directly — te/lb/db land at  */
    /* 2.1–2.3:1 on white and are unreadable. wr/k/def already pass, so they */
    /* are their tokens unchanged; rb and qb barely move.                    */
    --jgm-pos-qb-ink:  #db3351;
    --jgm-pos-rb-ink:  #218821;
    --jgm-pos-wr-ink:  #0066cc;
    --jgm-pos-te-ink:  #b26200;
    --jgm-pos-k-ink:   #8b5a2b;   /* sienna — 5.8:1 on white, no darkening needed */
    --jgm-pos-def-ink: #4b0082;
    --jgm-pos-dl-ink:  #bb4f74;
    --jgm-pos-lb-ink:  #ad631d;
    --jgm-pos-db-ink:  #158564;

    /* ── Position Colors — Roster (light/pastel bg) ──────────── */
    /* Used as roster-slot fills. Pastels chosen for readability against */
    /* dark text. Same in both themes (positions are identity).          */
    --jgm-pos-qb-light:   #f9b8c9;   /* pink — pulled off salmon, away from TE */
    --jgm-pos-tqb-light:  #fbcfe8;   /* pink-200 */
    --jgm-pos-rb-light:   #d1fadd;
    --jgm-pos-rbwr-light: #fef3c7;   /* amber-100 */
    --jgm-pos-wr-light:   #b6d4f7;   /* reads blue, not blue-grey */
    --jgm-pos-wrte-light: #ccfbf1;   /* teal-100 */
    --jgm-pos-te-light:   #ffc17d;   /* apricot — matches TE orange, was pale yellow */
    --jgm-pos-flex-light: #fef3c7;   /* amber-100 */
    --jgm-pos-op-light:   #e0e7ff;   /* indigo-100 */
    --jgm-pos-k-light:    #ddbe93;   /* caramel — the deepest of the warm group */
    --jgm-pos-p-light:    #f0e2c2;   /* wheat — K's lighter sibling, matching the fills */
    --jgm-pos-def-light:  #cdd6e2;   /* blue-grey — clear of DB's neutral #d1d5db */
    --jgm-pos-dt-light:   #fce7f3;   /* pink-100 */
    --jgm-pos-de-light:   #fbcfe8;   /* pink-200 */
    --jgm-pos-dl-light:   #f3f4f6;
    --jgm-pos-lb-light:   #e5e7eb;
    --jgm-pos-cb-light:   #cffafe;   /* cyan-100 */
    --jgm-pos-s-light:    #99f6e4;   /* teal-200 */
    --jgm-pos-db-light:   #d1d5db;
    --jgm-pos-dp-light:   #e2e8f0;   /* slate-200 */
    --jgm-pos-er-light:   #ffe4e6;   /* rose-100 */
    --jgm-pos-hc-light:   #fef9c3;   /* yellow-100 */

    /* ── Text on the pastel roster fills above ───────────────────
       The pastels do NOT flip with the theme (positions are identity),
       so text sitting on them must stay dark in BOTH themes. Never use
       --jgm-text-* on a pastel surface — it goes near-white in dark mode
       and vanishes against the light fill. Theme-independent, like
       --jgm-salary-chip-text / --jgm-team-chip-*. */
    --jgm-on-pastel-text:   #1f2937;   /* player names / primary */
    --jgm-on-pastel-muted:  #374151;   /* details row / secondary */
    --jgm-on-pastel-accent: #4338ca;   /* contract status — indigo-700 */

    /* ── Position Badge Accent Colors (borders/outlines) ─────── */
    /* Authoritative palette — Utils.getPositionColor() reads these.    */
    /* Keep CONFIG.POSITIONS.COLORS in config_v4.js as a fallback mirror.*/
    --jgm-pos-qb-accent:   #dc2626;   /* red-600 */
    --jgm-pos-tqb-accent:  #9d174d;   /* pink-800 — burgundy */
    --jgm-pos-rb-accent:   #16a34a;   /* green-600 */
    --jgm-pos-rbwr-accent: #f59e0b;   /* amber-500 */
    --jgm-pos-wr-accent:   #2563eb;   /* blue-600 */
    --jgm-pos-wrte-accent: #14b8a6;   /* teal-500 */
    --jgm-pos-te-accent:   #ea580c;   /* orange-600 */
    --jgm-pos-flex-accent: #f59e0b;   /* amber-500 */
    --jgm-pos-op-accent:   #4338ca;   /* indigo-700 */
    --jgm-pos-k-accent:    #a1662f;   /* sienna — 4.7:1 with white where it fills a badge */
    --jgm-pos-p-accent:    #a97b35;   /* tan — lighter sibling of K's sienna */
    --jgm-pos-def-accent:  #6b7280;   /* gray-500 */
    --jgm-pos-dt-accent:   #db2777;   /* pink-600 */
    --jgm-pos-de-accent:   #be185d;   /* pink-700 */
    --jgm-pos-dl-accent:   #f06595;   /* pink */
    --jgm-pos-lb-accent:   #ff922b;   /* orange */
    --jgm-pos-cb-accent:   #0891b2;   /* cyan-600 */
    --jgm-pos-s-accent:    #0d9488;   /* teal-600 */
    --jgm-pos-db-accent:   #20c997;   /* teal */
    --jgm-pos-dp-accent:   #475569;   /* slate-600 */
    --jgm-pos-er-accent:   #9f1239;   /* rose-800 */
    --jgm-pos-hc-accent:   #ca8a04;   /* yellow-600 */

    /* ── Contract Status Badge Formats ───────────────────────────── */
    --jgm-status-holdout: var(--jgm-status-warning);
    --jgm-status-stashed: var(--jgm-accent-purple);
    --jgm-status-expired: var(--jgm-text-muted);
    --jgm-status-unsigned: var(--jgm-accent-blue);   /* no contract — neutral "needs signing", distinct from expired grey */

    /* ── Trade-Asset Accent Colors ───────────────────────────── */
    /*    Picks and cap dollars aren't positions, but they render */
    /*    with the same badge component in trade UIs. Colocated   */
    /*    here so Utils.getPositionColor() can read them.         */
    --jgm-asset-pick-accent: #7c3aed;  /* Purple — draft picks  */
    --jgm-asset-cap-accent:  #0891b2;  /* Cyan   — cap dollars  */

    /* ── Trading Mode (purple-tinted dark surfaces) ──────────── */
    --jgm-trade-bg-deep:      #1a1a2e;
    --jgm-trade-bg-primary:   #252542;
    --jgm-trade-bg-secondary: #2d2d4a;
    --jgm-trade-bg-elevated:  #3d3d5c;
    --jgm-trade-border:       #3d3d5c;
    --jgm-trade-border-muted: #2d2d4a;

    /* ══════════════════════════════════════════════════════════
       BACKWARD-COMPATIBILITY ALIASES
       These map the OLD variable names (already used in component
       CSS with fallbacks) to the new --jgm-* tokens.
       Once a component file is migrated to --jgm-* names directly,
       the alias can be removed.
       ══════════════════════════════════════════════════════════ */

    /* --- Surface aliases (used by roster_management, trading mode, etc.) --- */
    --bg-primary:          var(--jgm-bg-primary);
    --bg-secondary:        var(--jgm-bg-secondary);
    --bg-tertiary:         var(--jgm-bg-tertiary);
    --bg-elevated:         var(--jgm-bg-elevated);
    --bg-hover:            var(--jgm-bg-hover);
    --secondary-bg:        var(--jgm-bg-secondary);
    --surface-color:       var(--jgm-trade-bg-deep);
    --surface-2:           var(--jgm-trade-bg-primary);
    --surface-3:           var(--jgm-trade-bg-secondary);
    --surface-raised:      var(--jgm-trade-bg-elevated);
    --card-bg:             var(--jgm-bg-secondary);
    --card-accent:         var(--jgm-accent-blue);
    --input-bg:            var(--jgm-bg-tertiary);

    /* --- Text aliases --- */
    --text-primary:        var(--jgm-text-primary);
    --text-secondary:      var(--jgm-text-secondary);
    --text-muted:          var(--jgm-text-muted);
    --text3:               var(--jgm-text-disabled);

    /* --- Border aliases --- */
    --border:              var(--jgm-border-primary);
    --border-color:        var(--jgm-border-primary);
    --border-hover:        var(--jgm-border-secondary);

    /* --- Accent aliases --- */
    --accent:              var(--jgm-accent-blue);
    --accent-color:        var(--jgm-accent-blue);
    --accent-primary:      var(--jgm-accent-blue);
    --accent-gold:         var(--jgm-accent-amber);
    --primary-color:       var(--jgm-accent-green);

    /* --- Status aliases --- */
    --success-color:       var(--jgm-accent-green);
    --warning-color:       var(--jgm-accent-amber);
    --error-color:         var(--jgm-accent-red);

    /* --- styles_v4.css :root migrations --- */
    /* Brand colors (same in both themes — identity, not surface).
       NOT the gradient's stops any more: these three stayed at the 600 level
       because mobile_styles uses them as FOREGROUND (.mobile-tab.active colour,
       borders, accent-color) where darkening would cut contrast, while
       --jgm-gradient-primary had to darken to carry #fff. Never rebuild the
       ramp from them — say var(--jgm-gradient-primary). Their own
       background+#fff uses (badges, pills) are still ~3.8:1 and unaudited. */
    --primary-gradient: var(--jgm-gradient-primary);
    --primary-start: #059669;
    --primary-end:   #0891b2;
    --primary-mid:   #0d9488;

    /* Header surface (flips in light mode) */
    --header-bg:       var(--jgm-bg-primary);
    --header-bg-hover: var(--jgm-bg-tertiary);
    --header-border:   var(--jgm-border-primary);

    /* Panel surface */
    --panel-bg:     #f8f9fa;
    --panel-border: #e5e7eb;

    /* Table headers (flips in light mode) */
    --table-header-bg:   #4a5568;
    --table-header-dark:  #2d3748;

    /* Position colors — Draft (dark bg, same both themes) */
    --pos-qb-dark:   var(--jgm-pos-qb);
    --pos-tqb-dark:  var(--jgm-pos-tqb);
    --pos-rb-dark:   var(--jgm-pos-rb);
    --pos-rbwr-dark: var(--jgm-pos-rbwr);
    --pos-wr-dark:   var(--jgm-pos-wr);
    --pos-wrte-dark: var(--jgm-pos-wrte);
    --pos-te-dark:   var(--jgm-pos-te);
    --pos-flex-dark: var(--jgm-pos-flex);
    --pos-op-dark:   var(--jgm-pos-op);
    --pos-k-dark:    var(--jgm-pos-k);
    --pos-p-dark:    var(--jgm-pos-p);
    --pos-def-dark:  var(--jgm-pos-def);
    --pos-dt-dark:   var(--jgm-pos-dt);
    --pos-de-dark:   var(--jgm-pos-de);
    --pos-dl-dark:   var(--jgm-pos-dl);
    --pos-lb-dark:   var(--jgm-pos-lb);
    --pos-cb-dark:   var(--jgm-pos-cb);
    --pos-s-dark:    var(--jgm-pos-s);
    --pos-db-dark:   var(--jgm-pos-db);
    --pos-dp-dark:   var(--jgm-pos-dp);
    --pos-er-dark:   var(--jgm-pos-er);
    --pos-hc-dark:   var(--jgm-pos-hc);

    /* Ink for the fills above — the -dark alias and its -on alias are a PAIR.
       A rule that sets one without the other is the contrast bug this family
       exists to prevent. */
    --pos-qb-on:    var(--jgm-pos-qb-on);
    --pos-tqb-on:   var(--jgm-pos-tqb-on);
    --pos-rb-on:    var(--jgm-pos-rb-on);
    --pos-rbwr-on:  var(--jgm-pos-rbwr-on);
    --pos-wr-on:    var(--jgm-pos-wr-on);
    --pos-wrte-on:  var(--jgm-pos-wrte-on);
    --pos-te-on:    var(--jgm-pos-te-on);
    --pos-flex-on:  var(--jgm-pos-flex-on);
    --pos-op-on:    var(--jgm-pos-op-on);
    --pos-k-on:     var(--jgm-pos-k-on);
    --pos-p-on:     var(--jgm-pos-p-on);
    --pos-def-on:   var(--jgm-pos-def-on);
    --pos-dt-on:    var(--jgm-pos-dt-on);
    --pos-de-on:    var(--jgm-pos-de-on);
    --pos-dl-on:    var(--jgm-pos-dl-on);
    --pos-lb-on:    var(--jgm-pos-lb-on);
    --pos-cb-on:    var(--jgm-pos-cb-on);
    --pos-s-on:     var(--jgm-pos-s-on);
    --pos-db-on:    var(--jgm-pos-db-on);
    --pos-dp-on:    var(--jgm-pos-dp-on);
    --pos-er-on:    var(--jgm-pos-er-on);
    --pos-hc-on:    var(--jgm-pos-hc-on);
    --pos-default-on: var(--jgm-pos-default-on);

    /* Position colors — Roster (light pastel, same both themes) */
    --pos-qb-light:   var(--jgm-pos-qb-light);
    --pos-tqb-light:  var(--jgm-pos-tqb-light);
    --pos-rb-light:   var(--jgm-pos-rb-light);
    --pos-rbwr-light: var(--jgm-pos-rbwr-light);
    --pos-wr-light:   var(--jgm-pos-wr-light);
    --pos-wrte-light: var(--jgm-pos-wrte-light);
    --pos-te-light:   var(--jgm-pos-te-light);
    --pos-flex-light: var(--jgm-pos-flex-light);
    --pos-op-light:   var(--jgm-pos-op-light);
    --pos-k-light:    var(--jgm-pos-k-light);
    --pos-p-light:    var(--jgm-pos-p-light);
    --pos-def-light:  var(--jgm-pos-def-light);
    --pos-dt-light:   var(--jgm-pos-dt-light);
    --pos-de-light:   var(--jgm-pos-de-light);
    --pos-dl-light:   var(--jgm-pos-dl-light);
    --pos-lb-light:   var(--jgm-pos-lb-light);
    --pos-cb-light:   var(--jgm-pos-cb-light);
    --pos-s-light:    var(--jgm-pos-s-light);
    --pos-db-light:   var(--jgm-pos-db-light);
    --pos-dp-light:   var(--jgm-pos-dp-light);
    --pos-er-light:   var(--jgm-pos-er-light);
    --pos-hc-light:   var(--jgm-pos-hc-light);

    /* Position border accents (same both themes) */
    --pos-qb-border:   var(--jgm-pos-qb-accent);
    --pos-tqb-border:  var(--jgm-pos-tqb-accent);
    --pos-rb-border:   var(--jgm-pos-rb-accent);
    --pos-rbwr-border: var(--jgm-pos-rbwr-accent);
    --pos-wr-border:   var(--jgm-pos-wr-accent);
    --pos-wrte-border: var(--jgm-pos-wrte-accent);
    --pos-te-border:   var(--jgm-pos-te-accent);
    --pos-flex-border: var(--jgm-pos-flex-accent);
    --pos-op-border:   var(--jgm-pos-op-accent);
    --pos-k-border:    var(--jgm-pos-k-accent);
    --pos-p-border:    var(--jgm-pos-p-accent);
    --pos-def-border:  var(--jgm-pos-def-accent);
    --pos-dt-border:   var(--jgm-pos-dt-accent);
    --pos-de-border:   var(--jgm-pos-de-accent);
    --pos-dl-border:   var(--jgm-pos-dl-accent);
    --pos-lb-border:   var(--jgm-pos-lb-accent);
    --pos-cb-border:   var(--jgm-pos-cb-accent);
    --pos-s-border:    var(--jgm-pos-s-accent);
    --pos-db-border:   var(--jgm-pos-db-accent);
    --pos-dp-border:   var(--jgm-pos-dp-accent);
    --pos-er-border:   var(--jgm-pos-er-accent);
    --pos-hc-border:   var(--jgm-pos-hc-accent);

    /* Trade-asset accents (alias to --jgm-asset-* for consistency) */
    --asset-pick-accent: var(--jgm-asset-pick-accent);
    --asset-cap-accent:  var(--jgm-asset-cap-accent);

    /* Status (same both themes) */
    --status-live:     var(--jgm-status-live);
    --status-complete: var(--jgm-status-complete);
    --status-warning:  var(--jgm-status-warning);
    --status-error:    var(--jgm-status-error);

    /* Transitions (not theme-dependent, but centralizing) */
    --transition-fast:   0.15s ease;
    --transition-normal: 0.3s ease;

    /* --- Light surface aliases (used in roster view) --- */
    --bg-light-primary:    #ffffff;
    --bg-light-secondary:  #f9fafb;
    --bg-light-tertiary:   #f3f4f6;
    --bg-light-elevated:   #e5e7eb;
    --text-light-primary:  #111827;
    --text-light-secondary: #4b5563;
    --text-light-muted:    #6b7280;
    --border-light-color:  #e5e7eb;

    /* ── On-dark surface tokens ─────────────────────────────────
       For elements that always sit on a dark background (purple
       subheader, gradient modal headers, etc.) regardless of the
       active theme. Easily overridable via user config for custom
       accent colors.                                              */
    --jgm-on-dark-text:          rgba(255, 255, 255, 0.88);
    --jgm-on-dark-text-dim:      rgba(255, 255, 255, 0.6);
    --jgm-on-dark-text-bright:   #fff;
    /* The mirror case: a fill that always stays LIGHT in both themes (amber /
       yellow brand ramps). --jgm-text-primary there measures 1.35:1 in dark, and
       --jgm-text-inverse flips, so neither works — this one doesn't move. */
    --jgm-on-light-text:         #111827;   /* gray-900 */
    /* BOTH washes DARKEN. They used to lighten (white 8% rest / 18% hover),
       which pushed the pill toward the white label — worst on the report
       header's teal end, where the label read 4.01 at rest and 3.36 on hover.
       Darkening moves the plate away from the ink instead:
                         rest            hover
         context bar     5.10 → 6.87     4.24 → 10.20
         modal header    7.43 → 10.28    5.67 → 12.61
         report teal     4.01 → 5.54     3.36 →  8.90
       Because rest darkens too, hover had to go DEEPER than the 0.25 it
       carried when rest still lightened — at 0.25 the rest→hover step fell to
       1.20, undoing the point of it. 0.42 restores 1.52 bar / 1.66 report.
       The modal header only reaches 1.26: it is already dark, so there is less
       room to darken into. Its border cue (--jgm-on-dark-border →
       -border-hover, white 25% → 50%) carries the state change there.
       Don't restore either to a white wash. */
    --jgm-on-dark-bg:            rgba(0, 0, 0, 0.12);
    --jgm-on-dark-bg-hover:      rgba(0, 0, 0, 0.42);
    /* Selected/active is NOT the hover wash, and now that BOTH washes darken it
       is the only lightening one left — which is right: a segmented control
       needs the chosen segment BRIGHTER than its neighbours, and against a
       darkened rest it reads more clearly than it ever did. Keeps the white 18%
       --jgm-on-dark-bg-hover used to carry. Split out when hover went dark and
       .roster-view-toggle .toggle-btn.active — the one consumer of the 22 that
       was never a hover — would otherwise have read as pressed-and-disabled. */
    --jgm-on-dark-bg-active:     rgba(255, 255, 255, 0.18);
    --jgm-on-dark-border:        rgba(255, 255, 255, 0.25);
    --jgm-on-dark-border-hover:  rgba(255, 255, 255, 0.5);
    /* Teal accent for the same always-dark surfaces. NOT overridden in the
       light block — that is the point: anything painting on a permanently dark
       header keeps these, while --jgm-combined-* below stays theme-aware for
       surfaces that do flip. */
    --jgm-on-dark-teal-text:     #67e8f9;                   /* cyan-300 */
    --jgm-on-dark-teal-bg:       rgba(6, 182, 212, 0.16);   /* cyan tint */
    --jgm-on-dark-teal-border:   #0e7490;                   /* cyan-700 */
    /* Purple accent for the same surfaces. --jgm-accent-purple-hover cannot do
       this job: it darkens to violet-600 in light mode, which on a dark header
       measures 2.05:1. */
    --jgm-on-dark-purple-text:   #c4b5fd;                   /* violet-300 */
    /* The rest of the accent hues, same contract. Every --jgm-accent-* darkens
       for light mode (correct on a themed card, wrong on a chip whose own
       background is a fixed dark hex), so position pills, grade badges and
       similar fixed-dark chips read from these instead. Values match the
       literals their sibling rules already use — .di-pos-RB's #86efac,
       .di-pos-WR's #93c5fd — so a family stays one colour set. */
    --jgm-on-dark-red-text:      #fca5a5;                   /* red-300 */
    --jgm-on-dark-green-text:    #86efac;                   /* green-300 */
    --jgm-on-dark-amber-text:    #fcd34d;                   /* amber-300 */
    --jgm-on-dark-blue-text:     #93c5fd;                   /* blue-300 */
    /* Same contract, indigo. --jgm-accent-indigo-hover cannot do this job: it
       flips to indigo-600 in light mode, which on #1a2332 measures 2.00. */
    --jgm-on-dark-indigo-text:   #a5b4fc;                   /* indigo-300 */

    /* ── Selected/ON fills for controls on an always-dark surface ──
       The purple context bar is linear-gradient(#4c1d95 → #6d28d9), FIXED in
       both themes, and its toggles fill solid when ON. Same contract as the
       text tokens above, for the same reason: a --jgm-accent-* fill darkens for
       light mode, which is exactly wrong on a permanently dark bar.

       Concretely, --jgm-accent-purple measures 1.25:1 against the bar's bright
       end in dark mode and IS #6d28d9 in light — the bar's own colour. An ON
       chip painted with it disappears. That is the bug these exist to stop.

       Each hue is a fill plus the ink that reads on it, chosen by measurement
       against #6d28d9 — fill ≥ 3:1 so the control separates from the bar, ink
       ≥ 4.5:1 so the label stays legible:

         green   #34d399  3.70:1   ink 8.67:1
         amber   #fbbf24  4.26:1   ink 8.79:1
         teal    #5eead4  4.80:1   ink 9.78:1
         purple  #d8b4fe  4.02:1   ink 8.62:1

       They are all LIGHT fills with DARK ink. Mid-tone fills with white ink
       are what failed: the shipped #10b981 + white measured 2.54:1 and
       #14b8a6 + white 2.49:1.

       A control on a LIGHT bar (the mobile #f9fafb strip) uses the plain
       --jgm-accent-* instead — these are for the dark bar only. */
    --jgm-on-dark-sel-green:     #34d399;   --jgm-on-dark-sel-green-ink:  #04231a;
    --jgm-on-dark-sel-amber:     #fbbf24;   --jgm-on-dark-sel-amber-ink:  #1f2937;
    --jgm-on-dark-sel-teal:      #67e8f9;   --jgm-on-dark-sel-teal-ink:   #083344;  /* cyan-300 on cyan-950 */
    --jgm-on-dark-sel-purple:    #d8b4fe;   --jgm-on-dark-sel-purple-ink: #2e1065;
    /* One ring for every hue — a white halo reads on all four fills and on the
       bar behind them, and it retires four hand-derived rgba() alphas that had
       to be recomputed by hand whenever a fill changed. */
    --jgm-on-dark-sel-ring:      0 0 0 2px rgba(255, 255, 255, 0.45);

    /* ── Standard report-header surface ──────────────────────────
       Single source for the shared report shell (reports_v4.css).
       DARK (default): the Player Notes navy→teal gradient — the
       agreed dark-theme look. Header sits on a permanently-dark
       surface, so its text/controls use the on-dark-* family
       regardless of the active theme. */
    --jgm-report-header-bg:      linear-gradient(135deg, #1e3a5f, #0f766e);
    --jgm-report-header-text:    var(--jgm-on-dark-text-bright);
    /* #fff, not white 85%. The gradient's TEAL end is the light one, and it is
       where the subtext and the action buttons land: at 85% the subtext read
       4.42 there (the navy end is fine at 8.76). Unlike the buttons this text
       sits DIRECTLY on the gradient with no pill under it, so darkening
       --jgm-on-dark-bg does nothing for it — the ink is the only lever short of
       changing the teal, and the teal is the agreed look. 5.47 at the teal end.
       The title stays distinguishable by size and weight rather than by ink,
       which is why -text and -subtext now resolve alike here. */
    --jgm-report-header-subtext: #fff;
    --jgm-report-header-border:  rgba(255, 255, 255, 0.12);

    /* ── Draft-board pick surfaces ───────────────────────────────
       Single source of truth for the draft board (desktop .pick-slot
       grid AND mobile .pick-list-item list). These FLIP per theme so
       the board no longer relies on hardcoded white/#e5e7eb + a pile
       of [data-theme="dark"] color patches.
       DARK: genuine dark surfaces so the board reads as dark, not as
       "light cards forced dark". */
    --jgm-pick-card-bg:      var(--jgm-bg-secondary);   /* gray-800  — filled/list card surface */
    --jgm-pick-card-border:  var(--jgm-border-primary); /* slate-700 — card edge */
    --jgm-pick-left-bg:      var(--jgm-bg-tertiary);    /* slate-700 — franchise-name left column */
    --jgm-pick-empty-bg:     var(--jgm-bg-primary);     /* slate-800 — empty/unfilled slot */
    --jgm-pick-number-bg:    var(--jgm-bg-deep);        /* slate-900 — R#/P# badge (dark both themes) */
    --jgm-pick-text:         var(--jgm-text-primary);   /* slate-200 — player name / primary */
    --jgm-pick-text-muted:   var(--jgm-text-secondary); /* slate-400 — team / bye / details */

    /* Compact salary chip (player-info one-line layout). Amber chip with
       dark text — same identity in both themes, like an NFL-team pill. */
    --jgm-salary-chip-bg:    var(--jgm-accent-amber-hover);  /* amber-400 */
    --jgm-salary-chip-text:  #1f2937;                       /* near-black, readable on amber */

    /* NFL-team badge chip (the team pill on a filled pick cell). A darkening
       overlay that sits on the saturated position-colored cell and keeps white
       text readable in BOTH themes — so it's theme-independent (no light-theme
       override). Use for .pick-slot .wide-team and any team pill on a colored
       pick surface. */
    --jgm-team-chip-bg:      rgba(0, 0, 0, 0.25);            /* darkening overlay on colored cell */
    --jgm-team-chip-text:    #fff;                           /* white, readable on the darkened color */

    /* Bye-week text on a filled, position-colored pick cell. A red bye
       (the old --jgm-accent-red-hover) is nearly invisible on the QB pink-red
       slot and poor on the TE orange slot. Bright translucent white reads
       cleanly on ALL position colors (red/green/blue/orange/purple) without
       clashing with any hue; theme-independent. Slightly dimmed from pure
       white so it stays secondary to the white player name. */
    --jgm-bye-on-color:      rgba(255, 255, 255, 0.92);
}


/* ================================================================
   LIGHT THEME
   Applied when <html data-theme="light"> is set.
   Flips surfaces/text; accents stay vibrant.
   ================================================================ */
[data-theme="light"] {

    /* ── Standard report-header surface (light) ──────────────────
       Clean elevated white look (matches the Draft Report header in
       light mode). Dark text on a light gradient. Controls in the
       header switch to dark-on-light via reports_v4.css's light block. */
    --jgm-report-header-bg:      linear-gradient(135deg, #ffffff, #f3f4f6);
    --jgm-report-header-text:    #111827;
    --jgm-report-header-subtext: #6b7280;
    --jgm-report-header-border:  #e5e7eb;

    /* ── Draft-board pick surfaces (light) ───────────────────────
       LIGHT: clean elevated cards, but empty/unfilled slots use a
       GREY TINT (slate-100) rather than pure white per design — pure
       white slots read as "missing"/washed out on the light board. */
    --jgm-pick-card-bg:      #ffffff;   /* filled/list card surface */
    --jgm-pick-card-border:  #d1d5db;   /* card edge */
    --jgm-pick-left-bg:      #f1f5f9;   /* slate-100 — franchise-name left column */
    --jgm-pick-empty-bg:     #f1f5f9;   /* slate-100 — empty slot (grey tint, NOT pure white) */
    --jgm-pick-number-bg:    #2d3748;   /* dark navy badge (same as dark theme) */
    --jgm-pick-text:         #111827;   /* near-black player name / primary */
    --jgm-pick-text-muted:   #4b5563;   /* gray-600 — team / bye / details (readable on tints) */

    /* Salary chip — identical amber identity in light mode. */
    --jgm-salary-chip-bg:    var(--jgm-accent-amber-hover);
    --jgm-salary-chip-text:  #1f2937;

    /* ── Header/Panel surfaces for light mode ────────────────── */
        --panel-bg:          #ffffff;
        --panel-border:      #d1d5db;
        --table-header-bg:   #e5e7eb;
        --table-header-dark: #d1d5db;

    /* ── Surfaces ────────────────────────────────────────────── */
    --jgm-bg-deep:      #f1f5f9;  /* slate-100 */
    --jgm-bg-primary:   #ffffff;
    --jgm-bg-secondary: #f9fafb;  /* gray-50 */
    --jgm-bg-tertiary:  #f3f4f6;  /* gray-100 */
    --jgm-bg-elevated:  #e5e7eb;  /* gray-200 */
    --jgm-bg-hover:     rgba(0, 0, 0, 0.04);

    /* ── Borders ─────────────────────────────────────────────── */
    --jgm-border-primary:   #e5e7eb;  /* gray-200 */
    --jgm-border-secondary: #d1d5db;  /* gray-300 */
    --jgm-border-muted:     rgba(0, 0, 0, 0.06);
    --jgm-border-focus:     #3b82f6;

    /* ── Text ────────────────────────────────────────────────── */
    --jgm-text-primary:   #111827;  /* gray-900 */
    --jgm-text-secondary: #4b5563;  /* gray-600 */
    --jgm-text-muted:     #6b7280;  /* gray-500 */
    --jgm-text-disabled:  #9ca3af;  /* gray-400 */
    --jgm-text-disabled-grey: #9ca3af;  /* gray-400 — see :root comment */
    --jgm-text-inverse:   #f9fafb;  /* gray-50 */

    /* ── Accents (deeper for light bg contrast) ───────────────────
       Tuned to clear WCAG AA (4.5) as TEXT on the lightest surfaces we
       actually paint on — worst case is --jgm-bg-deep (#f1f5f9); white is
       more forgiving. The -600 shades measured 2.9-3.4 and read washed
       out (amber especially), so amber/green/red/teal drop to -700.
       Ratios on #f1f5f9: blue 4.72, green 5.01, red 5.91, amber 4.58,
       purple 6.49, indigo 5.74, teal 5.00. Don't lighten these back
       without re-checking contrast — they are text colours, not just
       button fills (fills only improve as these darken).
       -hover shades stay lighter: they are hover/fill states, not text. */
    --jgm-accent-blue:    #2563eb;  /* blue-600 */
    --jgm-accent-blue-hover: #3b82f6;
    --jgm-accent-green:   #047857;  /* emerald-700 — was -600 (3.44, failed) */
    --jgm-accent-green-hover: #10b981;
    --jgm-accent-red:     #b91c1c;  /* red-700 — was -600 (4.41, marginal) */
    --jgm-accent-red-hover: #ef4444;
    --jgm-accent-amber:   #b45309;  /* amber-700 — was -600 (2.91, failed) */
    --jgm-accent-amber-hover: #f59e0b;
    --jgm-accent-purple:  #6d28d9;  /* violet-700 — fill (see the dark block) */
    /* violet-600 — the purple TEXT shade here, and already AA on every light
       surface (5.70 / 5.20 / 5.18 / 4.60 on white / deep / tertiary /
       elevated), so unlike indigo it needs no pin to the base. */
    --jgm-accent-purple-hover: #7c3aed;
    --jgm-accent-indigo:  #4f46e5;  /* indigo-600 */
    /* DELIBERATELY equal to the base, not lighter like the other -hover
       shades: every consumer of indigo-hover is TEXT (see the dark block).
       A lighter indigo-500 measures 4.0-4.5 on these surfaces and indigo-400
       drops to 2.4-3.0, which is the amber-hover-as-text trap. -600 keeps the
       5.08-6.29 the base already measures. Fills are unaffected — they use
       --jgm-accent-indigo. */
    --jgm-accent-indigo-hover: #4f46e5;
    --jgm-accent-teal:    #0e7490;  /* cyan-700 — see the dark block */
    --jgm-accent-teal-hover: #06b6d4; /* cyan-500 */

    /* Accent TEXT inks — see the dark block for why these exist. A step past
       the base, because the base itself only clears AA down to --jgm-bg-card:
       on --jgm-bg-elevated (#e5e7eb) amber-700 reads 4.06, emerald-700 4.43 and
       blue-600 4.17. One step deeper clears every surface the app paints.
       Measured white / deep / card / elevated:
         red-800    #991b1b   8.31  7.59  7.95  6.71
         amber-800  #92400e   7.09  6.47  6.78  5.73
         emerald-800 #065f46  7.68  7.01  7.35  6.21
         blue-700   #1d4ed8   6.70  6.12  6.41  5.41   ← worst on the page
       Blue needs only one step because its base is already a -600.
       Indigo and purple alias their -hover, which is measured in this block. */
    --jgm-accent-red-text:    #991b1b;  /* red-800 */
    --jgm-accent-amber-text:  #92400e;  /* amber-800 */
    --jgm-accent-green-text:  #065f46;  /* emerald-800 */
    --jgm-accent-blue-text:   #1d4ed8;  /* blue-700 */
    --jgm-accent-indigo-text: var(--jgm-accent-indigo-hover); /* indigo-600 */
    --jgm-accent-teal-text:   #155e75;  /* cyan-800 — the -hover shade is cyan-500 here
                                          and reads 2.04 on teal's own pale tint */
    --jgm-accent-purple-text: var(--jgm-accent-purple-hover); /* violet-600 */

    /* Combined-target packages — deeper fg + lighter bg for light-bg contrast */
    /* teal-800, not -700: the fg always paints on --jgm-combined-bg, and a 14%
       teal wash over --jgm-bg-deep left teal-700 at 4.44:1. The one consumer is
       .tf-structure-badge--combined; the acq-group header and its tag sit on a
       permanently dark surface and use --jgm-on-dark-teal-* instead. */
    --jgm-combined-fg:     #155e75;                 /* cyan-800 text */
    --jgm-combined-bg:     rgba(6, 182, 212, 0.14);  /* cyan tint */
    --jgm-combined-border: #06b6d4;                 /* cyan-500 */

    /* ── Gradients (stay vibrant) ─────────────────────────────── */
    /* Gradients stay the same — they look fine on light bg too */

    /* ── Modal / Overlay ─────────────────────────────────────── */
    --jgm-overlay-bg:       rgba(0, 0, 0, 0.35);
    --jgm-modal-bg:         #ffffff;
    --jgm-modal-border:     #e5e7eb;
    --jgm-modal-shadow:     0 24px 64px rgba(0, 0, 0, 0.18);

    /* ── Tooltip (JS overlay) — light surface + border for definition ── */
    --jgm-tooltip-bg:       #ffffff;
    --jgm-tooltip-text:     #111827;   /* gray-900 */
    --jgm-tooltip-border:   #d1d5db;   /* gray-300 */
    --jgm-tooltip-shadow:   0 4px 14px rgba(0, 0, 0, 0.18);

    /* ── Scrollbar ───────────────────────────────────────────── */
    --jgm-scrollbar-thumb:  rgba(0, 0, 0, 0.15);
    --jgm-scrollbar-hover:  rgba(0, 0, 0, 0.25);

    /* ── Trading Mode (light version — subtle warm gray) ─────── */
    --jgm-trade-bg-deep:      #f8f7ff;
    --jgm-trade-bg-primary:   #f3f2fb;
    --jgm-trade-bg-secondary: #ebebf7;
    --jgm-trade-bg-elevated:  #dddcf0;
    --jgm-trade-border:       #d1d0e4;
    --jgm-trade-border-muted: #e5e4f2;

    /* ── Backward-compat surface aliases update automatically ── */
    /* (They point to --jgm-* vars which are now light values)     */

    /* ── Light-surface aliases swap in light mode ────────────── */
    --bg-light-primary:    #ffffff;
    --bg-light-secondary:  #f9fafb;
    --bg-light-tertiary:   #f3f4f6;
    --bg-light-elevated:   #e5e7eb;
    --text-light-primary:  #111827;
    --text-light-secondary: #4b5563;
    --text-light-muted:    #6b7280;
    --border-light-color:  #e5e7eb;
}


/* ================================================================
   SHARED UTILITY CLASSES (theme-aware)
   These replace repeated patterns across component CSS files.
   ================================================================ */

/* ── Scrollbar (use on any scrollable container) ─────────────── */
.jgm-scroll {
    scrollbar-width: thin;
    scrollbar-color: var(--jgm-scrollbar-thumb) var(--jgm-scrollbar-track);
}
.jgm-scroll::-webkit-scrollbar {
    width: var(--jgm-scrollbar-width);
    height: var(--jgm-scrollbar-width);
}
.jgm-scroll::-webkit-scrollbar-track {
    background: var(--jgm-scrollbar-track);
}
.jgm-scroll::-webkit-scrollbar-thumb {
    background: var(--jgm-scrollbar-thumb);
    border-radius: 3px;
}
.jgm-scroll::-webkit-scrollbar-thumb:hover {
    background: var(--jgm-scrollbar-hover);
}

/* ── Injury Badges (shared across LDM, WPL, draft board, trade panels) ── */
.jgm-injury {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 14px;
    height: 14px;
    padding: 0 3px;
    border-radius: 3px;
    font-size: 0.65rem;
    font-weight: 700;
    cursor: help;
}
.jgm-injury.injury-q { background: var(--jgm-injury-q-bg); color: var(--jgm-injury-q-text); }
.jgm-injury.injury-d { background: var(--jgm-injury-d-bg); color: var(--jgm-injury-d-text); }
.jgm-injury.injury-o { background: var(--jgm-injury-o-bg); color: var(--jgm-injury-o-text); }
.jgm-injury.injury-i { background: var(--jgm-injury-i-bg); color: var(--jgm-injury-i-text); }
.jgm-injury.injury-p { background: var(--jgm-injury-p-bg); color: var(--jgm-injury-p-text); }
.jgm-injury.injury-s { background: var(--jgm-injury-s-bg); color: var(--jgm-injury-s-text); }

/* ── Modal Overlay Base ──────────────────────────────────────── */
.jgm-modal-overlay {
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background: var(--jgm-overlay-bg);
    backdrop-filter: var(--jgm-overlay-blur);
    -webkit-backdrop-filter: var(--jgm-overlay-blur);
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* ── Tier Gradients as Background Classes ────────────────────── */
.jgm-bg-elite   { background: var(--jgm-gradient-elite); }
.jgm-bg-pro     { background: var(--jgm-gradient-pro); }
.jgm-bg-success { background: var(--jgm-gradient-success); }
.jgm-bg-warning { background: var(--jgm-gradient-warning); }
.jgm-bg-danger  { background: var(--jgm-gradient-danger); }

/* ═══════════════════════════════════════════════════════════
   STEP C-BOARD — Auction nomination strip
   Sits in the .draft-status-compact bar to the right of the
   timer status. Hidden by default (snake leagues, between
   nominations). Color treatment encodes bid vs fair value:
     - nom-overpriced  → red    (bid >= 100% of fair)
     - nom-approaching → amber  (bid >= 80%)
     - nom-value       → green  (bid < 80%)
     - nom-neutral     → muted  (no fair value available)
   ═══════════════════════════════════════════════════════════ */

.nomination-strip {
    /* Match the existing .status-item flex pattern — sits inline in the
       header bar. Gap is the same as adjacent items. */
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.85rem;
    /* Subtle background so the strip reads as its own informational block
       rather than blending into the surrounding status row. */
    padding: 2px 8px;
    border-radius: 4px;
    background: rgba(255, 255, 255, 0.04);
    transition: background-color 0.15s ease;
}

.nomination-icon {
    font-size: 0.9rem;
    line-height: 1;
}

/* The strip lives on .header-row-context's permanently-dark purple gradient,
   so every text color below must use the --jgm-on-dark-* family. The theme-
   flipping --jgm-text-* tokens go dark in light mode → unreadable on purple. */
.nomination-label {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--jgm-on-dark-text-dim, rgba(255, 255, 255, 0.6));
}

.nomination-player {
    font-weight: 600;
    color: var(--jgm-on-dark-text-bright, #fff);
    /* Click target: opens player profile. Cursor + hover states declared
       even though they're set inline by JS; keeps CSS source-of-truth
       readable. */
    cursor: pointer;
    transition: color 0.12s;
}
.nomination-player:hover {
    color: var(--jgm-on-dark-purple-text, #c4b5fd);
    text-decoration: underline;
}

.nomination-bid {
    /* Bid amount — tabular numerals so $9 and $10 align cleanly */
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--jgm-on-dark-text-bright, #fff);
}

.nomination-by-prefix {
    font-size: 0.75rem;
    color: var(--jgm-on-dark-text-dim, rgba(255, 255, 255, 0.5));
    font-style: italic;
}

.nomination-franchise {
    /* Tighter — franchise names can be long ("Play Bad, Run Well") */
    font-size: 0.8rem;
    color: var(--jgm-on-dark-text, rgba(255, 255, 255, 0.7));
    max-width: 180px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: inline-block;
    vertical-align: middle;
}

.nomination-time {
    /* Countdown display — own subtle pill so it's always parseable as time
       even when the strip color shifts due to bid vs fair value. */
    font-size: 0.75rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--jgm-on-dark-text, rgba(255, 255, 255, 0.7));
    padding: 1px 6px;
    border-radius: 3px;
    background: rgba(255, 255, 255, 0.06);
    margin-left: 4px;
}

/* ── Color states (background tint + accent on bid amount) ───────────── */

/* Overpriced — red. Bid has reached or exceeded fair value. */
.nomination-strip.nom-overpriced {
    background: rgba(239, 68, 68, 0.15);  /* var(--jgm-status-error) at 15% */
}
.nomination-strip.nom-overpriced .nomination-bid {
    color: var(--jgm-on-dark-red-text, #fca5a5);
}

/* Approaching — amber. Bid is 80%-99% of fair value. */
.nomination-strip.nom-approaching {
    background: rgba(245, 158, 11, 0.12);  /* var(--jgm-status-warning) at 12% */
}
.nomination-strip.nom-approaching .nomination-bid {
    color: var(--jgm-on-dark-amber-text, #fcd34d);
}

/* Value — green. Bid still under 80% of fair. */
.nomination-strip.nom-value {
    background: rgba(34, 197, 94, 0.10);   /* var(--jgm-status-live) at 10% */
}
.nomination-strip.nom-value .nomination-bid {
    color: var(--jgm-on-dark-green-text, #86efac);
}

/* Neutral — no fair value to compare against. Leave default chrome. */
.nomination-strip.nom-neutral {
    /* explicit reset — same as base, kept for spec clarity */
    background: rgba(255, 255, 255, 0.04);
}

/* Time urgency — flips the time chip red when <=10 seconds remaining */
.nomination-time.nom-time-urgent {
    color: var(--jgm-on-dark-red-text, #fca5a5);
    background: rgba(239, 68, 68, 0.18);
    /* Subtle pulse to draw the eye when bid time is closing */
    animation: nom-time-pulse 1s ease-in-out infinite;
}

/* Negative cap-savings — dropping costs more than keeping. Subtle red. */
.sr-capsavings-negative { color: var(--jgm-accent-red); }

@keyframes nom-time-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.55; }
}

/* The nominationStripDot inherits .status-dot styling already defined
   elsewhere — no override needed. */

/* ── Reports ▾ menu separator (5.3) ───────────────────────────────
   Thin divider used in Utils.reportsClusterHTML to set the "Football
   Chat with Jerry" entry apart from the report buttons above it.
   Theme-safe via --jgm-* tokens. */
.reports-menu-separator {
    height: 0;
    margin: 4px 6px;
    border-top: 1px solid var(--jgm-border-muted, var(--jgm-border-secondary));
}

/* ── App update banner (JGMUpdateCheck, update_check_v4.js) ────────────
   Lives here, not in a component stylesheet: the banner mounts on BOTH
   entry points (index_v4.html and admin/index.html) and jgm_theme_v4.css
   is the only stylesheet they share. --font-scale is a styles_v4.css
   token the admin page doesn't load, hence the `, 1` fallbacks. */
.jgm-update-banner {
    position: fixed;
    /* Sit below the toast strip when one is up (Utils.showToast publishes its
       height as --jgm-toast-height); fall back to the top edge otherwise. */
    top: calc(var(--jgm-toast-height, env(safe-area-inset-top, 0px)) + 10px);
    transition: top 0.2s ease;
    left: 50%;
    transform: translateX(-50%);
    z-index: 100000;   /* above pills — an update prompt must stay reachable */
    display: flex;
    align-items: center;
    gap: 10px;
    background: var(--jgm-bg-primary);
    border: 1px solid var(--jgm-accent-teal);
    border-radius: 12px;
    padding: 9px 14px;
    box-shadow: var(--jgm-modal-shadow);
    max-width: min(94vw, 520px);
}
.jgm-update-text { font-size: calc(0.8rem * var(--font-scale, 1)); color: var(--jgm-text-primary); }
.jgm-update-build { color: var(--jgm-text-muted); font-size: calc(0.7rem * var(--font-scale, 1)); }
.jgm-update-btn {
    background: var(--jgm-gradient-primary);
    border: none;
    color: #fff;
    border-radius: 8px;
    padding: 7px 16px;
    font-size: calc(0.8rem * var(--font-scale, 1));
    font-weight: 800;
    cursor: pointer;
    white-space: nowrap;
}
/* Quiet, but not dead. At --jgm-text-muted with no hover state this read as a
   DISABLED control; --jgm-text-secondary keeps it subordinate to "Reload anyway"
   while the hover fill confirms it's clickable. */
.jgm-update-later {
    background: none;
    border: none;
    color: var(--jgm-text-secondary);
    font-size: calc(0.75rem * var(--font-scale, 1));
    cursor: pointer;
    padding: 6px 10px;
    border-radius: 8px;
    transition: background 0.15s ease, color 0.15s ease;
}
.jgm-update-later:hover,
.jgm-update-later:focus-visible {
    background: var(--jgm-bg-hover);
    color: var(--jgm-text-primary);
}

/* "You're on the latest version" state of the same banner. Confirmation, not
   a call to action — so the teal urgency border goes muted and "Reload anyway"
   drops the gradient fill to a quiet outline. Same layout and position. */
/* Phone: the banner is centred on its own content width, which on a 375px
   screen runs past both edges — the dismiss button is the one that falls off,
   so the prompt becomes unclosable. Pin it to both margins instead of centring,
   and let the row wrap so the buttons drop under the text rather than overflow. */
@media (max-width: 620px) {
    .jgm-update-banner {
        left: 8px;
        right: 8px;
        transform: none;
        flex-wrap: wrap;
        row-gap: 8px;
        padding: 10px 12px;
    }
    /* Text takes the first line on its own; the controls share the second. */
    .jgm-update-banner .jgm-update-text { flex: 1 1 100%; }
    .jgm-update-banner .jgm-update-later { margin-left: auto; }
}

.jgm-update-banner.is-uptodate {
    border-color: var(--jgm-border-muted);
}
.jgm-update-banner.is-uptodate .jgm-update-btn {
    background: none;
    border: 1px solid var(--jgm-border-muted);
    color: var(--jgm-text-secondary);
    font-weight: 600;
}
.jgm-update-banner.is-uptodate .jgm-update-btn:hover {
    border-color: var(--jgm-accent-teal);
    color: var(--jgm-text-primary);
}

/* ── Maintenance notice (same banner, different payload) ───────────────────
   Only the border colour and the leading icon change: identical geometry to
   the update prompt means nothing jumps when one replaces the other. Wider
   than the update prompt because the copy is a sentence, not a build id. */
.jgm-update-banner.jgm-notice {
    max-width: min(94vw, 620px);
    align-items: flex-start;
}
.jgm-update-banner.jgm-notice .jgm-update-text {
    line-height: 1.4;
    padding-top: 1px;
}
.jgm-notice-icon {
    font-size: calc(0.95rem * var(--font-scale, 1));
    line-height: 1.3;
    flex-shrink: 0;
}
.jgm-update-banner.jgm-notice-info     { border-color: var(--jgm-accent-teal); }
.jgm-update-banner.jgm-notice-warning  { border-color: var(--jgm-accent-amber); }
/* Critical carries no dismiss button, so the border is the only signal that
   this one is different in kind — worth the extra pixel. */
.jgm-update-banner.jgm-notice-critical {
    border-color: var(--jgm-accent-red);
    border-width: 2px;
}

/* The update banner's "What's new?" — quiet text button next to the CTA */
.jgm-update-whatsnew {
    background: none;
    border: none;
    color: var(--jgm-accent-teal, #06b6d4);
    font-size: 0.75rem;
    font-weight: 600;
    cursor: pointer;
    text-decoration: underline;
    padding: 2px 4px;
}
.jgm-update-whatsnew:hover { opacity: .85; }

/* ── Labeled filter dropdown (.jgm-lsel) ─────────────────────────────────────
   The app-standard filter control: label INSIDE the pill, choices in a native
   dropdown — "Franchise: All ▾" as one button. Required format for
   label+dropdown filters (CONVENTIONS.md §Filter controls); markup via
   Utils.labeledSelectHTML so every surface renders the same pill. */
.jgm-lsel {
    display: inline-flex; align-items: center; gap: 4px;
    background: var(--jgm-bg-tertiary);
    border: 1px solid var(--jgm-border-secondary);
    border-radius: 7px;
    padding: 3px 8px 3px 10px;
    cursor: pointer;
    position: relative;
}
.jgm-lsel:hover { border-color: var(--jgm-border-focus, var(--jgm-border-secondary)); }
.jgm-lsel-label {
    font-size: .72rem; font-weight: 700; color: var(--jgm-text-secondary);
    white-space: nowrap; pointer-events: none;
}
.jgm-lsel select {
    appearance: none; -webkit-appearance: none;
    background: transparent; border: none; outline: none;
    color: var(--jgm-text-primary);
    font-size: .76rem; font-weight: 600;
    padding-right: 14px; cursor: pointer; max-width: 180px;
    text-overflow: ellipsis;
}
.jgm-lsel select option { background: var(--jgm-bg-secondary); color: var(--jgm-text-primary); }
.jgm-lsel-caret {
    position: absolute; right: 7px; pointer-events: none;
    font-size: .6rem; color: var(--jgm-text-muted);
}
