/* Consensus Edge — Slate redesign theme tokens
 * Source: docs/superpowers/specs/2026-05-09-slate-redesign-design.md §4
 */
:root {
  /* FIX 2026-07-12 (UI cleanup Batch 2): --slate-* tokens are now aliases of
     the canonical main.css palette — this block previously defined a parallel
     near-duplicate palette (e.g. --slate-green duplicated --accent's #10b981).
     Also: --slate-accent and --slate-text-1 were referenced throughout this
     file but NEVER defined — every var(--slate-accent, #1ddc7a) usage was
     silently rendering its fallback, a third shade of green. Both are now
     defined, so the fallbacks below are inert. */
  --slate-bg: var(--bg-base);
  --slate-bg-card: var(--bg-card);
  --slate-bg-hover: rgba(16,185,129,0.04);
  /* The wash that marks the card you clicked. Deliberately NEUTRAL: green now
     belongs to --slate-mkt-card.recommended and means "this is our pick", so a
     green tint on a card the user merely clicked would say the wrong thing.
     Replaced --slate-bg-selected (rgba(16,185,129,0.06)) on 2026-08-31. */
  --slate-bg-viewing: rgba(255,255,255,0.05);
  --slate-border: rgba(255,255,255,0.08);
  --slate-border-soft: rgba(255,255,255,0.04);
  /* No -strong rung: --slate-border-strong (16%, 2026-09-01) existed for the
     takeover's back pill alone and went with its inversion on 2026-09-20;
     main.css --border-strong is still there for anything that needs it. */
  --slate-text: var(--text-primary);
  --slate-text-1: var(--text-primary);
  --slate-text-2: var(--text-muted);
  --slate-text-3: var(--text-dim);
  /* #52525b was 2.44:1 — WCAG 1.4.3 AA fail (FIX 2026-07-18 → #83838d, 5.0:1).
     FIX 2026-07-23: still read as dim gray; brightened to 6.9:1 with the rest
     of the muted ramp. Stays the lowest rung, below --text-dim. */
  --slate-text-4: #b0b0ba; /* was #9c9ca6 (2026-09-11 ladder) */
  --slate-green: var(--accent);
  --slate-green-bright: var(--accent-blue-light);
  --slate-blue: #60a5fa;
  --slate-amber: var(--accent-amber);
  --slate-red: var(--accent-red);
  --slate-accent: var(--accent);
  --slate-mono: var(--font-mono);
}

/* ===== Slate redesign — Tier 2 Filterable Board ===== */
.slate-section-h {
  display: flex; align-items: baseline; justify-content: space-between;
  margin: 0 0 12px;
}
.slate-section-title {
  font-size: 17px; color: var(--slate-text); text-transform: uppercase;
  letter-spacing: 0.18em; font-weight: 700;
}
.slate-section-meta { font-size: 16px; color: var(--slate-text-4); font-family: var(--slate-mono); }

.slate-row {
  display: grid;
  grid-template-columns: 56px 1fr 130px 96px 88px 80px 56px;
  gap: 12px; padding: 12px; align-items: center;
  border-bottom: 1px solid var(--slate-border-soft);
  cursor: pointer; transition: background 0.12s;
}
.slate-row:hover { background: var(--slate-bg-hover); }
.slate-row-sport {
  font-family: var(--slate-mono); font-size: 16px;
  color: var(--slate-text-3); font-weight: 700;
  padding: 4px 6px; background: rgba(255,255,255,0.04);
  border-radius: 4px; text-align: center; letter-spacing: 0.06em;
}
.slate-row-game { font-size: 16px; color: var(--slate-text); font-weight: 700; line-height: 1.3; }
.slate-row-pick { font-size: 17px; color: var(--slate-text); font-weight: 600; }

.slate-edge-chip {
  padding: 4px 8px; border-radius: 4px;
  font-family: var(--slate-mono); font-size: 16px;
  font-weight: 800; text-align: center;
}
.slate-edge-chip.pos {
  background: rgba(16,185,129,0.12);
  color: var(--slate-green);
  border: 1px solid rgba(16,185,129,0.3);
}
.slate-edge-chip.zero {
  color: var(--slate-text-4); font-weight: 600;
  border: 1px dashed var(--slate-border);
}
/* FIX 2026-07-20: a real edge on a pick the pipeline did NOT recommend is
   muted, not green. Green is reserved for BET/LEAN/LOCK — it means "we
   recommend this", not "this number is positive". */
.slate-edge-chip.muted {
  background: transparent;
  color: var(--slate-text-3);
  border: 1px solid var(--slate-border);
  font-weight: 700;
}
/* The pipeline's sizing grade, rendered INSIDE the EV chip (stacked above the
   number) so the verdict and the number are never separated — and so the row's
   child count is unchanged, which the positional phone selectors depend on. */
.slate-grade-chip {
  display: block;
  font-size: 16px; font-weight: 800;
  letter-spacing: 0.04em; line-height: 1.25;
  white-space: nowrap;
}
.slate-grade-chip.actionable { color: var(--slate-green); }
.slate-grade-chip.muted { color: var(--slate-text-3); }
.slate-row-add {
  background: transparent; color: var(--slate-text-3);
  border: 1px solid var(--slate-border);
  padding: 5px 0; border-radius: 4px;
  cursor: pointer; font-size: 17px; font-weight: 700;
  font-family: var(--slate-mono);
}
.slate-row-add:hover { border-color: var(--slate-green); color: var(--slate-green); }

/* ===== Slate redesign — Tier 3 Live Strip ===== */
.slate-live-strip { margin-top: 28px; }
.slate-live-row {
  display: grid;
  grid-template-columns: 56px 1fr 110px 90px 76px;
  gap: 12px; padding: 10px 12px; align-items: center;
  border-bottom: 1px solid var(--slate-border-soft);
  background: rgba(239,68,68,0.02);
}
.slate-live-status {
  font-family: var(--slate-mono); font-size: 16px;
  color: var(--slate-red); font-weight: 800;
  letter-spacing: 0.06em; margin-top: 2px;
}
.slate-live-score { font-family: var(--slate-mono); font-size: 16px; color: var(--slate-text-2); }
.slate-live-dot {
  display: inline-block; width: 6px; height: 6px;
  background: var(--slate-red); border-radius: 50%;
  animation: slate-pulse 2s infinite; margin-right: 6px;
  vertical-align: middle;
}
@keyframes slate-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

/* ===== Slate redesign — Drilldown takeover ===== */
.slate-drilldown[hidden] { display: none; }

/* Task 7 Step 1: freshness banner, first element of the takeover — a
   staleness disclosure that arrives after the numbers is an excuse, not a
   warning. Reuses .ob-freshness/.ob-freshness-text/.ob-stale/.ob-provisional
   from main.css (renderCardFreshness) so the card and takeover agree; this
   rule only relocates the rounded top corner here now that it renders first. */
.slate-takeover-freshness {
  padding: 10px 24px;
  background: var(--slate-bg-card);
  border-bottom: 1px solid var(--slate-border);
  border-radius: 12px 12px 0 0;
}
.slate-takeover-freshness .ob-stale { color: var(--slate-amber); }
.slate-takeover-freshness .ob-provisional { color: var(--slate-amber); }

.slate-takeover-h {
  padding: 16px 24px;
  border-bottom: 1px solid var(--slate-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  background: var(--slate-bg-card);
}

/* Task 7 Steps 2/3: one card per priced market under the header. `.dim` is a
   market buildDrilldown marked NOT a play (isPlay: tier > 0) — suppressed or
   below the sizing floor; its own `.slate-mkt-card-reason` states which,
   from the entry's own `reason` string (never synthesized here). `.selected`
   is the market currently driving the edge-math/line-shopping sections. */
.slate-mkt-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  padding: 16px 24px 0;
  background: var(--slate-bg-card);
}
.slate-mkt-card {
  background: var(--slate-bg);
  border: 1px solid var(--slate-border);
  border-radius: 8px;
  padding: 12px;
  text-align: left;
  cursor: pointer;
  font-family: var(--slate-mono);
  color: var(--slate-text);
}
.slate-mkt-card:hover { border-color: var(--slate-text-3); }
.slate-mkt-card.selected {
  background: var(--slate-bg-viewing);
}
/* The model's pick. Independent of .selected, so it stays put when the user
   clicks another market -- that separation IS the feature. Emitted only where
   buildDrilldown's `recommended` names the market, and that field is drawn
   only from markets where isPlay is true, so this class and .dim can never
   coexist. The honesty rule ("green never lands on a declined market") is
   therefore a property of the model, not of CSS specificity. Pinned by
   tests/js/game_drilldown.test.js and tests/test_signal_stack_css.py. */
.slate-mkt-card.recommended {
  border-color: var(--slate-green);
  box-shadow: 0 0 0 1px var(--slate-green);
}
.slate-mkt-card.dim { opacity: 0.55; }
.slate-mkt-card-label {
  font-size: 16px; text-transform: uppercase; letter-spacing: 0.1em;
  font-weight: 700; color: var(--slate-text-3);
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
}
.slate-mkt-card-badge {
  font-size: 16px; text-transform: none; letter-spacing: normal;
  font-weight: 700; color: var(--slate-text-2);
}
.slate-mkt-card-verdict {
  font-size: 17px; margin-top: 6px; color: var(--slate-text);
  line-height: 1.3;
}
.slate-mkt-card-row {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-top: 8px; font-size: 17px;
}
.slate-mkt-card-ev { color: var(--slate-text); font-weight: 700; }
/* 2026-08-31 (Finding 1, final review): the base rule above used to be
   var(--slate-green), so EVERY market card's EV figure rendered green --
   not just the recommended one. .dim greyed the declined-market case, but on
   any game with 2+ plays the non-recommended, non-dim card still painted
   green. Green now belongs to .recommended alone.

   CORRECTED 2026-09-01. This comment previously claimed the .recommended
   rule is (0,3,1) and "clears" the .dim rule at (0,2,1). Both tuples were
   wrong and so was the conclusion. `.slate-mkt-card.recommended
   .slate-mkt-card-ev` and `.slate-mkt-card.dim .slate-mkt-card-ev` are each
   THREE chained/descendant class selectors with no type selector -- (0,3,0)
   apiece, an exact tie. The .dim rule is written after this one, so if both
   ever matched the same card, .dim would WIN, not lose.

   What actually keeps this correct is a data invariant, not the cascade:
   .recommended and .dim can never both be set. buildDrilldown draws
   `recommended` only from markets.filter(m => m.isPlay), and .dim is exactly
   !isPlay (game_drilldown.js). Pinned by the pairing test in
   tests/js/game_drilldown.test.js -- if that invariant ever breaks, this
   rule loses the tie silently and a declined market's EV turns green.

   Do NOT add -ev to the .selected brightening group below. That group is
   (0,3,0) too and is written later still, so it would beat BOTH of these on
   a selected card. Pinned by tests/test_signal_stack_css.py. */
.slate-mkt-card.recommended .slate-mkt-card-ev { color: var(--slate-green); }
.slate-mkt-card.dim .slate-mkt-card-ev { color: var(--slate-text-3); font-weight: 400; }
.slate-mkt-card-stake { color: var(--slate-text-2); }
.slate-mkt-card-reason {
  margin-top: 6px; font-size: 16px; color: var(--slate-text-4); font-style: italic;
}
/* 2026-08-31: selection used to be border-only, so a selected card's text
   was indistinguishable from an unselected one's -- and on a .dim card,
   whose 0.55 opacity fades the border too, selecting it barely registered.

   ORDER AND SPECIFICITY ARE LOAD-BEARING HERE, and both failure modes are
   invisible in review:
   1. `.selected.dim` (0,3,0) exists only to beat `.selected` (0,2,0) and
      keep the live-play green off a market buildDrilldown declined. At
      (0,3,0) it beats `.selected`'s (0,2,0) from ANYWHERE in the file --
      position doesn't matter, specificity alone decides it. That is exactly
      why deleting it as "redundant" is the danger: nothing else in the
      cascade stands in for it.
   2. The brightening group must NOT grow to cover `.slate-mkt-card-ev`.
      This group is (0,3,0) -- three chained/descendant classes, no type
      selector -- and `.slate-mkt-card.dim .slate-mkt-card-ev` is (0,3,0)
      too: an exact tie, decided by document order, and this group is later.
      (Both tuples read (0,2,1) here until 2026-09-01; the tie was right, the
      arithmetic was not.) So adding -ev here would brighten a declined
      market's EV to a live play's weight. The dim EV stays grey on a
      selected dim card for free, because `.selected.dim` matches `.dim`.
   Both pinned by tests/test_signal_stack_css.py. */
.slate-mkt-card.selected .slate-mkt-card-label,
.slate-mkt-card.selected .slate-mkt-card-badge,
.slate-mkt-card.selected .slate-mkt-card-verdict,
.slate-mkt-card.selected .slate-mkt-card-stake { color: var(--slate-text); }
.slate-mkt-card.selected.dim {
  opacity: 1;
  border-color: var(--slate-text-3);
  box-shadow: 0 0 0 1px var(--slate-text-3);
}
/* The way OUT of the takeover, rendered twice on purpose (the header and the
   sticky float below): one look, one exit. INVERTED 2026-09-20 (Bill: "not
   bright enough and doesn't stand out, especially mobile"). Until then this
   was a tertiary control -- --slate-text-3 at weight 400 on --bg-elevated
   with a 16% ring, the 09-01 step up from a transparent fill -- which left
   it fainter than "Share" / "Log bet" beside it and than the body copy the
   float rides over, and a dim twin of the Ask FAB it sits under on phones.
   A --slate-text fill is the brightest neutral the palette has; the ink is
   the same dark the feedback FAB and tutorial play button put on a bright
   fill (main.css, ~17:1 here). Still not green: since 2026-08-31 green
   means the model's pick, and this is navigation. Padding, size and font
   are unchanged so the 44px phone floor (639px block) and the height
   .slate-takeover-body reserves for the float (50px / 60px) still hold.
   Pinned by tests/test_signal_stack_css.py. */
.slate-takeover-back {
  background: var(--slate-text);
  color: #0d1117;
  border: 1px solid var(--slate-text);
  padding: 6px 11px;
  border-radius: 999px;
  font-size: 16px;
  font-weight: 700;
  cursor: pointer;
  font-family: var(--slate-mono);
}
/* Hover greys the fill one rung, ring with it; the ink is inherited and must
   NOT be redeclared here -- the pre-inversion hover carried
   `color: var(--slate-text)` (its .slate-takeover-action siblings' recipe),
   which on this fill would paint white on white and blank the label. This
   rule used to paint --slate-green, which after 2026-08-31 is reserved for
   the model's recommendation; a navigation control turning green on hover
   was the last NAVIGATION control where it still meant "you are pointing at
   a button". (.slate-takeover-add below still paints #059669 on hover --
   left alone deliberately, since its base state is already a green CTA and
   the hover isn't introducing green meaning, just continuing it.) */
.slate-takeover-back:hover { background: var(--slate-text-2); border-color: var(--slate-text-2); }

/* 2026-09-13: the persistent exit (Bill: back to the slate from anywhere in
   the analysis). Rendered by slate.js _floatBackHtml as the LAST child of
   #slate-drilldown. Sticky, not fixed -- it binds to whichever ancestor
   scrolls (#view-premium above 1024px, the document below; slate.js
   _scrollerFor) and stays inside the takeover's own column, so on desktop it
   can never cover the sidebar or the feedback FAB that owns the viewport's
   bottom-left (main.css .feedback-fab). While the analysis is taller than the
   screen it rides the bottom edge; at the end it settles into normal flow.
   The wrapper spans the column but takes no clicks; only the button does.
   The button keeps .slate-takeover-back, so the 44px phone floor in the
   639px block below covers it. Guarded by tests/test_takeover_float_back.py. */
.slate-takeover-float {
  position: sticky;
  /* --parlay-slip-h: the props parlay slip shares this corner (app.js
     _publishParlaySlipHeight); 0 when it is hidden. */
  bottom: calc(16px + var(--parlay-slip-h, 0px));
  z-index: 5;
  display: flex;
  justify-content: flex-start;
  padding: 8px 24px;
  pointer-events: none;
}
.slate-takeover-back--float {
  pointer-events: auto;
  /* The Ask FAB's lift (chat.css, 0 4px 16px), not the card's 1px 3px: a
     white pill riding over body copy has to sit ABOVE the text, not on it. */
  box-shadow: var(--shadow-hover);
}
/* The fixed bottom nav shows at <=639px AND in the 640-799px band (main.css
   sets .bottom-nav display:block in both), 65px plus the safe-area inset --
   the same clearance .main-content reserves at <=639px. 799, not 639, or the
   button sits under the nav for iPhone landscape and small tablets. */
@media (max-width: 799px) {
  .slate-takeover-float { bottom: calc(65px + env(safe-area-inset-bottom) + 12px + var(--parlay-slip-h, 0px)); }
}

.slate-takeover-title-block { flex: 1; text-align: center; }
.slate-takeover-title { font-size: 18px; font-weight: 800; color: var(--slate-text); line-height: 1.2; }
.slate-takeover-title-ctx {
  font-size: 16px; color: var(--slate-text-3);
  font-family: var(--slate-mono); margin-top: 4px;
}
/* 2026-09-30: a started game's panel names what it shows (CESlateMarket.startedNote). */
.slate-takeover-started { color: var(--slate-amber); font-weight: 700; }

.slate-takeover-actions { display: flex; gap: 8px; }
.slate-takeover-action {
  background: transparent; border: 1px solid var(--slate-border);
  color: var(--slate-text-2); padding: 7px 13px; border-radius: 5px;
  font-size: 16px; cursor: pointer; font-weight: 700;
  font-family: var(--slate-mono);
}
.slate-takeover-action:hover { border-color: var(--slate-text-2); color: var(--slate-text); }
.slate-takeover-add {
  background: var(--slate-green); color: var(--slate-bg);
  border-color: var(--slate-green);
}
.slate-takeover-add:hover { background: #059669; border-color: #059669; }

/* Summary mosaic (4-cell, scaled larger than hero card) */
.slate-takeover-summary {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--slate-border-soft);
  border-bottom: 1px solid var(--slate-border);
}
.slate-ts-cell {
  background: var(--slate-bg);
  padding: 18px 12px;
  text-align: center;
  cursor: default;
}
.slate-ts-cell-l {
  font-size: 16px; color: var(--slate-text-3);
  text-transform: uppercase; letter-spacing: 0.14em; font-weight: 700;
}
.slate-ts-cell-v {
  font-family: var(--slate-mono);
  font-size: 28px; font-weight: 800;
  color: var(--slate-text);
  margin-top: 8px; line-height: 1;
}
.slate-ts-cell-v.accent { color: var(--slate-green); }
.slate-ts-cell-v.lock { color: var(--slate-blue); }
.slate-ts-cell-v.book {
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 18px;
}
.slate-ts-band {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px dashed var(--border);
}
.slate-ts-band-range {
  font-size: 17px;
  color: var(--text-dim);
  letter-spacing: 0.01em;
}
.slate-ts-band-mark {
  font-size: 16px;
  color: var(--text-secondary);
  margin-top: 2px;
}
.slate-ts-band-mark b { color: var(--text-primary); }
.slate-ts-grade {
  margin: 10px 0 0;
  padding: 10px 12px;
  border-left: 3px solid var(--accent);
  border-radius: 6px;
  background: var(--bg-surface);
  color: var(--text-secondary);
  font-size: 17px;
  line-height: 1.5;
}
.slate-ts-grade.is-refusal { border-left-color: var(--accent-amber, #f59e0b); }
.slate-ts-grade-k {
  font-size: 16px; font-weight: 700; color: var(--text-dim);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.slate-ts-grade b { color: var(--text-primary); }
/* Edge grades as coloured chips, the tutorial's step-5 tags (Bill,
   2026-09-25): the analysis grade line and All Bets print them through
   CESlateGradeChip.edgeGradeTagHTML. */
.slate-grade-tag {
  display: inline-flex; align-items: center; vertical-align: 1px;
  font-family: var(--font-mono, 'IBM Plex Mono', monospace); font-size: 16px; font-weight: 700;
  letter-spacing: 0.06em; line-height: 1; padding: 3px 8px; border-radius: 4px; border: 1px solid transparent;
}
.slate-grade-tag--a { background: var(--accent-green-bg); color: var(--accent-green); border-color: rgba(34,197,94,0.35); }
.slate-grade-tag--b { background: rgba(16,185,129,0.08); color: var(--accent); border-color: rgba(16,185,129,0.28); }
.slate-grade-tag--lean { background: var(--accent-amber-bg); color: var(--accent-amber); border-color: rgba(245,158,11,0.35); }
.slate-grade-tag--defer { background: var(--accent-amber-bg); color: var(--accent-amber); border-color: rgba(245,158,11,0.35); }
.slate-grade-tag--suspect { background: var(--accent-red-bg); color: var(--accent-red); border-color: rgba(239,68,68,0.35); }
.slate-ts-compelled {
  margin: 10px 0 0;
  padding: 10px 12px;
  border-left: 3px solid var(--text-dim);
  border-radius: 6px;
  background: var(--bg-surface);
  color: var(--text-secondary);
  font-size: 17px;
  line-height: 1.5;
}
.slate-ts-compelled-k {
  font-weight: 700;
  color: var(--text-primary);
}
.slate-ts-compelled-range {
  margin-top: 6px;
  font-family: var(--font-mono);
  font-size: 17px;
}
.slate-ts-compelled-range-v { color: var(--text-dim); }
.slate-ts-compelled-range-m { color: var(--text-secondary); margin-left: 8px; }
.slate-ts-compelled-range-m b { color: var(--text-primary); }
.slate-ts-cell-sub {
  font-size: 16px; color: var(--slate-text-4);
  margin-top: 6px; font-family: var(--slate-mono);
}

/* Narrative blocks */
.slate-takeover-body {
  padding: 24px;
  /* the sticky Back button (.slate-takeover-float, measured at 50px --
     8px padding + the 34px button + 8px -- Playwright at 1280x800, task-5
     wave 4) rides the bottom edge; reserve its height so the last card's
     text never sits under it */
  padding-bottom: calc(24px + 50px);
  display: grid;
  /* FIX 2026-07-24: bare 1fr sizes the track to the widest child's
     min-content (grid items default min-width:auto) — one wide child
     (props rows, line-shopping table) stretched EVERY drilldown panel to
     ~469px at a 390px viewport, pushing the Signal Stack weight column
     off-screen. minmax(0, 1fr) pins the track to the container width so
     each panel's own overflow-x:auto container handles its excess. */
  grid-template-columns: minmax(0, 1fr);
  gap: 18px;
}
/* 2026-09-07: ONE card for every analysis section. slate.js renders each
   section through CESectionCard.render (static/js/section_card.js); the
   five wrapper families (.slate-narr-block, .slate-sigstack, .slate-ctx,
   .gd-chalk, id-only hosts) and every green/grey/amber/blue left rule are
   gone from the drilldown. .slate-narr-block above is kept for any
   non-drilldown consumer only. The 2026-09-03 heading contract
   (.slate-narr-h / .slate-ctx-head / .gd-chalk-label) collapses to the one
   .slate-sec-h below. Guarded by tests/test_takeover_section_consistency.py.
   Spec: docs/superpowers/specs/2026-09-07-analysis-panel-uniform-sections-design.md */
.slate-sec {
  background: var(--slate-bg-card);
  border: 1px solid var(--slate-border);
  border-radius: 10px;
  padding: 18px 20px;
  min-width: 0;
}
.slate-sec-head {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px 10px;
  margin-bottom: 12px;
}
.slate-sec-h {
  font-size: 16px; color: var(--slate-text-3);
  text-transform: uppercase; letter-spacing: 0.16em;
  font-weight: 700; margin: 0; line-height: 1.4;
}
.slate-sec-meta {
  margin-left: auto; font-family: var(--slate-mono);
  font-size: 16px; color: var(--slate-text-3);
}
.slate-sec-badge {
  font-size: 16px; font-weight: 700; letter-spacing: 0.06em;
  padding: 1px 8px; border-radius: 10px; text-transform: uppercase;
}
.slate-sec-badge--amber { background: var(--accent-amber-bg); color: var(--accent-amber); }
.slate-sec-badge--blue { background: rgba(96,165,250,0.15); color: var(--slate-blue); }
/* The ⓘ. Same 17px dot as .info-btn (main.css) but the hit-area expansion
   is UNCONDITIONAL: this control sits in the body on every viewport, and a
   desktop trackpad user with a motor impairment needs the 44px reach too.
   Guarded by tests/test_slate_touch_targets.py. */
.slate-sec-info {
  position: relative; flex: 0 0 auto;
  width: 17px; height: 17px; border-radius: 50%;
  border: 1.5px solid var(--slate-green); background: transparent;
  color: var(--slate-green); font-size: 16px; font-weight: 700;
  font-family: var(--slate-mono); line-height: 1;
  cursor: pointer; display: inline-flex; align-items: center;
  justify-content: center; padding: 0; transition: background 0.15s, color 0.15s;
}
.slate-sec-info::after { content: ''; position: absolute; inset: -14px; }
.slate-sec-info:hover,
.slate-sec-info:focus-visible { background: var(--slate-green); color: var(--slate-bg); outline: none; }
.slate-sec-info:focus-visible { box-shadow: 0 0 0 2px var(--slate-bg), 0 0 0 4px var(--slate-green); }
.slate-sec-info[aria-expanded="true"] { background: var(--slate-green); color: var(--slate-bg); }
.slate-sec-def {
  font-size: 17px; line-height: 1.6; color: var(--slate-text-2);
  padding: 10px 12px; margin-bottom: 12px;
  background: rgba(255,255,255,0.03); border-radius: 6px;
}
.slate-sec-def p { margin: 0; }
.slate-sec-def p + p { margin-top: 6px; }
.slate-sec-def-why { color: var(--slate-text); }
.slate-sec-body { min-width: 0; }
/* Sub-labels inside a card (Injuries, Weather, Signal detail, AI modifier,
   the three favorites rows). One neutral style; green is for state only. */
.slate-sub-h {
  font-size: 16px; font-weight: 700; color: var(--slate-text-3);
  text-transform: uppercase; letter-spacing: 0.12em;
  margin: 12px 0 6px;
}
.slate-sub-h:first-child { margin-top: 0; }
.slate-sub-h .slate-ss-count { font-weight: 400; text-transform: none; letter-spacing: 0; margin-left: 6px; }
/* NHL Matchup card (2026-10-11, static/js/matchup_section.js). Two team
   columns that stack on a phone: min(240px, 100%) so a narrow card never
   forces a track wider than itself. No element here carries [hidden], so no
   display rule needs a [hidden] pairing. */
.mu-meta { font-size: 16px; color: var(--slate-text-3); margin: 0 0 10px; overflow-wrap: anywhere; }
.mu-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); gap: 14px 24px; }
.mu-team { min-width: 0; }
.mu-team-h {
  font-size: 16px; font-weight: 700; color: var(--slate-text-3);
  text-transform: uppercase; letter-spacing: 0.08em; margin: 0 0 6px; overflow-wrap: anywhere;
}
.mu-row { font-size: 16px; line-height: 1.5; color: var(--slate-text-2); margin: 0 0 4px; overflow-wrap: anywhere; }
.mu-goalie { color: var(--slate-text); }
.mu-name { font-weight: 600; }
.mu-sub { font-size: 16px; font-weight: 700; color: var(--slate-text-3); margin: 10px 0 4px; }
.mu-shooter { display: flex; justify-content: space-between; gap: 12px; }
.mu-val { font-family: var(--slate-mono); flex: 0 0 auto; }
.mu-chip {
  display: inline-block; font-size: 16px; font-weight: 700; line-height: 1.4;
  padding: 0 8px; border-radius: 10px; margin-left: 6px; vertical-align: baseline;
}
.mu-chip.confirmed { background: var(--accent-bg); color: var(--slate-green); }
.mu-chip.likely { background: var(--accent-amber-bg); color: var(--slate-amber); }
/* Disclosure summaries inside a card (NFL methods tree, NFL player props):
   were inline-styled; a class so the 44px touch floor can reach them. */
.slate-sec-disclose {
  cursor: pointer; color: var(--slate-green); font-weight: 600;
  min-height: 44px; display: flex; align-items: center;
}
/* Signal detail rows gain a magnitude cell once /signals-fired lands
   (static/js/signal_merge.js). Fourth column, mono, right-aligned. */
.slate-ss-sig { grid-template-columns: 1fr auto 52px auto; }
.slate-ss-sig-mag {
  font-family: var(--slate-mono); font-weight: 700; font-size: 16px;
  white-space: nowrap; text-align: right; min-width: 64px; color: var(--slate-text-3);
}
.slate-ss-sig-mag.good { color: var(--accent-green); }
.slate-ss-sig-mag.bad { color: var(--accent-red); }
.slate-ss-sig-name a { color: inherit; text-decoration: none; border-bottom: 1px dotted var(--slate-text-3); }
.slate-ss-sig-name a:hover { color: var(--slate-text); border-bottom-style: solid; }
/* Group label above each run of cards: Verdict, Evidence, Odds & Lines, Props.
   Hidden by slate.js _pruneEmptyGroups() when nothing follows it.
   2026-10-15 (Bill): it had the card title's exact treatment (16px, grey,
   uppercase, tracked -- .slate-sec-h), so the four sections read as four
   more cards. It is now a tier above: larger, full-strength text, title case. */
.slate-sec-group {
  display: flex; align-items: center; gap: 12px;
  font-size: 20px; font-weight: 800; color: var(--slate-text);
  letter-spacing: 0.01em; line-height: 1.2;
  margin-top: 18px;
  scroll-margin-top: 16px;
}
.slate-sec-group:first-child { margin-top: 0; }
.slate-sec-group:focus { outline: none; }
/* A jump chip scrolls to the label. Up to 1024px the document scrolls under the fixed 56px .top-nav,
   so clear it, as .slate-sec[data-sec="experts"] does below; above 1024px #view-premium scrolls. */
@media (max-width: 1024px) {
  .slate-sec-group { scroll-margin-top: calc(56px + env(safe-area-inset-top, 0px) + 12px); }
}
/* Jump bar + the Verdict label, between the header and the market cards. */
.slate-takeover-nav {
  display: grid; gap: 16px;
  padding: 20px 24px 0;
}
.slate-sec-jump { display: flex; flex-wrap: wrap; gap: 8px; }
.slate-sec-jump-chip {
  display: inline-flex; align-items: center;
  /* 44px on every viewport, as .slate-sec-info: the bar's whole job is to be hit. */
  min-height: 44px; padding: 6px 14px;
  border: 1px solid var(--slate-border); border-radius: 999px;
  background: transparent; color: var(--slate-text-2);
  font: inherit; font-size: 16px; font-weight: 700; cursor: pointer;
}
.slate-sec-jump-chip:hover { border-color: var(--slate-text-2); color: var(--slate-text); }
.slate-sec-jump-chip[hidden] { display: none; }
.slate-sec-group::after { content: ''; flex: 1; height: 1px; background: var(--slate-border); }
/* The display:flex above outranks the UA [hidden] rule, so without this the
   empty labels _pruneEmptyGroups() hides stayed on screen (found 2026-09-28
   by tests/test_hidden_needs_display_override.py once it could follow that
   site's classList guard). */
.slate-sec-group[hidden] { display: none; }
/* _pruneEmptyGroups hides the body itself when every group in it was pruned and every host is empty. */
.slate-takeover-body[hidden] { display: none; }
/* Async hosts render '<div id="..."></div>' until their fetch lands; an
   empty grid item still takes a gap, so hide it outright. Replaces the
   2026-09-04 `> *:not(:empty)` divider rule, which the bordered cards make
   redundant (a divider above a card double-borders). */
.slate-takeover-body > :empty { display: none; }
.slate-narr-body {
  color: var(--slate-text-2);
  font-size: 16px; line-height: 1.7;
}
.slate-narr-body p { margin: 0; }
/* The Market read (2026-09-27; slate.js _renderMarketRead): label over fact
   on phones, label beside fact from 640px. Text colours only -- facts, not a
   call. */
.slate-mread { margin: 0; display: grid; gap: 12px; }
.slate-mread-row { display: grid; grid-template-columns: 1fr; gap: 2px; }
.slate-mread dt { font-family: var(--slate-mono); font-size: 16px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--slate-text-3); }
.slate-mread dd { margin: 0; font-size: 17px; line-height: 1.5; color: var(--slate-text-2); }
@media (min-width: 640px) {
  .slate-mread-row { grid-template-columns: 140px 1fr; gap: 16px; align-items: baseline; }
}
/* The experts section (2026-09-28; rebuilt 2026-10-13 on the Market read rows above). Plain ink: green
   stays graded-only. The framing line leads the body; the consensus lines stack as blocks in one dd. */
.slate-exp-frame { margin: 0 0 16px; font-size: 17px; line-height: 1.5; color: var(--slate-text-2); }
.slate-exp-line + .slate-exp-line { margin-top: 4px; }
.slate-exp-rows { margin: 0; padding: 0; list-style: none; display: grid; gap: 8px; }
/* The Track record link is a 44px-tall flex box on its own row (the dd holds nothing else), never an
   inline link inside wrapping text. */
.slate-exp-rank { display: inline-flex; align-items: center; min-height: 44px; color: inherit; text-decoration: underline; }
/* The board chip scrolls the analysis to this section. Up to 1024px the document is the scroller and the
   fixed .top-nav (56px + the safe-area inset, main.css) overlays its top, so the section's title, badge
   and count would land behind it. Above 1024px #view-premium scrolls and already starts below the nav. */
@media (max-width: 1024px) {
  .slate-sec[data-sec="experts"] { scroll-margin-top: calc(56px + env(safe-area-inset-top, 0px) + 8px); }
}
/* One expert per row (2026-09-29): the name link on its own 44px line, outlet and pick below it as
   plain wrapping text, the CLV link a 44px box beside both. An inline-flex 44px link inside wrapping
   text made every wrapped line holding a link 44px tall: uneven, airy rows on phones. */
.slate-exp-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "name clv" "detail clv"; column-gap: 12px; align-items: center; font-size: 16px; line-height: 1.5; color: var(--slate-text-2); }
.slate-exp-name { grid-area: name; justify-self: start; }
.slate-exp-row a { color: inherit; text-decoration: underline; }
.slate-exp-row a.slate-exp-name { min-height: 44px; display: flex; align-items: center; }
.slate-exp-detail { grid-area: detail; min-width: 0; overflow-wrap: anywhere; }
.slate-exp-row .slate-exp-clv { grid-area: clv; min-height: 44px; min-width: 44px; display: flex; align-items: center; justify-content: flex-end; color: var(--slate-text-3); }
.slate-exp-outlet { color: var(--slate-text-3); }
.slate-narr-body p + p { margin-top: 10px; }
.slate-narr-body strong { color: var(--slate-text); }
.slate-narr-body code {
  background: rgba(255,255,255,0.06);
  padding: 1px 5px; border-radius: 3px;
  color: var(--slate-green);
  font-family: var(--slate-mono); font-size: 16px;
}
.slate-narr-math {
  font-family: var(--slate-mono);
  background: rgba(255,255,255,0.04);
  padding: 12px 14px; border-radius: 6px;
  font-size: 17px; line-height: 1.7;
  margin: 10px 0;
}
.slate-narr-math .accent { color: var(--slate-green); }

/* Line shopping table */
.slate-ls-table { width: 100%; border-collapse: collapse; }
.slate-ls-table th {
  font-family: var(--slate-mono);
  font-size: 16px; color: var(--slate-text-3);
  text-transform: uppercase; letter-spacing: 0.12em;
  font-weight: 700; padding: 8px 10px; text-align: left;
  border-bottom: 1px solid var(--slate-border);
}
.slate-ls-table td {
  padding: 10px; border-bottom: 1px solid var(--slate-border-soft);
  font-size: 17px; color: var(--slate-text);
}
.slate-ls-table tr.best { background: rgba(16,185,129,0.04); }
.slate-ls-table tr.best td { color: var(--slate-green); font-weight: 700; }
.slate-ls-price { font-family: var(--slate-mono); font-weight: 700; }

/* Line shopping: the user's own book, from the My book preference. */
.slate-ls-table tr.mine td:first-child { font-weight: 700; }
.slate-ls-mine {
  margin-left: 6px; font-size: 16px; font-weight: 500;
  color: var(--slate-text-3); text-transform: none; letter-spacing: 0;
}

/* Your price (2026-09-13): the pick repriced at the user's own book. Never
   green — green means "we recommend this" on every other surface, and this
   card is arithmetic at a price the model never saw. Negative tints red. */
.slate-yp { display: flex; flex-direction: column; gap: 10px; } /* the -10px in the :empty rule below must equal this gap (I2) */
.slate-yp-label {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px;
  font-size: 16px; color: var(--slate-text-2);
}
.slate-yp-input {
  font: inherit; font-family: var(--slate-mono); font-size: 17px; font-weight: 600;
  min-height: 44px; width: 9ch; padding: 6px 10px; box-sizing: border-box;
  background: var(--slate-bg-2, rgba(255, 255, 255, 0.06)); color: var(--slate-text);
  border: 1px solid var(--slate-border); border-radius: 6px;
}
.slate-yp-input:focus { outline: none; border-color: var(--slate-text-3); box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.08); }
.slate-yp-input[aria-invalid="true"] { border-color: var(--slate-red); }
.slate-yp-number { font: inherit; font-size: 17px; min-height: 44px; padding: 6px 10px; background: var(--slate-bg-2, rgba(255,255,255,0.06)); color: var(--slate-text); border: 1px solid var(--slate-border); border-radius: 6px; color-scheme: dark; }
.slate-yp-hint { color: var(--slate-text-2); text-decoration: underline; font-size: 16px; min-height: 44px; display: inline-flex; align-items: center; }
.slate-yp-sub, .slate-yp-note { margin: 0; font-size: 16px; color: var(--slate-text-3); line-height: 1.5; }
/* Fix round 2 (2026-09-14): [data-yp-approx] (slate.js _renderYourPrice) is
   ALWAYS rendered now, never [hidden] -- a live region that isn't in the
   accessibility tree at the moment its text changes is not reliably
   announced (reviewer, Important). Empty (no number chosen) must still take
   no space; `display: none` is NOT an option, it recreates the same
   accessibility-tree removal this fix exists to avoid. margin is already 0
   on the rule above, padding was never set, but both are pinned here too so
   this stays correct even if that rule's box model ever changes.
   I2 (final-review.md, measured 2026-09-15): margin/padding/min-height at 0
   still left 20px under an unchosen number instead of 10 -- an empty flex
   item has zero height but still draws .slate-yp's `gap` on BOTH sides.
   margin-top: -10px cancels one of those; it must equal .slate-yp's gap
   above, and both are pinned equal in tests/test_yp_approx_live_region.py. */
.slate-yp-note[data-yp-approx]:empty { margin: 0; margin-top: -10px; padding: 0; min-height: 0; } /* -10px must equal .slate-yp's gap above (I2) */
.slate-yp-results { display: flex; flex-wrap: wrap; gap: 8px 22px; }
.slate-yp-cell { display: flex; align-items: baseline; gap: 8px; }
.slate-yp-k { font-size: 16px; color: var(--slate-text-3); }
.slate-yp-v { font-family: var(--slate-mono); font-size: 17px; font-weight: 700; color: var(--slate-text); }
.slate-yp-v.neg { color: var(--slate-red); }
.slate-yp-open {
  font-size: 16px; color: var(--slate-text-2); text-decoration: underline;
  min-height: 44px; display: inline-flex; align-items: center; align-self: flex-start;
}
@media (max-width: 540px) {
  .slate-yp-results { flex-direction: column; gap: 6px; }
}

/* Reduced-juice EV display (NFL sprint W1 / spec M3 — 2026-05-10).
   "Juice" column shows implied-prob delta vs the sharpest book on the
   slate. Sharp users key on this number to choose where to bet.
   font-size was 0.92em, rendering 11.96px against a 13px <td> -- the other
   25 small-text classes in the drilldown are all pinned at exactly 12px.
   A relative unit here means a table restyle could move this number
   silently, so it is pinned instead. */
.slate-ls-juice {
  font-family: var(--slate-mono); font-size: 16px; font-weight: 600;
}
.slate-ls-juice--sharp {
  color: var(--slate-green); font-style: italic;
}
.slate-ls-juice--bad {
  /* Paying extra juice — warn-ish color but not alarming */
  color: var(--slate-warn, #c97a00);
}
.slate-ls-juice--good {
  /* Rare: book is sharper than the "sharpest" (outlier) */
  color: var(--slate-green);
}

/* Game props row (placeholder shape — Phase 2.5 fills the data) */
.slate-prop-row {
  display: grid;
  grid-template-columns: 1fr 90px 90px 80px 56px;
  gap: 10px; padding: 10px 12px; align-items: center;
  border-bottom: 1px solid var(--slate-border-soft);
}
.slate-prop-row:last-child { border-bottom: none; }
/* The props row's your-book line spans the grid so the tight columns above
   stay where they are; it appears only when there is something to say. */
.slate-prop-yp { grid-column: 1 / -1; display: flex; justify-content: flex-end; min-height: 24px; }
.slate-prop-name { font-size: 17px; color: var(--slate-text); font-weight: 600; }
.slate-prop-line {
  font-family: var(--slate-mono); font-size: 17px;
  color: var(--slate-text-2);
}

/* Phase 2.5 placeholder (shown for line-shopping and props blocks
   until enriched data is wired) */
.slate-coming-soon {
  padding: 32px 16px;
  text-align: center;
  color: var(--slate-text-3);
  font-size: 16px;
  font-family: var(--slate-mono);
  background: rgba(255,255,255,0.02);
  border: 1px dashed var(--slate-border);
  border-radius: 8px;
}
.slate-coming-soon strong { color: var(--slate-text-2); }

/* ===== Slate redesign — Parlay Cart (Phase 3) ===== */

/* The rail is a slide-in drawer at all viewport widths, hidden by default
   and toggled open via the top-nav "Parlay Builder" button.
   FIX 2026-05-09: was permanently visible at >=1024px and stole 352px of
   horizontal real estate from the slate; now only appears on demand. */
.parlay-rail {
  position: fixed;
  top: 72px;            /* below the top nav bar (~56px header + 16px gap) */
  right: 16px;
  width: 320px;
  max-height: calc(100vh - 88px);
  overflow-y: auto;
  background: var(--slate-bg-card);
  border: 1px solid var(--slate-border);
  border-radius: 12px;
  padding: 18px;
  font-family: 'IBM Plex Sans', sans-serif;
  z-index: 50;
  /* Closed state: slid off-screen to the right */
  transform: translateX(calc(100% + 24px));
  transition: transform 0.25s ease-out;
}
.parlay-rail.parlay-rail--open {
  transform: translateX(0);
}

.parlay-rail-h {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 14px;
}
.parlay-rail-title {
  font-size: 16px;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-weight: 800;
  color: var(--slate-text);
}
.parlay-rail-count {
  font-family: var(--slate-mono);
  font-size: 16px;
  color: var(--slate-text-3);
  padding: 2px 7px;
  background: rgba(255,255,255,0.04);
  border-radius: 999px;
}

/* Empty state */
.parlay-empty {
  padding: 24px 12px;
  text-align: center;
  background: rgba(255,255,255,0.02);
  border: 1px dashed var(--slate-border);
  border-radius: 8px;
  font-size: 16px;
  color: var(--slate-text-3);
  line-height: 1.6;
}

/* Leg cards */
.parlay-leg {
  /* Declared, not inherited from whatever else claims this class name.
     main.css:857 used to style .parlay-leg unscoped for the floating parlay
     SLIP; `display` was the one property it declared and slate.css did not,
     so the slip's flex row laid out this card and pushed .parlay-leg-meta
     under the corner button. main.css is scoped to .parlay-slip now, and
     this line makes the cart's layout its own statement rather than a
     default that a future collision can take over again.
     Stacked: pick on its own line, .parlay-leg-meta below it. */
  display: block;
  background: var(--slate-bg);
  border: 1px solid var(--slate-border);
  border-radius: 7px;
  padding: 10px 11px;
  /* Gutter for the absolutely-positioned .parlay-leg-x below: 6px offset +
     28px button + 2px clearance. Without it the button sits on top of
     .parlay-leg-meta — measured at 1024px AND 1440px, an overlap of 13.8px
     on every leg with the button winning the hit test over the price, so a
     click beside the odds removed the leg. The phone block at the end of
     this file widens both to 44/46px. */
  padding-right: 36px;
  margin-bottom: 6px;
  position: relative;
}
.parlay-leg-x {
  position: absolute;
  top: 6px;
  right: 6px;
  /* A declared box, not a glyph-sized one: it was 18.8x15, which is both
     under the 24px AA target floor and impossible for the card to reserve a
     matching gutter against. 28px matches .slate-row-star, the desktop
     convention elsewhere on the board. */
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--slate-text-4);
  cursor: pointer;
  font-size: 17px;
  padding: 0;
  border-radius: 3px;
  line-height: 1;
  background: transparent;
  border: none;
}
.parlay-leg-x:hover { color: var(--slate-red); background: rgba(239,68,68,0.06); }
.parlay-leg-pick {
  font-size: 17px;
  font-weight: 700;
  color: var(--slate-text);
  padding-right: 18px;
  line-height: 1.3;
}
.parlay-leg-meta {
  font-family: var(--slate-mono);
  font-size: 16px;
  color: var(--slate-text-3);
  margin-top: 5px;
  display: flex;
  justify-content: space-between;
}
.parlay-leg-meta strong { color: var(--slate-text-2); }

/* Summary block */
.parlay-summary {
  background: rgba(16,185,129,0.06);
  border: 1px solid rgba(16,185,129,0.25);
  border-radius: 8px;
  padding: 11px 12px;
  margin: 12px 0 10px;
}
.parlay-summary-row {
  display: flex;
  justify-content: space-between;
  font-size: 16px;
  color: var(--slate-text-2);
  margin-bottom: 5px;
}
.parlay-summary-row.big {
  font-size: 17px;
  font-weight: 800;
  color: var(--slate-text);
  margin-bottom: 0;
  padding-top: 7px;
  border-top: 1px solid rgba(16,185,129,0.18);
}
.parlay-summary-row strong { color: var(--slate-green); font-family: var(--slate-mono); }
.parlay-summary-row.neg strong { color: var(--slate-red); }
/* Grade-gated: a positive combined EV on a parlay with a non-recommended leg
   must NOT read green — dim it so it doesn't look like a model pick. */
.parlay-summary-row.muted strong { color: var(--slate-text-2); }
.parlay-summary-note {
  font-size: 16px;
  line-height: 1.35;
  color: var(--accent-amber, #e8a40f);
  margin-top: 6px;
}
/* A same-game cart's "DraftKings sets the price" note is information, not a
   warning (Bill 2026-10-14). */
.parlay-summary-note--plain {
  color: var(--slate-text-2);
  margin: 0 0 6px;
}

/* Stake input */
.parlay-stake-input {
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--slate-border);
  color: var(--slate-text);
  font-family: var(--slate-mono);
  font-size: 17px;
  font-weight: 700;
  padding: 3px 6px;
  border-radius: 4px;
  width: 70px;
  text-align: right;
}
.parlay-stake-input:focus {
  outline: none;
  border-color: var(--slate-green);
}

/* Helper text */
.parlay-helper {
  font-size: 16px;
  color: var(--slate-text-4);
  margin-top: 14px;
  line-height: 1.55;
  font-family: var(--slate-mono);
  text-align: center;
}

/* FIX 2026-05-09: removed the desktop right-padding reservation. The rail
   is now a slide-in drawer (closed by default) at all viewport widths,
   so .main-content reclaims the full screen width. */

/* Phase 4 — bottom-anchored drawer at <1024px, integrated with the
   existing bottom-nav Parlay tab. Hidden by default (translateY off-screen);
   .parlay-rail--open class slides it up. */
@media (max-width: 1023px) {
  .parlay-rail {
    /* Override Phase 3's fixed-position desktop rail */
    position: fixed;
    top: auto;
    right: 0;
    bottom: 0;
    left: 0;
    width: auto;
    max-width: 100vw;
    max-height: 75vh;
    border-radius: 16px 16px 0 0;
    border-bottom: none;
    transform: translateY(100%);
    transition: transform 0.25s ease-out;
    box-shadow: 0 -8px 24px rgba(0,0,0,0.4);
    /* Account for the bottom-nav height (~56px) so the drawer doesn't
       hide under it. The bottom-nav-padding env safe-area-inset is
       inherited from the V1 mobile foundations. */
    padding-bottom: calc(72px + env(safe-area-inset-bottom));
    z-index: 110;  /* above bottom-nav (main.css:73 sets nav z-index: 100) */
  }
  .parlay-rail.parlay-rail--open {
    transform: translateY(0);
  }
  /* Phase 4 does NOT add right-padding on .main-content for mobile —
     the drawer slides over content instead of pushing it. */
}

/* Backdrop when drawer is open — tap-to-close affordance */
.parlay-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.4);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease-out;
  z-index: 105;
}
/* Backdrop only goes opaque/active on mobile (modal drawer pattern).
   At desktop the drawer is non-modal — slate behind stays interactive. */
@media (max-width: 1023px) {
  .parlay-backdrop.parlay-backdrop--open {
    opacity: 1;
    pointer-events: auto;
  }
}

/* Ensure the bottom-nav button is the badge's positioning anchor.
   (V1 mobile work added .bottom-nav-item but may not have set
    position: relative; safe to set unconditionally — won't change
    layout, only enables abs-positioned children.) */
.bottom-nav-item { position: relative; }

/* ===== Slate redesign — Phase 4 Mobile Reflow ===== */

/* Tier 2 slate row — the SINGLE phone layout (<640px).
   FIX 2026-07-12 (Batch 2 follow-up): two overlapping phone blocks used to
   fight here — this one ("Phase 4": 3-col grid-template-areas card) and a
   later "Wave 3 #15" 4-col compact block (~L1131, now deleted). Both applied
   at phone widths: the later block overrode grid-template-columns (4 tracks)
   but NOT grid-template-areas (3 named columns), so the matchup cell rendered
   in a 70px track while the 131px 1fr track held only the pick, the 36px 4th
   track was orphaned, and the share/star chips auto-placed arbitrarily.
   Reproduced live on prod v415 (matchup w=70px) before this consolidation.
   Single layout now:
     matchup(1fr)  edge(84px)   star(44px)
     pick          price        add
   Hidden on phone (all carried by the tap-to-drilldown): column header,
   sport chip, confidence, multibook summary, share chip. */
@media (max-width: 639px) {
  .slate-row {
    grid-template-columns: 1fr 84px 44px;
    grid-template-areas:
      "matchup edge  star"
      "pick    price add";
    gap: 6px 8px;
    padding: 10px 8px;
  }
  .slate-row > .slate-row-sport { display: none; }  /* sport → drilldown */
  .slate-row > div:nth-child(2) { grid-area: matchup; }
  .slate-row > div:nth-child(3) { grid-area: pick; }
  .slate-row > div:nth-child(4) { grid-area: price; justify-self: end; text-align: right; }
  .slate-row > .slate-edge-chip { grid-area: edge; justify-self: end; align-self: start; }
  /* WCAG 2.5.5 touch targets: the row's 3rd grid column is already 44px, so
     size the ☆/+ controls to fill it. FIX 2026-07-18: were 28px (star) / 36px
     (add) — Fitts's-law misfires one-handed, and unreliable for motor-impaired
     users (see accessibility-audit skill). */
  .slate-row > .slate-row-add   {
    grid-area: add; justify-self: end; align-self: end;
    min-width: 44px; min-height: 44px; font-size: 18px; padding: 0;
  }

  .slate-row-game { font-size: 16px; }
  .slate-row-pick { font-size: 17px; }

  /* Drilldown prop rows: 5-col → 2-row card, same collapse as .slate-row
     above. FIX 2026-07-25: the desktop grid (1fr 90px 90px 80px 56px, gap
     10, padding 12) needs 380px of fixed width before the 1fr, and sits in
     a ~270px container inside the drilldown — so .slate-row-add rendered at
     x=467 against a 375px client width and pushed 107px of HORIZONTAL PAGE
     SCROLL. Pre-dates the shell-scroll fix (02ede1e); .main-content's old
     overflow:hidden had been MASKING it, which meant the price, EV chip and
     "+" add-to-parlay button were silently cut off and unreachable on
     phones — the same failure main.css:1718 documents for game cards.
     Guarded by tests/test_slate_prop_row_mobile.py. */
  .slate-prop-row {
    grid-template-columns: 1fr auto 44px;
    /* 2026-09-12: name and prop line span the row; price and edge chip share
       the third line. With the chip beside the name the prop line had ~100px
       and wrapped four deep at the 16px floor. */
    grid-template-areas:
      "name  name add"
      "line  line add"
      "price edge add";
    gap: 4px 8px;
    padding: 10px 8px;
  }
  .slate-prop-row > .slate-prop-name { grid-area: name; }
  .slate-prop-row > .slate-prop-line { grid-area: line; }
  .slate-prop-row > .slate-ls-price  { grid-area: price; justify-self: start; }
  .slate-prop-row > .slate-edge-chip { grid-area: edge; justify-self: end; }
  /* WCAG 2.5.5 — the desktop column was 56px; keep a real target here. */
  .slate-prop-row > .slate-row-add {
    grid-area: add; justify-self: end; align-self: center;
    min-width: 44px; min-height: 44px; font-size: 18px; padding: 0;
  }
}

/* Tier 3 live row: 5-col → 2-row card on <640px */
@media (max-width: 639px) {
  .slate-live-row {
    grid-template-columns: 56px 1fr 76px;
    grid-template-areas:
      "sport matchup edge"
      "pick  score   .";
    row-gap: 8px;
    padding: 10px 10px;
  }
  .slate-live-row > .slate-row-sport  { grid-area: sport; }
  .slate-live-row > div:nth-child(2)  { grid-area: matchup; }
  .slate-live-row > .slate-row-pick   { grid-area: pick; font-size: 17px; }
  .slate-live-row > .slate-live-score { grid-area: score; font-size: 16px; }
  .slate-live-row > .slate-edge-chip  { grid-area: edge; justify-self: end; align-self: start; }
}

/* Drilldown takeover mobile reflow at <640px */
@media (max-width: 639px) {
  /* Header: at <=539px back and title share a row (see the max-width:539px
     block below) with actions stacked on full-width rows underneath; at
     540-639px back, title and actions each still get their own row. Title-
     block sizing varies by sub-breakpoint accordingly. */
  .slate-takeover-h {
    flex-wrap: wrap;
    padding: 12px 14px;
    gap: 10px;
  }
  /* FIX 2026-08-11 (Slate mobile review): both of these were SHRUNK here — to
     ~26px (12px font + 5px padding + 1px border) — and nothing bumped them
     back. .slate-takeover-back is the way OUT of the drilldown, the primary
     back-navigation on a phone. .slate-takeover-action only regained 44px in
     the max-width:539px block below, so 540-639px shipped the compact size.
     Keep the compact type (the header has to fit); buy the height with
     padding instead. Guarded by tests/test_slate_touch_targets.py. */
  .slate-takeover-back {
    flex: 0 0 auto; font-size: 16px; padding: 5px 9px;
    min-height: 44px; display: inline-flex; align-items: center;
  }
  /* Side padding matches the header's at this width. Bottom-RIGHT on phones
     (Bill, 2026-09-13, after seeing 375px): at the left it covered the start
     of every text line it floated over; line ends are shorter and the right
     edge is the thumb zone. Desktop keeps the left placement. */
  .slate-takeover-float { padding: 8px 14px; justify-content: flex-end; }
  /* FIX 2026-09-04: this carried `order: 3`, which sorted the title AFTER the
     back button and both actions (each `order: 0` by default) -- so tapping a
     game showed Log bet and Add to parlay before naming the game, 158px down.
     That contradicted this block's own opening comment, "stack title above
     actions": the intent was right and the implementation inverted it. DOM
     order governs now (back, title, actions); `flex: 1 1 100%` still gives the
     title its own full-width row. Guarded by
     tests/test_takeover_header_order.py. */
  .slate-takeover-title-block { flex: 1 1 100%; text-align: left; }
  .slate-takeover-title { font-size: 18px; }
  .slate-takeover-title-ctx { font-size: 16px; }
  .slate-takeover-actions { flex: 0 0 auto; gap: 6px; }
  .slate-takeover-action {
    font-size: 16px; padding: 5px 9px;
    min-height: 44px; display: inline-flex; align-items: center;
    justify-content: center;
  }

  /* Freshness banner + market cards: match the tightened side padding. */
  .slate-takeover-freshness { padding: 8px 14px; }
  .slate-takeover-nav { padding: 14px 14px 0; gap: 12px; }
  .slate-sec-jump-chip { padding: 6px 12px; }
  .slate-mkt-cards {
    grid-template-columns: 1fr;
    padding: 12px 14px 0;
  }

  /* Summary mosaic: 4-cell → 2x2 grid */
  .slate-takeover-summary {
    grid-template-columns: repeat(2, 1fr);
    padding: 0 14px;
  }
  .slate-ts-cell { padding: 14px 8px; }
  .slate-ts-cell-v { font-size: 22px; margin-top: 6px; }
  .slate-ts-cell-v.book { font-size: 17px; }
  .slate-ts-cell-sub { font-size: 16px; }

  /* Body padding tighter */
  .slate-takeover-body {
    padding: 16px 14px;
    padding-bottom: calc(16px + 60px);
    gap: 14px;
  }
  .slate-sec { padding: 14px 14px; }
  /* FIX 2026-09-08: the Signal detail row is a 4-column grid on desktop
     (name | favored side | prob | magnitude). At 390px the three fixed
     columns left the name ~55px wide, so "Net Rating" and "Player Impact"
     broke into two lines (measured on the iPhone 14 Pro emulation). On a
     phone the name takes the full first line and the three numbers share
     the second. Scoped to the Signal detail list: the Confidence
     conviction rows reuse .slate-ss-sig with no magnitude cell and fit on
     one line as they are. Guarded by tests/test_signal_row_phone_layout.py. */
  .slate-ss-advanced .slate-ss-sig { grid-template-columns: 1fr auto auto; }
  .slate-ss-advanced .slate-ss-sig-name { grid-column: 1 / -1; }
  .slate-ss-advanced .slate-ss-sig-favor { text-align: left; }
  .slate-narr-body { font-size: 17px; line-height: 1.6; }
  .slate-narr-math { font-size: 16px; padding: 10px 12px; }

  /* Line-shopping table: horizontal scroll wrapper so all 4 columns
     stay readable without column collapse logic.
     FIX 2026-05-14 per reviewer audit: the prior CSS set thead/tbody/tr
     to display:table width:100%, which UNDID the overflow-x scroll
     because the inner table reset back to 100% width. Removed that
     override — table now inherits the parent's natural sizing and
     horizontally scrolls when columns exceed viewport. */
  .slate-sec .slate-ls-table {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    white-space: nowrap;
    min-width: 100%;
  }
}

/* NFL post-Q1 in-play update (Phase 6). 2026-09-07: the .slate-post-q1
   wrapper/header/ts rules went with the blue left rule; the block renders
   through CESectionCard now (LIVE badge + "updated" meta in the card head).
   The row-level rules below are unchanged. */
.slate-post-q1-probs {
  display: flex;
  gap: 0.7rem;
  align-items: center;
  flex-wrap: wrap;
  font-size: 1.125rem;
  margin: 0.4rem 0;
}
.slate-post-q1-arrow {
  color: var(--slate-text-3);
  font-weight: bold;
}
.slate-post-q1-delta {
  font-weight: 700;
  padding: 0.15rem 0.55rem;
  border-radius: 10px;
  background: var(--bg-raised);
  font-size: 1.0625rem;
}
.slate-post-q1-delta.good {
  background: var(--accent-green-bg);
  color: var(--accent-green);
}
.slate-post-q1-delta.bad {
  background: var(--accent-red-bg);
  color: var(--accent-red);
}
.slate-post-q1-state {
  font-family: -apple-system, monospace;
  font-size: 1rem;
  color: var(--slate-text-3);
  margin: 0.3rem 0;
}
.slate-post-q1-summary {
  font-size: 1.0625rem;
  color: var(--slate-text-2);
  font-style: italic;
  margin-top: 0.3rem;
}

/* Drilldown action buttons at <540px: stack to 2-row grid so
   Share / Log bet / + Add to parlay don't crush against each other
   on narrow phones. Per 2026-05-14 reviewer audit S3. */
@media (max-width: 539px) {
  .slate-takeover-actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
    width: 100%;
    flex: 1 1 100%;
  }
  .slate-takeover-action {
    width: 100%;
    text-align: center;
    min-height: 44px;
    padding: 8px 6px;
    box-sizing: border-box;
  }
  /* FIX 2026-09-04: Share, when present, takes a full-width row above the two
     primary actions. Targeted by its own attribute rather than :first-child,
     because Share is CONDITIONAL -- slate.js renders it only when
     game.prediction_id is set. With :first-child, a game without Share
     promoted "Log bet" to full width and stranded "+ Add to parlay" alone on
     a second row: two buttons that fit side by side, taking two rows, for a
     50px taller and visibly lopsided header. The old comment here said
     "Share is the first child", which was true only when Share rendered.
     Guarded by tests/test_takeover_header_order.py. */
  .slate-takeover-actions > [data-action="share"] {
    grid-column: 1 / -1;
  }
  /* FIX 2026-09-04: `flex: 1 1 100%` forced the title onto its own row while
     the back button used 127px of 284 beside it -- 55% of a row spent on
     nothing. Basis 0 plus min-width 0 here let the title take the remainder
     of the back button's row instead, which is 36px off the header.
     `text-align: left` is not repeated here -- the max-width:639px rule
     above still applies it at this width.

     BASIS 0 IS LOAD-BEARING. `flex: 1 1 auto` was tried first, applied
     cleanly, and changed nothing: with basis auto the block's basis is its
     CONTENT width, and the context line alone exceeds the 147px available, so
     it wrapped regardless of grow. min-width: 0 is needed too -- a flex item
     will not shrink below its content width without it.

     The context line wraps to two lines at this width, so the block is 60px
     against the back button's 44px. Accepted deliberately (Bill,
     2026-09-04): shortening the text was measured across three variants and
     does not prevent the wrap.

     SCOPED TO <=539px, NOT <=639px: this block is the one that stacks the
     actions (see the Share rule above). Above 539px the actions do not
     stack, so back, title and actions would share one flex line, and a zero
     basis there collapses the title instead of sharing gracefully --
     measured at 540px: title 512x42 -> 100x136 (game name wraps to three
     lines), header 187px -> 161px. The max-width:639px rule keeps
     `flex: 1 1 100%` for exactly that band.

     The ordering fix in 9d92b189 still holds -- reading order becomes
     left-to-right within the row (back, then title) rather than
     top-to-bottom, and the game is still ahead of both action buttons.
     Guarded by tests/test_takeover_header_order.py. */
  .slate-takeover-title-block { flex: 1 1 0; min-width: 0; }
}

/* 2026-09-07: the "Signals fired" card and its .sf-* rules are gone. The
   payload is merged into the Signal stack rows (static/js/signal_merge.js;
   .slate-ss-sig-mag above). */

/* Phase B Sprint 4 #4+++: sign-in CTA strip on slate for unauthed
   visitors. Hidden by default; slate.js toggles visible when authed=false.
   Mirrors the /pick/{id} CTA banner styling. */
.slate-signin-cta {
  background: linear-gradient(135deg, #1f6feb 0%, #1556c5 100%);
  color: #fff; padding: 0.85rem 1.1rem; border-radius: 8px;
  margin: 0 0 1rem; display: flex; gap: 0.8rem;
  align-items: center; justify-content: space-between;
  flex-wrap: wrap; font-size: 1.0625rem;
}
.slate-signin-cta strong { font-weight: 700; }
.slate-signin-cta-btn {
  background: #fff; color: #1f6feb; padding: 0.55rem 1.1rem;
  border-radius: 4px; text-decoration: none; font-weight: 700;
  min-height: 44px; display: inline-flex; align-items: center;
  flex-shrink: 0;
}
.slate-signin-cta-btn:hover { background: #f0f7ff; }

/* The freshness row (Bill, 2026-10-10): a chip saying how old the lines on
   screen are, the odds-format toggle and My book. It replaced a monospace
   block that printed every league's time (122px on the desktop board). On
   desktop slate.js _placeFreshness moves the row into .main-header-tools
   (.in-header); at 640px and under it stays in #view-premium after the board.
   The chip is neutral until the lines are old: amber at 15 min, red at 30
   (slate_status_line.js FRESH_WARN_MIN / FRESH_RED_MIN). */
.slate-freshness {
  font-size: 1rem;
  color: var(--slate-text-3, #a3a3ad);
  margin: 0 0 0.45rem;
  display: flex; align-items: center; gap: 10px;
  line-height: 1.3;
}
/* An author display rule outranks the UA [hidden] (main.css "FIX 2026-08-06"):
   app.js switchTab hides the row off the Games tab. */
.slate-freshness[hidden] { display: none; }
/* In the header the row is no box of its own: chip, US/Dec and My book wrap
   one by one in .main-header-tools (flex-wrap, right-aligned) with search and
   the Learn pill. As one nowrap line (~720px with a red chip) it hung off both
   edges of the screen at 641-800px (2026-10-13); as one wrapping box nothing
   could sit beside its last line, so Learn took a row alone (Bill, 2026-10-14).
   The [hidden] pair must follow: this rule outranks .slate-freshness[hidden]
   by order. Guarded by tests/test_header_tools_fit_browser.py. */
.slate-freshness.in-header { display: contents; }
.slate-freshness.in-header[hidden] { display: none; }
.fresh-chip-wrap { position: relative; display: inline-flex; flex: none; }
.fresh-chip {
  font: inherit; font-size: 1rem; line-height: 1.2;
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 36px; padding: 6px 14px;
  border: 1px solid var(--border-default, rgba(255, 255, 255, 0.1));
  border-radius: var(--radius-pill, 999px);
  background: var(--bg-surface, #111113);
  color: var(--slate-text, #e6edf3);
  white-space: nowrap; cursor: pointer;
}
.fresh-chip::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--accent-green, #22c55e);
}
.fresh-chip:hover { border-color: var(--border-strong, rgba(255, 255, 255, 0.16)); }
.fresh-chip:focus-visible { outline: 2px solid var(--accent, #22c55e); outline-offset: 2px; }
.fresh-chip.stale-warn {
  background: var(--accent-amber-bg, rgba(245, 158, 11, 0.12));
  border-color: var(--accent-amber, #f59e0b); color: var(--accent-amber, #f59e0b);
}
.fresh-chip.stale-warn::before { background: var(--accent-amber, #f59e0b); }
.fresh-chip.stale-red {
  background: var(--accent-red-bg, rgba(239, 68, 68, 0.12));
  border-color: var(--accent-red, #f87171); color: var(--accent-red, #f87171);
  font-weight: 600;
}
.fresh-chip.stale-red::before { background: var(--accent-red, #f87171); }
/* The popover: every sport's time and a reload. Opaque, floating under the
   chip; anchored to the chip's right edge in the header (the chip sits right
   of centre there) and to its left edge in the board. */
.fresh-pop {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 60;
  width: 19rem; max-width: calc(100vw - 32px); box-sizing: border-box;
  padding: 12px 14px;
  background: var(--bg-elevated, #18181b);
  border: 1px solid var(--border-strong, rgba(255, 255, 255, 0.16));
  border-radius: var(--radius-md, 8px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  color: var(--slate-text, #e6edf3); font-size: 1rem; white-space: normal;
}
.fresh-pop[hidden] { display: none; }
.slate-freshness:not(.in-header) .fresh-pop { left: 0; right: auto; }
.fresh-pop-head { margin: 0 0 8px; font-weight: 600; }
.fresh-pop-list { list-style: none; margin: 0 0 10px; padding: 0; }
.fresh-pop-list li {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 3px 0; color: var(--slate-text-3, #a3a3ad);
}
.fresh-pop-time { color: var(--slate-text, #e6edf3); white-space: nowrap; font-variant-numeric: tabular-nums; }
.fresh-pop-reload {
  font: inherit; font-size: 1rem; min-height: 36px; width: 100%;
  padding: 6px 12px; cursor: pointer;
  border: 1px solid var(--border-default, rgba(255, 255, 255, 0.1));
  border-radius: var(--radius-sm, 6px);
  background: transparent; color: var(--slate-text, #e6edf3);
}
.fresh-pop-reload:hover { border-color: var(--border-strong, rgba(255, 255, 255, 0.16)); }

/* American / Decimal odds-format toggle — sits at the end of the freshness row.
   Colour stays neutral (it is a format control, not a recommendation signal). */
.odds-format-toggle {
  margin-left: auto;
  align-self: center;
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--slate-border, #30363d);
  border-radius: 6px;
  flex: none;
}
.slate-freshness.in-header .odds-format-toggle { margin-left: 0; }
.odds-format-btn {
  font: inherit;
  font-size: 1.0625rem;
  line-height: 1.4;
  min-height: 26px;
  padding: 3px 9px;
  border: none;
  border-radius: 4px;
  background: transparent;
  color: var(--slate-text-3, #a3a3ad);
  cursor: pointer;
}
.odds-format-btn.active {
  background: var(--slate-bg-2, rgba(255, 255, 255, 0.08));
  color: var(--slate-text, #e6edf3);
  font-weight: 600;
}
.odds-format-btn:hover:not(.active) { color: var(--slate-text-2, #c9d1d9); }
/* My book (2026-09-13): the per-device book preference, beside the format
   toggle. Same neutral treatment: a setting, not a signal.
   FIX 2026-09-18: the select's background must be OPAQUE. Chrome draws the
   opened list in its own popup document whose body is always white
   (picker_common.css), and copies the select's computed background-color
   and color onto that list (list_picker.js) -- so the translucent 8% white
   the toggle beside it uses (fine over #09090b on the page) rendered every
   book white-on-white in the dropdown (Bill, Chrome 153: "whited out").
   --slate-bg-2 was never defined, so the rgba fallback was the value.
   Pinned by tests/test_my_book_select_opaque.py. */
.my-book-select {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  font-family: inherit; font-size: 1rem; color: var(--slate-text-3, #a3a3ad);
}
/* An author display rule outranks the UA [hidden]; pair it, as every other
   class-level display on this page does (main.css "FIX 2026-08-06"). */
.my-book-select[hidden] { display: none; }
.my-book-select select {
  font: inherit; font-size: 1rem; min-height: 44px; padding: 4px 8px;
  background: var(--bg-elevated, #18181b); color: var(--slate-text, #e6edf3);
  border: 1px solid var(--slate-border, #30363d); border-radius: 6px;
  color-scheme: dark; max-width: 11rem;
}
@media (max-width: 540px) {
  .slate-freshness { flex-wrap: wrap; }
  .my-book-select { width: 100%; justify-content: flex-end; }
}
/* Touch targets: the compact 26px reads fine with a mouse but is a fat-finger
   miss on a phone — and a mis-tap here silently reformats EVERY price on the
   board. Bump to the 44px standard the 2026-07-18 a11y sweep adopted (WCAG
   2.5.5). Also let the freshness row wrap instead of crowding the timestamp:
   at 375px the text and toggle sat 6px apart, and a full "Lines as of 3:47 PM
   EDT · refreshed 12 min ago" string needs the room.
   Pinned by tests/test_slate_touch_targets.py. */
@media (max-width: 639px) {
  .slate-freshness { flex-wrap: wrap; row-gap: 0.35rem; }
  .odds-format-toggle { margin-left: 0; }
  .odds-format-btn {
    min-height: 44px;
    padding: 3px 14px;
  }
}

/* 639, not 640: main.css:97 opens the tablet tier at min-width:640px, and the
   rest of the Slate's phone rules stop at 639. At exactly 640px this block used
   to reorder the page for mobile while the board stayed on its desktop
   7-column grid. Pinned by tests/test_slate_touch_targets.py. */
@media (max-width: 639px) {
  /* Phone order of the Games tab (2026-08-06, re-anchored 2026-08-29 and
     2026-09-04; the proof hero it once led with was retired 2026-09-21).
     Flex rather than a DOM move because the order must differ from desktop,
     and a DOM move would need JS on resize. Scoped entirely inside this
     media query — desktop order is untouched; there, #view-premium is normal
     block flow and source order is visual order.

     #top-slot needs an explicit order: as a direct child of #view-premium
     without one it fell to the `#view-premium > *` catch-all — measured on
     prod at 375px it landed at y=1127, below the freshness line, on an
     8-card stub. #best-bets shares its number: it replaces the slot in
     place and follows it in source. #game-cards-container takes the next
     slot so the board still leads #slate-signin-cta/#slate-freshness
     (Task 9, 2026-08-29). Pinned by tests/test_slate_phone_order.py.

     NOTE: flex stops margin collapsing between these children. That was
     checked visually at 375 and at desktop; if spacing ever looks off here,
     that is the mechanism. */
  #view-premium { display: flex; flex-direction: column; }
  #view-premium > #top-slot          { order: 1; }
  #view-premium > #best-bets         { order: 1; }   /* after #top-slot in source; it replaces the slot */
  #view-premium > #game-cards-container { order: 2; }
  #view-premium > #slate-signin-cta  { order: 3; }
  #view-premium > #slate-freshness   { order: 4; }
  #view-premium > *                  { order: 5; }

  /* The freshness popover on a phone (2026-10-10): in the flow under the
     row, full width, instead of floating -- the row sits after the board
     here, and a floating panel would cover the cards. display:contents lets
     the popover take its own line of the wrapping row. */
  #slate-freshness { flex-wrap: wrap; }
  #slate-freshness .fresh-chip-wrap { display: contents; }
  /* The red chip names the sport and an action ("Bundesliga lines 34 min
     old: check the book"); unwrapped it ran 72px past a 375px screen (push
     gate, test_rendered_text_clipping). On a phone it wraps. */
  #slate-freshness .fresh-chip {
    min-height: 44px; min-width: 0; max-width: 100%;
    white-space: normal; text-align: left; border-radius: 22px;
  }
  #slate-freshness .fresh-pop {
    position: static; order: 10; flex: 1 1 100%; width: 100%; max-width: none;
    box-shadow: none;
  }
  #slate-freshness .fresh-pop-reload { min-height: 44px; }

  /* (Wave 3 #15's 4-col compact slate block was removed 2026-07-12 — it
     half-overrode the Phase 4 card layout above and crushed the matchup
     cell into a 70px track. The consolidated phone layout now lives in the
     single <640px block near L728.) */
}

/* Phase B Sprint 5 #9: keyboard navigation focus ring + help overlay. */
.slate-kbd-help {
  position: fixed; inset: 0;
  background: rgba(11, 18, 32, 0.45);
  display: none;
  align-items: center; justify-content: center;
  z-index: 200;
}
.slate-kbd-help-card {
  background: var(--bg-elevated); border: 1px solid var(--slate-border); border-radius: 8px;
  padding: 1.4rem 1.6rem;
  max-width: 420px; width: calc(100% - 2rem);
  box-shadow: 0 8px 28px rgba(0,0,0,0.18);
}
.slate-kbd-help-title {
  font-size: 1.1rem; font-weight: 700;
  margin-bottom: 0.8rem;
}
.slate-kbd-help-list {
  display: grid; grid-template-columns: 6rem 1fr;
  gap: 0.4rem 0.8rem;
  margin: 0 0 1rem; font-size: 1.125rem;
}
.slate-kbd-help-list dt {
  font-family: ui-monospace, monospace;
  font-weight: 700; color: var(--slate-text);
}
.slate-kbd-help-list dd {
  margin: 0; color: var(--slate-text-2);
}
.slate-kbd-help-close {
  background: #1f6feb; color: #fff;
  border: 1px solid #1f6feb;
  padding: 0.55rem 1.1rem; border-radius: 4px;
  font-size: 1.0625rem; font-weight: 600;
  cursor: pointer; min-height: 44px;
}

/* F5 row-expander cells — MLB First-5-Innings (Task 7 — 2026-05-23).
   Rendered by _renderF5Block(game) inside slate-takeover-body when
   game.sport==='mlb' and game.f5 is present. */
.slate-row-f5-cells {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.slate-cell.f5-cell {
  /* FIX 2026-07-12 (Batch 2): --f5-bg was never defined, so the light #f9f9fb
     fallback always rendered — white cells on the dark drilldown. */
  background: var(--bg-elevated);
  border: 1px dashed var(--border);
  border-radius: 4px;
  padding: 6px;
  min-width: 140px;
}

/* ===== Mobile touch targets — WCAG 2.5.5 Target Size (min 44x44) =====
   FIX 2026-07-18: on a 375px viewport the primary "Take →" CTA rendered 22px
   tall (an outright WCAG 2.2 fail), the density toggle 32px, and the ▸ Compare
   / Line-Movement disclosures 34px. Expand the tap area on mobile only — the
   dense desktop layout is unchanged. Elevated priority for motor-impaired users
   (accessibility-audit skill). Placed at end-of-file so these override the
   base .slate-dense-toggle (min-height:32px) etc. by source order. The per-row
   ☆/+ are handled in the max-width:639px row block above. */
@media (max-width: 639px) {
  /* FIX 2026-08-11 (Slate mobile review): four more controls the 07-18 sweep
     never enumerated. Same end-of-file placement and the same reason — these
     must beat their base rules on source order. */

  /* Removes a leg from the parlay cart. 15px font + line-height:1 + no
     vertical padding rendered ~15x20px, and on a phone the rail IS the bottom
     drawer (max-width:1023px block above), so this is the only way to drop a
     leg.

     The gutter goes on the CARD, not on .parlay-leg-pick. main.css:857
     declares a SECOND .parlay-leg — the old parlay-builder component, same
     class name — with display:flex + justify-content:space-between, and
     slate.css never sets display, so that wins and the cart leg is a flex
     row. The pick is the LEFT item and never reaches the button; the item
     that sits under it is .parlay-leg-meta, which carries the odds price.
     Measured in a browser at 375px: padding on the pick left a 33x29px
     overlap, so a tap beside the price removed the leg. Reserving the
     button's own width on the card clears it under either layout. */
  .parlay-leg {
    padding-right: 46px;
  }
  .parlay-leg-x {
    top: 0; right: 0;
    min-width: 44px; min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
    padding: 0;
  }

}

/* A cart leg whose game started after it was added: priced pregame, so it
   cannot be placed. Marked here so the reason is visible BEFORE Build & Take
   refuses. Amber (a warning to act on), not red (not an error state). */
.parlay-leg--started { opacity: 0.75; }
.parlay-leg-started {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 4px;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.04em;
  vertical-align: middle;
  color: var(--accent-amber, #e8a40f);
  background: rgba(232, 164, 15, 0.12);
  border: 1px solid rgba(232, 164, 15, 0.35);
}

/* ── Mobile input zoom-prevention: .parlay-stake-input ──────────────────────
   iOS Safari zooms the viewport when a focused input renders below 16px and
   does not zoom back out. This field takes a STAKE, so that lands on the
   control that sets how much money a bet is for.

   WHY HERE AND NOT IN main.css's GUARD: .parlay-stake-input is set to 13px at
   file scope above (slate.css:630), and slate.css loads AFTER main.css
   (templates/index.html:23-24). Both are specificity (0,1,0), so source order
   decides and a media query adds none -- adding the selector to main.css's
   guard is a silent no-op, verified in a live browser. The winning rule is the
   one that has to change. Guarded by tests/test_input_zoom_guard.py, which
   asserts the cascade winner rather than membership in a list. */
@media (max-width: 639px) {
  .parlay-stake-input {
    font-size: 18px;
  }
}

/* 2026-09-03: the signed-in top slot. Hierarchy is the point -- the play is
   what matters, the lock time and bet count are context, so they are typed
   down rather than given equal weight. The accent rule appears only when
   there IS a play; the empty state carries no rule, so "nothing clears the
   floor" cannot be mistaken for a recommendation. */
.top-slot { padding: 9px 14px; }
.top-slot.has-play { border-left: 3px solid var(--slate-green); }
.top-slot-play {
  display: block; font-size: 16px; font-weight: 700;
  color: var(--slate-text); text-decoration: none;
}
.top-slot-play:hover { text-decoration: underline; }
.top-slot-meta { font-size: 16px; color: var(--slate-text-3); margin-top: 3px; }

/* -- Best Bets block (spec 2026-09-21-best-bets-cards-design.md s6) --
   A lead card per market with rows beneath it, above the board. Tokens
   only; nothing below the 16px floor. Colour has three jobs: green for the
   best price and a real edge, amber only inside the meter for the paid gap,
   white and two greys for everything else. One DOM for both layouts: three
   columns from 1280px, a tablist below it. */
.best-bets { background: var(--slate-bg-card); border: 1px solid var(--slate-border); border-radius: var(--radius-lg); padding: 18px 20px 16px; margin-bottom: 14px; }
.best-bets[hidden] { display: none; }
.bb-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
.bb-head-l { min-width: 0; }
.bb-eyebrow { font-family: var(--slate-mono); font-size: 20px; font-weight: 700; letter-spacing: 0.08em; color: var(--slate-text-2); text-transform: uppercase; }
.bb-title { font-size: 24px; font-weight: 600; color: var(--slate-text-1); margin: 2px 0 4px; line-height: 1.2; }
.bb-sub { font-size: 17px; color: var(--slate-text-2); margin: 0; line-height: 1.4; }
.bb-how { color: var(--slate-green-bright); text-decoration: none; white-space: nowrap; }
.bb-how:hover, .bb-how:focus-visible { text-decoration: underline; }
.bb-head-r { display: flex; align-items: center; gap: 10px; flex-shrink: 0; font-family: var(--slate-mono); font-size: 16px; color: var(--slate-text-3); }
.bb-risk, .bb-collapse { font: inherit; color: inherit; background: none; border: 1px solid var(--slate-border); border-radius: var(--radius-sm); padding: 5px 10px; min-height: 32px; cursor: pointer; transition: border-color var(--transition-fast), color var(--transition-fast); }
.bb-collapse { min-width: 44px; }
.bb-risk:hover, .bb-collapse:hover { border-color: var(--border-strong); color: var(--slate-text-1); }
.bb-risk:focus-visible, .bb-collapse:focus-visible, .bb-tab:focus-visible { outline: 2px solid var(--slate-green); outline-offset: 2px; }
.bb-summary { display: none; font-family: var(--slate-mono); font-size: 16px; color: var(--slate-text-2); margin-top: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.best-bets[data-collapsed="1"] .bb-body { display: none; }
.best-bets[data-collapsed="1"] .bb-summary { display: block; }
/* FIX 2026-10-06 (Bill): on a phone the one nowrap line clipped to its first pick ("New York Yankees
   59% · Clevela..."); 614px of picks in a 235px box at 320. One pick per line there; desktop keeps
   the single line. tests/test_best_bets_summary_phone.py. */
@media (max-width: 639px) {
  .bb-summary { white-space: normal; }
  .bb-summary-pick { display: block; }
  .bb-summary-sep { display: none; }
}
.bb-locked-copy { font-size: 17px; color: var(--slate-text-2); margin: 12px 0 0; }
.bb-tabs { display: none; }
.bb-tab { font-family: var(--slate-mono); font-size: 16px; }
.bb-cols { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: 20px; margin-top: 16px; }
.bb-col { min-width: 0; }
/* Three columns, one set of row heights (Bill, 2026-10-18: "the O/U column
   still ... not even"). The O/U lead card ran 40px shorter than the other
   two, so everything under it started higher. Each column with a lead card
   becomes a subgrid of the block's five rows -- heading, lead, Place
   button, Other books, then the list of picks 2-5 -- so every lead card is
   as tall as the tallest and the three lists start level. Inside a list the
   rows stack at their own heights (Bill, 2026-10-18: "it doesn't look like
   a list under each category" when rows were forced level across columns). */
@media (min-width: 1280px) {
  .bb-cols { grid-template-rows: repeat(5, auto); }
  .bb-col:has(> .bb-lead) { display: grid; grid-row: span 5; grid-template-rows: subgrid; align-content: start; }
  .bb-col:has(> .bb-lead) > .bb-col-h { grid-row: 1; }
  .bb-col:has(> .bb-lead) > .bb-lead { grid-row: 2; }
  .bb-col:has(> .bb-lead) > .bet-slip--place { grid-row: 3; }
  .bb-col:has(> .bb-lead) > .bet-slip-others { grid-row: 4; }
  .bb-col:has(> .bb-lead) > .bb-rows { grid-row: 5; align-self: start; }
}
.bb-col-h { display: flex; align-items: center; gap: 10px; font-family: var(--slate-mono); font-size: 16px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; color: var(--slate-text-3); margin: 0 0 10px; }
/* The books'-number tag and dot (spec s6.2). */
.bb-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--slate-blue); flex: none; }
.bb-src { display: inline-flex; align-items: center; gap: 6px; font-size: 16px; font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--slate-text-3); border: 1px solid var(--slate-border); border-radius: 6px; padding: 3px 8px; line-height: 1; white-space: nowrap; }
.bb-prob-dot { margin-left: 6px; vertical-align: middle; }
.bb-src-dot { display: none; }

/* Marks: a logo in a box, or the monogram (spec s4.5). */
.bb-mark { position: relative; display: inline-flex; align-items: center; justify-content: center; flex: none; }
.bb-mark--lg { width: 60px; height: 60px; }
.bb-mark--md { width: 36px; height: 36px; }
.bb-logo { width: 100%; height: 100%; object-fit: contain; filter: drop-shadow(0 1px 1px rgba(0,0,0,0.6)); }
.bb-mono { width: 100%; height: 100%; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-family: var(--slate-mono); font-weight: 700; font-size: 16px; letter-spacing: 0.02em; color: var(--slate-text-1); background: var(--bg-raised); box-shadow: inset 0 0 0 2px rgba(255,255,255,0.14); }
.bb-mark--lg .bb-mono { font-size: 20px; }
.bb-mono--dark { color: #0d1117; }
/* Both teams side by side on the same centre line as a single logo, away
   first as in "away @ home", the home logo drawn over the away one by a few
   pixels. Until 2026-09-24 the pair sat diagonally (top-left, bottom-right,
   63% each) and never lined up with the single logos beside it (Bill). */
.bb-mark2 .bb-logo { position: absolute; top: 50%; width: 64%; height: 64%; transform: translateY(-50%); }
.bb-mark2 .bb-logo:first-child { left: -4%; }
.bb-mark2 .bb-logo:last-child { right: -4%; filter: drop-shadow(-2px 0 1px rgba(0,0,0,0.75)); }

/* The lead card (spec s6.3). */
.bb-lead { display: block; position: relative; overflow: hidden; background: linear-gradient(180deg, var(--bg-elevated) 0%, var(--slate-bg-card) 100%); border: 1px solid var(--border-strong); border-radius: var(--radius-lg); padding: 16px 16px 14px; color: inherit; text-decoration: none; transition: border-color var(--transition-fast); }
.bb-lead::before { content: ''; position: absolute; top: 0; bottom: 0; left: 0; width: 4px; background: var(--slate-green); }
.bb-lead:hover { border-color: rgba(16,185,129,0.5); }
.bb-lead:focus-visible, .bb-row:focus-visible { outline: 2px solid var(--slate-green); outline-offset: 2px; }
.bb-lead-top { display: flex; align-items: center; gap: 10px; }
.bb-lead-id { min-width: 0; flex: 1 1 auto; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.bb-lead-name { display: block; min-width: 0; font-size: 24px; font-weight: 600; line-height: 1.15; color: var(--slate-text-1); }
.bb-pick-team { display: inline; white-space: normal; overflow-wrap: break-word; }
/* The line is glued to the name's last word by a no-break space (controller
   ruling, Task 9 fix round 1), so the name can wrap at a word boundary
   without the line ever separating onto its own line. */
.bb-pick-line { display: inline; white-space: nowrap; }
.bb-pick-line::before { content: '\00a0'; }
.bb-pick .slate-grade-chip, .bb-pick .bb-tag, .bb-lead-id .slate-grade-chip, .bb-lead-id .bb-tag { margin-left: 8px; vertical-align: middle; display: inline-flex; }
.bb-opponent { flex-basis: 100%; font-size: 16px; color: var(--slate-text-3); overflow-wrap: break-word; }
/* The lead card's matchup (Bill, 2026-10-06): the full names, or "TB @ NYY"
   where slate.js _bbFitOpponents measured them wrapping. */
.bb-opp-neutral { display: block; color: var(--slate-text-2, var(--slate-text-3)); }
.bb-opp-abbr { display: none; }
.bb-opponent--abbr .bb-opp-full { display: none; }
.bb-opponent--abbr .bb-opp-abbr { display: inline; }
.bb-lead-chance { margin-left: auto; text-align: right; flex: none; }
.bb-lead-chance .bb-chance { display: block; font-family: var(--slate-mono); font-size: 44px; font-weight: 700; line-height: 1; letter-spacing: -0.02em; color: var(--slate-text-1); }
.bb-chance-label { display: block; max-width: 78px; font-family: var(--slate-mono); font-size: 16px; color: var(--slate-text-3); margin-top: 2px; }

/* The meter (spec s6.3): 22px of room above the track for the label. */
.bb-meter { position: relative; display: block; height: 10px; margin: 30px 0 14px; background: var(--bg-raised); border-radius: 999px; }
.bb-meter-fill { position: absolute; top: 0; bottom: 0; left: 0; border-radius: 999px; background: linear-gradient(90deg, #0f766e, #10b981 60%, #34d399); }
.bb-meter-tick { position: absolute; top: -3px; bottom: -3px; left: 50%; width: 2px; background: rgba(255,255,255,0.35); }
.bb-meter-gap { position: absolute; top: 0; bottom: 0; }
.bb-meter-gap--pay { background: repeating-linear-gradient(135deg, rgba(245,158,11,0.55) 0 3px, transparent 3px 6px); }
.bb-meter-gap--edge { background: repeating-linear-gradient(135deg, rgba(52,211,153,0.85) 0 3px, transparent 3px 6px); }
.bb-meter-mark { position: absolute; top: -6px; width: 3px; height: 22px; border-radius: 2px; background: var(--slate-text-1); transform: translateX(-50%); }
.bb-meter-label { position: absolute; bottom: 26px; left: 50%; transform: translateX(-50%); font-family: var(--slate-mono); font-size: 16px; color: var(--slate-text-3); white-space: nowrap; }
.bb-meter-mark--end .bb-meter-label { left: auto; right: 0; transform: none; }
.bb-meter-fill, .bb-meter-gap, .bb-meter-mark { border-radius: 999px; }

.bb-lead-foot { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.bb-pill { display: inline-flex; align-items: center; font-family: var(--slate-mono); font-size: 16px; line-height: 1; padding: 7px 10px; border-radius: 6px; border: 1px solid var(--slate-border); color: var(--slate-text-2); white-space: nowrap; }
/* The lead pill's " · best price" note is a flex item, and a flex item's leading
   space is trimmed ("CS· best price", 2026-09-25); pre keeps it. */
.bb-pill-note { white-space: pre; }
.bb-pill--best { background: var(--accent-muted); border-color: rgba(16,185,129,0.45); color: var(--slate-text-1); }
.bb-pill--mine { border-color: var(--border-strong); }
.bb-pill--off { border-style: dashed; color: var(--slate-text-3); }
/* The saved book at a DIFFERENT number (Bill, 2026-10-01): amber, the slate's
   caution colour, so it never reads as a price for this bet; never green
   (green = a graded call). .bb-offnote under the pills says why. */
.bb-pill--offnum { border-style: dashed; border-color: var(--slate-amber); background: var(--accent-amber-bg); color: var(--slate-amber); flex-wrap: wrap; column-gap: 0.6ch; row-gap: 4px; }
/* Its estimated EV: a flex item, so on a narrow phone the pill wraps here
   instead of running past the card; the column gap is the space. */
.bb-pill-sub { white-space: nowrap; }
.bb-offnote { display: block; font-size: 16px; line-height: 1.35; color: var(--slate-text-2); margin-top: 8px; }
.bb-row .bb-offnote { grid-column: 3 / -1; grid-row: 4; margin-top: 0; }
.bb-evchip { margin-left: auto; display: inline-flex; align-items: center; font-family: var(--slate-mono); font-size: 16px; line-height: 1; padding: 7px 10px; border-radius: 6px; white-space: nowrap; }
.bb-evchip--ev { background: var(--bg-elevated); border: 1px solid var(--slate-border); color: var(--slate-text-2); }
.bb-evchip--edge { background: var(--accent-muted); border: 1px solid rgba(16,185,129,0.45); color: var(--slate-green-bright); font-weight: 600; }
.bb-lead-when { display: block; font-family: var(--slate-mono); font-size: 16px; color: var(--slate-text-3); margin-top: 10px; }

/* Rows 2-5 (spec s6.4): the chance keeps its own column so the name keeps
   the width; line 2 never ellipsizes -- the pills sit on their own line
   under the opponent, left-aligned (2026-10-18); when the text does not fit,
   the kickoff drops under the opponent as a unit (.bb-when is itself a
   wrapping flex row whose two items are the bare opponent text and the
   no-break kickoff, spaced by the column gap instead of a printed dot).
   Nothing ellipsizes and nothing breaks inside a pill or the kickoff
   (controller rulings, Task 9 fix rounds 1 and 4). */
/* Picks 2-5 are ONE list panel per market, rows split by a hairline (Bill,
   2026-10-18). Every row is the same three lines -- pick and chance; opponent,
   kickoff and the EV figure under the chance; the prices -- with the number
   1-5 in a narrow column the lead card's "1" shares, then the row's compact
   Place button under it. */
.bb-rows { list-style: none; margin: 12px 0 0; padding: 0; border: 1px solid var(--slate-border); border-radius: var(--radius-lg); background: var(--slate-bg-card); overflow: hidden; }
.bb-rows > li + li { border-top: 1px solid var(--slate-border); }
.bb-row { display: grid; grid-template-columns: 16px 36px minmax(0, 1fr) auto; grid-template-rows: auto auto auto; column-gap: 8px; row-gap: 4px; align-items: center; padding: 12px 16px 10px; color: inherit; text-decoration: none; transition: background var(--transition-fast); }
.bb-row:hover { background: var(--bg-elevated); }
/* The lead card's "1", in the rows' number column (16px; its 10px gap less 2). */
.bb-lead-top::before { content: '1'; flex: none; width: 16px; margin-right: -2px; text-align: center; font-family: var(--slate-mono); font-size: 16px; color: var(--slate-text-3); }
/* A row's Place button: the lead's button (bet_slip.css .bet-slip--place, the
   same light fill and book logo) at row size, under the pick's text.
   .bb-rows: bet_slip.css loads after this file and its .bb-slip.bet-slip--place
   margin would win a tie. */
.bb-rows .bb-slip--row.bet-slip--place { margin: 0 16px 12px 84px; min-height: 0; padding: 6px 10px 6px 8px; gap: 10px; border-radius: 8px; font-size: 16px; }
.bb-slip--row.bet-slip--place .bet-slip-logo { width: 24px; height: 24px; border-radius: 5px; }
.bb-slip--row.bet-slip--place .bet-slip-price { font-size: 16px; }
.bb-slip--row.bet-slip--place .bet-slip-odds { flex-direction: row; align-items: center; gap: 6px; }
/* Off the number the book's own line can be long ("Under 51.5"): beside it,
   bet_slip.css's zero-basis, break-anywhere name shrank to one letter per line
   at 375px and in the 1280-1439px columns (release 880, 2026-10-18). Here the
   line stacks over its price, as on the lead's button, and the name breaks
   only between words. */
.bb-slip--row.bet-slip--off.bet-slip--place .bet-slip-odds { flex-direction: column; align-items: flex-end; gap: 2px; }
.bb-slip--row.bet-slip--place .bet-slip-name { overflow-wrap: normal; min-width: min-content; }
/* Phones (one market per tab): the opponent and kickoff take the full width
   ("Tomorrow 8:15 PM ET" ran under the EV figure at 375px), the prices
   too, the EV figure gets its own line under them (beside them an amber
   off-number pill ran into it), and the Place button spans the row ("Place
   on FanDuel" broke mid-word beside an off-number "-1.5 -104"). */
@media (max-width: 639px) {
  .bb-row .bb-line2 { grid-column: 3 / -1; }
  .bb-row .bb-ev { grid-column: 3 / -1; grid-row: 4; justify-self: start; }
  .bb-row .bb-offnote { grid-row: 5; }
  .bb-rows .bb-slip--row.bet-slip--place { margin: 0 16px 12px; }
}
/* Rank and logo sit with the team name, not in the middle of the row: a row
   whose pills wrap grows downward, and centring both on it slid them away
   from the name they belong to (2026-09-24). */
.bb-rank { grid-column: 1; grid-row: 1 / span 2; align-self: start; line-height: 36px; font-family: var(--slate-mono); font-size: 16px; color: var(--slate-text-3); text-align: center; }
.bb-row .bb-mark { grid-column: 2; grid-row: 1 / span 2; align-self: start; }
.bb-pick { display: block; min-width: 0; font-size: 18px; font-weight: 500; color: var(--slate-text-1); }
.bb-prob { grid-column: 4; grid-row: 1; align-self: start; text-align: right; } /* level with the name's first line when it wraps */
.bb-prob .bb-chance { font-family: var(--slate-mono); font-size: 22px; font-weight: 600; line-height: 1.1; color: var(--slate-text-1); }
.bb-row .bb-pick { grid-column: 3; grid-row: 1; }
.bb-ev { grid-column: 4; grid-row: 2; justify-self: end; }
.bb-ev .bb-evchip { margin-left: 0; padding: 4px 8px; }
.bb-line2 { grid-column: 3; grid-row: 2; min-width: 0; }
.bb-row > .bb-pills { grid-column: 3 / -1; grid-row: 3; margin-top: 4px; }
.bb-when { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 8px; row-gap: 2px; font-size: 16px; color: var(--slate-text-3); }
.bb-kickoff { white-space: nowrap; }
/* The pills wrap inside the column. `flex: none` with no wrapping let a
   third chip spill into the gap between columns (at 1280 the Commanders
   row's EV chip ran into the next column's rank number, 2026-09-24). */
.bb-pills { display: flex; flex-wrap: wrap; justify-content: flex-start; gap: 6px; flex: 0 1 auto; max-width: 100%; }
.bb-pills .bb-pill, .bb-pills .bb-evchip { padding: 4px 8px; }
.bb-pills .bb-evchip { margin-left: 0; }
.bb-tag { font-family: var(--slate-mono); font-size: 16px; color: var(--slate-text-3); white-space: nowrap; }
.bb-empty { font-size: 17px; color: var(--slate-text-3); margin: 8px 0 0; }
.bb-counts { font-family: var(--slate-mono); font-size: 16px; color: var(--slate-text-3); margin: 2px 0 0; }
/* The graded-calls panel (beta feedback 2026-09-27): above the tabs, always,
   for a reader with upcoming games. The graded calls it lists carry the grade
   chip; everything else is text colours -- green stays the colour of a
   graded call. */
.bb-nocall { margin: 14px 0 4px; padding: 12px 14px; border: 1px solid var(--slate-border); border-radius: var(--radius-sm); }
.bb-nocall-h, .bb-nocall-p { font-size: 17px; line-height: 1.45; color: var(--slate-text-2); margin: 0; }
.bb-nocall-p { margin-top: 10px; }
.bb-nocall b { color: var(--slate-text-1); font-weight: 600; }
.bb-gaps { list-style: none; margin: 4px 0 0; padding: 0; }
.bb-gaps li { margin: 4px 0 0; }
.bb-gap { font-size: 17px; line-height: 1.45; color: var(--slate-text-2); text-decoration: none; display: inline-block; min-height: 24px; }
.bb-gap:hover, .bb-gap:focus-visible { color: var(--slate-text-1); text-decoration: underline; }
.bb-gap:focus-visible { outline: 2px solid var(--slate-green); outline-offset: 2px; }
/* "What moved" (2026-10-05, spec 2026-10-05-no-edge-board-state-design.md):
   the same text treatment as the price lines; facts, never green. */
.bb-moves { list-style: none; margin: 4px 0 0; padding: 0; }
.bb-moves li { margin: 4px 0 0; }
.bb-move { font-size: 17px; line-height: 1.45; color: var(--slate-text-2); text-decoration: none; display: inline-block; min-height: 24px; }
.bb-move:hover, .bb-move:focus-visible { color: var(--slate-text-1); text-decoration: underline; }
.bb-move:focus-visible { outline: 2px solid var(--slate-green); outline-offset: 2px; }
@media (max-width: 599px) { .bb-gaps li:nth-child(n+3) { display: none; } }
/* Phones: ONE What moved line, the top fact (Bill, 2026-10-05, the phone fold). */
@media (max-width: 599px) { .bb-moves li:nth-child(n+2) { display: none; } }
.bb-calls { list-style: none; margin: 4px 0 0; padding: 0; }
.bb-calls li { margin: 6px 0 0; }
.bb-call { font-size: 17px; line-height: 1.45; color: var(--slate-text-2); text-decoration: none; display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 0 6px; min-height: 24px; }
.bb-call:hover, .bb-call:focus-visible { color: var(--slate-text-1); text-decoration: underline; }
.bb-call:focus-visible { outline: 2px solid var(--slate-green); outline-offset: 2px; }
.bb-call-when { font-family: var(--slate-mono); font-size: 16px; color: var(--slate-text-3); margin-left: 4px; }

@media (max-width: 1279px) {
  .bb-head { flex-wrap: wrap; }
  .bb-head-l { flex: 1 1 320px; }
  .bb-tabs { display: flex; gap: 4px; margin-top: 12px; }
  .bb-tab { flex: 1; min-height: 44px; color: var(--slate-text-2); background: none; border: 1px solid var(--slate-border); border-radius: var(--radius-sm); cursor: pointer; }
  .bb-tab[aria-selected="true"] { background: var(--bg-elevated); color: var(--slate-text-1); box-shadow: inset 0 -2px 0 var(--slate-green); }
  .bb-src-dot { display: inline; margin-left: 6px; color: var(--slate-blue); }
  .bb-cols { grid-template-columns: minmax(0, 1fr); }
  .bb-col-h .bb-src { display: none; }
  .best-bets[data-tab="moneyline"] .bb-col:not([data-market="moneyline"]),
  .best-bets[data-tab="spread"] .bb-col:not([data-market="spread"]),
  .best-bets[data-tab="total"] .bb-col:not([data-market="total"]) { display: none; }
  /* The lead card stacks (spec s6.6). */
  .bb-mark--lg { width: 48px; height: 48px; }
  .bb-lead-name { font-size: 20px; }
  .bb-lead-chance .bb-chance { font-size: 36px; }
  .bb-lead-foot .bb-evchip { margin-left: 0; }
}
/* The three-column band is tight from 1280-1439px (controller ruling, Task 9
   fix round 2): same scaled lead-card sizes as the stacked layout above,
   full sizes (mark 60, name 24, chance 44) resume at 1440px. */
@media (min-width: 1280px) and (max-width: 1439px) {
  .bb-mark--lg { width: 48px; height: 48px; }
  .bb-lead-name { font-size: 20px; }
  .bb-lead-chance .bb-chance { font-size: 36px; }
}
/* Short forms for phones (Bill, 2026-09-27, the first-screen trim): the view
   prints a long and a short form of the subtitle and the panel's lines; one
   shows. */
.bb-copy-short { display: none; }
@media (max-width: 639px) {
  .best-bets { padding: 14px 16px 12px; }
  .bb-head { flex-direction: column; }
  .bb-head-l { flex: 0 1 auto; }
  .bb-copy-long { display: none; }
  .bb-copy-short { display: inline; }
  /* The column heading repeats the tab right above it ("MONEYLINE" under
     "ML"); its books'-number tag is already hidden below 1280px (the tab's
     dot carries it). Visually hidden, still a heading for screen readers. */
  .bb-col-h { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
}
@media (max-width: 479px) {
  .bb-head-r { flex-wrap: wrap; }
  .bb-risk { white-space: nowrap; }
  .bb-meter-mark--late .bb-meter-label { left: auto; right: 0; transform: none; }
}
/* The narrowest phones (320-359px). Measured 2026-09-25 on prod data at 320:
   the lead card's "-215 LV · best price" pill ran 10px past the card, the
   meter's "break-even 74%" up to 14px, a row's no-break kickoff ("Tomorrow
   10:00 PM ET") up to 24px, and "Cornhuskers" broke mid-word beside the lead
   chance. The pills and the kickoff keep their no-break rule (Task 9 rulings);
   the room comes from padding, the grid's fixed columns, the logo and the
   lead name instead. 360px and up are unchanged. */
@media (max-width: 359px) {
  .best-bets { padding-left: 10px; padding-right: 10px; }
  .bb-lead { padding: 14px 10px 12px 14px; }   /* the left side clears the 4px green rule */
  .bb-lead-top { gap: 8px; }
  .bb-mark--lg { width: 40px; height: 40px; }
  .bb-lead-name { font-size: 18px; }
  .bb-lead-chance .bb-chance { font-size: 32px; }
  .bb-row { grid-template-columns: 16px 32px minmax(0, 1fr) auto; column-gap: 6px; padding: 10px 10px 8px 14px; }   /* 14px left = the lead's */
  .bb-rank { line-height: 32px; }
  .bb-lead-top::before { margin-right: 0; }   /* lead gap 8 - row gap 6 = the 2px the number keeps */
  .bb-rows .bb-slip--row.bet-slip--place { margin: 0 10px 10px 14px; }
  .bb-row .bb-mark { width: 32px; height: 32px; }
  .bb-pills .bb-pill, .bb-pills .bb-evchip { padding: 4px 6px; }
  .bb-pill-note { display: none; }   /* the green pill already says "best" */
}

/* ── Line-movement chart host (2026-09-12). The wrapper used to carry an inline
   height:180px; a class lets phones give the plot room (see the phone block). */
.slate-lm-wrap { height: 180px; position: relative; }
/* The interpretation paragraph under the line movement chart (2026-09-13).
   Size and leading come from .slate-narr-body; this only sets it off the plot. */
.slate-lm-read { margin-top: 12px; }
/* The interpretation paragraph under the injury report (2026-09-30). Size and
   leading come from .slate-narr-body; this only sets it off the player rows. */
.slate-injury-read { margin-top: 10px; }
.slate-exchange-read { margin-top: 10px; }
.slate-weather-read { margin-top: 10px; }
.ss-th-short { display: none; } /* 'Wt' on phones; see the phone block */

/* ── Analysis drawer on phones (2026-09-12). Measured on the live drawer at
   375px after the 16px floor: the Signal Stack table scrolled sideways with
   the away column and the Weight column off-screen (366px in a 269px body);
   Line Shopping showed 2.3 of its 5 columns; the line-movement legend took
   three rows of a 180px box and left a 70px plot; the ⓘ wrapped alone under
   any section title that carried a meta; and a wide edge chip in a props row
   overflowed its fixed 84px column into the player name (fixed in the
   existing prop-row phone rules: 1fr auto 44px). */
@media (max-width: 639px) {
  /* Section head: when the row wraps, the META drops to the next line, not the ⓘ. */
  .slate-sec-head .slate-sec-h { order: 1; }
  .slate-sec-head .slate-sec-info { order: 2; }
  .slate-sec-head .slate-sec-badge { order: 3; }
  .slate-sec-head .slate-sec-meta { order: 4; }

  /* Signal Stack: four columns that fit 269px. Title case for the team headers
     (the global th rule upper-cases them: COLORADO is 84px, Colorado 66px). */
  .slate-ss-table { table-layout: fixed; width: 100%; font-size: 16px; }
  .slate-ss-table th, .slate-ss-table td { padding: 4px 2px; }
  .slate-ss-table td:first-child { overflow-wrap: anywhere; } /* 'Polymarket' is 84px; the safety net for a longer signal name */
  .slate-ss-table th { text-transform: none; font-weight: 600; letter-spacing: 0; -webkit-hyphens: auto; hyphens: auto; overflow-wrap: anywhere; }
  /* A one-word team name has no space to wrap on. Measured 2026-09-13 in the
     drawer's own fonts at 16px: Strasbourg 82px, Tottenham 80, Hurricanes 81,
     Leverkusen 85, Sunderland 86 -- and the team column was 71px. No engine
     on this desk hyphenates (headless Chromium, the app's browser and real
     Chrome 153 all lack the dictionary), so `hyphens: auto` is a bonus on
     phones that have one and `overflow-wrap: anywhere` is the floor.
     The room comes from the table itself: full-bleed to the section card's
     edge (269 -> 297px), 500-weight headers with a hair less tracking, and
     the columns rebalanced: Signal 91px (content 83, so the 81px bold
     'Consensus' never breaks), team columns 84px (content 80: Strasbourg at
     500 weight is 79), Wt 39px. Leverkusen and longer still break rather
     than spill. Measured on the live drawer, not in a probe page. */
  .slate-ss-scroll { margin: 0 -14px; }
  .slate-ss-table th:first-child, .slate-ss-table td:first-child { padding-left: 6px; }
  .slate-ss-table th:last-child, .slate-ss-table td:last-child { padding-right: 6px; }
  .slate-ss-table th:nth-child(1) { width: 30.5%; }
  .slate-ss-table th:nth-child(2), .slate-ss-table th:nth-child(3) { width: 28.25%; font-weight: 500; letter-spacing: -0.01em; }
  .slate-ss-table th:nth-child(4) { width: 13%; }
  /* 'Weight' broke as Wei/ght in the 38px column; the long form stays for screen readers. */
  .ss-th-long { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .ss-th-short { display: inline; }

  /* Line Shopping: Book · Price · Edge. Implied is the price restated and Juice
     is the gap to the sharpest book; both are one tap away on desktop and the
     summary line above the table already names the sharpest book. */
  .slate-ls-table--books .slate-ls-col-implied,
  .slate-ls-table--books .slate-ls-col-juice { display: none; }
  .slate-ls-table--books th, .slate-ls-table--books td { padding: 8px 6px; }
  .slate-ls-table--books td { font-size: 16px; }
  .slate-ls-table--books th { letter-spacing: 0.06em; }

  /* Line movement: room for a five-book legend AND a readable plot. */
  .slate-lm-wrap { height: 300px; }

}

/* Exchanges (plan 2) */
.ex-view { max-width: 900px; margin: 0 auto; padding: var(--space-lg); }
.ex-head { display: flex; align-items: baseline; justify-content: flex-end; gap: var(--space-md); margin-bottom: var(--space-md); }
.ex-checked { font-size: var(--text-xs); color: var(--text-muted); }
/* Final review F4: focus lands on the stamp after Refresh; a neutral ring, never green. */
.ex-checked:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }
.ex-refresh { font: inherit; font-size: var(--text-xs); min-height: 44px; background: none; border: none; padding: 0 var(--space-xs); color: var(--text-primary); text-decoration: underline; cursor: pointer; }
.ex-refresh:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }
/* Visually hidden, still read by screen readers (#ex-board-status). */
#view-exchanges .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.ex-filters { display: flex; gap: var(--space-md); margin-bottom: var(--space-md); flex-wrap: wrap; }
.ex-filters[hidden] { display: none; }
.ex-filters label { font-size: var(--text-sm); color: var(--text-secondary); display: flex; align-items: center; gap: var(--space-xs); }
/* Launch spec A13: the order toggle and the team search. The pressed order is marked in ink, never green. */
.ex-order { display: inline-flex; flex-wrap: wrap; gap: var(--space-xs); }
.ex-order-btn { font: inherit; font-size: var(--text-sm); min-height: 44px; padding: var(--space-xs) var(--space-sm); background: var(--bg-card); color: var(--text-secondary); border: 1px solid var(--border-subtle); border-radius: 6px; cursor: pointer; }
.ex-order-btn[aria-pressed="true"] { color: var(--text-primary); border-color: var(--text-primary); font-weight: 600; }
.ex-order-btn:focus-visible, .ex-search:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }
.ex-search { font-size: max(16px, var(--text-xs)); min-height: 44px; flex: 1 1 12em; min-width: 0; padding: var(--space-xs) var(--space-sm); background: var(--bg-card); color: var(--text-primary); border: 1px solid var(--border-subtle); border-radius: 6px; }
.ex-game { margin: 0 0 var(--space-lg); }
.ex-game-head { font-size: var(--text-sm); color: var(--text-primary); margin: 0 0 var(--space-sm); }
.ex-select { font-size: var(--text-xs); min-height: 44px; background: var(--bg-card); color: var(--text-secondary); border: 1px solid var(--border-subtle); border-radius: 6px; padding: var(--space-xs) var(--space-sm); }

.ex-counts { font-size: var(--text-xs); color: var(--text-muted); margin: 0 0 var(--space-sm); }
.ex-dev-banner { background: var(--bg-card); border: 1px solid var(--border-subtle); border-left: 3px solid var(--accent-amber); border-radius: 8px; padding: var(--space-lg); color: var(--text-secondary); font-size: var(--text-sm); line-height: 1.5; }
.ex-dev-banner p { margin: 0; }
.ex-dev-title { font-size: var(--text-base); color: var(--text-primary); margin: 0 0 var(--space-sm); }
.ex-none, .ex-empty { font-size: var(--text-sm); color: var(--text-muted); padding: var(--space-md) 0; }
.ex-howto { font-size: var(--text-xs); color: var(--text-secondary); line-height: 1.5; margin: 0 0 var(--space-sm); }
.ex-howto-label { color: var(--text-primary); font-weight: 600; }

.ex-row {
  display: block; width: 100%; min-height: 44px; text-align: left;
  background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: 8px;
  color: var(--text-secondary); font: inherit; font-size: var(--text-sm);
  padding: var(--space-sm) var(--space-md); margin-bottom: var(--space-sm); cursor: pointer;
}
.ex-row:hover { border-color: var(--text-muted); }
.ex-row:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }
.ex-row-top { display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm); }
.ex-row-title { font-size: var(--text-sm); color: var(--text-primary); font-weight: 500; }
.ex-row-sub { display: block; margin-top: var(--space-xs); font-size: var(--text-xs); color: var(--text-muted); }

.ex-chip {
  display: inline-block; font-size: var(--text-xs); font-weight: 600; letter-spacing: 0.03em;
  padding: 2px var(--space-sm); border-radius: 999px; white-space: nowrap;
  background: var(--bg-detail); color: var(--text-muted);
}
.ex-chip--defer { background: var(--accent-amber-bg); color: var(--accent-amber); }
.ex-chip--none { background: var(--bg-detail); color: var(--text-muted); }

.ex-card { max-width: 640px; margin: 0 auto; padding: var(--space-lg); background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: 8px; }
/* A <button>, like the Amount <input>, takes the browser's font unless told: inherit the app's (2026-10-09). */
.ex-back { background: none; border: none; color: var(--text-secondary); font: inherit; font-size: var(--text-sm); min-height: 44px; padding: 0; cursor: pointer; }
.ex-back:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }
.ex-card-title { font-size: var(--text-lg); color: var(--text-primary); margin: var(--space-sm) 0; }
.ex-card-title:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }
.ex-card-ctx { font-size: var(--text-xs); color: var(--text-muted); margin: 0 0 var(--space-md); }
/* Final review F5g: "Kalshi contract: NO on {B}" on a Kalshi winner NO card. */
.ex-card-contract { font-size: var(--text-xs); color: var(--text-secondary); margin: 0 0 var(--space-md); overflow-wrap: anywhere; }

.ex-amount-row { display: flex; align-items: center; gap: var(--space-sm); font-size: var(--text-sm); color: var(--text-secondary); margin-bottom: var(--space-sm); }
/* An <input> takes the browser's field font unless told: inherit the app's (Bill, 2026-10-09). The shorthand
   comes first: it would reset the size. */
.ex-amount { font: inherit; font-size: max(16px, var(--text-xs)); min-height: 44px; padding: var(--space-xs) var(--space-sm); background: var(--bg-detail); color: var(--text-primary); border: 1px solid var(--border-subtle); border-radius: 6px; width: 120px; }
.ex-status { font-size: var(--text-xs); color: var(--text-muted); min-height: var(--text-xs); margin: 0 0 var(--space-sm); }

.ex-table { width: 100%; border-collapse: collapse; margin-bottom: var(--space-md); }
.ex-table th, .ex-table td { text-align: left; font-size: var(--text-sm); padding: var(--space-xs) 0; border-bottom: 1px solid var(--border-subtle); }
.ex-table th { color: var(--text-muted); font-weight: 400; }
.ex-table td { color: var(--text-primary); }

.ex-verdict { font-size: var(--text-sm); color: var(--text-secondary); padding: var(--space-sm) 0; display: flex; align-items: center; gap: var(--space-sm); }
.ex-fresh { font-size: var(--text-xs); color: var(--text-secondary); margin: 0 0 var(--space-sm); }
.ex-depth { margin: var(--space-md) 0; }
.ex-depth-svg { display: block; max-width: 100%; }
.ex-depth-line { stroke: var(--text-secondary); stroke-width: 1.5; }
.ex-depth-max { stroke: var(--text-primary); stroke-dasharray: 4 3; }
.ex-depth-text { fill: var(--text-muted); font-size: var(--text-xs); }

.ex-terms { font-size: var(--text-xs); color: var(--text-muted); padding-left: var(--space-lg); margin: var(--space-sm) 0; }
.ex-summary { font-size: var(--text-xs); color: var(--text-muted); margin: var(--space-sm) 0; }
.ex-summary-label { font-weight: 600; color: var(--text-secondary); }
.ex-out { display: inline-block; margin-top: var(--space-sm); font-size: var(--text-sm); color: var(--text-primary); text-decoration: underline; min-height: 44px; line-height: 44px; }
.ex-foot { font-size: var(--text-xs); color: var(--text-muted); margin-top: var(--space-md); }

/* Exchanges redesign (plan 3, 2026-09-26): the cost ledger and the side-by-side card. */
.ex-summary-strip { font-size: var(--text-sm); color: var(--text-secondary); background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: 8px; padding: var(--space-sm) var(--space-md); margin: 0 0 var(--space-md); }
.ex-row--buy { border-color: var(--accent); }
.ex-row--post { border-left: 3px solid var(--accent); }
.ex-row-cost { display: flex; align-items: center; gap: var(--space-sm); margin-top: var(--space-xs); }
.ex-bar { position: relative; display: inline-block; flex-shrink: 0; width: 80px; height: 8px; background: var(--bg-detail); border-radius: 4px; }
.ex-bar-mid { position: absolute; left: 50%; top: -2px; bottom: -2px; width: 1px; background: var(--text-muted); }
.ex-bar-fill { position: absolute; top: 0; bottom: 0; border-radius: 4px; }
.ex-bar-fill--neutral { background: var(--text-muted); }
.ex-bar-fill--under { background: var(--accent); }
.ex-cost { font-size: var(--text-xs); color: var(--text-secondary); }
.ex-row-cheaper { color: var(--text-secondary); }
/* Launch spec A6/A11: the no-buys line under the strip; a row's cheaper-venue saving. Never green. */
.ex-row-saving { color: var(--text-secondary); }
.ex-nobuys { font-size: var(--text-sm); color: var(--text-secondary); margin: 0 0 var(--space-md); }
.ex-chip--buy { background: var(--accent-bg); color: var(--accent); }
.ex-chip--post { background: var(--bg-detail); color: var(--accent); border: 1px solid var(--accent); }
.ex-chip--noside { background: var(--bg-detail); color: var(--text-muted); }
/* Launch spec A1/A12: alternate lines, one closed section per game, NOT RATED in grey. */
.ex-chip--notrated { background: var(--bg-detail); color: var(--text-muted); }
.ex-alt { margin: var(--space-md) 0; border-top: 1px solid var(--border-subtle); padding-top: var(--space-sm); }
.ex-alt-toggle { display: block; width: 100%; min-height: 44px; text-align: left; background: none; border: none; padding: var(--space-xs) 0; font: inherit; font-size: var(--text-sm); color: var(--text-primary); cursor: pointer; }
.ex-alt-toggle::before { content: "▸ "; color: var(--text-muted); }
.ex-alt-toggle[aria-expanded="true"]::before { content: "▾ "; }
.ex-alt-toggle:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }
.ex-alt-game, .ex-alt-reason { font-size: var(--text-xs); color: var(--text-muted); margin: 0 0 var(--space-xs); }
.ex-row--alt { border-style: dashed; }
.ex-verdict--notrated { color: var(--text-muted); }

.ex-card-worth { font-size: var(--text-base); color: var(--text-primary); margin: 0 0 var(--space-xs); }
.ex-card-basis { font-size: var(--text-xs); color: var(--text-muted); margin: 0 0 var(--space-md); }
.ex-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--space-sm); margin: var(--space-sm) 0 var(--space-md); }
.ex-col { background: var(--bg-detail); border: 1px solid var(--border-subtle); border-radius: 8px; padding: var(--space-sm) var(--space-md); }
.ex-col p { margin: 0 0 var(--space-xs); }
.ex-col--best { border: 2px solid var(--accent); }
/* Launch spec A5b: the other venue's column is a button to that venue's card. */
button.ex-col { display: block; width: 100%; min-height: 44px; text-align: left; font: inherit; color: inherit; cursor: pointer; }
button.ex-col:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }
.ex-col--other span { display: block; margin: 0 0 var(--space-xs); }
.ex-col--other span.ex-chip, .ex-col--other span.ex-col-best { display: inline-block; margin: 0; }
.ex-col-note { font-size: var(--text-sm); color: var(--text-secondary); }
.ex-col-head { font-size: var(--text-sm); color: var(--text-secondary); font-weight: 500; }
.ex-col-best { font-size: var(--text-xs); color: var(--accent); font-weight: 600; margin-left: var(--space-xs); }
.ex-col-price { font-size: var(--text-base); color: var(--text-primary); }
.ex-col-cost { font-size: var(--text-xs); color: var(--text-secondary); }
.ex-stake { font-size: var(--text-sm); color: var(--text-primary); }
.ex-stake--stale { grid-column: 1 / -1; color: var(--text-muted); }
.ex-warn { font-size: var(--text-xs); color: var(--accent-amber); }
.ex-samegame { font-size: var(--text-xs); color: var(--text-secondary); margin: 0 0 var(--space-sm); }
.ex-route { font-size: var(--text-xs); color: var(--text-secondary); margin: 0 0 var(--space-sm); }

.ex-math { margin: var(--space-md) 0; border-top: 1px solid var(--border-subtle); }
.ex-math-summary { display: list-item; min-height: 44px; line-height: 44px; cursor: pointer; font-size: var(--text-sm); color: var(--text-secondary); }
.ex-math-summary:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }
.ex-math-head { font-size: var(--text-sm); color: var(--text-primary); margin: var(--space-md) 0 var(--space-xs); }
.ex-math-line { font-size: var(--text-xs); color: var(--text-muted); margin: 0 0 var(--space-xs); }

@media (max-width: 1024px) {
  .ex-view { padding: var(--space-md); max-width: 100%; }
  .ex-filters { flex-direction: column; gap: var(--space-sm); }
  .ex-card { max-width: 100%; padding: var(--space-md); overflow-x: hidden; }
  .ex-table, .ex-table tbody, .ex-table tr, .ex-table th, .ex-table td { display: block; width: 100%; }
  .ex-table th { border-bottom: none; padding-bottom: 0; }
  .ex-table td { padding-top: 0; padding-bottom: var(--space-sm); }
}

/* Phone order (Bill, 2026-10-05): release 770 measured the first Most-likely
   card at y~880 on a phone whose usable bottom is 748 (812 less the 64px bottom
   nav). On a phone the verdict stays on top, the lead-in and the list follow,
   and the price lines and What moved drop BELOW the list. The view renders the
   panel's three parts in desktop order (slate_best_bets_view.js _panel); the
   box dissolves into the block body's flex column here and each part takes
   its place. Desktop is untouched: these classes carry no style outside this
   block. The collapse rule (.best-bets[data-collapsed="1"] .bb-body, 0,3,0)
   still outranks the flex below (0,2,0). */
@media (max-width: 639px) {
  .best-bets .bb-body { display: flex; flex-direction: column; }
  .bb-body > .bb-nocall { display: contents; }
  .bb-nocall-top { order: 0; margin: 14px 0 4px; padding: 12px 14px; border: 1px solid var(--slate-border); border-radius: var(--radius-sm); }
  .bb-nocall-lead { order: 1; }
  .bb-body > .bb-tabs { order: 2; }
  .bb-body > .bb-cols { order: 3; }
  .bb-body > .bb-empty, .bb-body > .bb-counts { order: 3; }
  .bb-nocall-more { order: 4; margin: 16px 0 4px; padding: 2px 14px 12px; border: 1px solid var(--slate-border); border-radius: var(--radius-sm); }
}

/* The Arbitrage tab (spec 2026-10-07 section 4; plan 3 Task 7): rows are stacked cards, the return under
   the game and the legs below it; the card is a dialog over the board, a bottom sheet on a phone. Type is
   16px or more; every control is 44px tall. No underlines or info icons on rows or cards (Bill 2026-10-04):
   the one "How arbitrage works" link sits under the board. tests/test_arb_template.py reads the block below
   up to the next comment, so it carries none. */
/* Arbitrage (plan 3) */
.arb-view { max-width: 900px; margin: 0 auto; padding: var(--space-lg); }
.arb-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-md); margin-bottom: var(--space-md); }
.arb-title { font-size: var(--text-lg); color: var(--text-primary); margin: 0; }
.arb-paused { font-size: var(--text-sm); color: var(--accent-amber); margin: 0; }
.arb-filters { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-md); margin-bottom: var(--space-sm); }
.arb-filters label { font-size: var(--text-sm); color: var(--text-secondary); display: flex; align-items: center; gap: var(--space-xs); }
.arb-input, .arb-select { font-size: 16px; min-height: 44px; padding: var(--space-xs) var(--space-sm); background: var(--bg-card); color: var(--text-primary); border: 1px solid var(--border-subtle); border-radius: 6px; box-sizing: border-box; }
.arb-input { width: 7em; }
.arb-books { display: flex; flex-wrap: wrap; gap: 0 var(--space-md); margin: 0; padding: 0 var(--space-md) var(--space-xs); min-width: 0; border: 1px solid var(--border-subtle); border-radius: 8px; }
.arb-books legend { font-size: var(--text-sm); color: var(--text-secondary); padding: 0 var(--space-xs); }
.arb-check { min-height: 44px; cursor: pointer; }
.arb-check input { width: 20px; height: 20px; margin: 0; accent-color: var(--text-primary); }
.arb-hint { font-size: var(--text-sm); color: var(--text-muted); margin: 0 0 var(--space-md); }
.arb-filters .arb-hint { flex-basis: 100%; }
.arb-empty { font-size: var(--text-sm); color: var(--text-muted); padding: var(--space-md) 0; margin: 0; }
.arb-row { display: flex; flex-direction: column; align-items: stretch; gap: var(--space-xs); width: 100%; min-height: 44px; box-sizing: border-box; text-align: left; background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: 8px; color: var(--text-secondary); font: inherit; font-size: var(--text-sm); padding: var(--space-sm) var(--space-md); margin-bottom: var(--space-sm); cursor: pointer; }
.arb-row:hover { border-color: var(--text-muted); }
.arb-row-top { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-sm); }
.arb-game { font-size: var(--text-sm); color: var(--text-primary); font-weight: 500; }
.arb-row .arb-return { order: 1; font-size: var(--text-base); color: var(--text-primary); font-weight: 600; }
.arb-row .arb-leg { order: 2; display: block; }
.arb-row .arb-fill { order: 3; }
.arb-leg { font-size: var(--text-sm); color: var(--text-secondary); overflow-wrap: anywhere; }
.arb-age { color: var(--text-muted); white-space: nowrap; }
.arb-fill, .arb-limits { font-size: var(--text-xs); color: var(--text-muted); }
.arb-chip { display: inline-block; font-size: var(--text-xs); font-weight: 600; padding: 2px var(--space-sm); border-radius: 999px; white-space: nowrap; background: var(--bg-detail); color: var(--text-muted); }
.arb-chip--confirmed { background: var(--accent-green-bg); color: var(--accent-green); }
.arb-chip--gone { background: var(--accent-red-bg); color: var(--accent-red); }
.arb-chip--unchecked { background: var(--accent-amber-bg); color: var(--accent-amber); }
.arb-row .arb-chip { white-space: normal; overflow-wrap: anywhere; }
.arb-dialog { position: fixed; inset: 0; z-index: 9000; display: flex; align-items: center; justify-content: center; padding: var(--space-lg); box-sizing: border-box; background: color-mix(in srgb, var(--bg-page) 75%, transparent); }
.arb-dialog[hidden] { display: none; }
.arb-dialog:focus { outline: none; }
.arb-sheet { width: 100%; max-width: 560px; max-height: calc(100vh - 32px); overflow-y: auto; box-sizing: border-box; background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: 8px; padding: var(--space-sm) var(--space-lg) var(--space-lg); }
.arb-close { display: block; margin-left: auto; min-height: 44px; min-width: 44px; padding: 0 var(--space-sm); background: none; border: none; color: var(--text-secondary); font: inherit; font-size: var(--text-sm); cursor: pointer; }
.arb-card-title { font-size: var(--text-lg); color: var(--text-primary); margin: 0 0 var(--space-sm); }
.arb-card-status { margin: 0 0 var(--space-sm); }
.arb-card-status .arb-chip { white-space: normal; }
.arb-msg, .arb-note, .arb-void { font-size: var(--text-sm); color: var(--text-secondary); margin: 0 0 var(--space-sm); }
.arb-card-leg { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-xs); padding: var(--space-sm) 0; border-top: 1px solid var(--border-subtle); }
.arb-stake { font-size: var(--text-sm); color: var(--text-primary); }
.arb-out { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; box-sizing: border-box; padding: 0 var(--space-md); font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); background: var(--bg-detail); border: 1px solid var(--border-subtle); border-radius: 6px; text-decoration: none; }
.arb-round, .arb-recheck { min-height: 44px; margin-top: var(--space-sm); padding: 0 var(--space-md); font: inherit; font-size: var(--text-sm); color: var(--text-primary); background: var(--bg-detail); border: 1px solid var(--border-subtle); border-radius: 6px; cursor: pointer; }
.arb-round[aria-pressed="true"] { border-color: var(--text-primary); font-weight: 600; }
.arb-learn { display: inline-flex; align-items: center; min-height: 44px; margin-top: var(--space-md); font-size: var(--text-sm); color: var(--text-secondary); }
.arb-row:focus-visible, .arb-close:focus-visible, .arb-round:focus-visible, .arb-recheck:focus-visible, .arb-out:focus-visible, .arb-learn:focus-visible, .arb-input:focus-visible, .arb-select:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }
@media (max-width: 1024px) {
  .arb-view { padding: var(--space-md); max-width: 100%; }
  .arb-filters { flex-direction: column; align-items: stretch; gap: var(--space-sm); }
  .arb-card-leg { align-items: stretch; }
  .arb-out { width: 100%; }
  .arb-dialog { align-items: flex-end; padding: 0; }
  .arb-sheet { max-width: 100%; max-height: 90vh; border-radius: 12px 12px 0 0; padding: var(--space-sm) var(--space-md) var(--space-lg); }
}
#view-arb .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
@media (max-width: 639px) {
  .arb-filters-toggle { display: inline-flex; align-items: center; align-self: flex-start; min-height: 44px; max-width: 100%; margin: 0 0 var(--space-sm); padding: var(--space-xs) var(--space-md); background: var(--bg-card); color: var(--text-primary); border: 1px solid var(--border-subtle); border-radius: 6px; font: inherit; font-size: 16px; cursor: pointer; }
  .arb-filters-toggle::after { content: '\25BE' / ''; padding-left: var(--space-xs); }
  .arb-filters-toggle[aria-expanded="true"]::after { content: '\25B4' / ''; }
  .arb-filters-toggle:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }
}
.arb-filters[hidden], .arb-filters-toggle[hidden] { display: none; }

/* KEY FINDING (Bill, 2026-10-15, "Fill = finding"): a solid amber fill with dark
   ink marks the one conclusion a card or section exists to deliver, the look of
   Market Gaps' >5pt gap chip (market_gaps.css .mg-chip--high). Sparse: at most
   one per card. Amber as CAUTION stays amber text or a faint tint, never a fill.
   Last in the sheet so it wins over each element's own rule above (and main.css).
   - Best Bets: the lead card's "+X% edge" chip (a graded call in the reader's favour)
   - Game card: the verdict strip's EV on a recommended play
   - Exchanges: the BUY call chip (row and detail)
   - Arbs: the return line of a Confirmed (re-checked) arb
   - Analytics: .key-finding, the profit tile when the whole interval clears zero
   - Props (Bill, 2026-10-15): a row's EV at +3.0% or more (desktop, phone line, game card); the
     per-book EVs in the expanded book list (.pm-bev) stay green
   - Steam: the card's STEAM badge, and the modal's headline
   - Line shopping (Bill, 2026-10-18): the best row's price when it beats our fair price, on a play
   - Market read (Bill, 2026-10-18): the over-5-point Market Gaps chip (.mg-chip--high is filled
     by market_gaps.css already; listed here so this rule stays the one inventory)
   - /me/clv has no slate.css: its Mean CLV tile carries its own copy (routes/me_clv_dashboard.py) */
.key-finding,
.slate-ls-table tr.best .slate-ls-beats-fair,
.slate-mread .mg-chip--high,
.pm-ev.pm-ev--pos:not(.pm-bev),
.steam-badge,
.bb-lead .bb-evchip--edge,
.ob-verdict-play .ob-verdict-ev,
.ex-chip--buy,
.arb-row:has(.arb-chip--confirmed) .arb-return {
  background: var(--accent-amber); border-color: var(--accent-amber); color: #0d1117; font-weight: 700;
}
.key-finding { display: inline-block; padding: 2px 10px; border-radius: 6px; }
.ob-verdict-play .ob-verdict-ev { padding: 1px 8px; border-radius: 6px; white-space: nowrap; }
.arb-row:has(.arb-chip--confirmed) .arb-return { align-self: flex-start; padding: 2px 8px; border-radius: 6px; }
.pm-ev.pm-ev--pos:not(.pm-bev) { padding: 1px 6px; border-radius: 6px; }
.slate-ls-table tr.best .slate-ls-beats-fair { display: inline-block; padding: 1px 8px; border-radius: 6px; white-space: nowrap; }
/* The phone line has no width to spare (tests/test_props_headline_phone.py): a 2px pad there. */
.pm-ph-ev .pm-ev.pm-ev--pos { padding: 1px 2px; }
