:root {
  /* Two surface levels, not one. Cards used to sit on the page at the same
     lightness as the page itself, so nothing read as "raised" and the whole
     board looked like one flat sheet. */
  --bg: #0b0d12;
  --panel: #141821;
  --panel-2: #1b202b;
  --border: #242a37;
  --border-strong: #333b4d;
  --text: #e8eaf0;
  --muted: #878ea0;
  --accent: #5b93ff;
  --accent-soft: rgba(91, 147, 255, 0.14);
  --pos: #2fd075;
  --pos-soft: rgba(47, 208, 117, 0.13);
  --pos-line: rgba(47, 208, 117, 0.32);
  --neg: #f2565d;
  --neg-soft: rgba(242, 86, 93, 0.12);
  --warn: #e8b021;

  --r-sm: 7px;
  --r-md: 11px;
  --r-lg: 15px;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.35), 0 6px 18px -8px rgba(0, 0, 0, 0.55);
  --shadow-lift: 0 2px 4px rgba(0, 0, 0, 0.3), 0 14px 32px -12px rgba(0, 0, 0, 0.7);
  --header-bg: rgba(11, 13, 18, 0.82);
}

/* Light mode is an attribute, not a media query, so it can be chosen rather
   than only inherited from the OS. app.js resolves "follow the system" into a
   concrete data-theme before first paint — one source of truth here, and the
   toggle and the system preference end up in the same place instead of
   fighting over specificity. */
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #f2f4f8;
  --panel: #ffffff;
  --panel-2: #f7f9fc;
  --border: #e0e4ec;
  --border-strong: #c6ccda;
  --text: #161922;
  --muted: #5d6478;
  /* The dark-mode green and blue are tuned to glow against near-black; on
     white they vibrate and fail contrast. Darker variants, same hues. */
  --accent: #2563eb;
  --accent-soft: rgba(37, 99, 235, 0.1);
  --pos: #12924c;
  --pos-soft: rgba(18, 146, 76, 0.1);
  --pos-line: rgba(18, 146, 76, 0.28);
  --neg: #d22630;
  --neg-soft: rgba(210, 38, 48, 0.09);
  --warn: #a97400;
  --shadow: 0 1px 2px rgba(16, 24, 40, 0.06), 0 6px 16px -8px rgba(16, 24, 40, 0.14);
  --shadow-lift: 0 2px 4px rgba(16, 24, 40, 0.08), 0 14px 30px -12px rgba(16, 24, 40, 0.22);
  --header-bg: rgba(242, 244, 248, 0.85);
}

/* No-JS fallback only: without the inline resolver nothing sets data-theme,
   so honour the OS the old way. Scoped so it never overrides an explicit
   choice. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    color-scheme: light;
    --bg: #f2f4f8; --panel: #ffffff; --panel-2: #f7f9fc;
    --border: #e0e4ec; --border-strong: #c6ccda;
    --text: #161922; --muted: #5d6478;
    --accent: #2563eb; --accent-soft: rgba(37, 99, 235, 0.1);
    --pos: #12924c; --pos-soft: rgba(18, 146, 76, 0.1);
    --pos-line: rgba(18, 146, 76, 0.28);
    --neg: #d22630; --neg-soft: rgba(210, 38, 48, 0.09);
    --warn: #a97400;
    --shadow: 0 1px 2px rgba(16, 24, 40, 0.06), 0 6px 16px -8px rgba(16, 24, 40, 0.14);
    --shadow-lift: 0 2px 4px rgba(16, 24, 40, 0.08), 0 14px 30px -12px rgba(16, 24, 40, 0.22);
    --header-bg: rgba(242, 244, 248, 0.85);
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Every figure on this page is meant to be compared down a column — edges,
   odds, hit rates. Proportional digits make that comparison harder for no
   gain, so lining figures are the default and prose opts back out. */
body { font-variant-numeric: tabular-nums; }
p, .hint, .explainer { font-variant-numeric: normal; }

header {
  position: sticky;
  top: 0;
  z-index: 20;
  /* Align the wordmark with the content column instead of the window edge.
     `main` is a 1100px box centred with 1.5rem of its own padding, so on a
     wide screen the header sat ~170px to the left of everything under it and
     the page read as two unrelated halves. Falls back to a flat 1.5rem once
     the viewport is narrower than the column. */
  padding: 0.75rem max(1.5rem, calc((100% - 1100px) / 2 + 1.5rem));
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 1.5rem;
  background: var(--header-bg);
  backdrop-filter: saturate(180%) blur(12px);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
}

header h1 {
  font-size: 1.02rem;
  margin: 0;
  letter-spacing: -0.01em;
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
/* A small mark so the header has an anchor point besides the wordmark. */
header h1::before {
  content: "";
  width: 0.62rem; height: 0.62rem; border-radius: 3px;
  background: linear-gradient(140deg, var(--pos), var(--accent));
  box-shadow: 0 0 12px var(--pos-line);
}

.theme-toggle {
  margin-left: auto;
  flex: 0 0 auto;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--panel);
  cursor: pointer;
  font-size: 0.95rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.15s ease, transform 0.1s ease;
}
.theme-toggle:hover { border-color: var(--border-strong); }
.theme-toggle:active { transform: scale(0.94); }
/* The auth widget also claims margin-left:auto; whichever comes first wins the
   gap, so the toggle sits beside it rather than pushing it off. */
#auth-widget:not(:empty) { margin-left: 0.6rem; }

/* Segmented control rather than loose buttons: the tabs are one thing with a
   selected member, and spacing alone never said that. */
nav {
  display: flex;
  gap: 0.15rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.22rem;
  overflow-x: auto;
  scrollbar-width: none;
}
nav::-webkit-scrollbar { display: none; }

.tab-btn {
  background: transparent;
  border: none;
  color: var(--muted);
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  cursor: pointer;
  font-size: 0.875rem;
  font-weight: 500;
  white-space: nowrap;
  transition: color 0.15s ease, background 0.15s ease;
}
.tab-btn:hover { color: var(--text); }
.tab-btn.active {
  background: var(--panel-2);
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--border-strong);
}

main { padding: 1.5rem; max-width: 1100px; margin: 0 auto; }

.tab-panel { display: none; }
.tab-panel.active { display: block; animation: panel-in 0.22s ease both; }

@keyframes panel-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}

/* Keyboard users get a visible ring; mouse users don't get it on every click. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}
/* Expandable rows and cards are focusable but are not <button>s, so the ring
   has to be pulled inside — an offset outline on a table row is clipped by the
   table's overflow and simply doesn't render. */
.pick-card:focus-visible,
.leg-row:focus-visible,
#props-table tbody tr:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}

.toolbar { display: flex; gap: 0.6rem; margin-bottom: 1rem; flex-wrap: wrap; align-items: center; }
.toolbar input {
  flex: 1;
  padding: 0.5rem 0.7rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.toolbar input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.toolbar button, .card button, #news-sync {
  padding: 0.5rem 1rem;
  background: var(--accent);
  border: none;
  border-radius: var(--r-sm);
  color: white;
  font-weight: 550;
  cursor: pointer;
  transition: filter 0.15s ease, transform 0.1s ease;
}
.toolbar button:hover, .card button:hover, #news-sync:hover { filter: brightness(1.1); }
.toolbar button:active, .card button:active { transform: translateY(1px); }
.toolbar select {
  padding: 0.5rem 0.7rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text);
}
/* Inline labels in a toolbar read as a caption above their control, not as a
   word floating beside it. */
.toolbar > label {
  display: flex; flex-direction: column; gap: 0.22rem;
  font-size: 0.68rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.05em;
}
.checkbox-label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--muted);
  font-size: 0.9rem;
  white-space: nowrap;
}

.game-block { margin-bottom: 0.5rem; }
.game-row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 0.65rem 0.9rem;
  box-shadow: var(--shadow);
  transition: border-color 0.15s ease, transform 0.15s ease;
}
.game-row.expandable { cursor: pointer; }
.game-row.expandable:hover { border-color: var(--border-strong); transform: translateY(-1px); }
.expander { width: 1em; color: var(--muted); }
.game-props {
  margin: 0 0.5rem;
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--border);
  border-top: none;
  border-radius: 0 0 8px 8px;
  background: var(--bg);
}
.props-mini { font-size: 0.85rem; }
.props-mini th, .props-mini td { padding: 0.35rem 0.6rem; }
.game-note { color: var(--muted); font-size: 0.75rem; font-style: italic; }

.game-props { padding: 0.6rem 0.9rem 0.9rem; border: 1px solid var(--border); border-top: none; border-radius: 0 0 8px 8px; background: var(--panel); margin-top: -0.5rem; margin-bottom: 0.5rem; }
.game-section { margin-bottom: 0.9rem; }
.section-title { font-size: 0.75rem; text-transform: uppercase; color: var(--muted); margin-bottom: 0.5rem; letter-spacing: 0.03em; }
.roster-side { margin-bottom: 0.7rem; }
.roster-team { font-weight: 600; font-size: 0.85rem; margin-bottom: 0.35rem; }
.roster-chips { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.roster-chip {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.3rem 0.6rem;
  font-size: 0.82rem;
  color: var(--text);
  cursor: pointer;
}
.roster-chip:hover { border-color: var(--accent); color: var(--accent); }

#odds-key-row, #odds-pull-row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
#odds-key { flex: 1; min-width: 200px; padding: 0.4rem 0.6rem; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--text); }
#odds-key-status, #odds-status { color: var(--muted); font-size: 0.85rem; }
#odds-sport { padding: 0.4rem 0.6rem; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--text); }

#kalshi-bulk-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; padding: 0.6rem; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; }
#kalshi-bulk-sport { padding: 0.4rem 0.6rem; background: var(--panel); border: 1px solid var(--border); border-radius: 6px; color: var(--text); }
#kalshi-bulk-btn { background: var(--pos); color: white; border: none; border-radius: 6px; padding: 0.45rem 1rem; cursor: pointer; }
#kalshi-bulk-status { color: var(--muted); font-size: 0.85rem; }

#promo-card { margin-top: 1rem; }
#promo-input-row { display: flex; gap: 0.5rem; align-items: flex-start; }
#promo-text { flex: 1; padding: 0.5rem 0.7rem; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-family: inherit; resize: vertical; }
#promo-parse-btn { background: var(--accent); color: white; border: none; border-radius: 6px; padding: 0.5rem 1rem; cursor: pointer; white-space: nowrap; }
.manual-boost { display: flex; flex-wrap: wrap; gap: 0.7rem; align-items: flex-end; margin-top: 0.6rem; }
.manual-boost label {
  display: flex; flex-direction: column; gap: 0.28rem;
  font-size: 0.66rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.06em;
}
.manual-boost select, .manual-boost input {
  padding: 0.45rem 0.55rem; background: var(--bg);
  border: 1px solid var(--border); border-radius: var(--r-sm); color: var(--text);
}
.manual-boost #mb-apply {
  background: var(--accent); color: white; border: none; cursor: pointer;
  padding: 0.5rem 1.1rem; border-radius: var(--r-sm); font-weight: 550;
}
.manual-boost #mb-apply:hover { filter: brightness(1.1); }

/* Line Odds model */
.model-search-row { display: flex; gap: 0.6rem; }
.model-search-row input { flex: 1; padding: 0.5rem 0.7rem; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--text); }
#model-results { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.8rem; }
.player-chip {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 0.4rem 0.9rem;
  cursor: pointer;
  color: var(--text);
  font-size: 0.85rem;
}
.player-chip:hover { border-color: var(--accent); }
.chip-sub { color: var(--muted); font-size: 0.8rem; }
.player-chip.unsupported { opacity: 0.55; }
.chip-tag {
  font-size: 0.68rem;
  text-transform: uppercase;
  background: var(--border);
  color: var(--muted);
  padding: 0.1rem 0.4rem;
  border-radius: 4px;
  margin-left: 0.3rem;
}

.model-form { display: flex; flex-wrap: wrap; gap: 0.8rem; align-items: flex-end; margin: 0.8rem 0; }
.model-form label { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.78rem; color: var(--muted); text-transform: uppercase; }
.model-form select, .model-form input {
  padding: 0.45rem 0.6rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text);
  font-size: 0.9rem;
}

/* One focus treatment for every control in the app, rather than each component
   inventing its own (or, mostly, having none). */
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
#model-eval-btn { align-self: flex-end; background: var(--accent); color: white; border: none; border-radius: 6px; padding: 0.5rem 1.2rem; cursor: pointer; }

.spark-row { display: flex; flex-wrap: wrap; gap: 4px; margin: 0.4rem 0; }
.spark-bar {
  min-width: 26px;
  text-align: center;
  padding: 0.25rem 0.3rem;
  border-radius: 4px;
  font-size: 0.8rem;
  font-weight: 600;
}
.spark-bar.hit { background: rgba(53, 196, 107, 0.22); color: var(--pos); }
.spark-bar.miss { background: rgba(240, 85, 92, 0.18); color: var(--neg); }

.add-slip-row { display: flex; align-items: center; gap: 0.7rem; margin-top: 0.8rem; }
#add-model-to-slip { background: var(--accent); color: white; border: none; border-radius: 6px; padding: 0.45rem 1rem; cursor: pointer; white-space: nowrap; }
#screener-btn { background: var(--pos); color: white; }

#cleanup-actions { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; margin-top: 0.6rem; }
#cleanup-actions button { padding: 0.45rem 1rem; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--text); cursor: pointer; }
#cleanup-actions button.danger { background: var(--neg); color: white; border-color: var(--neg); }
#cleanup-status { color: var(--muted); font-size: 0.85rem; }
/* Written with enough specificity to survive `.card button` / `.toolbar
   button`, which paint a solid accent fill. Without this a "Show graph" link
   inside a card rendered as a full blue button. */
.link-btn,
.card .link-btn,
.toolbar .link-btn {
  background: none;
  border: none;
  color: var(--accent);
  cursor: pointer;
  padding: 0;
  font: inherit;
  font-weight: inherit;
  text-decoration: underline;
  min-height: 0;
  width: auto;
  flex: 0 0 auto;
}
.link-btn:hover { filter: none; text-decoration-thickness: 2px; }

.news-panel { margin-top: 1rem; border-top: 1px solid var(--border); padding-top: 0.8rem; }
.news-item { padding: 0.4rem 0; border-bottom: 1px solid var(--border); }
.news-item:last-child { border-bottom: none; }
.news-item a { color: var(--text); text-decoration: none; }
.news-item a:hover { color: var(--accent); text-decoration: underline; }
.news-meta { display: block; color: var(--muted); font-size: 0.75rem; margin-top: 0.15rem; }

#site-footer {
  max-width: 1100px;
  margin: 2rem auto 1rem;
  padding: 1rem 1.5rem;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: 0.78rem;
  line-height: 1.5;
}
#site-footer p { margin: 0.4rem 0; }
#site-footer strong { color: var(--text); }
#site-footer a { color: var(--accent); }
#site-footer .footer-attr { font-size: 0.72rem; opacity: 0.85; }
.badge {
  font-size: 0.7rem;
  font-weight: 700;
  padding: 0.15rem 0.5rem;
  border-radius: 4px;
  min-width: 68px;
  text-align: center;
}
.badge {
  border-radius: 999px; letter-spacing: 0.05em; text-transform: uppercase;
}
/* A live game is the one row on the page whose state is changing; a quiet
   pulse says so without another column of text. */
.badge-live { background: var(--neg); color: white; animation: live-pulse 2.4s ease-in-out infinite; }
@keyframes live-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(242, 86, 93, 0.5); }
  50%      { box-shadow: 0 0 0 5px rgba(242, 86, 93, 0); }
}
.badge-final { background: var(--border); color: var(--muted); }
.badge-upcoming { background: var(--accent-soft); color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.game-sport { color: var(--muted); font-size: 0.8rem; min-width: 90px; }
.game-matchup { font-weight: 600; flex: 1; }
.game-status { color: var(--muted); font-size: 0.85rem; }

table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
  box-shadow: var(--shadow);
}
th, td {
  text-align: left;
  padding: 0.58rem 0.7rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.88rem;
}
th {
  color: var(--muted); font-weight: 600; font-size: 0.7rem;
  text-transform: uppercase; letter-spacing: 0.06em;
  background: var(--panel-2);
}
tr:last-child td { border-bottom: none; }

#props-table th, #props-table td { padding: 0.58rem 0.6rem; }
/* Not sticky. A pinned thead under an already-sticky page header parks the
   column labels in the middle of the table with rows scrolled above them —
   it reads as a misaligned row rather than as a header. The page header is
   sticky and that is the one worth keeping. */
#props-table tbody tr:hover > td { background: var(--panel-2); }

.props-empty {
  color: var(--muted); font-size: 0.88rem; line-height: 1.65;
  padding: 2rem 1.5rem; text-align: center;
}
.props-empty strong { color: var(--text); }
/* An empty table should read as a state, not as a broken table. */
.props-empty::before {
  content: "—"; display: block; font-size: 1.6rem; color: var(--border-strong);
  margin-bottom: 0.5rem; letter-spacing: 0.2em;
}

.edge-pos { color: var(--pos); font-weight: 600; }
.edge-neg { color: var(--neg); }
.edge-na { color: var(--muted); }

.hint { color: var(--muted); font-size: 0.85rem; margin-top: 0.8rem; }

.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 1.05rem 1.25rem;
  margin-bottom: 1rem;
  box-shadow: var(--shadow);
}
.card h3 { margin-top: 0; font-size: 0.95rem; letter-spacing: -0.01em; }
.card input[type="text"], .card input:not([type]) {
  padding: 0.4rem 0.6rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  margin-right: 0.5rem;
}

code {
  background: var(--bg);
  padding: 0.1rem 0.35rem;
  border-radius: 4px;
  font-size: 0.85em;
}

h2 {
  font-size: 1.15rem; letter-spacing: -0.02em; margin: 0 0 0.6rem;
}

/* Base treatment for every figure block in the app. #combo-result and
   .prop-detail-inner refine the sizes below; #model-result had no scoped rule
   at all and was rendering the Line Odds numbers as bare divs. */
.stat-row { display: flex; gap: 0.6rem; flex-wrap: wrap; margin: 0.8rem 0; }
.stat {
  min-width: 118px; flex: 1 1 118px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 0.55rem 0.7rem;
}
.stat .label {
  color: var(--muted); font-size: 0.64rem;
  text-transform: uppercase; letter-spacing: 0.07em;
}
.stat .value {
  font-size: 1.25rem; font-weight: 700; letter-spacing: -0.02em;
  margin-top: 0.15rem;
}

#combo-result {
  margin-top: 1rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 1.1rem 1.25rem;
  box-shadow: var(--shadow);
}
#combo-result .warn {
  color: var(--warn);
  font-size: 0.85rem;
  margin-top: 0.4rem;
}
/* Stat blocks read as a row of figures, so they get cells rather than gaps —
   at a glance you can tell how many there are and where each one ends. */
#combo-result .stat-row { display: flex; gap: 0.6rem; flex-wrap: wrap; }
#combo-result .stat {
  min-width: 132px; flex: 1 1 132px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 0.6rem 0.75rem;
}
#combo-result .stat .label {
  color: var(--muted); font-size: 0.64rem;
  text-transform: uppercase; letter-spacing: 0.07em;
}
#combo-result .stat .value {
  font-size: 1.35rem; font-weight: 700; letter-spacing: -0.02em;
  margin-top: 0.15rem;
}

button.add-btn, button.remove-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 6px;
  cursor: pointer;
  padding: 0.2rem 0.6rem;
}
button.remove-btn { color: var(--neg); }

/* Props rows open the player sheet. The whole row is the target; the caret is
   an affordance only. Rows no longer expand in place, so the .prop-detail
   sibling row and every rule that styled it are gone. */
#props-table tbody tr { cursor: pointer; user-select: none; }
.prop-player { white-space: nowrap; font-weight: 550; }
#props-table tbody tr:hover .prop-player { color: var(--accent); }
.prop-player .caret {
  display: inline-block; width: 0.9em; color: var(--muted); font-size: 0.8em;
  pointer-events: none;
  transition: color 0.15s ease;
}
#props-table tbody tr:hover .caret { color: var(--accent); }
/* The checkbox keeps its own cursor so it still reads as a separate control. */
#props-table tbody td.slip-cell { cursor: auto; }

/* The edge column is the reason the table exists — give it the weight. The
   empty state is a colspan cell and therefore also :last-child, so it still
   has to be excluded or the whole message gets right-aligned and bolded. */
#props-table tbody td:last-child:not(.props-empty) {
  font-weight: 700; font-size: 0.95rem; text-align: right;
}
#props-table th:last-child { text-align: right; }
/* The pick badge is smaller in a dense table than it is on a card. */
#props-table .pick-dir { font-size: 0.66rem; padding: 0.08rem 0.36rem; }
#props-table .pick-num { font-size: 0.92rem; }
.prop-detail-inner .prop-detail-head { font-weight: 650; margin-bottom: 0.7rem; letter-spacing: -0.01em; }
.prop-detail-inner .stat-row { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.prop-detail-inner .stat {
  min-width: 104px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 0.45rem 0.6rem;
}
.prop-detail-inner .stat .label {
  color: var(--muted); font-size: 0.63rem;
  text-transform: uppercase; letter-spacing: 0.06em;
}
.prop-detail-inner .stat .value { font-size: 1.12rem; font-weight: 700; letter-spacing: -0.02em; }

/* Props table is now six columns wide, so it fits without sideways scrolling. */
.prop-sport {
  display: block; color: var(--muted); font-size: 0.72rem;
  font-weight: 400; margin-top: 0.1rem;
}
.prop-pick { white-space: normal; }
.prop-pick .prop-stat { color: var(--muted); }
.split-row { margin-top: 0.6rem; padding-top: 0.6rem; border-top: 1px solid var(--border); }
.split-row .stat { min-width: 62px; }
.market-row { margin-top: 0.6rem; padding-top: 0.6rem; border-top: 1px solid var(--border); }
.market-row .stat { min-width: 78px; }
/* Stands in for the vig/EV row when the book posts no real per-pick price. */
.market-none {
  margin-top: 0.6rem; padding: 0.6rem 0.8rem;
  background: var(--bg); border: 1px solid var(--border);
  border-left: 3px solid var(--warn); border-radius: var(--r-sm);
  line-height: 1.55;
}
.market-none strong { color: var(--text); }

/* EV sits under the odds it belongs to, so the table stays six columns wide. */
.prop-ev {
  display: block; font-size: 0.72rem; font-weight: 500; margin-top: 0.1rem;
  white-space: nowrap;
}
/* A price outside the tradeable band still shows its EV — struck through,
   because the number is real arithmetic on an offer nobody would honour. */
.ev-illiquid { color: var(--muted); text-decoration: line-through; }
.prop-source { margin-top: 0.8rem; }
.prop-source a { color: var(--accent); }

/* Props tab: auto-sync bar + sport picker */
#autosync-bar {
  display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap;
  margin-bottom: 1rem; padding: 0.6rem 0.85rem;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: var(--shadow);
}
#autosync-status { margin-top: 0; flex: 1; }
.sport-grid {
  display: grid; gap: 0.6rem; margin-bottom: 0.5rem;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.sport-card {
  display: flex; flex-direction: column; gap: 0.15rem; text-align: left;
  padding: 0.85rem 0.95rem; cursor: pointer;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-md);
  color: var(--text); font: inherit;
  box-shadow: var(--shadow);
  transition: transform 0.16s ease, box-shadow 0.16s ease, border-color 0.16s ease;
}
.sport-card:hover {
  border-color: var(--border-strong);
  transform: translateY(-1px);
  box-shadow: var(--shadow-lift);
}
.sport-name { font-weight: 650; font-size: 1rem; letter-spacing: -0.01em; }
.sport-count { color: var(--pos); font-size: 0.85rem; font-weight: 600; }
.sport-players { color: var(--muted); font-size: 0.78rem; }

/* Props tab quick-import bar */
.props-import-bar { align-items: center; gap: 0.6rem; }
.props-import-bar strong { color: var(--muted); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.03em; }
#props-import-status { margin-left: 0.3rem; }

/* Line Odds → market prop cross-reference */
.match-props { margin-top: 1rem; border-top: 1px solid var(--border); padding-top: 0.8rem; }
.match-props .props-mini { width: 100%; }
.match-props tr.match-nomodel { opacity: 0.55; }
.use-prop-btn { background: var(--accent); color: #fff; border: none; border-radius: 6px; padding: 0.25rem 0.7rem; cursor: pointer; font-size: 0.85rem; }
.use-prop-btn:hover { filter: brightness(1.1); }

/* Auth widget (header) */
#auth-widget { margin-left: auto; display: flex; align-items: center; gap: 0.6rem; }
.auth-avatar { width: 1.6rem; height: 1.6rem; border-radius: 999px; }
.google-signin {
  display: inline-flex; align-items: center; gap: 0.45rem;
  padding: 0.35rem 0.85rem; border-radius: 999px;
  border: 1px solid var(--border); background: var(--panel);
  color: var(--text); text-decoration: none;
  font-size: 0.85rem; font-weight: 550; white-space: nowrap;
  transition: border-color 0.15s ease;
}
.google-signin:hover { border-color: var(--border-strong); }
.g-mark {
  font-weight: 700;
  /* Google's four brand colours, without shipping their asset. */
  background: conic-gradient(from -45deg, #ea4335, #fbbc05, #34a853, #4285f4, #ea4335);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.auth-user { color: var(--muted); font-size: 0.85rem; }
#logout-btn { font-size: 0.85rem; }

/* Quick check (paste / OCR a pick) */
#quick-text {
  width: 100%;
  padding: 0.6rem 0.7rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  font-family: inherit;
  font-size: 0.95rem;
  resize: vertical;
}
.quick-actions { display: flex; align-items: flex-end; gap: 0.6rem; margin-top: 0.6rem; }
.quick-actions label {
  display: flex; flex-direction: column; gap: 0.25rem;
  font-size: 0.75rem; color: var(--muted); text-transform: uppercase;
}
.quick-actions select {
  padding: 0.45rem 0.6rem; background: var(--bg);
  border: 1px solid var(--border); border-radius: 6px; color: var(--text);
}
#quick-btn { background: var(--accent); color: #fff; border: none; border-radius: 6px; padding: 0.5rem 1.2rem; cursor: pointer; }
#quick-btn:disabled { opacity: 0.6; cursor: default; }
.quick-pick {
  margin-top: 0.7rem; padding: 0.7rem 0.85rem;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
}
.quick-pick.quick-bad { opacity: 0.75; }
.quick-head { font-weight: 600; margin-bottom: 0.4rem; }
.quick-dir { color: var(--accent); text-transform: capitalize; }
.quick-flag {
  font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.03em;
  background: var(--border); color: var(--muted);
  padding: 0.1rem 0.35rem; border-radius: 4px;
}
.quick-stats { display: flex; flex-wrap: wrap; gap: 0.9rem; font-size: 0.85rem; color: var(--muted); margin-bottom: 0.5rem; }
.quick-stats b { color: var(--text); font-size: 0.95rem; }
.quick-splits {
  display: flex; flex-wrap: wrap; gap: 0.75rem;
  font-size: 0.78rem; color: var(--muted);
  padding: 0.45rem 0; margin-bottom: 0.5rem;
  border-top: 1px solid var(--border);
}

/* Hit-rate split cells (props table + quick check) */
#props-table td small, .quick-splits small {
  display: block; font-size: 0.72em; opacity: 0.75; line-height: 1.1;
}
.quick-splits small { display: inline; margin-left: 0.2rem; }

/* Per-pick over/under toggle — only shown when the slip didn't state a side,
   so one ambiguous pick can't force a choice onto the others. */
.quick-row-actions { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.dir-toggle { display: inline-flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
.dir-btn {
  background: var(--bg); border: none; color: var(--muted);
  padding: 0.3rem 0.75rem; cursor: pointer; font-size: 0.82rem;
}
.dir-btn:hover { color: var(--text); }
.dir-btn.on { background: var(--accent); color: #fff; }
#quick-image-row { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.6rem; flex-wrap: wrap; }
/* Reads as a drop target rather than a button, because pasting an image onto
   it is the primary path and a solid button does not invite that. */
.quick-upload {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem;
  padding: 0.85rem 1.3rem; background: var(--bg);
  border: 1px dashed var(--border-strong); border-radius: var(--r-md);
  color: var(--text); font-size: 0.9rem; font-weight: 550; cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.quick-upload::before { content: "⇪"; font-size: 1.05rem; opacity: 0.75; }
.quick-upload:hover {
  border-color: var(--accent); color: var(--accent);
  background: var(--accent-soft); border-style: solid;
}
/* Shown but inert when no reader is configured, so the feature reads as
   "needs setup" rather than silently not existing. */
#quick-image-row.disabled .quick-upload {
  opacity: 0.45; cursor: not-allowed; color: var(--muted);
}
#quick-image-row.disabled .quick-upload:hover {
  border-color: var(--border); color: var(--muted);
}
.copy-note {
  margin-left: 0.5rem; font-size: 0.8rem; color: var(--accent);
}
.ocr-dump { margin-top: 0.7rem; font-size: 0.8rem; color: var(--muted); }
.ocr-dump summary { cursor: pointer; }
.ocr-dump pre {
  margin: 0.4rem 0 0; padding: 0.6rem; background: var(--bg);
  border: 1px solid var(--border); border-radius: 6px;
  white-space: pre-wrap; word-break: break-word; font-size: 0.78rem;
}

/* iOS "Add to Home Screen" hint — shown by app.js only on iOS Safari,
   because Apple gives no install prompt the way Android does. */
#ios-install-hint {
  display: none;
  margin: 0.8rem 0 0;
  padding: 0.7rem 0.9rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.5;
}
#ios-install-hint strong { color: var(--text); }
#ios-install-hint button {
  background: none; border: none; color: var(--accent);
  cursor: pointer; font: inherit; text-decoration: underline; padding: 0;
}

/* ==========================================================================
   Mobile / tablet
   The desktop layout is table- and toolbar-heavy; below 760px the data tables
   become stacked cards (labels come from <thead> via labelTableCells() in
   app.js) and every row of controls goes full width.
   ========================================================================== */
@media (max-width: 760px) {
  header {
    flex-wrap: wrap;
    gap: 0.6rem;
    padding: 0.8rem 1rem;
    padding-top: max(0.8rem, env(safe-area-inset-top));
    position: sticky;
    top: 0;
    z-index: 20;
    background: var(--bg);
  }
  header h1 { font-size: 1.05rem; }
  #auth-widget { margin-left: auto; }

  /* Five tabs won't fit on a phone — scroll them instead of wrapping, and
     hide the scrollbar so it reads as a native segmented control. */
  nav {
    order: 3;
    width: 100%;
    overflow-x: auto;
    gap: 0.3rem;
    padding-bottom: 0.15rem;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  nav::-webkit-scrollbar { display: none; }
  .tab-btn {
    flex: 0 0 auto;
    padding: 0.55rem 0.85rem;
    font-size: 0.88rem;
    min-height: 44px;
  }

  main {
    padding: 1rem 0.85rem;
    padding-bottom: max(2rem, env(safe-area-inset-bottom));
  }

  /* 16px minimum stops iOS Safari zooming in when a field takes focus. */
  input, select, textarea, button { font-size: 16px; }

  .toolbar { flex-wrap: wrap; gap: 0.5rem; }
  .toolbar input, .toolbar select { flex: 1 1 100%; min-height: 44px; }
  .toolbar button { flex: 1 1 auto; min-height: 44px; }
  .props-import-bar strong { flex: 1 1 100%; }

  .card { padding: 0.9rem; }

  /* Forms: one control per line, full width. */
  .model-search-row { flex-wrap: wrap; }
  .model-search-row input { flex: 1 1 100%; min-height: 44px; }
  .model-search-row button { flex: 1 1 100%; min-height: 44px; }

  .model-form, .manual-boost { gap: 0.6rem; }
  .model-form label, .manual-boost label { flex: 1 1 100%; }
  .model-form select, .model-form input,
  .manual-boost select, .manual-boost input {
    width: 100%;
    min-height: 44px;
  }
  /* These three are narrow on desktop via inline width — override. */
  #model-games, #model-market, #model-mult { width: 100% !important; }
  #model-eval-btn, #add-model-to-slip, #promo-parse-btn { width: 100%; min-height: 44px; }

  #promo-input-row { flex-wrap: wrap; }
  #promo-text { flex: 1 1 100%; }

  .quick-actions { flex-wrap: wrap; }
  .quick-actions label { flex: 1 1 100%; }
  .quick-actions select { width: 100%; min-height: 44px; }
  #quick-btn { width: 100%; min-height: 44px; }
  .quick-stats { gap: 0.6rem 1rem; }
  .quick-upload { width: 100%; justify-content: center; min-height: 44px; }
  #quick-image-row .hint { flex: 1 1 100%; }

  #odds-key-row, #odds-pull-row, #kalshi-bulk-row, #sleeper-bulk-row, #cleanup-actions {
    flex-direction: column;
    align-items: stretch;
  }
  #odds-key-row > *, #odds-pull-row > *, #kalshi-bulk-row > *,
  #sleeper-bulk-row > *, #cleanup-actions > * { width: 100%; }
  #odds-key-row button, #kalshi-bulk-btn, #cleanup-actions button { min-height: 44px; }

  .game-row { flex-wrap: wrap; gap: 0.5rem 0.7rem; padding: 0.7rem 0.8rem; }
  .game-sport { min-width: 0; }
  .game-matchup { flex: 1 1 100%; order: 3; }
  .game-status { flex: 1 1 100%; order: 4; }
  .roster-chip { min-height: 40px; display: inline-flex; align-items: center; }

  /* ---- Data tables become cards ---- */
  #props-table, #slip-table { border: none; background: none; border-radius: 0; }
  #props-table thead, #slip-table thead { display: none; }
  #props-table, #slip-table,
  #props-table tbody, #slip-table tbody,
  #props-table tr, #slip-table tr { display: block; width: 100%; }

  #props-table tbody tr, #slip-table tbody tr {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 8px;
    margin-bottom: 0.6rem;
    padding: 0.15rem 0;
    overflow: hidden;
  }

  #props-table tbody td, #slip-table tbody td {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    border-bottom: none;
    padding: 0.32rem 0.8rem;
    font-size: 0.9rem;
  }
  /* Label text comes from the matching <th>, stamped on as data-label. */
  #props-table tbody td[data-label]::before,
  #slip-table tbody td[data-label]::before {
    content: attr(data-label);
    color: var(--muted);
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    flex: 0 0 auto;
  }

  /* Player name is the card heading, not just another row. */
  #props-table tbody td.prop-player, #slip-table tbody td:first-child {
    font-size: 1rem;
    font-weight: 600;
    padding: 0.6rem 0.8rem;
    border-bottom: 1px solid var(--border);
    margin-bottom: 0.2rem;
    white-space: normal;
  }
  #props-table tbody td.prop-player::before, #slip-table tbody td:first-child::before { content: none; }

  /* Checkbox (first col) and Delete (last col) have blank headers. */
  #props-table tbody td:first-child { justify-content: flex-start; gap: 0.6rem; }
  #props-table tbody td:first-child::before {
    content: "Add to slip";
    color: var(--muted); font-size: 0.72rem;
    font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em;
    order: 2;
  }
  #props-table tbody td:first-child input[type="checkbox"] { width: 22px; height: 22px; }
  #props-table tbody td:last-child, #slip-table tbody td:last-child {
    padding: 0.5rem 0.8rem 0.6rem;
  }
  #props-table tbody td:last-child button, #slip-table tbody td:last-child button {
    width: 100%; min-height: 40px;
  }

  /* The empty-state row spans the whole card. */
  .props-empty { display: block; padding: 1rem; }
  .props-empty::before { content: none; }

  .prop-detail-inner .stat, #combo-result .stat { min-width: 46%; }
  #combo-result { padding: 0.9rem; }
  #combo-result .stat-row { gap: 0.8rem; }
  #combo-result .stat .value { font-size: 1.15rem; }

  /* --- Pre-Built Slip legs ---
     .slip-legs is a table too, and it was the one that never got converted
     here alongside #props-table/#slip-table. That mattered once legs became
     expandable: a colspan detail cell takes its min-content width from the
     flex tiles inside it (118px each, seven of them), and a table grows to
     fit its cells rather than wrapping them. Measured at a 390px viewport,
     one open leg stretched the document to 1168px -- the whole page went
     three times too wide and every other column shrank into a strip.
     Block layout removes table sizing from the equation entirely. */
  /* Every selector here is prefixed with .slip-card to outrank the base
     .slip-legs rules, which sit LATER in the file at equal specificity. A
     media query adds no specificity of its own, so without the prefix the
     desktop rules simply win and the mobile layout silently does nothing. */
  .slip-card .slip-legs, .slip-card .slip-legs tbody,
  .slip-card .slip-legs tr, .slip-card .slip-legs td {
    display: block;
    width: 100%;
  }
  /* The base `table` rule sets overflow:hidden for its border radius. Once
     this is a block box that stops being cosmetic and starts silently
     clipping — long stat names and the whole right-hand edge of the detail
     panel just vanished rather than wrapping. */
  .slip-card .slip-legs { margin-top: 0.4rem; overflow: visible; border-radius: 0; }
  /* border-bottom as well as border-top: the base `td` rule draws one, and
     once these are block-level each cell painted its own stub of a line,
     broken at the grid gap. */
  .slip-card .slip-legs td { padding: 0; border-top: none; border-bottom: none; }
  /* Two lines per leg: who and how much, then what and at what price.
     minmax(0, 1fr), not 1fr: a bare 1fr floors at min-content, so a long
     stat_category like "three_pointers_attempted" pushes the price column
     off the screen instead of wrapping. */
  .slip-card .slip-legs tr.leg-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 0.1rem 0.6rem;
    padding: 0.55rem 0;
    border-top: 1px solid var(--border);
  }
  .slip-card .slip-legs .leg-player, .slip-card .slip-legs .leg-pick {
    min-width: 0;
    overflow-wrap: anywhere;
  }
  /* Stat names are single unbroken tokens, so they need an explicit break
     opportunity or they overhang every container they land in. */
  .prop-detail-head, .market-none { overflow-wrap: anywhere; }
  .slip-card .slip-legs .leg-player { grid-column: 1; grid-row: 1; }
  .slip-card .slip-legs .leg-edge   { grid-column: 2; grid-row: 1; text-align: right; }
  .slip-card .slip-legs .leg-pick   { grid-column: 1; grid-row: 2; }
  .slip-card .slip-legs .leg-odds   { grid-column: 2; grid-row: 2; text-align: right; }
  /* Small nested tables keep their shape but scroll sideways. */
  .game-props, .match-props { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .props-mini { min-width: 460px; }

  .spark-row { gap: 3px; }
  .spark-bar { min-width: 22px; font-size: 0.75rem; }

  #site-footer {
    padding: 1rem;
    padding-bottom: max(1rem, env(safe-area-inset-bottom));
  }
}

/* Installed as a standalone app: no browser chrome, so reclaim the space. */
@media (display-mode: standalone) {
  #ios-install-hint { display: none !important; }
}

/* Editable slip legs — adjust the line, side or price to match the book you're
   actually using; the model re-prices that leg and the combo updates. */
.slip-edit {
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--r-sm);
  color: var(--text); padding: 0.3rem 0.45rem; font: inherit; font-size: 0.85rem;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.slip-line, .slip-odds { width: 76px; }
.slip-dir { width: 88px; }
.slip-edit:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
#slip-table td { vertical-align: middle; }
#slip-table .slip-model { font-weight: 650; }
/* Destructive, so it stays quiet until you reach for it. */
button.remove-btn {
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
button.remove-btn:hover {
  background: var(--neg-soft); border-color: var(--neg); color: var(--neg);
}
button.add-btn:hover { border-color: var(--accent); color: var(--accent); }
@media (max-width: 760px) {
  .slip-line, .slip-odds, .slip-dir { width: 100%; min-height: 40px; }
}

/* Suggested slips */
.slip-card {
  margin-bottom: 0.85rem;
  transition: transform 0.16s ease, box-shadow 0.16s ease, border-color 0.16s ease;
}
.slip-card:hover {
  border-color: var(--border-strong);
  transform: translateY(-1px);
  box-shadow: var(--shadow-lift);
}
.slip-actions {
  display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap;
  margin-top: 0.85rem; padding-top: 0.8rem; border-top: 1px solid var(--border);
}
.add-slip-btn {
  background: var(--pos-soft); color: var(--pos);
  border: 1px solid var(--pos-line); border-radius: var(--r-sm);
  padding: 0.5rem 1.05rem; font-size: 0.88rem; font-weight: 650; cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}
.add-slip-btn:hover { background: var(--pos); color: #fff; border-color: var(--pos); }
.add-slip-btn:active { transform: translateY(1px); }
.slip-added {
  color: var(--muted); font-size: 0.82rem;
  opacity: 0; transition: opacity 0.2s ease;
}
.slip-added.show { opacity: 1; }

/* Slip legs expand in place, like the Props table rows. Caret is an indicator;
   the row is the target. */
.leg-caret {
  display: inline-block; width: 1.1em; user-select: none; pointer-events: none;
  color: var(--muted); font-size: 0.8em;
  transition: color 0.15s ease;
}
.leg-row { cursor: pointer; user-select: none; }
.leg-row:hover { background: var(--panel-2); }
.leg-row:hover .leg-caret { color: var(--accent); }
.slip-legs { width: 100%; margin-top: 0.6rem; font-size: 0.9rem; }
.slip-legs td { padding: 0.35rem 0.4rem; border-top: 1px solid var(--border); }
/* Still scoped to .leg-row rather than every td:last-child. The detail row it
   used to also catch is gone, but leaving it unscoped invites the same bug
   back the next time anything spans these columns. */
.slip-legs .leg-row td:last-child { text-align: right; white-space: nowrap; }
#suggest-bar { gap: 0.6rem; align-items: center; flex-wrap: wrap; }

/* Last-N games chart. Position against the dashed threshold carries hit/miss,
   because green-vs-red fails colourblind separation (deutan dE 5.4); misses are
   hatched so the distinction survives without colour. */
.spark-caption { margin: 0.7rem 0 0.4rem; }
.spark-toggle { margin-left: 0.4rem; }
.spark-chart { padding: 0.2rem 0 0; }
.sc-plot {
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: 2px;                       /* surface gap between adjacent bars */
  height: 104px;
  padding-right: 2.2rem;
  border-bottom: 1px solid var(--border);
}
.sc-col { flex: 1; height: 100%; display: flex; align-items: flex-end; }
.sc-bar {
  width: 100%;
  border-radius: 4px 4px 0 0;     /* rounded data-end, anchored to baseline */
  background: var(--pos);
  min-height: 2px;
}
.sc-bar.miss {
  background: var(--neg);
  /* Secondary encoding, so hit/miss is never colour-alone. */
  background-image: repeating-linear-gradient(
    135deg, rgba(255,255,255,0.42) 0 3px, transparent 3px 6px);
}
.sc-threshold {
  position: absolute; left: 0; right: 2.2rem;
  border-top: 1px dashed var(--muted);
  pointer-events: none;
}
.sc-threshold span {
  position: absolute; right: -2.1rem; top: -0.62em;
  font-size: 0.72rem; color: var(--muted);
}

/* Numbers vs graph. Class-driven rather than the [hidden] attribute, because
   .spark-row sets display:flex and an author rule beats [hidden]{display:none}. */
.spark-view.show-numbers .spark-chart { display: none; }
.spark-view:not(.show-numbers) .spark-row { display: none; }

/* Primary view switch: single props vs pre-built slips. Deliberately heavier
   than the book pills below it — this changes what you're looking at, the
   book tabs only filter it. */
.view-toggle {
  display: inline-flex; gap: 0.2rem; margin-bottom: 0.9rem;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 999px; padding: 0.25rem;
}
.view-btn {
  background: transparent; border: none; color: var(--muted);
  padding: 0.48rem 1.15rem; border-radius: 999px; cursor: pointer;
  font-size: 0.9rem; font-weight: 600; white-space: nowrap;
  transition: color 0.15s ease, background 0.15s ease;
}
.view-btn:hover { color: var(--text); }
.view-btn.active {
  background: var(--accent); color: #fff;
  box-shadow: 0 2px 10px -2px var(--accent);
}
/* Class, not [hidden]: an author display rule beats [hidden]{display:none}
   and that collision has already cost this project once. */
.view-off { display: none; }

@media (max-width: 640px) {
  .view-toggle { display: flex; width: 100%; }
  .view-btn { flex: 1 1 50%; padding: 0.55rem 0.6rem; }
}

/* Best-value cards: book first, EV as the headline number. */
.book-tabs { display: flex; gap: 0.4rem; margin-bottom: 0.9rem; flex-wrap: wrap; align-items: center; }
.book-tab {
  background: var(--panel); border: 1px solid var(--border); color: var(--muted);
  padding: 0.42rem 0.95rem; border-radius: 999px; cursor: pointer; font-size: 0.86rem;
  font-weight: 500;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.book-tab:hover { color: var(--text); border-color: var(--border-strong); }
.book-tab.active {
  border-color: var(--accent); color: var(--accent);
  background: var(--accent-soft); font-weight: 600;
}
/* Filters are a rarely-touched refinement, so they collapse behind one control
   rather than pushing the picks below the fold on every visit. */
.book-tab-more {
  margin-left: auto; background: none; border: 1px dashed var(--border-strong);
  color: var(--muted); padding: 0.42rem 0.9rem; border-radius: 999px;
  cursor: pointer; font-size: 0.82rem;
}
.book-tab-more:hover { color: var(--text); border-color: var(--accent); }
.book-tab-more[aria-expanded="true"] { color: var(--accent); border-style: solid; border-color: var(--accent); }
/* Class-driven, not [hidden]: .toolbar sets display:flex and an author rule
   beats [hidden]{display:none}. That exact collision made the graph toggle
   inert once already. */
.toolbar.collapsed { display: none; }

/* Long-form reasoning, kept but folded away. */
.explainer {
  margin: 1.6rem 0 0.5rem;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--panel);
  overflow: hidden;
}
.explainer > summary {
  cursor: pointer; padding: 0.7rem 1rem;
  font-size: 0.84rem; font-weight: 600; color: var(--muted);
  list-style: none; display: flex; align-items: center; gap: 0.5rem;
}
.explainer > summary::-webkit-details-marker { display: none; }
.explainer > summary::before {
  content: "▸"; font-size: 0.75rem; transition: transform 0.18s ease;
}
.explainer[open] > summary { color: var(--text); border-bottom: 1px solid var(--border); }
.explainer[open] > summary::before { transform: rotate(90deg); }
.explainer > summary:hover { color: var(--text); }
.explainer p {
  margin: 0.7rem 1rem; color: var(--muted);
  font-size: 0.84rem; line-height: 1.6;
}
.explainer p:last-child { margin-bottom: 0.9rem; }
.explainer strong { color: var(--text); }
.explainer code { background: var(--bg); }
.slip-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
}
.book-badge {
  display: inline-block; padding: 0.16rem 0.55rem; border-radius: 999px;
  font-size: 0.7rem; font-weight: 650; letter-spacing: 0.03em;
  text-transform: uppercase;
  background: var(--bg); border: 1px solid var(--border);
}
.book-prizepicks { color: #a78bfa; border-color: rgba(167, 139, 250, 0.5); background: rgba(167, 139, 250, 0.1); }
.book-underdog   { color: #fbbf24; border-color: rgba(251, 191, 36, 0.5); background: rgba(251, 191, 36, 0.1); }
.book-sleeper    { color: #22d3ee; border-color: rgba(34, 211, 238, 0.5); background: rgba(34, 211, 238, 0.1); }
/* Book colours are picked for contrast against near-black; on white the
   pastel versions wash out, so they darken with the theme. */
:root[data-theme="light"] .book-prizepicks { color: #6d28d9; }
:root[data-theme="light"] .book-underdog   { color: #a16207; }
:root[data-theme="light"] .book-sleeper    { color: #0e7490; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .book-prizepicks { color: #6d28d9; }
  :root:not([data-theme]) .book-underdog   { color: #a16207; }
  :root:not([data-theme]) .book-sleeper    { color: #0e7490; }
}
.slip-meta { color: var(--muted); font-size: 0.82rem; margin-left: 0.5rem; }
.slip-ev { text-align: right; line-height: 1.15; }
.slip-ev-num {
  font-size: 1.7rem; font-weight: 800; letter-spacing: -0.03em;
}
.slip-ev-num.edge-pos { text-shadow: 0 0 26px var(--pos-line); }
.slip-ev-lab {
  display: block; font-size: 0.64rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.07em; margin-top: 0.2rem;
}
.slip-stats { margin-top: 0.7rem; }
.leg-player { font-weight: 500; }
.leg-odds, .leg-edge { text-align: right; white-space: nowrap; }
.leg-edge { font-variant-numeric: tabular-nums; }
/* Head-to-head context in an expanded prop row. */
.matchup-row { border-top: 1px solid var(--border); padding-top: 0.6rem; margin-top: 0.6rem; }

/* Graph/numbers toggle sits on its own line under the caption, left-aligned,
   rather than trailing the sentence. */
.spark-toggle-row { margin: 0 0 0.35rem; }
.spark-toggle-row .spark-toggle { margin-left: 0; padding-left: 0; }

/* Top picks — the landing view. The edge is the headline; trends are the
   receipts under it, because a bare probability can't be argued with. */
.section-head {
  font-size: 0.78rem; margin: 1.6rem 0 0.75rem;
  text-transform: uppercase; letter-spacing: 0.07em; color: var(--muted);
  display: flex; align-items: baseline; gap: 0.5rem;
}
.section-head::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}
.section-sub {
  font-weight: 400; color: var(--muted); font-size: 0.72rem;
  text-transform: none; letter-spacing: 0; margin-left: 0;
}

.pick-card {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 0.85rem 1.05rem 0.9rem 1.35rem;
  margin-bottom: 0.55rem;
  box-shadow: var(--shadow);
  transition: transform 0.16s ease, box-shadow 0.16s ease, border-color 0.16s ease;
  overflow: hidden;
}
/* A green edge on the edge. Carries "this one is live" down the whole list
   without another line of text. */
.pick-card::before {
  content: "";
  position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: linear-gradient(180deg, var(--pos), var(--pos-line));
}
.pick-card:hover {
  border-color: var(--border-strong);
  transform: translateY(-1px);
  box-shadow: var(--shadow-lift);
}

.pick-main { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }

/* Rank reads as position in the list, so it stays quiet — it is not a score. */
.pick-rank {
  flex: 0 0 auto;
  font-size: 0.78rem; font-weight: 700; color: var(--muted);
  min-width: 1.5rem;
}

.pick-who { min-width: 170px; flex: 1 1 170px; }
.pick-player {
  font-weight: 650; font-size: 1.02rem; display: block;
  letter-spacing: -0.01em; line-height: 1.25;
}
.pick-tags {
  color: var(--muted); font-size: 0.72rem;
  text-transform: uppercase; letter-spacing: 0.05em;
}
.pick-line { flex: 1 1 190px; font-size: 0.95rem; }
/* The side of the bet is the one word that decides the pick — it should not
   look like the stat name next to it. */
.pick-dir {
  font-weight: 700; text-transform: uppercase; font-size: 0.74rem;
  letter-spacing: 0.05em; color: var(--pos);
  background: var(--pos-soft); border: 1px solid var(--pos-line);
  padding: 0.12rem 0.42rem; border-radius: var(--r-sm); margin-right: 0.4rem;
}
.pick-dir.dir-under { color: var(--accent); background: var(--accent-soft); border-color: var(--accent); }
.pick-num { font-weight: 650; font-size: 1.02rem; }
.pick-line .prop-stat { color: var(--muted); }

.pick-right { text-align: right; margin-left: auto; }
.pick-edge {
  font-size: 1.95rem; font-weight: 800; line-height: 1;
  letter-spacing: -0.03em;
  color: var(--pos);
  text-shadow: 0 0 26px var(--pos-line);
}
.pick-edge-lab {
  display: block; font-size: 0.62rem; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--muted); margin-top: 0.22rem;
}
.pick-odds { color: var(--muted); font-size: 0.8rem; margin-top: 0.3rem; }
.pick-odds .book-badge { margin-left: 0.35rem; }

/* Skeletons rather than the word "Loading". The board already returns in well
   under a second; a bare text swap makes that read as a stall because the
   layout jumps twice. These hold the shape the cards are about to take. */
.skel-card {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-md); padding: 0.85rem 1.05rem 0.9rem 1.35rem;
  margin-bottom: 0.55rem; position: relative; overflow: hidden;
}
.skel-card::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--border-strong);
}
.skel-row { display: flex; align-items: center; gap: 1rem; }
.skel-line {
  height: 0.72rem; border-radius: 999px; background: var(--border);
  position: relative; overflow: hidden;
}
.skel-line.tall { height: 1.5rem; }
.skel-stack { display: grid; gap: 0.4rem; flex: 1 1 auto; }
/* Row-shaped placeholder for the games list, which is rows rather than cards. */
.skel-rowcard { padding: 0.75rem 0.9rem; margin-bottom: 0.5rem; }
.skel-rowcard::before { display: none; }
.skel-rowcard .skel-row { gap: 0.9rem; }
.skel-card .skel-trends {
  margin-top: 0.75rem; padding-top: 0.7rem;
  border-top: 1px solid var(--border); display: grid; gap: 0.4rem;
}
/* One sweep across the whole card, not per-element, so the placeholders read
   as a single object loading rather than a dozen independent blinks. */
.skel-card::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, var(--panel-2), transparent);
  transform: translateX(-100%);
  animation: skel-sweep 1.25s ease-in-out infinite;
}
@keyframes skel-sweep {
  100% { transform: translateX(100%); }
}
@media (prefers-reduced-motion: reduce) {
  .skel-card::after { animation: none; opacity: 0.5; }
}

/* Affordance only — the whole card is the click target, so this must never
   swallow the click aimed at the card behind it. */
.pick-caret {
  flex: 0 0 auto; user-select: none; pointer-events: none;
  color: var(--muted); font-size: 0.82rem; line-height: 1;
}
.pick-card { cursor: pointer; user-select: none; }
.pick-card:hover .pick-caret { color: var(--accent); }

/* ---- Player sheet ----
   One panel, one parent, one set of sizing rules. The inline panels it
   replaces each inherited their container's layout — a card, a table row, a
   nested colspan cell — which is how the same content rendered fine in one
   place and dragged the page to 1168px in another. */
.sheet-off { display: none; }
body.sheet-open { overflow: hidden; }

#player-sheet { position: fixed; inset: 0; z-index: 60; }
.sheet-backdrop {
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, 0.62);
  backdrop-filter: blur(2px);
}
.sheet-panel {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: min(680px, 100%);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--panel);
  border-left: 1px solid var(--border);
  box-shadow: var(--shadow-lift);
  animation: sheet-in 0.2s ease both;
  padding-bottom: max(1.5rem, env(safe-area-inset-bottom));
}
@keyframes sheet-in {
  from { transform: translateX(18px); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
.sheet-head {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: flex-start; gap: 1rem;
  padding: 1rem 1.2rem 0.85rem;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
}
.sheet-head h2 { margin: 0; font-size: 1.15rem; letter-spacing: -0.02em; }
.sheet-sub {
  margin: 0.2rem 0 0; color: var(--muted);
  font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em;
}
.sheet-close {
  margin-left: auto; flex: 0 0 auto;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--r-sm);
  color: var(--muted); font-size: 1.35rem; line-height: 1;
  width: 2rem; height: 2rem; cursor: pointer;
}
.sheet-close:hover { color: var(--text); border-color: var(--border-strong); }
.sheet-body { padding: 1rem 1.2rem 0; }
.sheet-body .prop-detail-inner { background: none; padding: 0; }
.sheet-matchups { margin-top: 1.4rem; }

/* On a phone it is a bottom sheet, not a side panel — the thumb is at the
   bottom and a full-height slide-in from the right reads as a page change. */
@media (max-width: 760px) {
  .sheet-panel {
    top: 8vh; left: 0; right: 0; bottom: 0;
    width: 100%;
    border-left: none;
    border-top: 1px solid var(--border);
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    animation-name: sheet-up;
  }
  @keyframes sheet-up {
    from { transform: translateY(24px); opacity: 0; }
    to   { transform: none; opacity: 1; }
  }
  .sheet-head { padding: 0.9rem 1rem 0.75rem; }
  .sheet-body { padding: 0.9rem 1rem 0; }
}

/* Sheet sections all hide themselves when their data isn't available, so a
   player with no weather, no alternates and no news shows no empty headings. */
.sheet-weather:empty, .sheet-news:empty,
.sheet-matchups:empty, .sheet-chatter:empty { display: none; }
.sheet-chatter { margin-top: 1.4rem; }

/* Collapsible sheet section. Headline-shaped when shut so it still reads as
   part of the same stack as the open sections above it, rather than as a
   control bolted on underneath. The count lives in the summary so the thing
   is informative while closed — otherwise you have to open it to find out
   whether it was worth opening. */
.sheet-fold { border: none; background: none; margin: 0; }
.sheet-fold > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--muted);
  padding: 0.35rem 0;
  transition: color 0.15s ease;
}
.sheet-fold > summary::-webkit-details-marker { display: none; }
.sheet-fold > summary::before {
  content: "▸";
  font-size: 0.7rem;
  transition: transform 0.18s ease;
  display: inline-block;
}
.sheet-fold[open] > summary::before { transform: rotate(90deg); }
.sheet-fold > summary:hover { color: var(--text); }
.sheet-fold[open] > summary { color: var(--text); }
/* The rule that .section-head draws, kept so an open fold lines up with the
   headings around it. */
.sheet-fold > summary::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}
.fold-body { padding-top: 0.4rem; }
.chat-post {
  padding: 0.6rem 0.75rem; margin-bottom: 0.5rem;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--r-sm);
}
.chat-text { font-size: 0.86rem; line-height: 1.5; overflow-wrap: anywhere; }
.chat-meta { color: var(--muted); font-size: 0.74rem; margin-top: 0.3rem; }
.sheet-weather, .sheet-news { margin-top: 1.4rem; }
.sheet-news .news-item { padding: 0.5rem 0; border-bottom: 1px solid var(--border); }
.sheet-news .news-item:last-of-type { border-bottom: none; }
.alt-trend { color: var(--muted); }

/* Goblins & demons */
.sheet-alt:empty { display: none; }
.sheet-alt { margin-top: 1.4rem; }
.alt-kind { text-transform: capitalize; font-weight: 600; }
.alt-goblin { color: var(--pos); }
.alt-demon { color: var(--neg); }
.alt-standard { color: var(--muted); }
.alt-be { font-weight: 700; }
/* The line actually on the board, so the alternatives read as alternatives. */
.alt-current > td { background: var(--accent-soft); }
.alt-current .alt-kind::after {
  content: "on board"; margin-left: 0.45rem;
  font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--accent); border: 1px solid var(--accent);
  padding: 0.05rem 0.3rem; border-radius: 4px; font-weight: 600;
}

/* Defensive matchups */
.matchup-table { font-size: 0.85rem; }
.matchup-table th, .matchup-table td { padding: 0.45rem 0.6rem; }
.matchup-table td:not(.mu-name) { text-align: right; }
.matchup-table th:not(:first-child) { text-align: right; }
.mu-name { font-weight: 550; }
.mu-rate { font-weight: 700; }
.mu-thin { color: var(--muted); }
.mu-flag {
  font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.05em;
  background: var(--border); color: var(--muted);
  padding: 0.05rem 0.32rem; border-radius: 4px; margin-left: 0.3rem;
}

.pick-trends {
  margin-top: 0.75rem; padding-top: 0.7rem;
  border-top: 1px solid var(--border);
  display: grid; gap: 0.34rem;
}
.trend {
  display: grid; grid-template-columns: 5.5rem 1fr 3rem 2.7rem;
  align-items: center; gap: 0.6rem; font-size: 0.79rem;
}
.trend-label { color: var(--muted); }
.trend-bar {
  background: var(--bg); border-radius: 999px; height: 5px; overflow: hidden;
  box-shadow: inset 0 0 0 1px var(--border);
}
.trend-bar i {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--pos-line), var(--pos));
}
/* A window that clears the strong-rate bar gets a fuller colour; the rest stay
   muted so the eye lands on the ones that actually carry the pick. */
.trend:not(.trend-strong) .trend-bar i { background: var(--border-strong); }
.trend-count { color: var(--muted); text-align: right; }
.trend-pct { text-align: right; font-weight: 650; }

/* Wide screens: two columns of trends instead of one. A single column
   stretched each bar to ~800px, which reads as an empty gauge rather than a
   hit rate, and made every card twice as tall as it needed to be. */
@media (min-width: 900px) {
  .pick-trends {
    grid-template-columns: 1fr 1fr;
    column-gap: 2.5rem;
  }
}

@media (max-width: 760px) {
  .pick-card { padding-left: 1.15rem; }
  .pick-rank { display: none; }
  .pick-edge { font-size: 1.6rem; }
  .trend { grid-template-columns: 4.5rem 1fr 2.8rem 2.4rem; }

  /* Keep the edge on the right of the player's name rather than dropping it
     onto its own line — the number is the point of the card and pushing it
     below the fold on a phone defeats the whole layout. */
  .pick-main { gap: 0.5rem 0.8rem; }
  .pick-who { flex: 1 1 55%; min-width: 0; }
  .pick-right { margin-left: auto; text-align: right; flex: 0 0 auto; }
  .pick-line { flex: 1 1 100%; order: 3; }
}

/* Phone-only refinements for the elements added in this pass. The header,
   nav, main padding and table-to-card conversion are all handled by the
   760px block below — deliberately not repeated here, because re-declaring
   `header { padding }` at a narrower breakpoint silently drops that block's
   `max(..., env(safe-area-inset-top))` and puts the title back under the
   notch. */
@media (max-width: 640px) {
  .sport-grid { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }
  .slip-head { gap: 0.5rem; }
  .slip-ev { text-align: left; }
  .book-tab-more { margin-left: 0; }
}
