:root {
  --bg: #fbfaf7;
  --bg2: #ffffff;
  --bg3: #f1efe9;
  --border: #e5e2da;
  --text: #191c1f;
  --text2: #4b5257;
  --text3: #7a8187;
  --green: #1f8a52;
  --red: #c23a4f;
  --amber: #b3720a;
  --primary: #20306e;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* Landing */
#landing {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.logo {
  font-size: 36px;
  font-weight: 900;
  margin-bottom: 8px;
  background: linear-gradient(135deg, var(--primary), var(--green));
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.tagline {
  color: var(--text2);
  margin-bottom: 32px;
  font-size: 14px;
}

.login-box {
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 32px;
  width: 100%;
  max-width: 380px;
}

.login-box h3 {
  font-size: 18px;
  margin-bottom: 20px;
  font-weight: 700;
}

input {
  width: 100%;
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px;
  color: var(--text);
  font-size: 14px;
  margin-bottom: 12px;
  outline: none;
}

input:focus {
  border-color: var(--primary);
}

button {
  width: 100%;
  background: var(--primary);
  color: white;
  border: none;
  border-radius: 8px;
  padding: 12px;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: opacity 0.2s;
}

button:hover { opacity: 0.9; }
button:disabled { opacity: 0.5; cursor: not-allowed; }

.error {
  background: rgba(194,58,79,0.1);
  border: 1px solid rgba(194,58,79,0.3);
  color: var(--red);
  padding: 10px;
  border-radius: 6px;
  font-size: 12px;
  margin-top: 12px;
  display: none;
}

/* App */
#app { display: none; }

/* .sticky-header-group (2026-09-21) is the ONE sticky element covering
   both .header and #top-ticker -- see index.html's comment for why this
   replaced two independently-sticky elements. */
.sticky-header-group {
  position: sticky;
  top: 0;
  z-index: 100;
}

.header {
  background: var(--bg2);
  border-bottom: 1px solid var(--border);
  padding: 16px 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.header-logo {
  font-size: 20px;
  font-weight: 900;
  background: linear-gradient(135deg, var(--primary), var(--green));
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

/* Top Scorers ticker (2026-09-10) -- an infinite CSS-transform marquee,
   not a native scroll container, so it can never contribute to the page's
   own scrollWidth (this session's whole mobile audit was about exactly
   that class of bug) -- .top-ticker-viewport clips with overflow:hidden,
   and the oversized .top-ticker-track only ever moves via `transform`,
   which doesn't affect layout/scroll dimensions the way real overflow
   content does. Track content is rendered TWICE by loadTopTicker() so
   the loop point (translateX(-50%), i.e. exactly one copy's width) lands
   on an identical frame to the start -- no visible seam/jump. */
.top-ticker {
  display: flex;
  align-items: center;
  background: var(--bg2);
  border-bottom: 1px solid var(--border);
  overflow: hidden;
  /* No position/sticky of its own -- lives inside .sticky-header-group,
     which is the single sticky element (2026-09-21, see its comment). */
}
.top-ticker-label {
  flex-shrink: 0;
  padding: 8px 12px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.05em;
  color: var(--primary);
  background: rgba(139,63,209,0.12);
  border-right: 1px solid var(--border);
  white-space: nowrap;
}
.top-ticker-viewport {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}
.top-ticker-track {
  display: inline-flex;
  animation: top-ticker-scroll linear infinite;
  animation-duration: 45s;
}
.top-ticker-viewport:hover .top-ticker-track {
  animation-play-state: paused;
}
@keyframes top-ticker-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
.top-ticker-item {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 8px 20px 8px 0;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  flex-shrink: 0;
}
.top-ticker-item:hover .top-ticker-name { color: var(--primary); }
.top-ticker-rank { color: var(--text3); font-weight: 800; font-size: 11px; }
.top-ticker-name { color: var(--text); }
.top-ticker-meta { color: var(--text3); font-weight: 600; font-size: 11px; }
.top-ticker-pts { color: var(--primary); font-weight: 800; }

@media (max-width: 640px) {
  .top-ticker-label { padding: 6px 8px; font-size: 9px; }
  .top-ticker-item { padding: 6px 14px 6px 0; font-size: 12px; gap: 4px; }
  .top-ticker-meta { display: none; } /* position/team dropped on mobile -- name + points is enough at this width */
}

.user-info {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 13px;
  color: var(--text2);
}

.avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--primary);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  color: white;
  font-size: 14px;
}

/* League Selector */
.league-bar {
  background: var(--bg2);
  border-bottom: 1px solid var(--border);
  padding: 12px 20px;
}

.league-select-wrap {
  position: relative;
}

.league-select-btn {
  width: 100%;
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  text-align: left;
}

.league-select-chevron {
  color: var(--text2);
  font-size: 12px;
}

.league-select-list {
  display: none;
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 8px;
  max-height: 280px;
  overflow-y: auto;
  z-index: 50;
  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}

.league-select-list.open {
  display: block;
}

.league-select-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 600;
}

.league-select-row:hover {
  background: var(--bg2);
}

.league-select-row.selected {
  background: rgba(139,92,246,0.15);
}

/* League format badge -- shown on the right side of both the closed
   league bar and each row in the dropdown (2026-07-30, Bernard's ask:
   "have what league type included in this menu, on the opposite end of
   the bar"). Colors deliberately echo the same real distinction driving
   pick/player valuation now (see PICK_CURVE_BY_FORMAT) -- this badge is
   literally telling you which curve is active for that league. */
.format-badge {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 6px;
  flex-shrink: 0;
}

.format-badge.dynasty { background: rgba(139,63,209,0.2); color: #8b3fd1; }
.format-badge.keeper { background: rgba(47,111,214,0.18); color: #2f6fd6; }
.format-badge.redraft { background: rgba(91,102,114,0.18); color: #5b6672; }

/* App shell: vertical side nav (2026-09-04, Bernard's ask -- "add a
   little style", buttons designed like they're coming out from the
   side, pointed at the right edge, hover pronounces them). Replaces the
   old horizontal .tab-nav strip. */
.app-body {
  display: flex;
  align-items: flex-start;
}

.side-nav {
  display: flex;
  flex-direction: column;
  gap: 6px;
  background: var(--bg2);
  border-right: 1px solid var(--border);
  padding: 16px 0;
  flex-shrink: 0;
  /* Locked in place (2026-09-04, Bernard) -- sticks to the viewport
     below the header so only the main content scrolls, not the nav.
     64px matches the header's real rendered height (32px vertical
     padding + a 32px-tall avatar setting the row height). overflow:
     hidden -- all 8 buttons are sized/gapped to fit within that fixed
     height with no internal scrollbar of their own. */
  position: sticky;
  top: var(--sidenav-top, 64px);
  height: calc(100vh - var(--sidenav-top, 64px));
  overflow: hidden;
  /* Stacking (2026-10-16): the nav was position: sticky with z-index: auto, so every positioned or transformed
     element in the page content painted over it in DOM order, while plain ones went behind it, and text showed through
     in places. An explicit layer fixes the order: content < nav (90) < header/ticker group (100) < panels, modals. */
  z-index: 90;
}

/* Each button is flush against the sidebar's left wall (no left border/
   radius, so it reads as attached to it) and clipped into a flag/arrow
   shape that comes to a point on the right -- "coming out from the side,
   pointed at the right edge." Sits on --bg3 against the sidebar's own
   --bg2 so it visibly protrudes even before any hover/active state. */
.tab-btn {
  position: relative;
  width: 172px;
  padding: 11px 30px 11px 22px;
  background: var(--bg3);
  border: 1px solid var(--border);
  border-left: none;
  color: var(--text3);
  font-size: 14px;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
  clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 50%, calc(100% - 16px) 100%, 0 100%);
  transition: width 0.2s ease, background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
}

/* Hover "pronounces" the button -- it visibly grows further out of the
   sidebar (not just recolors) and picks up a glow pointing the same
   direction it's extending, like it's sliding out to greet you. */
.tab-btn:hover {
  width: 196px;
  background: linear-gradient(90deg, var(--bg3), rgba(32,48,110,0.20));
  color: var(--text);
  box-shadow: 6px 0 28px rgba(32,48,110,0.15);
}

.tab-btn.active {
  width: 196px;
  background: linear-gradient(135deg, rgba(32,48,110,0.22), rgba(31,138,82,0.10));
  color: var(--primary);
  box-shadow: inset 3px 0 0 var(--primary), 6px 0 22px rgba(32,48,110,0.12);
}

.side-nav-label {
  pointer-events: none;
}

.tab-content {
  display: none;
}

.tab-content.active {
  display: block;
}

/* Main Content */
.app-main {
  flex: 1;
  min-width: 0; /* lets .container's own max-width govern it instead of the flex item refusing to shrink */
}

/* Mobile app shell (2026-09-09, real bug: the app was untested on mobile
   at all -- Bernard dropped the live link in his league chat, opened it
   himself, and the fixed 172px vertical .side-nav alone ate close to
   half a phone's screen width permanently, with no @media rule anywhere
   touching .side-nav/.header/.account-widget-inline. This is the actual
   fix, not a cosmetic tweak: below this width the side nav becomes a
   horizontal scrollable strip under the header instead of a permanent
   left column, and the header's user-info row (avatar + username +
   Phase-2 account widget once logged in) wraps instead of overflowing.
   Bernard's explicit choice of the three options offered: horizontal
   scrollable strip over a hamburger drawer or a bottom tab bar --
   fastest to ship, lowest risk, closest to the existing design. */
@media (max-width: 780px) {
  .app-body {
    flex-direction: column;
    /* THE REAL ROOT CAUSE of the whole-page horizontal scroll Bernard's
       screenshots showed -- found by reproducing his exact Trade Finder
       content in a headless browser at a real phone width and measuring
       which element actually forced document.documentElement.scrollWidth
       past the viewport. .app-body's base rule (align-items: flex-start,
       written for the original desktop ROW layout, where cross-axis =
       height) never got revisited when this mobile block switched
       .app-body to a COLUMN layout, where cross-axis = WIDTH. With
       flex-start instead of stretch, .app-main (and .side-nav) stopped
       filling 100% of the screen width and instead sized themselves to
       their WIDEST content's max-content width -- in this case the
       League Need/Surplus matrix table -- dragging the entire page wide
       along with it. .finder-grid-wrap's own overflow-x: auto was
       already correct and did nothing wrong; it simply never got the
       chance to contain anything, because its ANCESTOR had already grown
       to match the table before the wrapper's own scroll logic ran.
       This is why 612c920 (.trade-player-row) and 562e717 (.side-nav
       grid tracks) didn't fix it -- neither was the actual cause. */
    align-items: stretch;
  }
  /* 2026-09-09 follow-up, Bernard: "nobody likes to side scroll on
     mobile" -- the horizontal-scroll strip technically worked but still
     asked for a sideways swipe just to see the rest of the tabs. Replaced
     with a real 4-column grid (Bernard's own "stack the nav buttons four
     by four") -- 8 tabs become two full rows, everything visible with no
     scrolling in either direction. */
  .side-nav {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    width: 100%;
    height: auto;
    position: sticky;
    top: var(--sidenav-top, 64px);
    overflow: visible;
    border-right: none;
    border-bottom: 1px solid var(--border);
    padding: 10px;
    gap: 8px;
  }
  .tab-btn {
    width: auto;
    min-width: 0;
    overflow-wrap: break-word;
    white-space: normal;
    text-align: center;
    line-height: 1.25;
    font-size: 12px;
    clip-path: none;
    border-radius: 8px;
    border-left: 1px solid var(--border);
    padding: 8px 4px;
    transition: background 0.2s ease, color 0.2s ease;
  }
  .tab-btn:hover {
    width: auto;
    box-shadow: none;
  }
  .tab-btn.active {
    width: auto;
    box-shadow: inset 0 -3px 0 var(--primary);
  }

  /* Header: avatar + username + (once logged in) the Phase-2 account
     widget's whole pill -- email, Sleeper-link status, Link Sleeper
     button, Log Out button -- all landed in one unwrapped flex row with
     no mobile handling at all. Let it wrap onto a second line instead of
     clipping/overflowing off the right edge of the screen. */
  .header {
    flex-wrap: wrap;
    row-gap: 8px;
    padding: 12px 14px;
  }
  .user-info {
    flex-wrap: wrap;
    row-gap: 6px;
  }
  .account-pill {
    flex-wrap: wrap;
    row-gap: 6px;
  }
  .account-pill .account-email {
    max-width: 110px;
  }
}

.container {
  max-width: 960px;
  margin: 0 auto;
  padding: 20px;
}

/* Recommendations Card */
.recs-card {
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 20px;
  margin-bottom: 20px;
}

.recs-title {
  font-size: 18px;
  font-weight: 700;
  margin-bottom: 16px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.rec-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px;
  background: var(--bg3);
  border-radius: 10px;
  margin-bottom: 8px;
  border-left: 3px solid;
}

.rec-item.start { border-color: var(--green); }
.rec-item.sit { border-color: var(--amber); }
.rec-item.add { border-color: var(--primary); }

.rec-icon {
  font-size: 24px;
  width: 32px;
  text-align: center;
}

.rec-content {
  flex: 1;
  min-width: 0;
}

.rec-action {
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 3px;
}

.rec-item.start .rec-action { color: var(--green); }
.rec-item.sit .rec-action { color: var(--amber); }
.rec-item.add .rec-action { color: var(--primary); }

.rec-player {
  font-size: 15px;
  font-weight: 700;
  margin-bottom: 2px;
}

.rec-detail {
  font-size: 12px;
  color: var(--text2);
}

/* Player Cards */
.section-title {
  font-size: 14px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text3);
  margin: 24px 0 12px;
}

.player-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px;
  background: var(--bg2);
  border: 2px solid var(--border);
  border-radius: 10px;
  margin-bottom: 8px;
  transition: all 0.2s;
  cursor: pointer;
}

.player-card:hover {
  border-color: var(--primary);
  transform: translateX(2px);
}

.player-card.tier-green {
  border-left: 4px solid var(--green);
  background: linear-gradient(90deg, rgba(31,138,82,0.08) 0%, var(--bg2) 20%);
}

.player-card.tier-yellow {
  border-left: 4px solid var(--amber);
  background: linear-gradient(90deg, rgba(179,114,10,0.08) 0%, var(--bg2) 20%);
}

.player-card.tier-red {
  border-left: 4px solid var(--red);
  background: linear-gradient(90deg, rgba(194,58,79,0.08) 0%, var(--bg2) 20%);
}

.pos {
  width: 36px;
  height: 36px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 800;
  flex-shrink: 0;
}

.pos.QB { background: rgba(139,63,209,0.2); color: #8b3fd1; }
.pos.RB { background: rgba(47,158,92,0.18); color: #2f9e5c; }
.pos.WR { background: rgba(47,111,214,0.18); color: #2f6fd6; }
.pos.TE { background: rgba(201,106,26,0.2); color: #c96a1a; }
.pos.PICK { background: rgba(91,102,114,0.18); color: #5b6672; }
.pos.PG, .pos.SG, .pos.SF, .pos.PF, .pos.C {
  background: rgba(179,114,10,0.2);
  color: var(--amber);
}

.player-info {
  flex: 1;
  min-width: 0;
}

.player-name {
  font-size: 15px;
  font-weight: 700;
  margin-bottom: 2px;
}

/* Opens the player card from any list where the row itself is already
   doing something else on click (adding to a trade, etc.) -- 2026-09-04,
   Bernard: 'anywhere there is a player name, I should be able to click
   on that name to see the card.' Callers stop propagation on their own
   onclick so this doesn't also trigger the row's action. */
.player-name-link {
  cursor: pointer;
}

.player-name-link:hover {
  color: var(--text);
  text-decoration: underline;
}

.player-meta {
  font-size: 12px;
  color: var(--text2);
}

.player-proj {
  font-size: 20px;
  font-weight: 900;
  color: var(--primary);
  margin-right: 8px;
}

.badge {
  padding: 4px 8px;
  border-radius: 6px;
  font-size: 10px;
  font-weight: 800;
  text-transform: uppercase;
}

.badge.strong-start {
  background: rgba(31,138,82,0.2);
  color: var(--green);
}

.badge.start {
  background: rgba(155,109,223,0.2);
  color: var(--primary);
}

.badge.stream {
  background: rgba(179,114,10,0.2);
  color: var(--amber);
}

.badge.sit {
  background: rgba(194,58,79,0.2);
  color: var(--red);
}

/* Roster/Waiver Table (2026-07-29) -- replaces the old stacked .player-card
   layout, which printed getContext()'s full narrative paragraph inline for
   every single player. That reads fine for one player, but a 15-player
   roster or a 50-player waiver list becomes a wall of text with no way to
   scan it -- exactly the gap Bernard flagged after comparing against
   KeepTradeCut/FantasyCalc, both of which use a dense table (rank, name,
   one or two numbers) and push analysis into a hover/detail view instead of
   printing it per row. The full narrative now lives in the player detail
   panel (see the new "Analysis" section in renderPlayerPanelContent) --
   this table only shows a short status chip, not the whole sentence. */
.roster-table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 8px;
}

.roster-table thead th {
  text-align: left;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text3);
  padding: 0 10px 8px;
  border-bottom: 1px solid var(--border);
}

.roster-table thead th.num {
  text-align: right;
}

.roster-row {
  cursor: pointer;
  transition: background 0.15s;
  border-bottom: 1px solid var(--border);
}

.roster-row:hover {
  background: var(--bg3);
}

.roster-row td {
  padding: 10px;
  vertical-align: middle;
}

.roster-row td:first-child {
  border-left: 3px solid transparent;
}

.roster-row.tier-green td:first-child { border-left-color: var(--green); }
.roster-row.tier-yellow td:first-child { border-left-color: var(--amber); }
.roster-row.tier-red td:first-child { border-left-color: var(--red); }

.roster-row .pos {
  width: 32px;
  height: 32px;
  font-size: 10px;
}

.roster-row .player-name {
  font-size: 14px;
  margin-bottom: 1px;
}

.roster-row .player-meta {
  font-size: 11px;
}

.roster-table td.num {
  text-align: right;
  white-space: nowrap;
}

.roster-table td.proj-cell {
  font-size: 17px;
  font-weight: 900;
  color: var(--primary);
}

.status-chip {
  display: inline-block;
  font-size: 10px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  padding: 3px 7px;
  border-radius: 5px;
  white-space: nowrap;
}

.status-chip.alert {
  background: rgba(194,58,79,0.18);
  color: var(--red);
}

.status-chip.caution {
  background: rgba(179,114,10,0.18);
  color: var(--amber);
}

.status-chip.flag {
  background: var(--bg3);
  border: 1px solid var(--border);
  color: var(--text2);
}

.status-chip.trend-up {
  background: rgba(31,138,82,0.14);
  color: var(--green);
  margin-left: 4px;
}

.status-chip.trend-down {
  background: rgba(179,114,10,0.14);
  color: var(--amber);
  margin-left: 4px;
}

.player-name .status-chip {
  vertical-align: middle;
  margin-left: 6px;
}

.roster-table .dyn-cell {
  font-size: 12px;
  font-weight: 700;
}

.roster-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* League-at-a-glance ticker strip + per-row trend/delta columns
   (2026-09-03) -- brokerage-style pass on the My Team roster view.
   Ticker tiles reuse the same flat bg2/border box as everywhere else in
   the app (no glow/gradient on the tiles themselves, matching the design
   canvas Bernard reviewed). Roster Value and Injury Watch are real corpus
   aggregates computed synchronously from State.corpus; Biggest Riser/
   Faller and the per-row trend sparkline both come from real per-player
   positional-rank history (see fetchRankTrend()), fetched in parallel
   once per My Team render -- same pos_rank_ppr/pos_rank_half_ppr/
   pos_rank_std data buildRankTrajectorySection() already charts in the
   player panel, just reused here at roster-list scale. There is
   deliberately no "dynasty score" trend/sparkline anywhere yet -- no
   pipeline exists that snapshots a player's dynasty score week over week,
   so a score-based trend would have to be fabricated. Positional rank
   trend is the real substitute. */
.ticker-strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  margin-bottom: 20px;
}

@media (max-width: 640px) {
  .ticker-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.ticker-tile {
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
}

.ticker-label {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text3);
  margin-bottom: 8px;
}

.ticker-value {
  font-size: 22px;
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  margin-bottom: 4px;
}

.ticker-sub {
  font-size: 12px;
  color: var(--text2);
}

.delta-chip {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 12px;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 6px;
}

.delta-chip.up { color: var(--green); background: rgba(31,138,82,0.12); }
.delta-chip.down { color: var(--red); background: rgba(194,58,79,0.12); }
.delta-chip.flat { color: var(--text2); background: rgba(122,129,135,0.12); }

.roster-table td.trend-cell { white-space: nowrap; }

/* Depth Charts (2026-08-24) -- team-picker grid + per-position tables,
   reusing .roster-table for the tables themselves so it matches every
   other list in the app. */
.depth-team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(46px, 1fr));
  gap: 6px;
  margin-bottom: 8px;
}

.depth-team-btn {
  /* Override the global `button { width: 100% }` rule (see base button
     style above) -- without this every team button stretched to fill its
     flex row, which put all 32 teams into one column and forced a long
     scroll to reach any team past the first few. A CSS grid of small
     square tiles (below) needed this fixed regardless. */
  width: auto;
  aspect-ratio: 1 / 1;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text2);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.3px;
  cursor: pointer;
}

.depth-team-btn:hover {
  color: var(--text);
  border-color: var(--text3);
}

.depth-team-btn.active {
  background: rgba(32,48,110,0.15);
  border-color: var(--primary);
  color: var(--primary);
}

.depth-slot {
  width: 36px;
  height: 28px;
  border-radius: 6px;
  background: var(--bg3);
  color: var(--text2);
  font-size: 11px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Depth Charts -- vertical stack visual (2026-09-03), replacing the old
   flat per-position table with a real depth-chart look: one column per
   position, players stacked top (starter) to bottom (deepest bench),
   connected by a vertical ladder line with a marker dot per player. */
.depth-chart-columns {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 4px;
}

.depth-chart-col {
  flex: 1 1 200px;
  min-width: 180px;
}

.depth-chart-stack {
  position: relative;
  padding-left: 20px;
}

.depth-chart-stack::before {
  content: '';
  position: absolute;
  left: 8px;
  top: 16px;
  bottom: 16px;
  width: 2px;
  background: var(--border);
}

.depth-chart-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 8px;
  cursor: pointer;
}

.depth-chart-card:hover {
  border-color: var(--text3);
  background: var(--bg3);
}

.depth-chart-card::before {
  content: '';
  position: absolute;
  left: -16px;
  top: 50%;
  transform: translateY(-50%);
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--bg);
  border: 2px solid var(--text3);
}

.depth-chart-card.starter::before {
  border-color: var(--primary);
  background: var(--primary);
}

.depth-chart-card.unavailable::before {
  border-color: var(--red);
  background: var(--red);
}

.depth-chart-card.unavailable {
  opacity: 0.6;
}

.depth-chart-card-slot {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.3px;
  color: var(--text3);
  min-width: 34px;
}

.depth-chart-card.unavailable .depth-chart-card-slot {
  color: var(--red);
}

.depth-chart-card-body {
  flex: 1;
  min-width: 0;
}

.depth-chart-card-name {
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.depth-chart-card-score {
  font-size: 12px;
  font-weight: 700;
  margin-top: 2px;
}

@media (max-width: 640px) {
  /* 2026-09-09, Bernard's ask: My Team/Waiver Wire were still too wide on
     mobile even after the panel/nav fixes -- drop the Status column (3rd
     th/td in both tables: pos icon, Player, Status, [Trend, Delta Rank on
     My Team only], Dyn/Keep/Red, Proj) to free up real width for the
     columns that actually matter for a quick scan. Status chips (OUT/IR/
     Injury Risk/etc.) are still visible in the full player detail panel
     one tap away -- this only drops the compact roster-row summary badge.
     min-width dropped from 480 to 360 now that one column is gone -- was
     tuned assuming Status stayed, forcing an unnecessary horizontal
     scroll on most real phone widths (~375-430px) even for the columns
     that DID fit. */
  .roster-table { min-width: 320px; }
  /* Real bug caught live 2026-09-17 (Bernard, on mobile): this rule used
     to be .roster-table (every table using this component), which was
     correct back when Status/Trend was always column 3 everywhere it was
     used. It stopped being true the moment Waiver Wire, Start/Sit, and
     Start/Sit's own Roster Depth Chart section added a real Opp column
     as column 3 (2026-09-17) -- this then started silently hiding real
     opponent data on mobile instead of the intended Trend column, on
     three different tables it was never meant to touch. Scoped to My
     Team specifically, the only .roster-table where column 3 is still
     Trend. */
  #my-team-content .roster-table th:nth-child(3), #my-team-content .roster-table td:nth-child(3) { display: none; }
  /* 2026-09-09 follow-up, Bernard: My Team still a little too wide even
     with Status gone -- tighten cell padding, the position badge, the
     sparkline (fixed 72px SVG, the single biggest remaining column), and
     the projection number's font size. CSS width on an <svg> scales it
     via its viewBox rather than clipping, so this is a clean shrink, not
     a crop. */
  .roster-table thead th { padding: 0 4px 6px; }
  .roster-row td { padding: 6px 4px; }
  .roster-row .pos { width: 26px; height: 26px; font-size: 9px; }
  .trend-cell svg { width: 46px; height: auto; display: block; }
  .roster-table td.proj-cell { font-size: 14px; }
  .rank-tag { font-size: 0.72em; margin-left: 3px; }
  .depth-team-grid { grid-template-columns: repeat(auto-fill, minmax(38px, 1fr)); gap: 4px; }
  /* 2026-09-17, Bernard: down to ~4px of real overflow left on his own
     phone after the Matchup stack (my own headless check runs a few px
     more generous than a real device -- font rendering/rounding differs
     enough that "exactly 359px" here doesn't always mean 0px there).
     A tiny, Start/Sit-only font trim on the cells that still drive this
     table's width (name, meta, matchup tier word) rather than touching
     the shared .roster-table rule everywhere -- My Team/Waiver already
     fit and don't need to shrink too. */
  #start-sit-content .player-name { font-size: 13px; }
  #start-sit-content .player-meta { font-size: 10px; }
  #start-sit-content .matchup-tag .matchup-rank { font-size: 10px; }
  #start-sit-content .matchup-tag .matchup-tier { font-size: 8px; }
  #start-sit-content .startsit-opp-cell { font-size: 11px; }
  #start-sit-content .opp-ou-line { font-size: 10px; }

  /* Depth Charts, 2026-09-09 follow-up: the 4 position columns (QB/RB/
     WR/TE) were a flex ROW with overflow-x: auto -- even at their
     smallest min-width (150px x 4 + gaps) that's ~650px, forcing a side
     scroll on any phone. Bernard: 'nobody likes to side scroll on
     mobile' -- stack the columns vertically instead, full-width, so the
     whole page scrolls down like everything else instead of sideways. */
  .depth-chart-columns { flex-direction: column; overflow-x: visible; gap: 20px; }
  .depth-chart-col { flex: none; min-width: 0; width: 100%; }

  /* Trade Finder, same follow-up: tighten the partner-recommendation
     cards and the surplus/need matrix table so less scrolling (ideally
     none) is needed to read them. The matrix keeps its own
     .finder-grid-wrap horizontal scroll as a fallback for real narrow
     phones -- a genuine data matrix (one row per team) doesn't have
     anywhere else to put its columns the way the nav/roster did. */

  /* 2026-09-09 follow-up, Bernard: the matrix still needed its own
     side-scroll to read even after the page-level fix. Root cause this
     time: a plain HTML table defaults to table-layout: auto, which sizes
     each column to fit its content regardless of max-width/overflow
     rules on individual cells -- confirmed live, the earlier max-width:
     74px on the team-name column had zero effect; the browser just grew
     that column to fit "A-Rod's Ayahuasca Retreat" anyway (measured
     124px, not 74px). table-layout: fixed forces the browser to respect
     explicit column widths instead of content, so the table is always
     exactly 100% of its wrapper -- no scroll needed -- and the ellipsis
     rule on the team-name column finally actually clips, since the cell
     itself is now a real fixed size instead of a suggestion. */
  /* !important needed here: the base (non-mobile) .finder-grid rule
     further down the file sets width: 100%/min-width: 480px, and since
     it's later in source order it otherwise wins this cascade tie
     regardless of being outside the media query -- confirmed live, the
     first version of this fix (no !important) left the table at exactly
     480px, ignoring table-layout: fixed entirely. */
  .finder-grid { table-layout: fixed; width: 100% !important; min-width: 0 !important; }
  .finder-grid th, .finder-grid td { padding: 5px 3px; font-size: 10px; }
  .finder-grid th { font-size: 9px; }
  .finder-grid th:first-child, .finder-grid td:first-child { width: 30%; }
  .finder-grid th:nth-child(2), .finder-grid td:nth-child(2),
  .finder-grid th:nth-child(3), .finder-grid td:nth-child(3),
  .finder-grid th:nth-child(4), .finder-grid td:nth-child(4),
  .finder-grid th:nth-child(5), .finder-grid td:nth-child(5) { width: 12%; }
  .finder-grid th:last-child, .finder-grid td:last-child { width: 22%; }
  .finder-grid td:first-child, .finder-grid th:first-child {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

/* News Feed (2026-08-26, renamed from "Trending" same day -- see the real
   add/drop Trending tab further down. This is the "what changed" pipeline-
   events feed, one card per changelog entry. */

/* News buckets (2026-09-21) -- reuses .position-filters/.position-filter
   (same toggle-row pattern as the Waiver Wire position filter) so the tab
   row style matches the rest of the app; the count badge is the only
   addition needed on top of that. */
.news-bucket-count {
  display: inline-block;
  margin-left: 4px;
  padding: 1px 6px;
  border-radius: 10px;
  background: rgba(255,255,255,0.15);
  font-size: 11px;
  font-weight: 800;
}
.position-filter:not(.active) .news-bucket-count {
  background: var(--bg1);
  color: var(--text3);
}

.news-feed {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.news-item {
  background: var(--bg2);
  border: 1px solid var(--border);
  border-left: 4px solid var(--text3);
  border-radius: 10px;
  padding: 12px 14px;
  cursor: pointer;
}

.news-item:hover {
  border-color: var(--text3);
  background: var(--bg3);
}

.news-item.event-injury { border-left-color: var(--red); }
.news-item.event-flag { border-left-color: var(--amber); }
.news-item.event-retirement { border-left-color: var(--text3); }
.news-item.event-volume { border-left-color: var(--primary); }
.news-item.event-depth { border-left-color: var(--green); }

.news-item-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 4px;
}

.news-player-name {
  font-weight: 800;
  color: var(--text);
}

.news-player-meta {
  font-size: 11px;
  color: var(--text3);
  font-weight: 600;
}

.news-timestamp {
  font-size: 11px;
  color: var(--text3);
  white-space: nowrap;
}

.news-event-label {
  font-size: 12px;
  font-weight: 700;
  margin-bottom: 4px;
}

.news-item.event-injury .news-event-label { color: var(--red); }
.news-item.event-flag .news-event-label { color: var(--amber); }
.news-item.event-retirement .news-event-label { color: var(--text2); }
.news-item.event-volume .news-event-label { color: var(--primary); }
.news-item.event-depth .news-event-label { color: var(--green); }

.news-reason {
  font-size: 13px;
  color: var(--text2);
  line-height: 1.4;
}

/* Trending (2026-08-26) -- REAL Sleeper-style trending: platform-wide
   add/drop counts over the last 24-48h, from Sleeper's own free public
   endpoint (api.sleeper.app/v1/players/nfl/trending/add|drop). Fetched
   directly client-side, same as the rest of this app's Sleeper calls --
   no new backend work, and player_id here is already Sleeper's own ID
   space, matching State.corpus/State.players directly with no mapping. */
.trend-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

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

.trend-side-title {
  font-size: 13px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text2);
  margin-bottom: 10px;
}

.trend-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}

.trend-row:hover { background: var(--bg3); }
.trend-row:last-child { border-bottom: none; }

.trend-rank {
  width: 20px;
  color: var(--text3);
  font-size: 11px;
  font-weight: 700;
}

.trend-count {
  font-size: 12px;
  font-weight: 800;
}

.trend-count.add { color: var(--green); }
.trend-count.drop { color: var(--red); }

/* Trade Finder (2026-08-26) -- see the JS section comment above
   renderTradeFinder() for the full rationale. */


@media (max-width: 640px) {
}

.finder-load-btn {
  margin-top: 12px;
  padding: 8px 14px;
  background: rgba(139,63,209,0.15);
  border: 1px solid var(--primary);
  border-radius: 8px;
  color: var(--primary);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}

.finder-load-btn:hover { background: rgba(139,63,209,0.25); }

.finder-grid-wrap { overflow-x: auto; margin-top: 8px; }

.finder-grid {
  border-collapse: collapse;
  width: 100%;
  min-width: 480px;
}

.finder-grid th, .finder-grid td {
  padding: 8px 10px;
  text-align: center;
  font-size: 12px;
  border-bottom: 1px solid var(--border);
}

.finder-grid th {
  color: var(--text3);
  font-weight: 700;
  text-transform: uppercase;
  font-size: 10px;
}

.finder-grid td:first-child, .finder-grid th:first-child {
  text-align: left;
  font-weight: 700;
  color: var(--text);
}

.finder-cell.surplus { color: var(--green); font-weight: 800; }
.finder-cell.need { color: var(--red); font-weight: 800; }
.finder-cell.balanced { color: var(--text3); }

.finder-cell-mine { text-decoration: underline; text-decoration-color: var(--primary); }

/* Team roster viewer (2026-09-21) -- team name cells in the League
   Need/Surplus grid are clickable, opening that team's roster via
   showTeamRosterModal() in the shared player-panel overlay. */
.finder-cell-team { cursor: pointer; transition: color 0.15s; }
.finder-cell-team:hover { color: var(--primary); text-decoration: underline; text-decoration-color: var(--primary); }

/* Compare (2026-08-27) -- see the JS section comment above renderCompare()
   for the full rationale. Search boxes reuse .trade-search-results/.trade-
   search-row directly (same dropdown pattern as Trade Tool), just a new
   two-column picker shell around them. */
.compare-picker {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
  margin-bottom: 20px;
}

.compare-search-box { position: relative; }

.compare-vs {
  font-size: 13px;
  font-weight: 800;
  color: var(--text3);
  text-transform: uppercase;
}

.compare-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

.compare-col {
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px;
}

.compare-col-name {
  font-size: 17px;
  font-weight: 800;
  color: var(--text);
}

.compare-col-meta {
  font-size: 12px;
  color: var(--text3);
  font-weight: 600;
  margin-bottom: 12px;
}

.compare-stat-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}

.compare-stat-row:last-of-type { border-bottom: none; }

.compare-stat-label { color: var(--text3); font-weight: 600; }
/* Positional rank tag (2026-08-29) -- small muted label riding next to a
   real score wherever one is shown ("238 RB1"), and next to the position
   badge on player cards ("RB · RB1"). See computePositionRanks/rankLabel. */
.rank-tag { color: var(--text3); font-weight: 600; font-size: 0.78em; margin-left: 5px; }
.compare-stat-value { color: var(--text); font-weight: 700; text-align: right; }
.compare-stat-value.better { color: var(--green); }

.compare-status-line {
  font-size: 13px;
  color: var(--text2);
  line-height: 1.4;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}

.compare-section-label {
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text3);
  margin: 14px 0 6px;
}

.compare-trajectory-item {
  font-size: 12px;
  color: var(--text2);
  padding: 6px 0;
  border-bottom: 1px solid var(--border);
}

.compare-trajectory-item:last-child { border-bottom: none; }
.compare-trajectory-date { color: var(--text3); font-weight: 600; margin-right: 6px; }
.compare-empty { font-size: 12px; color: var(--text3); font-style: italic; padding: 6px 0; }

/* Player-panel entry point (2026-08-27) -- Bernard's real-world correction
   after trying the standalone Compare tab: no draft/mock-draft flow exists
   in the app to use it from, so the real entry point is "I'm already
   looking at a player card, show me who else is worth a look." Reuses
   .trade-search-row for each suggestion row (see showCompareSuggestions()). */
.panel-compare-list { display: none; margin-top: 10px; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.panel-compare-list.visible { display: block; }

/* Trade Tool (2026-07-29) -- see the JS section comment above renderTradeTool()
   for the full rationale. */
.trade-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-bottom: 16px;
}

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

.trade-side {
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px;
}

.trade-side-title {
  font-size: 13px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text2);
  margin-bottom: 10px;
}

.trade-side input[type="text"] {
  margin-bottom: 0;
}

.trade-search-results {
  display: none;
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 4px);
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 8px;
  z-index: 20;
  max-height: 260px;
  overflow-y: auto;
  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}

.trade-search-results.visible { display: block; }

.trade-search-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  font-size: 13px;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
}

.trade-search-row:last-child { border-bottom: none; }
.trade-search-row:hover { background: var(--bg2); }

.trade-player-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
}

.trade-player-row:last-child { border-bottom: none; }

/* 2026-09-09: real flexbox overflow, not a missing scroll wrapper -- a
   flex child's default min-width is auto (its own content width), not 0,
   so a long player name ("Christian McCaffrey (RB)") refused to shrink
   and forced this row -- and the page around it -- wider than the
   screen. This is what was still making Trade Finder (and Trade Tool,
   which reuses this exact row) "a little too wide" on mobile even after
   .finder-package itself was already collapsed to one column. Truncating
   the name with an ellipsis instead of letting it dictate the row's
   width fixes the real cause. */
.trade-player-row .player-name-link {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.trade-remove {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--bg3);
  color: var(--text2);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 16px;
  flex-shrink: 0;
}

.trade-remove:hover {
  background: rgba(194,58,79,0.2);
  color: var(--red);
}

.trade-totals {
  display: flex;
  gap: 24px;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px 20px;
  margin-bottom: 12px;
}

.trade-total-label {
  display: block;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text3);
  margin-bottom: 4px;
}

.trade-total-value {
  font-size: 24px;
  font-weight: 900;
  color: var(--primary);
}

.trade-verdict {
  font-size: 14px;
  font-weight: 700;
  padding: 4px 0 16px;
}

.roster-picker-list {
  max-height: 340px;
  overflow-y: auto;
}

/* Loading */
.loading {
  text-align: center;
  padding: 60px 20px;
  color: var(--text2);
}

.spinner {
  width: 40px;
  height: 40px;
  border: 3px solid var(--border);
  border-top-color: var(--primary);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  margin: 0 auto 16px;
}

@keyframes spin { to { transform: rotate(360deg); } }

.empty {
  text-align: center;
  padding: 40px 20px;
  color: var(--text3);
  font-size: 14px;
}

/* Waiver Wire */
.position-filters {
  display: flex;
  gap: 8px;
  padding: 16px 0;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.position-filter {
  /* Override the global `button { width: 100% }` rule (see base button
     style above) -- without this every filter button stretches to fill
     the whole flex row, which is what was forcing ALL/QB/RB/WR/TE onto a
     horizontal scrollbar instead of sitting compactly side by side. */
  width: auto;
  padding: 8px 16px;
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text2);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.2s;
  flex-shrink: 0;
}

.position-filter:hover {
  border-color: var(--primary);
  color: var(--text);
}

.position-filter.active {
  background: var(--primary);
  border-color: var(--primary);
  color: white;
}

.roster-table th.sortable {
  cursor: pointer;
  user-select: none;
}

.roster-table th.sortable:hover {
  color: var(--primary);
}

/* Player Detail Panel */
.player-panel {
  position: fixed;
  top: 0;
  right: -100%;
  width: 100%;
  max-width: 720px;
  height: 100vh;
  background: var(--bg);
  box-shadow: -4px 0 24px rgba(0,0,0,0.18);
  z-index: 200;
  overflow-y: auto;
  transition: right 0.3s ease;
}

.player-panel.open {
  right: 0;
}

/* Compare-from-panel, "two windows side by side" (2026-08-27, round 3) --
   Bernard's explicit correction to the condensed .compare-col version:
   he wants the SAME rich single-player card (bio cells, injury alert,
   quick stats, flags, Analysis narrative) mirrored twice, not a
   summarized stat sheet. That means the panel itself needs to widen to
   fit two full cards -- see .compare-mirror-columns below for the layout,
   and renderComparePanel()/buildMirroredCardHtml() for the markup reuse. */
.player-panel.compare-mode {
  max-width: 1320px;
}

.compare-mirror-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

.compare-mirror-columns > .compare-mirror-col {
  border-right: 1px solid var(--border);
}

.compare-mirror-columns > .compare-mirror-col:last-child {
  border-right: none;
}

.panel-header {
  position: relative;
  padding: 28px 20px 16px;
  background: linear-gradient(135deg, var(--bg2) 0%, var(--bg3) 100%);
  border-bottom: 1px solid var(--border);
}

.panel-bio-line {
  display: flex;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

.panel-bio-cell {
  flex: 1;
  padding: 0 10px;
  border-left: 1px solid var(--border);
}

.panel-bio-cell:first-child {
  padding-left: 0;
  border-left: none;
}

.panel-bio-label {
  font-size: 10px;
  color: var(--text3);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 3px;
}

.panel-bio-value {
  font-size: 15px;
  font-weight: 900;
  color: var(--text);
}

.panel-close {
  position: absolute;
  top: 16px;
  right: 16px;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(0,0,0,0.3);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--text);
  font-size: 20px;
  transition: all 0.2s;
}

.panel-close:hover {
  background: rgba(0,0,0,0.5);
  transform: scale(1.1);
}

.panel-name {
  font-size: 19px;
  font-weight: 900;
  margin-bottom: 2px;
}

.panel-meta {
  display: flex;
  gap: 10px;
  font-size: 13px;
  color: var(--text2);
  flex-wrap: wrap;
  align-items: center;
}

/* League ownership pill (2026-09-21) -- shows who owns this player in the
   currently loaded league, right in the player panel header instead of
   only being discoverable via the Trade Tool's roster pickers. Three
   states: "Your Team" (green, stands out since it's the actionable one --
   you already have this player), another team's real name (neutral), or
   "Free Agent" (dashed border, deliberately quieter -- absence of
   ownership, not a claim). */
.ownership-pill {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  padding: 3px 9px;
  border-radius: 20px;
  border: 1px solid var(--border);
}

.ownership-pill.mine {
  color: var(--green);
  background: rgba(31,138,82,0.12);
  border-color: var(--green);
}

.ownership-pill.other {
  color: var(--text2);
  background: var(--bg3);
}

.ownership-pill.free-agent {
  color: var(--text3);
  background: transparent;
  border-style: dashed;
}


.panel-flags-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 12px;
}

.panel-flag-pill {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  color: var(--text2);
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 4px 10px;
}

.panel-section {
  padding: 20px;
  border-bottom: 1px solid var(--border);
  /* 2026-09-09: game-log/season-totals/injury-history tables inside here
     have no width limit of their own (position-specific stat lines run
     8-10 columns wide for QB/RB). On mobile, with the panel correctly
     capped to 100% viewport width, an unconstrained table would blow the
     whole section out sideways instead of just scrolling itself. Harmless
     on desktop -- these tables never actually overflow a 720px panel. */
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.panel-section-title {
  font-size: 14px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text3);
  margin-bottom: 12px;
}

/* Season tabs (2026-09-09, Bernard's ask ahead of week 1) -- once real
   current-season game logs exist, Season Totals / Weekly Points / Game Log
   would otherwise hard-cut to the new season with no way back to last
   season's data. These small pills let the viewer flip between whichever
   seasons fetchGameLogs() found real data for (most recent first), and
   govern all three sections together since they're the same underlying
   per-season data just presented three ways. */
.panel-section-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}
.panel-section-header-row .panel-section-title { margin-bottom: 0; }
.season-tabs {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}
.season-tab {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.02em;
  padding: 4px 10px;
  border-radius: 6px;
  cursor: pointer;
  background: var(--bg3);
  color: var(--text3);
  border: 1px solid var(--border);
  transition: background 0.15s ease, color 0.15s ease;
}
.season-tab:hover { color: var(--text); }
.season-tab.active {
  background: rgba(32,48,110,0.2);
  color: var(--primary);
  border-color: transparent;
}

.game-log-table {
  /* 2026-09-17, Bernard: stats table/narrative box still read too wide
     even next to the now-tightened, centered donut cluster above -- a
     4px inset wasn't enough to register visually. Matching the amount
     of breathing room the donut cluster gets on each side. */
  width: calc(100% - 32px);
  margin: 0 16px;
  border-collapse: collapse;
}

.game-log-table th {
  background: var(--bg3);
  padding: 8px;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  color: var(--text3);
  text-align: left;
}

.game-log-table td {
  padding: 10px 8px;
  font-size: 13px;
  border-bottom: 1px solid var(--border);
  /* Default wrap for any cell that doesn't opt into white-space:nowrap
     inline (Season Totals/Game Log's own narrow columns do, via their
     template literals) -- the Injury History table's "Injury" column
     (e.g. "Fractured Clavicle / AC Joint Sprain") has no such override
     and was overflowing once the table's own side margin tightened,
     2026-09-17. */
  word-break: break-word;
}

.game-log-table tr:hover {
  background: var(--bg3);
}

/* Rank trajectory chart (2026-08-31) -- week-by-week positional rank for
   the player's most recent season, plotted like a price chart: rank 1 is
   drawn at the TOP of the SVG so an upward line reads as "getting
   better," matching how a stock chart reads. Built from the real
   pos_rank_ppr/pos_rank_half_ppr/pos_rank_std values already present on
   each weekly game-log entry (backend/server.js's getGameLog, sourced
   from Sleeper's own weekly stats endpoint -- see scripts/update-weekly-
   stats.js) -- no new pipeline work, this data already existed and was
   simply never charted. Renders nothing (see buildRankTrajectorySection)
   when fewer than 2 real ranked weeks exist for the current season, e.g.
   early in a fresh season before enough games have been played. */
.rank-chart-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 8px;
}

.rank-chart-current {
  font-size: 20px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.rank-chart-current .unit {
  color: var(--text3);
  font-size: 12px;
  font-weight: 600;
  margin-left: 6px;
  text-transform: none;
  letter-spacing: 0;
}

.rank-chart-delta {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 13px;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 6px;
}

.rank-chart-delta.up { color: var(--green); background: rgba(31,138,82,0.12); }
.rank-chart-delta.down { color: var(--red); background: rgba(194,58,79,0.12); }
.rank-chart-delta.flat { color: var(--text2); background: rgba(122,129,135,0.12); }

.rank-chart-foot {
  display: flex;
  justify-content: space-between;
  color: var(--text3);
  font-size: 10.5px;
  margin-top: 4px;
  padding: 0 2px;
}

/* Legend for the dual-ring Snap %/Target % donuts (2026-09-17) -- the
   two rings' colors no longer distinguish the 3 windows (each window is
   still labeled by its own card-label below the donut, unchanged); here
   color distinguishes the two SIGNALS instead, so this legend is what
   tells a first-time viewer which ring is which before they hover for
   the tooltip. */
.snap-share-legend {
  display: flex;
  gap: 14px;
  font-size: 11px;
  color: var(--text2);
  margin-bottom: 10px;
}

.snap-share-legend .legend-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 5px;
  vertical-align: middle;
}

.snap-share-row {
  display: flex;
  align-items: flex-start;
  /* 2026-09-17, Bernard: "plenty of room between donuts" -- space-between
     was stretching all 3 cards to the row's full width, so on a panel
     much wider than 3x96px SVGs the leftover space all became inter-card
     gaps. flex-start + a real gap keeps them clustered together instead,
     which also reads more consistent with the stats table/narrative box
     right below (see .game-log-table's own matching inset). */
  justify-content: center;
  gap: 20px;
  flex-wrap: wrap;
}

.snap-share-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 0 1 90px;
  min-width: 84px;
}

/* The donut SVGs (snapShareDonut/dualRingDonut in app.js) are built with
   a hard-coded width="96" height="96" attribute -- that's a couple px
   wider than .snap-share-card's own flex-basis, and since an SVG's fixed
   pixel attribute doesn't shrink just because its flex-item parent does,
   each card was quietly overflowing its own box by ~3px (confirmed via
   a headless render at 320-375px, 2026-09-17). Overriding both
   dimensions here lets the SVG scale down to whatever the card actually
   has, using its viewBox (0 0 96 96) to keep it square, and caps back to
   its original 96px on anything roomier than that.  */
.snap-share-card svg {
  width: 100%;
  height: auto;
  max-width: 96px;
}

.snap-share-card-label {
  font-size: 11px;
  font-weight: 700;
  color: var(--text2);
  margin-top: 4px;
  text-align: center;
}

.snap-share-card-sub {
  font-size: 10px;
  color: var(--text3);
  margin-top: 1px;
}

.volume-head {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text3);
  margin: 16px 0 6px;
}

.injury-alert {
  background: rgba(194,58,79,0.1);
  border: 1px solid rgba(194,58,79,0.3);
  border-radius: 8px;
  padding: 12px;
  /* 2026-09-21 fix, Bernard: this sits directly below .panel-header (a
     sibling, not a child -- panel-header's own bottom padding was the
     ONLY space between the bio row above and this box, so it visually
     pressed right up against it). Added margin-top to match the existing
     margin-bottom, giving it the same breathing room on both sides
     instead of only below. */
  margin: 16px 0;
  display: flex;
  align-items: center;
  gap: 10px;
}

.injury-alert.questionable {
  background: rgba(179,114,10,0.1);
  border-color: rgba(179,114,10,0.3);
}

.panel-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.6);
  z-index: 199;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.panel-overlay.visible {
  opacity: 1;
  pointer-events: all;
}

/* Mobile */
@media (max-width: 640px) {
  .container { padding: 8px; }
  .recs-card { padding: 16px; }
  .rec-detail { font-size: 11px; }
  .player-panel { max-width: 100%; }
  .player-panel.compare-mode { max-width: 100%; }
  .compare-mirror-columns { grid-template-columns: 1fr; }
  .panel-header { padding: 22px 16px 14px; }
  .panel-name { font-size: 17px; }
  .panel-bio-line {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px 12px;
  }
  .panel-bio-cell {
    border-left: none;
    padding: 0;
    min-width: 0;
    overflow-wrap: break-word;
  }
  .panel-section { padding: 16px; }
  .compare-mirror-columns > .compare-mirror-col { border-right: none; border-bottom: 1px solid var(--border); }
  .compare-mirror-columns > .compare-mirror-col:last-child { border-bottom: none; }
}

/* ── Account widget (Phase 2, 2026-09-08) ──────────────────────
   Supabase Auth is deliberately additive, not a gate -- the existing
   Sleeper-username landing flow above is completely untouched. This is a
   small fixed-position widget, independent of #landing/#app, so it's
   visible in both states. See fantasy-edge_scaling-decisions-and-plan.md,
   Phase 2 and Decision 3 (Sleeper becomes link-only once accounts exist,
   but there is no forced migration -- using the app without ever signing
   up keeps working exactly as it did before this widget existed). */
.account-widget {
  position: fixed;
  top: 12px;
  right: 12px;
  z-index: 1000;
  font-size: 13px;
}
/* 2026-09-09: applied once the Sleeper-logged-in app view is showing --
   the widget gets moved from being fixed over the whole page into the
   app header's own .user-info row (see switchToApp's JS below), so it
   sits in normal flex flow next to the avatar/username instead of
   floating on top of them. */
.account-widget.account-widget-inline {
  position: static;
  top: auto;
  right: auto;
  z-index: auto;
}
.account-widget button {
  background: var(--bg3);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 14px;
  cursor: pointer;
  font-size: 13px;
}
.account-widget button:hover { border-color: var(--primary); }
.account-widget button.primary { background: var(--primary); color: #fbfaf7; border-color: var(--primary); font-weight: 600; }
.account-pill {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 10px;
}
.account-pill .account-email { color: var(--text2); max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.account-pill .account-linked { color: var(--green); font-size: 12px; }
.account-pill .account-unlinked { color: var(--text3); font-size: 12px; }
.account-modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.55);
  z-index: 1001;
  align-items: center;
  justify-content: center;
}
.account-modal-overlay.open { display: flex; }
.account-modal {
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 24px;
  width: 340px;
  max-width: 90vw;
}
.account-modal-title-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; }
.account-modal-title-row h3 { margin: 0; font-size: 16px; }
.account-modal input {
  width: 100%;
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 9px 10px;
  color: var(--text);
  margin-bottom: 10px;
  font-size: 13px;
}
/* 2026-09-09: Log In / Sign Up used to be a top tab-switcher (removed --
   see the markup change) plus one bottom submit button whose label/mode
   followed whichever tab was active. Bernard's feedback: the top "Log
   In" tab was already the default view, so clicking it did nothing --
   it read as a dead, redundant button. Now both actions are real submit
   buttons side by side at the bottom instead. */
.account-modal .account-submit-row { display: flex; gap: 8px; }
.account-modal .account-submit-btn {
  flex: 1;
  background: var(--primary);
  color: #fbfaf7;
  border: none;
  border-radius: 6px;
  padding: 10px 0;
  font-weight: 600;
  cursor: pointer;
  font-size: 13px;
}
.account-modal .account-submit-btn.account-submit-btn-alt {
  background: var(--bg3);
  color: var(--text2);
  border: 1px solid var(--border);
}
.account-modal .account-submit-btn.account-submit-btn-alt:hover { border-color: var(--primary); color: var(--text); }
.account-modal .account-forgot { display: block; text-align: center; margin-top: 10px; color: var(--text3); font-size: 12px; cursor: pointer; text-decoration: underline; }
.account-modal .account-close {
  cursor: pointer; color: var(--text3); font-size: 22px; flex-shrink: 0;
  /* 2026-09-09: was a <span>, now a real <button type="button"> (see the
     form change below -- a bare span isn't keyboard-operable and isn't
     announced as a button to screen readers). Reset default button
     chrome so it still looks identical to the old span, then give it a
     real click target -- Bernard's feedback: it was tiny (18px, no
     padding) and cramped right next to "Sign Up". A second round of
     feedback moved it from absolute-positioned (which sat too low,
     misaligned with the heading text) into a real flex row alongside
     the <h3> (see .account-modal-title-row) so it's always exactly
     vertically centered against the heading, not eyeballed pixels. */
  background: none; border: none; font-family: inherit; line-height: 1;
  width: 32px; height: 32px; border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
}
.account-modal .account-close:hover { background: var(--bg3); color: var(--text); }
.account-modal .account-tos {
  display: flex; align-items: flex-start; gap: 8px; margin: 10px 0 4px;
  font-size: 12px; color: var(--text3); text-align: left; cursor: pointer;
}
.account-modal .account-tos input { width: auto; margin: 2px 0 0; }
.account-modal .account-tos a { color: var(--primary); text-decoration: underline; }
.account-modal-wrap { position: relative; }
.account-modal .account-msg { font-size: 12px; margin-bottom: 10px; min-height: 0; }
.account-modal .account-msg.error { color: var(--red); }
.account-modal .account-msg.success { color: var(--green); }

/* Start/Sit (2026-09-16) -- two .roster-table instances side by side
   (Current Lineup vs Recommended Lineup), same row-per-slot order in
   both so they line up for direct comparison. Reworked from an earlier
   card-grid layout that read as confusing/redundant. */
.startsit-banner {
  padding: 12px 16px;
  border-radius: 10px;
  font-weight: 700;
  font-size: 14px;
  margin-bottom: 20px;
}
.startsit-banner.change {
  background: rgba(179,114,10,0.12);
  color: var(--amber);
  border: 1px solid rgba(179,114,10,0.3);
}
.startsit-banner.match {
  background: rgba(31,138,82,0.10);
  color: var(--green);
  border: 1px solid rgba(31,138,82,0.25);
}
.startsit-columns {
  display: grid;
  /* minmax(0, 1fr), not bare 1fr -- this is the real bug behind Bernard's
     "player card only side-scrolls over Start/Sit" report (2026-09-17).
     A bare 1fr track still defaults to min-width:auto, i.e. it will
     expand to fit its content's min-content size -- which, for a grid
     item containing .roster-table-wrap's intentionally-wide, horizontally
     -scrollable table, defeats that wrapper's own overflow-x:auto
     clipping and pushes the real page width past the viewport (confirmed
     headless: document.body.scrollWidth > window.innerWidth on this tab
     specifically, not on My Team, which has no grid ancestor). The fixed
     -position player panel doesn't cause this itself, it just rides
     along whenever the PAGE has genuine horizontal overflow underneath
     it. minmax(0, 1fr) opts the track out of that content-based minimum,
     so the wrapper's own scroll (not the page's) is what runs wide. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 20px;
}
@media (max-width: 640px) {
  .startsit-columns {
    grid-template-columns: minmax(0, 1fr);
  }
}
/* Slot/Rank badge (2026-09-17, replaces the old dedicated .startsit-
   slot-cell/-head column) -- Bernard's own diagnosis: My Team/Waiver
   Wire never needed this table's mobile scroll fixes because their
   position indicator lives INSIDE the player cell (the small round .pos
   badge), not as its own column. No amount of shrinking a 7th column
   (tried the cell alone, then the cell + header together -- see git
   history on this file) beats just not having that column at all.
   Inline badge, same idea as .pos but text-based since slot values
   aren't single positions (FLEX, or depth-chart ranks like WR2). */
.startsit-player-cell {
  /* 2026-09-17 follow-up, Bernard: "see the difference in the way the
     position indicator and the player name look?" -- the badge above
     was still just inline TEXT at the front of .player-name, so once a
     long name wrapped to a 2nd line (Bo Nix -> "QB Bo" / "Nix"), it
     looked nothing like My Team's .pos, which is a fixed block that
     never moves regardless of how the name wraps next to it (there, .pos
     and the name+meta block are two separate flex/table items, not one
     run of text). Flex row here does the same thing without needing a
     whole extra <td>: the badge is a non-shrinking item pinned to the
     left, and the name/meta block is free to wrap in the remaining
     space on its own, never dragging the badge along with it. */
  display: flex;
  align-items: flex-start;
  gap: 8px;
}
.startsit-slot-badge {
  flex-shrink: 0;
  display: inline-flex;
  /* flex-direction: column + min-height (not a fixed height) so this
     same box handles both a normal single-line label (QB/RB/FLEX/etc,
     unchanged look) and the stacked Super/Flex lines below -- see
     slotBadgeLabel() in app.js, 2026-09-17 (Bernard: superflex's raw
     "SUPER_FLEX" text was the widest thing in the whole column, badge
     included, on any superflex league). */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 26px;
  min-height: 20px;
  box-sizing: border-box;
  font-size: 10px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  color: var(--text3);
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 2px 4px;
  margin-top: 1px;
  line-height: 1.15;
}
.startsit-slot-badge-line {
  display: block;
}
/* Color-coded by real position (2026-09-17, Bernard) -- same palette
   .pos already uses elsewhere (My Team/Waiver's position icon), so a
   player reads the same color everywhere in the app regardless of which
   table it's rendered in. FLEX/DEF/K get no override here, same as .pos
   itself leaves them at the neutral default -- there's no case where the
   badge TEXT is "QB"/"RB"/"WR"/"TE" without also being that real
   position, so this only ever needs to match player.position, never
   slotType/rank directly (a FLEX slot holding a WR still colors WR). */
.startsit-slot-badge.pos-QB { background: rgba(139,63,209,0.2); color: #8b3fd1; border-color: transparent; }
.startsit-slot-badge.pos-RB { background: rgba(47,158,92,0.18); color: #2f9e5c; border-color: transparent; }
.startsit-slot-badge.pos-WR { background: rgba(47,111,214,0.18); color: #2f6fd6; border-color: transparent; }
.startsit-slot-badge.pos-TE { background: rgba(201,106,26,0.2); color: #c96a1a; border-color: transparent; }
/* 2026-09-21 fix, Bernard: "needs more visible highlights" -- the
   original was a gradient fading to transparent by 30% of the row's
   width, at only 10% opacity, and (unnoticed until now) actually lost
   even that faint tint on hover, since .roster-row:hover's background
   rule has higher specificity (class+pseudo-class) and was overriding
   it. This is the table's single most actionable signal -- "swap this
   player in" -- so it should read at a glance, not require squinting.
   Full-width tint at real opacity, an inset left accent bar (matching
   the .startsit-starting-tag/amber warning color used elsewhere), and
   an explicit :hover rule so the highlight survives mouseover instead
   of vanishing. */
.startsit-swap-row {
  background: rgba(179,114,10,0.16);
  box-shadow: inset 4px 0 0 var(--amber);
}
.startsit-swap-row:hover {
  background: rgba(179,114,10,0.24);
}
.startsit-starting-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 4px;
  background: rgba(31,138,82,0.14);
  color: var(--green);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.4px;
  vertical-align: middle;
}
/* 2026-09-21, same fix as .startsit-swap-row above -- a text tag makes
   the "swap this player in" signal explicit, not just a color tint
   (color alone isn't a great signal on its own -- easy to miss, and
   doesn't work for colorblind users). Same shape as
   .startsit-starting-tag, amber to match the row highlight/warning
   color instead of green. */
.startsit-swap-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 4px;
  background: rgba(179,114,10,0.18);
  color: var(--amber);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.4px;
  vertical-align: middle;
}
.startsit-opp-cell {
  font-size: 12px;
  color: var(--text2);
}
/* Two-line Opp cell (2026-09-17, Bernard's ask -- real mobile side
   scroll on Waiver Wire/Start/Sit) -- "vs JAX" and "O/U 45.5" stacked
   instead of one wide line, so this cell (previously the widest in the
   row) can go narrower without dropping any data. */
.opp-team-line {
  white-space: nowrap;
}
.opp-ou-line {
  font-size: 10.5px;
  color: var(--text3);
  white-space: nowrap;
  margin-top: 1px;
}

.matchup-tag {
  /* Stacked, not inline (2026-09-17, Bernard, chasing the last of the
     mobile scroll on Start/Sit) -- was a single-line pill needing the
     combined width of "#18" + "Neutral" side by side; a column-flex tag
     only needs to be as wide as its wider line alone, usually the tier
     word since ranks are 1-2 digits. */
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  font-weight: 700;
  white-space: nowrap;
  padding: 2px 6px;
  border-radius: 4px;
  line-height: 1.35;
}
.matchup-tag .matchup-rank {
  font-size: 11px;
}
.matchup-tag .matchup-tier {
  font-weight: 600;
  text-transform: uppercase;
  font-size: 9px;
  letter-spacing: 0.3px;
  opacity: 0.85;
}
.matchup-tag.matchup-tough { color: var(--red); background: rgba(194,58,79,0.10); }
.matchup-tag.matchup-neutral { color: var(--text2); background: rgba(122,129,135,0.10); }
.matchup-tag.matchup-smash { color: var(--green); background: rgba(31,138,82,0.10); }

/* Trade Finder v2: rank badges + usage line under each player (2026-10-11) */

.finder-rank-row { display: flex; gap: 6px; flex-wrap: wrap; }
.rank-badge { font-size: 11px; font-weight: 700; padding: 1px 7px; border-radius: 10px; background: var(--surface-2, #eef0f2); color: var(--text, #1f2933); }
.rank-badge-l { font-weight: 600; opacity: 0.6; margin-right: 4px; }
.rank-badge-empty { opacity: 0.5; }
.table-rank-row { margin-top: 3px; gap: 4px; }
.table-rank-row .rank-badge { font-size: 10px; padding: 0 5px; }
.rank-cell { display: inline-flex; flex-direction: row; align-items: baseline; justify-content: flex-end; gap: 10px; font-size: 12px; font-weight: 700; line-height: 1.25; white-space: nowrap; color: var(--text, #1f2933); }
.rank-cell-l { font-weight: 600; opacity: 0.55; margin-right: 4px; font-size: 10px; }
.rank-cell-item.strong { color: var(--primary, #1d3a8a); }
/* Compact variant for narrow rows (search lists, trade rows, depth cards): smaller, may wrap, never stacks on phones. */
.rank-cell.rank-cell-sm { font-size: 11px; gap: 6px; flex-wrap: wrap; justify-content: flex-end; white-space: normal; }
.rank-cell.rank-cell-sm .rank-cell-l { font-size: 9px; margin-right: 2px; }
/* Phones: stack the three ranks so the column stays narrow. */
@media (max-width: 700px) {
  .rank-cell:not(.rank-cell-sm) { flex-direction: column; align-items: flex-end; gap: 1px; font-size: 11px; }
}
.edge-tag { font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 10px; background: var(--amber-bg, #fff4d6); color: var(--amber, #b26a00); white-space: nowrap; }
.usage-stat { white-space: nowrap; }
.usage-l { opacity: 0.8; }
.usage-up { color: var(--green, #1a7f37); font-size: 9px; margin-left: 2px; }
.usage-down { color: var(--red, #c0392b); font-size: 9px; margin-left: 2px; }

/* Trade Tool Edge value note (2026-10-13) */
.trade-edge-note { font-size: 12px; color: var(--text2); margin: -8px 0 16px; line-height: 1.4; }

/* Trade evidence view, step 1 (2026-10-14): verdict banner and per-player expandable evidence cards.
   Phones: rows collapsed by default (one open at a time, see tradeCardOpen/toggleTradeCard in app.js). */
.trade-banner { background: var(--bg2); border: 1px solid var(--border); border-left: 5px solid var(--border); border-radius: 12px; padding: 14px 16px; margin-bottom: 12px; }
.trade-banner.fair { border-left-color: var(--green); }
.trade-banner.lean { border-left-color: var(--amber); }
.trade-banner .trade-verdict { padding: 0; font-size: 15px; }
.trade-banner + .trade-edge-note { margin-top: 0; }
.tv-bars { margin-top: 10px; }
.tv-row { display: flex; align-items: center; gap: 10px; margin: 5px 0; font-size: 13px; }
.tv-l { width: 56px; color: var(--text2); font-weight: 700; font-size: 12px; }
.tv-t { flex: 1; height: 18px; background: var(--bg3); border-radius: 6px; overflow: hidden; }
.tv-f { height: 100%; border-radius: 6px; }
.tv-f.a { background: #9aa3c7; }
.tv-f.b { background: var(--primary); }
.tv-row b { width: 52px; text-align: right; color: var(--primary); }

.trade-player-block { border-bottom: 1px solid var(--border); }
.trade-player-block:last-child { border-bottom: none; }
.trade-player-block .trade-player-row { border-bottom: none; }
.trade-player-row-tap { cursor: pointer; min-height: 48px; display: grid; grid-template-columns: 28px minmax(0, 1fr) auto auto; grid-template-areas: "pos name exp rm" ". rank rank rank"; column-gap: 10px; row-gap: 4px; align-items: center; }
.trade-player-row-tap .tr-pos { grid-area: pos; }
.trade-player-row-tap .tr-name { grid-area: name; }
.trade-player-row-tap .tr-rank { grid-area: rank; }
.trade-player-row-tap .tr-exp { grid-area: exp; }
.trade-player-row-tap .tr-rm { grid-area: rm; }
.trade-expand { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; color: var(--text3); font-size: 10px; border-radius: 6px; }
.trade-player-row-tap:hover .trade-expand { background: var(--bg3); }
.tev-chip { display: inline-block; font-size: 11px; line-height: 1.5; padding: 0 8px; border-radius: 999px; background: var(--bg3); color: var(--text2); font-weight: 600; margin-left: 4px; }
.tev-chip.warn { background: #fbe9ec; color: var(--red); }
.tev-chip.ok { background: #e6f4ec; color: var(--green); }
.tev-chip.hot { background: #fdf0dc; color: var(--amber); }
.tev-chip.cool { background: #e6f4ec; color: var(--green); }

.tev { padding: 2px 2px 14px; }
.tev-role { font-size: 12px; color: var(--text3); margin-bottom: 6px; }
.tev-br { display: flex; align-items: center; gap: 8px; margin: 4px 0; font-size: 12px; }
.tev-bl { width: 92px; color: var(--text2); flex-shrink: 0; }
.tev-trk { flex: 1; height: 14px; background: var(--bg3); border-radius: 4px; position: relative; }
.tev-fl { height: 100%; border-radius: 4px; }
.tev-fl.cur { background: #c9a24b; }
.tev-fl.edge { background: var(--primary); }
.tev-fl.rep { background: #b7bcc2; }
.tev-trk em { position: absolute; right: 6px; top: -1px; font-style: normal; font-size: 11px; font-weight: 700; color: var(--text); }
.tev-us { width: 100%; border-collapse: collapse; margin: 8px 0; font-size: 12.5px; }
.tev-us td { padding: 3px 0; border-top: 1px solid var(--bg3); }
.tev-us td.tev-s { text-align: right; color: var(--text3); }
.tev-chips { margin: 8px 0 6px; }
.tev-chips .tev-chip { margin: 0 6px 4px 0; }
.tev-why { margin: 6px 0 8px 18px; font-size: 13px; color: var(--text2); }
.tev-why li { margin: 3px 0; }
.tev-note { font-size: 12px; color: var(--text3); border-top: 1px solid var(--bg3); padding-top: 8px; }
.tev-note summary { font-weight: 700; color: var(--text2); cursor: pointer; margin-bottom: 4px; }
@media (max-width: 760px) {
  .trade-player-row-tap { min-height: 52px; padding: 8px 0; }
  .trade-expand { width: 36px; height: 36px; font-size: 12px; }
  .trade-remove { width: 32px; height: 32px; font-size: 18px; }
  .tev-opt { display: none; }
  .tv-l { width: 48px; }
}

/* Trade pitch (2026-10-14) */
.trade-pitch { background: var(--bg2); border: 1px solid var(--border); border-left: 5px solid var(--primary); border-radius: 12px; padding: 14px 16px; margin: 14px 0; }
.trade-pitch.muted { border-left-color: var(--border); }
.trade-pitch .tp-reason { font-size: 13px; color: var(--text2); line-height: 1.45; }
.trade-pitch .tp-msg { font-size: 15px; line-height: 1.5; padding: 10px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; user-select: all; }
.trade-pitch .tp-actions { display: flex; gap: 8px; margin: 10px 0 8px; }
.trade-pitch .tp-actions .finder-load-btn { min-width: 84px; margin-top: 0; }
.trade-pitch .tp-num { font-size: 12px; color: var(--text3); }
.trade-pitch .tp-heads { font-size: 12px; color: var(--text2); margin-top: 8px; }
.trade-pitch .tp-heads ul { margin: 4px 0 0 18px; padding: 0; }
@media (max-width: 760px) { .trade-pitch .tp-actions .finder-load-btn { flex: 1; min-height: 44px; } }
.finder-cell .finder-rk { display: block; font-size: 9px; font-weight: 500; opacity: 0.7; }

/* Trade Finder sections: each is its own panel */

@media (max-width: 640px) {
}

/* Mobile tables as their own panels (2026-10-16, Bernard: swipes kept getting caught by tables that scroll sideways, and
   there was no visible gap between elements to put a finger on). Phone widths only. Each sideways-scrolling table now sits
   in a bordered white panel with a gap below it, so there is an obvious place to grab the page, and a soft shadow shows on
   whichever edge still has more to scroll. overflow-y: hidden stops a wrapper from ever scrolling vertically on its own;
   overscroll-behavior-x stops a sideways swipe at a table's edge from turning into a page gesture. */
@media (max-width: 780px) {
  .roster-table-wrap, .finder-grid-wrap, .depth-chart-columns {
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    background-color: var(--bg2);
    background-image:
      linear-gradient(to right, var(--bg2) 30%, rgba(255,255,255,0)),
      linear-gradient(to left, var(--bg2) 30%, rgba(255,255,255,0)),
      radial-gradient(farthest-side at 0 50%, rgba(25,28,31,0.22), rgba(25,28,31,0)),
      radial-gradient(farthest-side at 100% 50%, rgba(25,28,31,0.22), rgba(25,28,31,0));
    background-position: 0 0, 100% 0, 0 0, 100% 0;
    background-repeat: no-repeat;
    background-size: 36px 100%, 36px 100%, 12px 100%, 12px 100%;
    background-attachment: local, local, scroll, scroll;
  }
  .roster-table-wrap, .depth-chart-columns {
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 6px 8px;
    margin: 0 0 18px;
  }
  /* inside the Trade Finder panel the grid already has a panel around it -- keep just the edge shadows there */
  .finder-grid-wrap { border-radius: 8px; }
}

/* Trade Tool pick lists on phones (2026-10-16, Bernard: stacked roster and draft-pick lists each scrolled inside the page,
   so a swipe on any of them was caught and the only place that moved the page was the label between two lists).
   On a phone these lists now grow to their full height and the page does all the scrolling; each one is a bordered panel
   with a gap below it. Desktop keeps the 340px scrolling box. */
@media (max-width: 780px) {
  .roster-picker-list {
    max-height: none;
    overflow-y: visible;
    border: 1px solid var(--border);
    border-radius: 10px;
    margin-bottom: 14px;
  }
}

/* Collapsible draft picks in the Trade Tool (2026-10-16) */
.trade-picks-details { margin-top: 12px; }
.trade-picks-summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 12px;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 12px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text2);
}
.trade-picks-summary::-webkit-details-marker { display: none; }
.trade-picks-summary::after { content: '\25BE'; color: var(--text3); font-size: 14px; margin-left: auto; }
.trade-picks-details[open] > .trade-picks-summary::after { content: '\25B4'; }
.trade-picks-details[open] > .trade-picks-summary { margin-bottom: 8px; }
.trade-picks-count {
  background: var(--bg3);
  border-radius: 999px;
  padding: 1px 8px;
  font-size: 11px;
  color: var(--text3);
}

/* Sticky verdict bar (2026-10-16): stays under the header (and the nav on phones) while the rosters scroll.
   z-index 80 keeps it under the nav (90) and header group (100). Once stuck it collapses to the verdict line only. */
.trade-banner { position: sticky; top: var(--tool-sticky-top, 64px); z-index: 80; }
.trade-banner.stuck { padding: 8px 14px; box-shadow: 0 3px 8px rgba(0,0,0,0.12); }
.trade-banner.stuck .tv-bars { display: none; }
.trade-banner.stuck .trade-verdict { font-size: 13px; line-height: 1.3; }

/* Trade Tool pick controls (2026-10-16) */
.trade-side-hint { font-size: 11px; font-weight: 600; color: var(--text3); margin-left: 6px; text-transform: none; letter-spacing: 0; }
.trade-pick-box { display: flex; align-items: center; justify-content: center; width: 40px; min-height: 40px; margin: -6px 0 -6px -6px; flex: none; cursor: pointer; }
.trade-pick-box input { width: 20px; height: 20px; margin: 0; cursor: pointer; accent-color: var(--primary, #1d3a8a); }
.trade-pick-box input:disabled { cursor: default; opacity: 0.35; }
/* only the name text opens the player card, not the whole row width */
.roster-picker-list .trade-player-row .player-name-link { display: block; flex: none; max-width: 100%; width: fit-content; }
.trade-suggest { background: var(--bg2); border: 1px solid var(--border); border-radius: 12px; padding: 10px 14px; margin-bottom: 12px; }
.trade-suggest-title { font-size: 12px; font-weight: 700; color: var(--text2); margin-bottom: 4px; }

/* Round pick dots instead of checkboxes (2026-10-16): small dot, filled with a white ring when picked, 36px tap area. */
.trade-pick-box { width: 36px; min-height: 36px; margin: -6px 0 -6px -8px; }
.trade-pick-box input { -webkit-appearance: none; appearance: none; width: 14px; height: 14px; border-radius: 50%; border: 2px solid var(--text3, #9aa3b5); background: transparent; padding: 0; transition: background 0.12s, border-color 0.12s; }
.trade-pick-box input:checked { background: var(--primary, #1d3a8a); border-color: var(--primary, #1d3a8a); box-shadow: inset 0 0 0 2px #fff; }
.trade-pick-box input:disabled { opacity: 0.3; }
/* Close-the-gap suggestions: compact rows, the Add button keeps its natural size (it was stretching the full row) */
.trade-suggest { padding: 8px 12px; }
.trade-suggest-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text2); margin-bottom: 2px; }
.trade-suggest-sub { font-size: 12px; color: var(--text3); margin-bottom: 4px; }
.trade-suggest .trade-player-row { padding: 6px 0; }
.trade-suggest .player-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.trade-suggest .finder-load-btn { flex: none; width: auto; margin: 0; padding: 6px 16px; }

/* Trade Center (2026-10-16): Ideas (left) + builder (right) on desktop; Ideas / Build / Teams segments on phones. */
.tc-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.tc-top b { font-size: 15px; }
.tc-top select { width: auto; font-size: 13px; padding: 5px 8px; }
.mode { display: inline-flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; background: #fff; }
.mode button { border: 0; background: none; padding: 6px 12px; font-size: 12px; font-weight: 700; color: var(--text2); cursor: pointer; }
.mode button.on { background: var(--primary); color: #fff; }
.tc-seg { display: grid; grid-template-columns: repeat(4, 1fr); background: #fff; border: 1px solid var(--border); border-radius: 10px; overflow: hidden; margin-bottom: 12px; max-width: 640px; }
.tc-seg button { border: 0; background: none; padding: 10px 0; font-size: 13px; font-weight: 700; color: var(--text2); border-bottom: 3px solid transparent; cursor: pointer; }
.tc-seg button.on { color: var(--primary); border-bottom-color: var(--primary); }
.tc-grid { display: block; }
#tc-root[data-pane="ideas"] .tc-pane-build, #tc-root[data-pane="ideas"] .tc-pane-teams,
#tc-root[data-pane="build"] .tc-pane-ideas, #tc-root[data-pane="build"] .tc-pane-teams,
#tc-root[data-pane="teams"] .tc-pane-ideas, #tc-root[data-pane="teams"] .tc-pane-build { display: none; }
#tc-root[data-pane="compare"] .tc-pane { display: none; }
.tc-compare { display: none; }
#tc-root[data-pane="compare"] ~ .tc-compare { display: block; }
#tc-root:not([data-pane="build"]) .tc-top:not(.tc-ideas-filter) { display: none; }
@media (max-width: 780px) { .tc-seg { max-width: none; } }

/* Trade Center layout fixes (2026-10-16): narrow Ideas column with compact cards; builder columns may shrink instead of overflowing the page. */
.tc-grid { grid-template-columns: minmax(280px, 360px) minmax(0, 1fr); }
.tc-pane { min-width: 0; }
.tc-pane-build .trade-columns, .tc-pane-build .trade-columns > * { min-width: 0; }
.tc-pane-build .trade-columns { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
@media (max-width: 1100px) { .tc-pane-build .trade-columns { grid-template-columns: minmax(0, 1fr); } }
.mode button { white-space: nowrap; }
.tc-pane-build .trade-player-row > div[style*="flex:1"] { min-width: 110px; }
.tc-pane-build .rank-cell.rank-cell-sm { flex-wrap: wrap; justify-content: flex-end; max-width: 52%; row-gap: 2px; }
.idea-tabs { display: flex; gap: 6px; margin-bottom: 10px; flex-wrap: wrap; }
.idea-tabs button { border: 1px solid var(--border); background: #fff; border-radius: 999px; padding: 6px 12px; font-size: 12px; font-weight: 700; color: var(--text2); cursor: pointer; }
.idea-tabs button.on { background: var(--primary); border-color: var(--primary); color: #fff; }
.idea-count { opacity: 0.7; margin-left: 2px; }
#tc-ideas[data-tier="starter"] .idea-group:not([data-t="starter"]),
#tc-ideas[data-tier="depth"] .idea-group:not([data-t="depth"]),
#tc-ideas[data-tier="upgrade"] .idea-group:not([data-t="upgrade"]),
#tc-ideas[data-tier="other"] .idea-group:not([data-t="other"]) { display: none; }
.idea-card { background: #fff; border: 1px solid var(--border); border-left: 4px solid var(--green); border-radius: 10px; padding: 10px 12px; margin-bottom: 8px; }
.idea-card.idea-starter { border-left-color: var(--red, #c0392b); }
.idea-card.idea-upgrade { border-left-color: #d99a22; }
.idea-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.idea-team { font-size: 13px; font-weight: 800; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.idea-tag { font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text3); flex: none; }
.idea-why { font-size: 12px; color: var(--text2); margin: 4px 0 6px; line-height: 1.35; }
.idea-swap { display: flex; gap: 8px; font-size: 13px; font-weight: 600; margin: 3px 0; align-items: baseline; }
.idea-l { flex: none; width: 52px; font-size: 10px; font-weight: 800; text-transform: uppercase; color: var(--text3); }
.idea-pos { font-size: 10px; font-weight: 700; color: var(--text3); }
.idea-meta { font-size: 11px; color: var(--green); font-weight: 700; margin: 6px 0 2px; }
.idea-card .finder-load-btn { margin-top: 6px; width: 100%; padding: 6px 10px; }

/* Trade Center roster lists (2026-10-16, take 2): names were being squeezed out by the rank chips. The row is now
   [dot][pos][name + team] on top and the ADP / CUR / EDGE chips on their own line under the name, so a name always shows in full. */
.tc-pane-build .roster-picker-list .trade-player-row { flex-wrap: wrap; row-gap: 2px; align-items: center; }
.tc-pane-build .roster-picker-list .trade-player-row > div[style*="flex:1"] { flex: 1 1 140px; min-width: 140px; }
.tc-pane-build .roster-picker-list .player-name-link { display: inline-block; width: auto; max-width: 100%; white-space: normal; overflow: visible; text-overflow: clip; flex: none; }
.tc-pane-build .roster-picker-list .trade-player-row > div[style*="font-weight:800"] { flex: 1 0 100%; padding-left: 76px; margin-top: -2px; }
.tc-pane-build .roster-picker-list .rank-cell.rank-cell-sm { max-width: none; justify-content: flex-start; flex-wrap: wrap; }
.tc-pane-build .roster-picker-list .trade-player-row > div:last-child:not([style*="font-weight:800"]) { margin-left: auto; }

.tc-partner { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 700; color: var(--text2); }
.tc-partner select { width: auto; max-width: 220px; font-size: 13px; padding: 5px 8px; }
.tc-ideas-filter { margin-bottom: 12px; }
.idea-note { font-size: 11px; color: var(--amber); font-weight: 700; margin: 6px 0 0; }

/* Trade Center team strip */
.tc-strip { display: flex; gap: 6px; overflow-x: auto; padding: 2px 0 10px; -webkit-overflow-scrolling: touch; }
.tc-chip { flex: 0 0 auto; width: auto; min-width: 0; padding: 6px 10px !important; gap: 3px !important; display: flex; flex-direction: column; gap: 4px; align-items: flex-start; border: 1px solid var(--border); background: #fff; border-radius: 10px; padding: 8px 12px; cursor: pointer; text-align: left; }
.tc-chip.on { border-color: var(--primary); box-shadow: 0 0 0 2px var(--primary) inset; }
.tc-chip-name { font-size: 12px; font-weight: 700; color: var(--text); max-width: 110px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tc-chip.mine .tc-chip-name { color: var(--primary); }
.tc-dots { display: flex; gap: 4px; }
.tc-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--border); display: inline-block; }
.tc-dot.surplus { background: var(--green); } .tc-dot.need { background: var(--red); }
.tc-team-card { background: #fff; border: 1px solid var(--border); border-radius: 12px; padding: 14px; }
.tc-team-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; font-size: 15px; }
.tc-team-head span { font-size: 12px; font-weight: 700; color: var(--text2); }
.tc-team-sub { font-size: 12px; color: var(--text2); margin: 4px 0 10px; }
.tc-pos-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.tc-pos { border: 1px solid var(--border); border-radius: 8px; padding: 8px; display: flex; flex-direction: column; gap: 2px; text-align: center; }
.tc-pos b { font-size: 11px; color: var(--text2); } .tc-pos span { font-size: 16px; font-weight: 800; } .tc-pos small { font-size: 10px; color: var(--text3); }
.tc-pos.surplus { background: rgba(34,160,90,.1); } .tc-pos.need { background: rgba(214,69,69,.1); }
.tc-team-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.tc-team-actions button { width: auto !important; flex: 0 0 auto; display: inline-block; border: 1px solid var(--border); background: #fff; color: var(--text); border-radius: 8px; padding: 8px 14px; font-size: 13px; font-weight: 700; cursor: pointer; }
.tc-team-actions button.primary { background: var(--primary); border-color: var(--primary); color: #fff; }
.tc-full summary { width: auto; }
.tc-full { margin-top: 12px; } .tc-full summary { cursor: pointer; font-size: 13px; font-weight: 700; color: var(--primary); }
@media (min-width: 781px) {
  .tc-pane-ideas .idea-group { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 12px; align-items: start; }
  .tc-strip { flex-wrap: wrap; overflow: visible; }
  .tc-team-card { max-width: 640px; }
}

/* Weekly stat reports card (2026-10-17) */
.wr-card { border: 1px solid var(--border); border-radius: 10px; background: var(--bg2); padding: 14px 16px; margin-bottom: 16px; }
.wr-title { font-size: 14px; font-weight: 700; color: var(--text); }
.wr-badge { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--primary); border: 1px solid var(--primary); border-radius: 999px; padding: 1px 7px; margin-left: 6px; vertical-align: 1px; }
.wr-sub { font-size: 12px; color: var(--text2); margin: 4px 0 10px; line-height: 1.45; }
.wr-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.wr-btn { display: inline-block; width: auto; font-size: 12px; font-weight: 600; text-decoration: none; color: var(--primary); background: var(--bg2); border: 1px solid var(--border); border-radius: 8px; padding: 7px 12px; }
.wr-btn.wr-primary { color: #fff; background: var(--primary); border-color: var(--primary); }
.wr-group { margin-top: 10px; }
.wr-group-label { display: block; font-size: 11px; font-weight: 600; color: var(--text3); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 5px; }
.wr-week { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600; color: var(--text2); margin-bottom: 8px; }
.wr-week select { width: auto; font-size: 12px; padding: 5px 8px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg2); color: var(--text); }
.wr-group + .wr-group { border-top: 1px solid var(--border); padding-top: 12px; margin-top: 12px; }

/* Waiver report (2026-10-20) */
.wv-card { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; margin: 12px 0 16px; background: var(--bg2); }
.wv-title { font-weight: 700; font-size: 15px; margin-bottom: 8px; }
.wv-beta { font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 999px; background: var(--bg3); color: var(--text2); margin-left: 6px; vertical-align: middle; }
.wv-item { padding: 8px 0; border-top: 1px solid var(--border); }
.wv-top { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.wv-name { font-weight: 600; }
.wv-pos { color: var(--text2); font-size: 12px; }
.wv-chip { margin-left: auto; font-size: 11px; font-weight: 600; padding: 1px 8px; border-radius: 999px; }
.wv-chip.up { background: #e6f4ec; color: var(--green); }
.wv-chip.dp { background: var(--bg3); color: var(--text2); }
.wv-chip.st { background: #fff3dd; color: #8a5a00; }
.wv-line { font-size: 13px; color: var(--text2); margin-top: 2px; }
.wv-meta { font-size: 13px; margin-top: 3px; }
.wv-est { color: var(--text2); font-size: 12px; }
.wv-empty, .wv-foot { font-size: 12px; color: var(--text2); }
.wv-foot { margin-top: 8px; }

/* Health check (2026-10-20) */
.hc-card { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; margin: 12px 0 16px; background: var(--bg2); }
.hc-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.hc-title { font-weight: 700; font-size: 15px; }
.hc-label { font-size: 12px; font-weight: 700; padding: 2px 10px; border-radius: 999px; background: var(--bg3); color: var(--text2); }
.hc-label.hc-contender { background: #e6f4ec; color: var(--green); }
.hc-label.hc-rebuilder { background: #e8f0fe; color: #1a56c4; }
.hc-label.hc-stuck-in-the-middle, .hc-label.hc-long-shot { background: #fbe9ec; color: var(--red); }
.hc-stats { display: flex; gap: 18px; flex-wrap: wrap; }
.hc-stats > div { display: flex; flex-direction: column; font-size: 12px; color: var(--text2); }
.hc-stats b { font-size: 20px; color: var(--text); line-height: 1.2; }
.hc-tip { font-size: 13px; margin-top: 8px; }
.hc-foot { font-size: 12px; color: var(--text2); margin-top: 8px; }

/* Trade grader (2026-10-20) */
.tc-seg { grid-template-columns: repeat(5, 1fr); }
#tc-root:not([data-pane="grade"]) .tc-pane-grade { display: none; }
#tc-root[data-pane="grade"] .tc-pane:not(.tc-pane-grade) { display: none; }
.tg-intro { font-size: 13px; color: var(--text2); margin-bottom: 10px; }
.tg-result { margin: 12px 0; }
.tg-empty { font-size: 13px; color: var(--text2); padding: 12px; border: 1px dashed var(--border); border-radius: 10px; }
.tg-grade { display: flex; align-items: center; gap: 14px; padding: 12px 14px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg2); }
.tg-letter { font-size: 38px; font-weight: 800; line-height: 1; min-width: 54px; text-align: center; }
.tg-ok .tg-letter { color: var(--green); } .tg-bad .tg-letter { color: var(--red); } .tg-mid .tg-letter { color: #b26a00; }
.tg-verdict { font-weight: 700; font-size: 16px; }
.tg-sub { font-size: 13px; color: var(--text2); }
.tg-facts { margin: 10px 0 0 18px; padding: 0; font-size: 13px; }
.tg-counter { font-size: 13px; margin-top: 8px; padding: 8px 10px; background: var(--bg3); border-radius: 8px; }
.tg-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }
@media (max-width: 760px) { .tg-cols { grid-template-columns: minmax(0, 1fr); } }
.tg-list { border: 1px solid var(--border); border-radius: 10px; max-height: 420px; overflow-y: auto; background: var(--bg2); }
.tg-row { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-top: 1px solid var(--border); font-size: 13px; cursor: pointer; }
.tg-row:first-child { border-top: 0; }
.tg-name { flex: 1; min-width: 0; font-weight: 600; }
.tg-pos { color: var(--text2); font-size: 12px; }
.tg-val { font-weight: 700; min-width: 28px; text-align: right; }

/* Alerts (2026-10-20) */
.alert-bell { position: relative; background: none; border: 0; padding: 4px; color: var(--text2); cursor: pointer; display: inline-flex; align-items: center; }
.alert-count { position: absolute; top: -4px; right: -6px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px; background: var(--primary); color: #fff; font-size: 10px; font-weight: 700; align-items: center; justify-content: center; }
.alert-count.hi { background: var(--red); }
.alert-panel { display: none; position: fixed; top: 56px; right: 12px; width: min(380px, calc(100vw - 24px)); max-height: 70vh; overflow-y: auto; z-index: 1000; background: var(--bg2); border: 1px solid var(--border); border-radius: 12px; box-shadow: 0 8px 28px rgba(0,0,0,.18); }
.alert-panel.open { display: block; }
.alert-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--border); }
.alert-head b { flex: 1; }
.alert-clear, .alert-dis { border: 0; background: none; color: var(--primary); font-size: 12px; font-weight: 600; cursor: pointer; }
.alert-x { border: 0; background: none; font-size: 20px; line-height: 1; color: var(--text2); cursor: pointer; }
.alert-item { padding: 10px 12px; border-bottom: 1px solid var(--border); border-left: 3px solid transparent; }
.alert-item.sev-high { border-left-color: var(--red); } .alert-item.sev-med { border-left-color: #e0a100; } .alert-item.sev-low { border-left-color: var(--green); }
.alert-t { display: flex; justify-content: space-between; gap: 8px; font-weight: 600; font-size: 13px; }
.alert-t a { color: var(--text); text-decoration: none; }
.alert-b { font-size: 13px; color: var(--text2); margin-top: 2px; }
.alert-empty { padding: 18px 14px; font-size: 13px; color: var(--text2); }
.alert-foot { padding: 8px 12px; font-size: 11px; color: var(--text2); }

.alert-bell, .alert-clear, .alert-dis, .alert-x { width: auto; }
.alert-bell { padding: 4px 6px; margin-right: 6px; }
.alert-bell:hover { opacity: 1; color: var(--text); }

.hc-big { display: flex; align-items: baseline; gap: 5px; }
.hc-unit { font-style: normal; font-size: 13px; color: var(--text2); }

.bye-tag { font-size: 12px; font-weight: 700; letter-spacing: .04em; color: var(--text2); background: var(--bg3); padding: 2px 8px; border-radius: 999px; }

/* Grade list rows, rebuilt 2026-10-20: the global input rule (width 100%, padding) was stretching the checkbox; rows are now tap targets with a drawn check box. */
.tg-row { display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; align-items: center; column-gap: 10px; padding: 10px 12px; border-top: 1px solid var(--border); cursor: pointer; font-size: 14px; }
.tg-row:first-child { border-top: 0; }
.tg-row:hover { background: var(--bg3); }
.tg-row.sel { background: #e8f0fe; }
.tg-row .tg-cb { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; padding: 0; pointer-events: none; }
.tg-box { width: 20px; height: 20px; border: 2px solid var(--border); border-radius: 6px; background: var(--bg2); box-sizing: border-box; display: inline-block; }
.tg-row.sel .tg-box { background: var(--primary); border-color: var(--primary); box-shadow: inset 0 0 0 3px var(--bg2); }
.tg-main { display: flex; flex-direction: column; min-width: 0; }
.tg-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tg-pos { color: var(--text2); font-size: 12px; }
.tg-val { font-weight: 700; text-align: right; white-space: nowrap; }
.tg-val small { font-size: 11px; font-weight: 500; color: var(--text2); margin-left: 2px; }
.tg-hint { font-size: 11px; font-weight: 500; color: var(--text2); margin-left: 6px; text-transform: none; letter-spacing: 0; }
.tg-row { position: relative; }

/* Compare tab retired 2026-10-20 (Grade covers it); its code stays dormant, no tab opens it. */
.tc-seg { grid-template-columns: repeat(4, 1fr); }

/* Vegas lines (2026-10-20): TT = implied team total; green = top third of this week's slate, red = bottom third. */
.lines-ticker .top-ticker-label { color: var(--green); background: rgba(46,160,90,0.12); }
.ln-meta { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 2px 6px; margin-left: 6px; font-size: 11px; color: var(--text2); }
.ln-meta > span + span:not(.ln-tt)::before, .ln-meta > span:not(.ln-tt) + .ln-tt::before { content: ""; }
.ln-tt { font-weight: 700; padding: 0 6px; border-radius: 999px; background: var(--bg3); color: var(--text2); white-space: nowrap; }
.ln-tt.ln-hi { background: #e6f4ec; color: var(--green); }
.ln-tt.ln-lo { background: #fbe9ec; color: var(--red); }
.panel-bio-value.ln-tt { background: none; padding: 0; border-radius: 0; }

/* Player card quick-stats row, regrouped 2026-10-20: equal-width tiles that wrap, wide ones (Ranks, Season) span two. */
.panel-bio-line.panel-quick-line { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 10px; border-top: none; padding-top: 0; margin-top: 16px; }
.panel-quick-line .panel-bio-cell { border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; background: var(--bg2); min-width: 0; overflow-wrap: anywhere; }
.panel-quick-line .panel-bio-cell:first-child { padding-left: 12px; }
.panel-quick-line .panel-cell-wide { grid-column: span 2; }
.panel-quick-line .panel-bio-label { margin-bottom: 4px; }
.panel-bio-sub { font-size: 12px; font-weight: 600; color: var(--text2); margin-top: 2px; }
@media (max-width: 420px) { .panel-bio-line.panel-quick-line { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Sign-in gate, plan label, locked leagues, picker and info modal (2026-10-20) */
body.auth-gate .account-modal-overlay#account-modal-overlay { background: var(--bg); flex-direction: column; gap: 22px; }
body.auth-gate .account-close { display: none; }
body.auth-gate .account-widget, body.auth-gate #landing { visibility: hidden; }
.gate-brand { display: none; text-align: center; }
body.auth-gate .gate-brand { display: block; }
.gate-logo { font-size: 32px; font-weight: 900; background: linear-gradient(135deg, var(--primary), var(--green)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
.gate-tag { color: var(--text2); font-size: 14px; margin-top: 2px; }
.landing-status { text-align: center; color: var(--text2); font-size: 14px; margin-top: 18px; min-height: 20px; }
.landing-note { color: var(--text2); font-size: 13px; margin-bottom: 12px; }
.account-plan { font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 999px; background: var(--bg3); color: var(--text2); }
.account-plan.paid { background: #e6f4ec; color: var(--green); }
.league-select-row.locked { opacity: .75; }
.lock-pill { display: inline-flex; align-items: center; gap: 3px; font-size: 10px; font-weight: 700; padding: 1px 7px; border-radius: 999px; background: var(--bg3); color: var(--text2); margin-right: 4px; }
.info-modal { width: 380px; }
.info-sub { font-size: 13px; color: var(--text2); margin: 6px 0 10px; }
.info-list { margin: 0 0 10px 18px; padding: 0; font-size: 13px; }
.info-btn { margin-top: 8px; }
.info-btn.alt { background: var(--bg3); color: var(--text); border: 1px solid var(--border); }
.fl-list { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; max-height: 50vh; overflow-y: auto; }
.fl-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; background: var(--bg2); color: var(--text); border: 1px solid var(--border); text-align: left; font-weight: 600; }
.fl-item:hover { border-color: var(--primary); }

.key-input { text-transform: uppercase; letter-spacing: 1px; margin: 10px 0 4px; }
