:root {
  /* Without this, browsers that can't fully suppress native form-control
     chrome via appearance:none (Safari especially) fall back to their
     light-mode default colors for the parts they don't suppress — the
     Teams button rendered as a native white/black pill even though every
     color on it is explicitly overridden below. */
  color-scheme: dark;
  --turf-950: #0f2119;
  --turf-800: #16301f;
  --turf-700: #1e3d28;
  --turf-600: #2a4d33;
  --chalk: #f1ecdd;
  --chalk-dim: #a9b6a9;
  --flag-gold: #d3a73d;
  --flag-gold-bright: #e8c15e;
  --leather: #8a5a3b;
  --rush-clay: #b4482e;
  --font-display: "Anton", "Arial Narrow", sans-serif;
  --font-body: "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
  --font-mono: "IBM Plex Mono", "SF Mono", monospace;
}

* { box-sizing: border-box; }

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

html, body {
  margin: 0;
  background: var(--turf-950);
  color: var(--chalk);
  font-family: var(--font-body);
  min-height: 100%;
}

body {
  position: relative;
  overflow-x: hidden;
}

/* subtle yard-line hash texture on the page background */
.field-texture {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: repeating-linear-gradient(
    100deg,
    rgba(241, 236, 221, 0.028) 0px,
    rgba(241, 236, 221, 0.028) 2px,
    transparent 2px,
    transparent 96px
  );
}

.hero, .layout { position: relative; z-index: 1; }

.hero {
  padding: 28px 24px 20px;
  border-bottom: 1px solid var(--turf-600);
}

.hero-inner {
  max-width: 860px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 20px;
}

.hero-logo {
  flex: none;
  height: 210px;
  width: auto;
  border-radius: 10px;
}

.hero-text { min-width: 0; }

.hero-github {
  position: absolute;
  top: 20px;
  right: 24px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--chalk);
  opacity: 0.8;
  transition: color 0.15s ease, opacity 0.15s ease;
}
.hero-github svg { width: 20px; height: 20px; display: block; }
.hero-github:hover,
.hero-github:focus-visible {
  color: var(--flag-gold-bright);
  opacity: 1;
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--flag-gold);
  margin: 0 0 6px;
}

.hero-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(28px, 5vw, 52px);
  line-height: 0.95;
  letter-spacing: 0.01em;
  margin: 0 0 10px;
  color: var(--chalk);
  text-shadow: 0 2px 0 rgba(0,0,0,0.25);
}

.hero-byline {
  font-size: 14px;
  line-height: 1.5;
  color: var(--chalk-dim);
  max-width: 640px;
  margin: 0;
}

.layout {
  max-width: 1180px;
  margin: 0 auto;
  padding: 28px 24px 60px;
}

/* --- mobile filters drawer trigger ---
   Hidden on desktop by default; the `@media (max-width: 860px)` block at
   the bottom of this file both reveals it and turns .filters-drawer into a
   fixed bottom sheet. On desktop .filters-drawer is just a plain wrapper
   around .control-bar, so nesting it costs nothing there. */
.controls-toggle { display: none; }

#toggle-filters {
  width: 100%;
  background: var(--turf-800);
  color: var(--chalk);
  border: 1px solid var(--turf-600);
  border-radius: 8px;
  padding: 12px 16px;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}
#toggle-filters:hover,
#toggle-filters:focus-visible {
  border-color: var(--flag-gold);
  color: var(--flag-gold-bright);
}

/* --- control bar --- */
.control-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 18px;
  align-items: end;
  padding: 18px 20px;
  background: var(--turf-800);
  border: 1px solid var(--turf-600);
  border-radius: 10px;
  margin-bottom: 22px;
}

.control {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 140px;
}

.control label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--chalk-dim);
}

.control select {
  /* Fixed height + border-box, rather than trusting padding alone to
     produce equal heights — a native <select>'s internal chrome doesn't
     always size identically to a plain element with the same padding
     (see .teams-select-btn, which sits in the same row and needs to
     line up with these pixel-for-pixel). */
  box-sizing: border-box;
  height: 38px;
  background: var(--turf-950);
  color: var(--chalk);
  border: 1px solid var(--turf-600);
  border-radius: 6px;
  padding: 9px 10px;
  font-family: var(--font-body);
  font-size: 14px;
  min-width: 150px;
}
.control select:focus-visible,
.control input:focus-visible {
  outline: 2px solid var(--flag-gold);
  outline-offset: 2px;
}

.control-slider { min-width: 220px; flex: 1 1 220px; }

.threshold-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

#threshold-slider {
  flex: 1 1 auto;
  width: 100%;
  accent-color: var(--flag-gold);
  height: 28px;
}

#threshold-number {
  flex: none;
  width: 70px;
  background: var(--turf-950);
  color: var(--chalk);
  border: 1px solid var(--turf-600);
  border-radius: 6px;
  padding: 6px 8px;
  font-family: var(--font-mono);
  font-size: 13px;
  text-align: center;
}
/* Chrome/Safari spinner arrows clash with the turf theme — hide them,
   the range slider next to it already covers coarse adjustment. */
#threshold-number::-webkit-inner-spin-button,
#threshold-number::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
#threshold-number { -moz-appearance: textfield; }

.control-apply { min-width: auto; }
.control-png { min-width: auto; }
/* .control already lays its children out in a column with a 6px gap —
   that's exactly the "one on top of the other" stack these two checkboxes
   need, so no flex-direction override here, just the same min-width reset
   every other non-select control gets. */
.control-toggles { min-width: auto; }

/* Empty flex items with no explicit sizing don't force a wrap on their
   own — a flex-basis: 100% item does, without occupying any visible
   space (width/height: 0). This is what makes control-bar row grouping
   deterministic across viewport widths instead of depending on exactly
   how much space happens to be left on a given line. */
.row-break {
  flex-basis: 100%;
  width: 0;
  height: 0;
}

/* Season only ever holds a 4-digit year — the shared 150px .control select
   min-width was sized for longer option text (metric names, position
   labels) and left Season looking as wide as Category for no reason. */
.control-season { min-width: auto; }
.control-season select { width: 72px; min-width: 72px; padding-right: 4px; }

/* --- Teams multi-select ---
   Built as a custom button + checkbox-list dropdown rather than a native
   <select multiple> — multi-selecting 32 options via ctrl/cmd-click isn't
   discoverable and doesn't work at all on touch. Positioned/sized to sit
   next to Season/Category like a normal .control select. */
.control-teams { position: relative; }

.teams-select-btn {
  /* Buttons default to appearance:"push-button" (vs. a <select>'s
     "menulist"), which WebKit renders with native chrome — a light
     gray/white system background — that fights `background` overrides.
     Selects mostly get away without this reset; buttons don't. */
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  box-shadow: none;
  box-sizing: border-box;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  background: var(--turf-950);
  color: var(--chalk);
  border: 1px solid var(--turf-600);
  border-radius: 6px;
  padding: 9px 10px;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: normal;
  min-width: 150px;
  cursor: pointer;
  text-align: left;
}
.teams-select-btn:hover,
.teams-select-btn:focus-visible {
  border-color: var(--flag-gold);
  outline: none;
}
.teams-select-btn .chevron {
  flex: none;
  opacity: 0.7;
  transition: transform 0.15s ease;
}
.teams-select-btn[aria-expanded="true"] .chevron { transform: rotate(180deg); }

.teams-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 50;
  width: 300px;
  max-height: 380px;
  overflow-y: auto;
  background: var(--turf-800);
  border: 1px solid var(--turf-600);
  border-radius: 8px;
  padding: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}
.teams-dropdown[hidden] { display: none; }

.teams-dropdown-actions {
  display: flex;
  gap: 8px;
  margin-bottom: 8px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--turf-600);
}
.teams-dropdown-actions button {
  flex: 1;
  background: var(--turf-700);
  color: var(--chalk);
  border: 1px solid var(--turf-600);
  border-radius: 5px;
  padding: 5px 8px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  cursor: pointer;
}
.teams-dropdown-actions button:hover,
.teams-dropdown-actions button:focus-visible {
  border-color: var(--flag-gold);
}

/* Mimics NFL.com's team-picker layout, compressed to fit the dropdown's
   width: AFC in the left column, NFC in the right, each stacked
   East/North/South/West top to bottom (rather than NFL.com's spacious
   4-columns-per-conference grid, which doesn't fit here). */
.teams-checklist {
  display: flex;
  gap: 14px;
  align-items: flex-start;
}
.teams-column {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.teams-column-header {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--flag-gold);
  padding-bottom: 4px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--turf-600);
}
.teams-division + .teams-division { margin-top: 8px; }
.teams-division-header {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--chalk-dim);
  opacity: 0.75;
  margin-bottom: 2px;
}
.team-option {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 4px;
  border-radius: 4px;
  font-family: var(--font-body);
  font-size: 12.5px;
  color: var(--chalk-dim);
  cursor: pointer;
}
.team-option:hover { background: rgba(241, 236, 221, 0.06); }
.team-option input {
  flex: none;
  width: 13px;
  height: 13px;
  accent-color: var(--flag-gold);
}
.team-option-logo {
  flex: none;
  width: 16px;
  height: 16px;
  object-fit: contain;
}
/* Fallback for a team with no logo file — same color-dot treatment the
   scouting card uses when its own logo <img> 404s (see showScoutCard's
   scoutLogo.onerror in app.js). */
.team-swatch {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 2px;
}

/* Governs every filter control now (category/season/position/axes/threshold
   all sit "pending" in their own inputs until this is clicked), so it's
   styled as the form's one primary action rather than tucked next to the
   threshold slider like the old threshold-only Set button was. */
.apply-btn {
  flex: none;
  background: var(--flag-gold);
  color: var(--turf-950);
  border: 1px solid var(--flag-gold);
  border-radius: 6px;
  padding: 9px 20px;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}
.apply-btn:hover,
.apply-btn:focus-visible {
  background: var(--flag-gold-bright);
  border-color: var(--flag-gold-bright);
}
/* Lit up while any control (category/season/position/axes/threshold) holds
   a value that differs from what's actually drawn on the chart — the only
   cue that pending selections are sitting unapplied. */
.apply-btn.pending {
  box-shadow: 0 0 0 2px var(--turf-950), 0 0 0 4px var(--flag-gold-bright), 0 0 10px rgba(232, 193, 94, 0.55);
}
/* Secondary action next to Apply — black bg + chalk text, gold reserved for
   Apply since that's the primary action. Exports whatever's currently
   rendered on the chart, so it's disabled whenever the chart itself is
   empty (see the `< 2` check in render()). */
.png-btn {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: #000;
  color: var(--chalk);
  border: 1px solid var(--turf-600);
  border-radius: 6px;
  padding: 9px 20px;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}
.png-btn:hover,
.png-btn:focus-visible {
  background: #1a1a1a;
  border-color: var(--flag-gold);
}
.png-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.png-btn:disabled:hover {
  background: #000;
  border-color: var(--turf-600);
}

/* Mobile-only separator between the toggle controls and the Save Plot /
   Apply button pair — see @media (max-width: 860px) for the visible rule
   and the `order` overrides that pull it (and Save Plot) down next to
   Apply without moving them in the DOM, which would also shift Save
   Plot's desktop position in the filters bar. */
.drawer-divider {
  display: none;
}
.png-btn .icon-download {
  width: 13px;
  height: 13px;
  opacity: 0.8;
}

.checkbox-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--chalk-dim);
  text-transform: none;
  letter-spacing: normal;
  white-space: nowrap;
  cursor: pointer;
}
.checkbox-label input { accent-color: var(--flag-gold); width: 15px; height: 15px; }

/* --- board: chart + scouting card --- */
.board {
  display: grid;
  grid-template-columns: 1fr;
  gap: 18px;
  align-items: start;
  position: relative; /* positioning context for the scout-card overlay */
}

.chart-panel {
  background: var(--turf-800);
  border: 1px solid var(--turf-600);
  border-radius: 10px;
  padding: 12px;
  min-height: 720px;
  position: relative;
  /* Without this, a pinch gesture aimed at a data point is read by the
     browser as a page-zoom gesture, blowing the whole layout (controls
     included) off screen instead of hovering the point. Plotly's own
     scrollZoom handling (see app.js) covers zoom instead. */
  touch-action: none;
}

#chart { width: 100%; height: 720px; }
/* The actual hit-testing layer is Plotly's .nsewdrag overlay rect, which
   sits above every marker and would otherwise keep its own injected
   crosshair cursor (a leftover affordance for drag-to-zoom, which is off
   now that dragmode:false makes click the only interaction) — this is the
   rule that's actually visible. .points path is kept too: inert today since
   the overlay covers it, but it becomes the effective rule for free if a
   future Plotly version changes the layer stacking. */
#chart .nsewdrag { cursor: pointer; }
#chart .points path { cursor: pointer; }

.empty-state {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 40px;
  color: var(--chalk-dim);
  font-size: 15px;
  background: rgba(15, 33, 25, 0.85);
  border-radius: 10px;
}
/* the [hidden] attribute alone isn't enough to beat the rule above once we
   set display:flex below — without this, an "empty" state panel with
   position:absolute; inset:0 would sit on top of the chart and silently
   swallow every hover/click even while invisible. */
.empty-state:not([hidden]) {
  display: flex;
}

/* Shares .empty-state's positioning + the [hidden]-safe display rule above —
   only the layout direction and content (spinner + text) differ. */
.logo-preload-state {
  flex-direction: column;
  gap: 10px;
}
.spinner {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 3px solid rgba(241, 236, 221, 0.15);
  border-top-color: var(--flag-gold);
  animation: spin 0.8s linear infinite;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}

/* --- scouting card, the signature element ---
   Desktop: a floating overlay pinned to the chart panel's top-right
   corner. Collapsed by default (just the gold PROSPECT tab peeking out,
   ~24px tall) so it never squeezes the chart width, even with 60+ dots.
   The tab lives outside `.scout-card-panel`'s overflow:hidden box so it
   never gets clipped while the panel collapses/expands. */
.scout-card {
  position: absolute;
  top: 24px;
  right: 24px;
  /* Widened from 260px to fit the rank/percentile column added alongside
     the value column without wrapping metric labels like "TPS Pressure
     Rate". */
  width: 380px;
  z-index: 5;
}

/* Positioned relative to .scout-card itself (not .scout-card-panel) so the
   same top/right offsets land in the right corner whether .scout-card is
   the absolute desktop overlay or the relative mobile stacked block —
   both share .scout-card as their positioning root. Hidden until a card is
   pinned via the shared .is-active toggle, same as the panel body. */
.scout-close {
  display: none;
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 6;
  width: 22px;
  height: 22px;
  align-items: center;
  justify-content: center;
  background: rgba(15, 33, 25, 0.55);
  border: 1px solid var(--turf-600);
  border-radius: 50%;
  color: var(--chalk-dim);
  font-size: 16px;
  line-height: 1;
  padding: 0;
  cursor: pointer;
}
.scout-close:hover,
.scout-close:focus-visible {
  color: var(--flag-gold-bright);
  border-color: var(--flag-gold);
}
.scout-card.is-active .scout-close { display: flex; }

.scout-card-tab {
  position: relative;
  display: inline-block;
  background: var(--flag-gold);
  color: var(--turf-950);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.14em;
  padding: 6px 9px;
  border-radius: 4px 4px 0 0;
  line-height: 1;
}

.scout-card-panel {
  width: 100%;
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
  background: linear-gradient(165deg, rgba(30, 61, 40, 0.9), rgba(22, 48, 31, 0.94));
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid var(--turf-600);
  border-radius: 0 8px 8px 8px;
  box-shadow: 0 14px 30px rgba(0, 0, 0, 0.35);
  transition: max-height 0.28s ease, opacity 0.22s ease;
}

.scout-card.is-active .scout-card-panel {
  max-height: 480px;
  opacity: 1;
  pointer-events: auto;
}

/* placeholder text never shows on the desktop overlay — collapsed state
   is just the 24px tab, see class rule above */
.scout-card-empty { display: none; }

/* same [hidden]-losing-to-author-CSS trap as .empty-state above: display:flex
   on this class beats the UA [hidden]{display:none} rule by origin, not
   specificity, so the hidden attribute alone won't hide it without this. */
.scout-card-body { display: none; flex-direction: column; gap: 16px; padding: 18px 16px 16px; }
.scout-card-body:not([hidden]) { display: flex; }

/* Doubles as the drag handle on desktop (see beginScoutDrag() in app.js) —
   cursor + touch-action here, mobile reset alongside the rest of the
   stacked-layout overrides in the @media (max-width: 860px) block below. */
.scout-card-top {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-bottom: 14px;
  border-bottom: 1px dashed var(--turf-600);
  cursor: grab;
  touch-action: none;
  user-select: none;
}
.scout-card.is-dragging .scout-card-top {
  cursor: grabbing;
}
.scout-card.is-dragging .scout-card-panel {
  box-shadow: 0 20px 44px rgba(0, 0, 0, 0.5);
}

.scout-logo-wrap {
  width: 52px; height: 52px;
  flex: none;
  display: flex; align-items: center; justify-content: center;
}
.scout-logo-wrap img { width: 100%; height: 100%; object-fit: contain; }
.scout-badge {
  width: 52px; height: 52px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display);
  font-size: 15px;
  color: var(--chalk);
  letter-spacing: 0.02em;
}

.scout-name {
  font-family: var(--font-display);
  font-size: 20px;
  letter-spacing: 0.01em;
  color: var(--chalk);
  line-height: 1.1;
}
.scout-meta {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--flag-gold-bright);
  margin-top: 4px;
}

/* Three grid children per <dl> row: label, value, rank/percentile — see the
   addRow() comment in app.js on why every row emits all three even when the
   rank cell is empty (Games / threshold field), rather than letting grid
   auto-placement drift out of alignment on short rows. */
.scout-stats {
  display: grid;
  grid-template-columns: 1fr auto auto;
  column-gap: 12px;
  row-gap: 9px;
  margin: 0;
  align-items: baseline;
}
.scout-stats dt {
  font-size: 12.5px;
  color: var(--chalk-dim);
}
.scout-stats dd {
  margin: 0;
  text-align: right;
  white-space: nowrap;
}
.scout-stats dd.scout-stat-value {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--chalk);
  font-weight: 600;
}
.scout-stats dd.scout-stat-value.is-highlighted {
  color: var(--flag-gold-bright);
}
/* Left un-highlighted even on gold rows so it stays legible as a quieter
   third column next to the bright value. */
.scout-stats dd.scout-stat-rank {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--chalk-dim);
  min-width: 64px;
}

.footnote {
  margin-top: 26px;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--chalk-dim);
}
.footnote a { color: var(--flag-gold-bright); }

@media (max-width: 860px) {
  .board { grid-template-columns: 1fr; }

  /* Bleed the chart panel out to the viewport edges — .layout's 24px
     horizontal padding is otherwise doubled up with the panel's own 12px,
     eating ~36px of chart width per side on a 390px phone. */
  .chart-panel {
    padding: 0;
    margin: 0 -24px;
    border-radius: 0;
  }
  #chart, .chart-panel { min-height: 600px; }
  #chart { height: 75vh; min-height: 600px; }
  .hero { padding: 16px 16px 12px; }
  .hero-inner { gap: 12px; }
  .hero-logo { height: 44px; border-radius: 8px; }
  .hero-title { font-size: clamp(24px, 6vw, 40px); }
  .hero-byline { display: none; }
  .hero-github { top: 14px; right: 16px; }
  .hero-github svg { width: 18px; height: 18px; }

  /* Below 860px there's no room to float the card over the chart without
     covering data, so it drops back to a plain block stacked underneath —
     the pre-overlay behavior, undoing all the desktop overlay rules above.
     `.scout-card-panel` takes over as the visible card box (padding,
     background, min-height); `.scout-card` is just a relative anchor for
     the tab, and `.scout-card-body` gives up its desktop padding since
     the panel now owns it. */
  .scout-card {
    position: relative;
    top: auto;
    right: auto;
    width: auto;
  }
  /* Dragging is desktop-only (app.js's isDesktopScoutLayout() guard) — undo
     the handle's cursor/touch-action here so mobile can still scroll the
     page by touching the card header. */
  .scout-card-top {
    cursor: auto;
    touch-action: auto;
  }
  .scout-card-tab {
    position: absolute;
    top: -11px;
    left: 18px;
    border-radius: 3px;
  }
  .scout-card-panel {
    max-height: none;
    overflow: visible;
    opacity: 1;
    pointer-events: auto;
    background: linear-gradient(165deg, var(--turf-700), var(--turf-800));
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-radius: 10px;
    box-shadow: none;
    padding: 20px 18px 18px;
    min-height: 320px;
  }
  .scout-card-empty {
    display: none;
    align-items: center;
    justify-content: center;
    height: 100%;
    min-height: 200px;
    text-align: center;
    color: var(--chalk-dim);
    font-size: 13.5px;
    padding: 0 8px;
  }
  .scout-card-empty:not([hidden]) { display: flex; }
  .scout-card-body { padding: 0; }

  /* Filter controls collapse into a bottom-sheet drawer here — left open
     and flex-wrapped like on desktop, five selects + a slider would eat
     most of a phone screen before the chart even starts. */
  .controls-toggle { display: block; margin-bottom: 14px; }

  .filters-drawer {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    max-height: 60vh;
    overflow-y: auto;
    background: var(--turf-800);
    padding: 16px;
    border-top: 1px solid var(--turf-600);
    border-radius: 14px 14px 0 0;
    z-index: 100;
    transform: translateY(100%);
    transition: transform 0.3s ease;
  }
  .filters-drawer.open {
    transform: translateY(0);
  }

  /* Undo the desktop card chrome — the drawer itself is already the card —
     and stack controls in a single column instead of wrapping flex rows. */
  .filters-drawer .control-bar {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 14px;
    background: none;
    border: none;
    padding: 0;
    margin-bottom: 0;
    border-radius: 0;
  }
  .filters-drawer .control select,
  .filters-drawer #threshold-number {
    width: 100%;
  }
  .filters-drawer .control-season select { width: 100%; }

  /* The teams dropdown is absolutely positioned on desktop; inside the
     fixed bottom-sheet drawer (which scrolls) that risks clipping/overlap,
     so it's laid out inline instead — same "stack in the column flow"
     treatment every other drawer control gets. */
  .filters-drawer .teams-dropdown {
    position: static;
    width: 100%;
    margin-top: 8px;
    box-shadow: none;
  }
  /* .control-slider's `flex: 1 1 220px` sizes it as a 220px-wide item in
     the desktop row layout above. In this column-direction drawer, that
     same flex-basis is read as a 220px *height* instead — the control's
     actual content (label + slider row) is ~80px, so without this reset
     it sits in a box ~140px taller than its content, which is exactly
     the empty gap this rule fixes. */
  .filters-drawer .control-slider {
    flex: none;
    min-width: 0;
  }
  .filters-drawer .apply-btn,
  .filters-drawer .png-btn {
    width: 100%;
    justify-content: center;
    padding: 12px 20px;
  }

  /* Save Plot and Apply are pulled to the bottom of the drawer, below every
     other control, via `order` rather than a DOM move — every other control
     stays at the default `order: 0` and keeps sorting by its existing DOM
     position, so this only affects mobile without touching the desktop
     filters bar (Save Plot stays put there, next to the axis selects). */
  .filters-drawer .drawer-divider {
    display: block;
    order: 1;
    height: 1px;
    background: var(--turf-600);
    margin: 12px 0;
    opacity: 0.5;
  }
  .filters-drawer .control-png {
    order: 2;
  }
  .filters-drawer .control-apply {
    order: 3;
  }

  /* Apply is the drawer's sticky, primary commit action — stays pinned to
     the bottom of the scrollable drawer (.filters-drawer has
     overflow-y: auto) while Save Plot and every filter above it scroll
     underneath it. */
  .filters-drawer .apply-btn {
    position: sticky;
    bottom: 0;
  }
}
