/* LiveSlate — global styles */

@keyframes ping-slow {
  0%    { transform: scale(1);   opacity: 0.7; }
  70%   { transform: scale(2.2); opacity: 0;   }
  100%  { transform: scale(2.2); opacity: 0;   }
}
.animate-ping-slow {
  animation: ping-slow 2.4s cubic-bezier(0, 0, 0.2, 1) infinite;
}

/* Slow spin — used for the Settings gear on hover/active */
@keyframes spin-slow {
  to { transform: rotate(360deg); }
}
.animate-spin-slow { animation: spin-slow 4s linear infinite; }

/* Hide number input spinners */
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }

/* System font stack — sharp native rendering on every OS */
:root {
  --font: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
  --font-size: 0.75rem; /* 12px — matches text-xs */

  /* ── Type scale (canonical, Xav 2026-06-14) ─────────────────────
     The documented LiveSlates type scale (was only in the design
     skill; now loaded into the app). Widget CSS should reference
     these instead of hardcoding px so sizes stay consistent across
     panels. The app is a dense data tool — the base is deliberately
     small. */
  --ls-fs-9:   9px;    /* micro: chips, rank badges, fine print */
  --ls-fs-10: 10px;    /* uppercase eyebrow labels, table headers */
  --ls-fs-11: 11px;    /* table body, secondary meta */
  --ls-fs-12: 12px;    /* buttons, filters, input values, body */
  --ls-fs-14: 14px;    /* section titles, settings labels */
  --ls-fs-16: 16px;    /* widget hero numbers */
  --ls-fs-18: 18px;    /* inner-page h1, big stat */
  --ls-fs-20: 20px;    /* logo wordmark, hero stat */
  --ls-fs-24: 24px;    /* extra-large hero stat */
  --ls-lh-tight:  1.1;
  --ls-lh-snug:   1.25;
  --ls-lh-normal: 1.4;
  --ls-ls-wide:   0.05em;
  --ls-ls-wider:  0.10em;
  --ls-ls-widest: 0.15em;

  /* ── Subnav-menu design tokens (Xav 2026-06-06) ─────────────────
     Every dropdown menu (Slate picker, Mode picker, Settings, Admin,
     Shared picker, future menus) MUST consume these tokens — no
     hardcoded font-size / font-weight / colour inside menu CSS.
     Adding a new menu class? Reference --ls-menu-* below; do not
     re-declare values. This is the only way we stop drifting back
     into the mix-and-match state.
     Update notes in CLAUDE.md or memory:design-tokens before
     changing any of these — every menu inherits at once. */

  /* Section eyebrow ("MLB DASHBOARDS", "PREFERENCES", "SLATE TOOLS") */
  --ls-menu-section-size:    9px;
  --ls-menu-section-weight:  800;
  --ls-menu-section-tracking: 0.16em;
  --ls-menu-section-color:   #94a3b8;
  --ls-menu-section-bg:      transparent;
  --ls-menu-section-padding: 12px 12px 4px;

  /* Item title (the primary row label — "Simple", "Switch to dark mode") */
  --ls-menu-title-size:   13px;
  --ls-menu-title-weight: 700;
  --ls-menu-title-color:  #0f172a;
  --ls-menu-title-tracking: 0.01em;

  /* Item description / blurb (the secondary line — "Calm view · headline numbers") */
  --ls-menu-blurb-size:   11px;
  --ls-menu-blurb-weight: 500;
  --ls-menu-blurb-color:  #94a3b8;

  /* Row chrome — padding + radius + hover applied to every clickable row */
  --ls-menu-row-padding:  8px 12px;
  --ls-menu-row-radius:   8px;
  --ls-menu-row-hover-bg: rgba(15, 23, 42, 0.04);
  --ls-menu-row-active-bg: rgba(16, 185, 129, 0.08);

  /* Status pill on toggle rows (ON / OFF / count badges) */
  --ls-menu-state-size:   10px;
  --ls-menu-state-weight: 800;
  --ls-menu-state-tracking: 0.1em;
}
.dark {
  --ls-menu-section-color: #64748b;
  --ls-menu-title-color:   #f1f5f9;
  --ls-menu-blurb-color:   #64748b;
  --ls-menu-row-hover-bg:  rgba(255, 255, 255, 0.04);
  --ls-menu-row-active-bg: rgba(16, 185, 129, 0.14);
}
html {
  overflow-y: scroll; /* always reserve scrollbar space — prevents layout shift */
  scrollbar-gutter: stable;
  color-scheme: dark; /* default — overridden below for light mode */
}
html:not(.dark) {
  color-scheme: light;
  -webkit-filter: none !important;
  filter: none !important;
  background: #dde4e7; /* matches .ls-canvas midpoint — explicit color defeats forced-dark heuristic */
}
html, body,
input, select, textarea, button,
th, td, label, span, div, a, p {
  font-family: var(--font) !important;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
input, select, textarea {
  font-size: var(--font-size) !important;
  line-height: 1.4 !important;
}
button {
  font-size: inherit !important;
  line-height: inherit !important;
}

/* Scrollbar */
::-webkit-scrollbar { width: 6px; height: 6px; }
.no-scrollbar { scrollbar-width: none; -ms-overflow-style: none; }
.no-scrollbar::-webkit-scrollbar { display: none; }
::-webkit-scrollbar-track { background: #1e293b; }
::-webkit-scrollbar-thumb { background: #475569; border-radius: 3px; }

/* Theme-coloured scrollbar for tab scroll containers */
.themed-scroll::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--scroll-thumb, #475569) 35%, transparent); border-radius: 3px; }
.themed-scroll::-webkit-scrollbar-track  { background: transparent; }
.themed-scroll { scrollbar-color: color-mix(in srgb, var(--scroll-thumb, #475569) 35%, transparent) transparent; scrollbar-width: thin; }

/* Always show full team names; fall back to abbrev only on very narrow screens */
.team-abbrev { display: none; }
.team-full    { display: inline; }
@media (max-width: 500px) {
  .team-abbrev { display: inline; }
  .team-full   { display: none; }
}

/* Live duel-card pulse: soft emerald inner glow on currently-live games */
@keyframes dash-live-pulse {
  0%, 100% { box-shadow: inset 0 0 0   0 rgba(16,185,129,0.0); }
  50%      { box-shadow: inset 0 0 24px 0 rgba(16,185,129,0.18); }
}
.dash-live-glow { animation: dash-live-pulse 2.4s ease-in-out infinite; }


/* Lineup ticker */
.ticker-track {
  display: inline-flex;
  align-items: center;
  height: 100%;
  white-space: nowrap;
  will-change: transform;
}
.ticker-track:hover { animation-play-state: paused; }

/* Stat cell flash on update */
@keyframes stat-flash {
  0%   { background-color: rgba(16,185,129,0.3); }
  100% { background-color: transparent; }
}
.stat-flash {
  animation: stat-flash 2s ease-out forwards;
  border-radius: 3px;
}

/* Vidiprinter newest entry: quick pop-in only — background set via inline style */
@keyframes vidi-popin {
  0%   { opacity: 0; }
  100% { opacity: 1; }
}
.vidi-fresh-top,
.vidi-fresh-bottom {
  animation: vidi-popin 0.3s ease-out forwards;
}

/* Apply the highlight to every cell of the freshest entry uniformly.
   Solid colours (not alpha) on each td: alpha-blending against whatever
   sat underneath produced visible "two shades of green" between cells
   (the gameTime cell read darker than the player-name cell, even
   though both had the same rgba rule). !important pins it against any
   Tailwind utility that might come along. */
.vidi-fresh > tr > td {
  background-color: #d1fae5 !important; /* emerald-50 */
}
.dark .vidi-fresh > tr > td {
  background-color: #064e3b !important; /* emerald-900 */
}

/* Pin each vidi row to a fixed height so events with +fpts/TOTAL
   (taller text-[13px] / text-[9px] children) and events without
   (court/start/end events) line up at a uniform stride.
   Heights are derived at runtime from --vidi-event-h, set by the
   dashboard's init() based on the scroll area's actual clientHeight
   divided by vidiLimit. So 6 events fill the available space exactly
   regardless of window size, panel chrome changes, or zoom. The 60/40
   split keeps row 1 (player+fpts) heavier than row 2 (desc+total).
   Fallback 32px (19+13) is the previous hardcoded value, kept for
   the brief window before init() runs. */
.vidi-panel tbody > tr:first-child { height: calc(var(--vidi-event-h, 32px) * 0.6); }
.vidi-panel tbody > tr:last-child  { height: calc(var(--vidi-event-h, 32px) * 0.4); }

/* ───────────────────────────────────────────────
   Vidiprinter panel: container-query adaptive sizing.
   When the panel narrows, the description cell shrinks
   (truncating with ellipsis) so the score column never
   gets clipped at the right edge.
─────────────────────────────────────────────── */
.vidi-panel {
  container-type: inline-size;
  container-name: vidi;
}
/* Description cell: by default sizes to content, ellipses only when the
   panel is too narrow to comfortably show "Rebound" / "Free Throw" etc. */
td.vidi-desc {
  white-space: nowrap;
}
@container vidi (max-width: 360px) {
  td.vidi-desc {
    max-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}
@container vidi (max-width: 320px) {
  .vidi-fpts span  { font-size: 0.875rem; }   /* text-sm — was text-base */
}
@container vidi (max-width: 260px) {
  .vidi-fpts span  { font-size: 0.75rem;  }   /* text-xs */
  .vidi-total      { display: none; }         /* drop TOTAL row content when really tight */
}

/* ───────────────────────────────────────────────
   Match cards (.bgc): container-query adaptive sizing.
   Element sizes shrink/grow with the card's actual
   rendered width — not the viewport.
─────────────────────────────────────────────── */
.bgc {
  container-type: inline-size;
  container-name: bgc;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.bgc:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.12);
}

/* Status accent strip on the left edge — always visible, instant scan */
.bgc-strip {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  border-top-left-radius: inherit;
  border-bottom-left-radius: inherit;
  pointer-events: none;
}
.bgc-live  .bgc-strip { background: #10b981; animation: bgc-strip-pulse 2.4s ease-in-out infinite; }
.bgc-soon  .bgc-strip { background: #fbbf24; }
.bgc-fin   .bgc-strip { background: #cbd5e1; }
.dark .bgc-fin .bgc-strip { background: #475569; }

@keyframes bgc-strip-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(16,185,129, 0.0); }
  50%      { box-shadow: 0 0 8px 1px rgba(16,185,129, 0.55); }
}

/* Background wash by state — bolder, status reads at a glance */
.bgc-live { background: linear-gradient(135deg, rgba(16,185,129,0.18), rgba(16,185,129,0.06) 100%), #ffffff; }
.dark .bgc-live { background: linear-gradient(135deg, rgba(16,185,129,0.30), rgba(16,185,129,0.10) 100%), #0f172a; }
.bgc-soon { background: linear-gradient(135deg, rgba(251,191,36,0.18), rgba(251,191,36,0.06) 100%), #ffffff; }
.dark .bgc-soon { background: linear-gradient(135deg, rgba(251,191,36,0.25), rgba(251,191,36,0.08) 100%), #0f172a; }
.bgc-fin  { background: linear-gradient(135deg, rgba(100,116,139,0.18), rgba(100,116,139,0.06) 100%), #ffffff; }
.dark .bgc-fin  { background: linear-gradient(135deg, rgba(100,116,139,0.25), rgba(100,116,139,0.08) 100%), #0f172a; }

/* ───────────────────────────────────────────────
   Scoreboard responsive density (rewrite 2026-05-07).
   Single source of truth: cards size themselves via flex
   (min 110, max 320) and adapt INTERNAL layout via
   @container queries on each card's own width. Score
   readability is the constant — name/photo/status sacrifice
   in that order as space tightens. The row carousel-scrolls
   when content overflows, and switches to full-width snap
   pages at phone widths.
─────────────────────────────────────────────── */

/* Row: centre by default, scroll-snap when overflowing,
   container-named so its own width can drive phone mode.
   `safe center` falls back to flex-start when content overflows the
   container — without this, plain `justify-content: center` clipped
   BOTH edges symmetrically and the user couldn't see the first/last
   card without scrolling left from a centred origin. */
.bgc-row {
  container-type: inline-size;
  container-name: scoreboard;
  justify-content: safe center;
  scroll-snap-type: x proximity;
  scrollbar-width: none;            /* Firefox */
  transition: transform 220ms ease-out, opacity 220ms ease-out;
  will-change: transform, opacity;
}
.bgc-row::-webkit-scrollbar { display: none; }
.bgc { scroll-snap-align: start; }

/* Tier 1 — Full (≥ 260px): photo + LASTNAME / Firstname + sets + games */
@container bgc (min-width: 260px) {
  .bgc .bgc-logo       { width: 2.25rem; height: 2.25rem; }
  .bgc .bgc-score      { font-size: 1.5rem; line-height: 1; }
  .bgc .bgc-name-full  { display: -webkit-box; }
  .bgc .bgc-name-abbr  { display: none; }
}

/* Tier 2 — Compact (180-259px): smaller photo + LASTNAME only;
   first-name line auto-truncates via existing flex layout. */
@container bgc (min-width: 180px) and (max-width: 259px) {
  .bgc .bgc-logo       { width: 1.75rem; height: 1.75rem; }
  .bgc .bgc-score      { font-size: 1.125rem; line-height: 1; }
  .bgc .bgc-name-full  { display: -webkit-box; font-size: 0.6875rem; }
  .bgc .bgc-name-abbr  { display: none; }
  .bgc .bgc-status     { font-size: 0.5rem; }
}

/* Tier 3 — Mini (120-179px): no photo, 3-letter abbreviation,
   sets+games kept legible. */
@container bgc (min-width: 120px) and (max-width: 179px) {
  .bgc                 { padding: 0.25rem 0.375rem; }
  .bgc .bgc-logo       { display: none; }
  .bgc .bgc-name-full  { display: none; }
  .bgc .bgc-name-abbr  { display: inline; font-size: 0.6875rem; }
  .bgc .bgc-score      { font-size: 1rem; line-height: 1; }
  .bgc .bgc-status     { font-size: 0.5rem; }
}

/* Tier 4 — Tiny (< 120px): abbreviation + score only.
   Score remains the dominant element. Status hidden. */
@container bgc (max-width: 119px) {
  .bgc                 { padding: 0.125rem 0.375rem; }
  .bgc .bgc-logo       { display: none; }
  .bgc .bgc-name-full  { display: none; }
  .bgc .bgc-name-abbr  { display: inline; font-size: 0.625rem; }
  .bgc .bgc-score      { font-size: 0.9rem; line-height: 1; font-weight: 900; }
  .bgc .bgc-status     { display: none; }
}

/* Smooth tier transitions when card width drifts. */
.bgc, .bgc-name-full, .bgc-score, .bgc-status, .bgc-logo {
  transition: padding 0.18s ease, font-size 0.18s ease, width 0.18s ease, height 0.18s ease;
}

/* Mobile dashboard top-bar layout — controls deck collapses to a
   single kebab toggle so the slate dropdown gets nearly the full row
   width and the bulky 6-button deck doesn't dominate phone real
   estate. The deck still exists in the DOM and re-appears as an
   absolute popover beneath the kebab when toggled.

   Mobile (<768px):
     row 1: [slate (1fr)]  [kebab (40px)]
     row 2: [matches matches]
   Controls deck: display:none until .is-mobile-open is toggled,
   then absolute-positioned under the kebab. */
@media (max-width: 767px) {
  .dashboard-topbar {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "slate    kebab"
      "matches  matches";
    gap: 0.5rem;
  }
  .dashboard-topbar-slate         { grid-area: slate; }
  .dashboard-topbar-matches       { grid-area: matches; }
  .dashboard-topbar-controls-wrap { grid-area: kebab; position: relative; }

  /* Chained specificity (.wrap > .controls = 2 classes) wins over
     Tailwind CDN's `.flex` utility (1 class), which would otherwise
     keep the deck visible because Tailwind's runtime <style> tag
     loads AFTER style.css and ties on specificity. */
  .dashboard-topbar-controls-wrap > .dashboard-topbar-controls {
    display: none;
  }
  .dashboard-topbar-controls-wrap > .dashboard-topbar-controls.is-mobile-open {
    display: flex;
    position: absolute;
    top: calc(100% + 0.5rem);
    right: 0;
    z-index: 50;
  }
}

/* Phone / narrow-row mode — when the SCOREBOARD container is < 480px,
   OR the viewport itself is < 700px (covers half-screen on desktop),
   cards become full-width snap pages. Each pinned game gets its own
   "page"; user swipes through them. */
@container scoreboard (max-width: 480px) {
  .bgc {
    flex: 0 0 92%;
    max-width: 92%;
    scroll-snap-align: center;
  }
}
@media (max-width: 700px) {
  .bgc-row .bgc {
    flex: 0 0 92%;
    max-width: 92%;
    scroll-snap-align: center;
  }
}

/* Balanced per-row sizing for the wildcard scoreboard (Xav 2026-06-01).
   The host's _perRow getter writes --cols (1..5) onto .bgc-row; each
   card's width is then (100% - (cols-1)*gap) / cols, so 9 cards
   render as 5+4 (last row centred via justify-content:center on the
   row) instead of 7+2. min/max keep the visual scale sane on extreme
   counts. Overrides the Tailwind flex-1 / min-w-[110px] / max-w-[320px]
   utilities on .bgc thanks to the higher-specificity selector. */
.bgc-row .bgc {
  flex: 0 0 auto;
  width: calc((100% - (var(--cols, 5) - 1) * 8px) / var(--cols, 5));
  min-width: 130px;
  max-width: 360px;
}

/* ── Tennis Wimbledon-style card — responsive grid ─────────────────────
   Uses a CSS variable for grid-template-columns so we can swap column
   set per @container width. Score (Sets / Games) is the constant —
   first the prev-sets column drops, then the photo, then the
   first-name line, then the WINNER pill morphs to a "W" then a row
   tint. Header row (Sets/Games labels) and 'v' separator share the
   same grid template so columns line up. */
.tn-card  { --tn-grid: auto 28px minmax(0, 1fr) 26px 26px; }
.tn-grid  { display: grid; align-items: center; gap: 0.375rem;
            grid-template-columns: var(--tn-grid); }
.tn-row-winner { background: rgba(16,185,129,0.08); border-radius: 0.25rem; }
.tn-winner-mini { display: none; }   /* shown via @container query below */

/* Tier 2 — Compact (180-259): smaller photo, drop the first-name line.
   WINNER pill switches to "W" already because at this width the full
   "WINNER" text was eating into the lastname (SABALENKA → SABALE...). */
@container bgc (min-width: 180px) and (max-width: 259px) {
  .bgc .tn-card { --tn-grid: auto 22px minmax(0,1fr) 22px 22px; }
  .bgc .tn-pic  { width: 1.375rem; height: 1.375rem; }
  .bgc .tn-firstname { display: none; }
  .bgc .tn-winner-pill { padding: 0 0.3125rem; font-size: 0.5625rem; }
  .bgc .tn-winner-full { display: none; }
  .bgc .tn-winner-mini { display: inline; }
}

/* Tier 3 — Mini (120-179): drop photo + prev-sets columns. WINNER
   pill stays as a tiny "W" so it doesn't eat the lastname. */
@container bgc (min-width: 120px) and (max-width: 179px) {
  .bgc .tn-card { --tn-grid: minmax(0,1fr) 22px 22px; }
  .bgc .tn-prev,
  .bgc .tn-pic,
  .bgc .tn-pic-cell  { display: none; }
  .bgc .tn-firstname { display: none; }
  .bgc .tn-winner-pill { padding: 0 0.25rem; font-size: 0.5rem; }
  .bgc .tn-winner-full { display: none; }
  .bgc .tn-winner-mini { display: inline; }
}

/* Tier 4 — Tiny (< 120px): no WINNER pill, winner row gets emerald
   tint instead. Just LASTNAME + sets/games. */
@container bgc (max-width: 119px) {
  .bgc .tn-card { --tn-grid: minmax(0,1fr) 18px 18px; gap: 0.125rem; }
  .bgc .tn-prev,
  .bgc .tn-pic,
  .bgc .tn-pic-cell  { display: none; }
  .bgc .tn-firstname { display: none; }
  .bgc .tn-winner-pill { display: none; }
  .bgc .tn-row-winner { background: rgba(16,185,129,0.18); }
}

/* ── WINNER pill on default cards — graceful degradation ───────────────
   Same idea as tennis: full pill at wide cards, single-letter "W" pill
   at mid widths, hidden + row-tint at narrowest. Targets the .bgc-winner
   class added on default-card winning rows (h2h sport types). Tennis
   has its own .tn-winner-pill class above, so they don't collide. */
.bgc-winner-mini { display: none; }
@container bgc (min-width: 120px) and (max-width: 179px) {
  .bgc .bgc-winner-pill { padding: 0 0.25rem; font-size: 0.5rem; }
  .bgc .bgc-winner-full { display: none; }
  .bgc .bgc-winner-mini { display: inline; }
}
@container bgc (max-width: 119px) {
  .bgc .bgc-winner-pill { display: none; }
}
.bgc-row.bgc-slide-out  { transform: translateX(-12%); opacity: 0; }
.bgc-row.bgc-slide-jump { transform: translateX(12%);  opacity: 0; transition: none; }
.bgc-row.bgc-slide-in   { transform: translateX(0);    opacity: 1; }

/* Score number punch when a team scores */
@keyframes score-bump {
  0%   { transform: scale(1); }
  25%  { transform: scale(1.65); text-shadow: 0 0 12px rgba(16,185,129,0.95); }
  60%  { transform: scale(1.1);  text-shadow: 0 0 5px  rgba(16,185,129,0.5); }
  100% { transform: scale(1);    text-shadow: none; }
}
.score-bump {
  display: inline-block;
  animation: score-bump 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}

/* ── Premium player-grid chrome (.ls-grid-premium) ─────────────────────
   Shared polish layer for any widget that wraps the `player_grid` Jinja
   macro (my_players, my_lineups, all_players, dreamteam, top10_*).
   Lifted out of widgets/my_players.html 2026-06-06 so the chrome is
   defined once (CSS-over-duplication rule). The macro renders a vanilla
   <table class="player-grid"> with Tailwind utilities on its cells;
   these descendant selectors polish it without touching the macro.

   Status-state spines were deliberately dropped — the inline status dot
   in the name cell already carries game phase. Only the gold Dream Team
   spine survives (a signal the dot doesn't carry), driven by the ★
   glyph's .text-yellow-* class via :has(). */

.ls-grid-premium .player-grid {
  font-variant-numeric: tabular-nums;
  border-spacing: 0;
}

/* Sticky thead — subtle gradient + emerald accent rule */
.ls-grid-premium .player-grid thead tr {
  background: linear-gradient(180deg, #fafbfc 0%, #f1f5f9 100%) !important;
  border-bottom: 1px solid rgba(16, 185, 129, 0.32) !important;
  box-shadow: 0 1px 0 0 rgba(15, 23, 42, 0.04);
}
.dark .ls-grid-premium .player-grid thead tr {
  background: linear-gradient(180deg, #0b1220 0%, #0f172a 100%) !important;
  border-bottom-color: rgba(52, 211, 153, 0.30) !important;
  box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.03);
}
.ls-grid-premium .player-grid thead th {
  font-size: 9px !important;
  font-weight: 800 !important;
  letter-spacing: 0.14em !important;
  color: #64748b !important;
  padding-top: 8px !important;
  padding-bottom: 8px !important;
}
.dark .ls-grid-premium .player-grid thead th {
  color: #94a3b8 !important;
}

/* Body rows — emerald hover wash + soft separators */
.ls-grid-premium .player-grid tbody tr {
  position: relative;
  transition: background 120ms ease;
  border-bottom: 1px solid rgba(15, 23, 42, 0.04) !important;
}
.dark .ls-grid-premium .player-grid tbody tr {
  border-bottom-color: rgba(148, 163, 184, 0.07) !important;
}
.ls-grid-premium .player-grid tbody tr:last-child {
  border-bottom: none !important;
}
.ls-grid-premium .player-grid tbody tr > td:first-child {
  position: relative;
}
.ls-grid-premium .player-grid tbody tr:hover {
  background: linear-gradient(90deg, rgba(16, 185, 129, 0.05) 0%, transparent 60%) !important;
}
.dark .ls-grid-premium .player-grid tbody tr:hover {
  background: linear-gradient(90deg, rgba(52, 211, 153, 0.06) 0%, transparent 60%) !important;
}

/* Dream Team rows — gold spine + faint gold row tint */
.ls-grid-premium .player-grid tbody tr:has(.text-yellow-500) > td:first-child,
.ls-grid-premium .player-grid tbody tr:has(.text-yellow-400) > td:first-child {
  box-shadow: inset 3px 0 0 0 #f59e0b;
}
.ls-grid-premium .player-grid tbody tr:has(.text-yellow-500),
.ls-grid-premium .player-grid tbody tr:has(.text-yellow-400) {
  background: linear-gradient(90deg, rgba(245, 158, 11, 0.05) 0%, transparent 50%);
}
.dark .ls-grid-premium .player-grid tbody tr:has(.text-yellow-500),
.dark .ls-grid-premium .player-grid tbody tr:has(.text-yellow-400) {
  background: linear-gradient(90deg, rgba(251, 191, 36, 0.06) 0%, transparent 50%);
}

/* Pre-game rows — slightly softer than the macro's default opacity-70 */
.ls-grid-premium .player-grid tbody tr.opacity-70 {
  opacity: 0.62;
}

/* ── Typography modernisation (Xav 2026-06-12) ─────────────────────
   The data tables read like legacy LiveSlates next to the new premium
   widgets: light weights, no mono numerals, flat text chips. These
   rules lift every player_grid at once via the data-label hooks the
   macro already stamps on each cell. */

/* Taller row rhythm + heavier base weight. */
.ls-grid-premium .player-grid tbody td {
  padding-top: 5px;
  padding-bottom: 5px;
  font-weight: 600;
}
/* Player name cell — type comes from the shared .ls-player-name class
   on the td (see below), NOT from rules here, so the table names and
   the scoreboard names can never drift apart again. */
/* Numeric cells — mono + tabular so columns read like a box score. */
.ls-grid-premium .player-grid tbody td:not(:first-child) {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* Live FPts — the money column, a step bigger and heavier. */
.ls-grid-premium .player-grid tbody td[data-label="Live"] {
  font-size: 12.5px;
  font-weight: 800;
}
/* Team / Pos — small bold uppercase chips, sports-data style. */
.ls-grid-premium .player-grid tbody td[data-label="Team"],
.ls-grid-premium .player-grid tbody td[data-label="Pos"] {
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}
/* Status text stays quiet. */
.ls-grid-premium .player-grid tbody td[data-label="Status"] {
  font-size: 9.5px;
  letter-spacing: 0.03em;
}

/* ── THE player-name type (Xav 2026-06-13) ─────────────────────────
   Single source of truth for how a player's name renders anywhere in
   the app — scoreboard pitcher rows, My Players / My Lineups / All
   Players tables. Sized/weighted/coloured HERE and nowhere else, with
   !important so no Tailwind utility or widget-local rule can wash it
   out again. If a name looks grey somewhere, that element is missing
   this class — add the class, don't write new font rules. */
.ls-player-name,
.ls-player-name span {
  font-size: 13px !important;
  font-weight: 700 !important;
  color: #0f172a !important;
  letter-spacing: -0.005em;
}
.dark .ls-player-name,
.dark .ls-player-name span {
  color: #f1f5f9 !important;
}
/* Emoji/star glyphs inside a name keep their own colours. */
.ls-player-name .text-yellow-500,
.ls-player-name .text-yellow-400 { color: #eab308 !important; }

/* ── Canvas + card depth (Xav 2026-06-12 "white/bright" pass) ──────────
   The page canvas drops half a step below the old flat slate-200 and
   picks up a vertical top-lit gradient with the faintest green-grey
   cast (harmonises with the emerald scoreboard band). White cards now
   read as floating panels instead of dissolving into a white page.

   .ls-canvas lives on <body> (base.html). Don't add Tailwind bg-*
   classes alongside it — the CDN injects after this file and wins
   specificity ties. */
.ls-canvas {
  background: linear-gradient(180deg, #dfe6e8 0%, #d7dfe3 55%, #cfd9de 100%);
  background-attachment: fixed;
}
.dark .ls-canvas { background: #0a0f1e; }

/* Widget card surface — kill the flat-white interior on tall panels
   with a barely-there vertical gradient (white → cool off-white). Two
   class levels of specificity so it beats Tailwind's .bg-white in
   light mode while the CDN's .dark .dark\:bg-* still wins dark ties. */
.ls-widget > .bg-white {
  background: linear-gradient(180deg, #ffffff 0%, #fdfeff 45%, #f6f9fa 100%);
}

/* ── Top-player card list (.tp-*) — shared by top10 + top10_my_players
   (Xav 2026-06-14). Fixed-content-height cards in a scroll area, so the
   number of players visible tracks the PANEL height: expand the panel
   → more players, shrink it → fewer. Type from the --ls-fs-* scale. */
.tp-card {
  display: flex; align-items: center; gap: 9px;
  padding: 6px 10px; border-radius: 10px;
  border: 1px solid rgba(15, 23, 42, 0.06);
  background: rgba(15, 23, 42, 0.02);
  min-width: 0; flex-shrink: 0;
}
.dark .tp-card { background: rgba(255, 255, 255, 0.02); border-color: rgba(255, 255, 255, 0.05); }
.tp-card-leader {
  border-color: rgba(16, 185, 129, 0.35);
  background: linear-gradient(90deg, rgba(16, 185, 129, 0.08) 0%, rgba(16, 185, 129, 0.02) 60%);
  box-shadow: inset 3px 0 0 0 #10b981;
}
.dark .tp-card-leader {
  border-color: rgba(52, 211, 153, 0.35);
  background: linear-gradient(90deg, rgba(52, 211, 153, 0.10) 0%, rgba(52, 211, 153, 0.02) 60%);
}

.tp-rank {
  flex-shrink: 0;
  display: inline-flex; align-items: center; gap: 2px;
  min-width: 30px; justify-content: center;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--ls-fs-12); font-weight: 900;
  padding: 2px 6px; border-radius: 7px;
  background: rgba(148, 163, 184, 0.16); color: #64748b;
}
.dark .tp-rank { background: rgba(148, 163, 184, 0.18); color: #94a3b8; }
.tp-rank-1 { background: linear-gradient(135deg, rgba(245,158,11,0.24), rgba(245,158,11,0.10)); color: #b45309; box-shadow: 0 0 0 1px rgba(245,158,11,0.30); }
.dark .tp-rank-1 { color: #fcd34d; }
.tp-rank-2 { background: rgba(148,163,184,0.26); color: #475569; }
.dark .tp-rank-2 { color: #e2e8f0; }
.tp-rank-3 { background: rgba(180,83,9,0.16); color: #92400e; }
.dark .tp-rank-3 { color: #fdba74; }
.tp-move { font-size: 7px; line-height: 1; }
.tp-move-up   { color: #10b981; animation: ls-rank-move-in 500ms ease-out; }
.tp-move-down { color: #ef4444; animation: ls-rank-move-in 500ms ease-out; }

.tp-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.tp-head { display: flex; align-items: center; gap: 5px; min-width: 0; }
.tp-name {
  font-size: var(--ls-fs-12); font-weight: 700; color: #0f172a;
  letter-spacing: -0.005em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.dark .tp-name { color: #f1f5f9; }
.tp-glyph { flex-shrink: 0; font-size: var(--ls-fs-10); line-height: 1; }
.tp-star  { flex-shrink: 0; font-size: var(--ls-fs-10); color: #eab308; }
.tp-sub { display: flex; align-items: center; gap: 4px; min-width: 0; }
.tp-logo { width: 13px; height: 13px; object-fit: contain; flex-shrink: 0; }
.tp-team {
  font-size: var(--ls-fs-9); font-weight: 800; letter-spacing: 0.06em;
  text-transform: uppercase; color: #64748b;
}
.dark .tp-team { color: #94a3b8; }
.tp-dot { color: rgba(148,163,184,0.6); font-size: var(--ls-fs-9); }
.tp-pos { font-size: var(--ls-fs-9); font-weight: 700; color: #94a3b8; text-transform: uppercase; }

.tp-fpts {
  flex-shrink: 0;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-weight: 900; font-size: var(--ls-fs-16); color: #059669;
}
.dark .tp-fpts { color: #34d399; }
.tp-fpts-proj { color: #3b82f6; }
.dark .tp-fpts-proj { color: #93c5fd; }
