/* schedule-dashboard.css — the >1000px dashboard layout for the schedule page.
   Sport-agnostic; scheduleCore turns it on per sport via the `dashboard` config.

   SEPARATE SHEET on purpose: schedule-layout.css is loaded ungated on EVERY page
   of the app (templates/index.html:92) and already opens with a global
   box-sizing reset. Adding dashboard rules there would ship them to every route
   and every sport. Everything here is scoped under .wb-dash, which only exists
   when the dashboard renderer runs.

   Colors are neutral white-alpha, NOT the --wb-card-* custom properties. Those
   resolve to the teal accent (schedule-layout.css:20-21 — accent at 20/30%),
   which put a green outline on the counts block and the buttons; the accent
   belongs on things that carry meaning, not on chrome.

   Never touches .wb-card internals — the cards are buildGameCard()'s output
   verbatim so the 5s live poll can keep mutating them in place. */

/* EVERY WIDTH, not just desktop. This sheet used to sit inside a single
   `@media (min-width: 1001px)` wrapper because ≤1000px rendered the flat list
   instead. Both breakpoints now run the same dashboard renderer, so the
   structure is: these ungated rules are the design, and the two width blocks at
   the BOTTOM of the file carry only what genuinely differs — how many cards sit
   per row, and the touch-scale overrides mobile needs undone. Anything added
   here without a width block applies to phones too, which is the point. */

/* .wb-main is capped at 80rem (~1280px) with a -10px top margin. The negative
   margin has to go or the header collides. The desktop block widens this to
   1720px; the mobile default is edge-to-edge with a small gutter. */
.wb-dash {
  margin-top: 0;
  max-width: 100%;
  padding-left: 0.75rem;
  padding-right: 0.75rem;
}

/* ── header ─────────────────────────────────────────────────────── */
.wb-dash-head {
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
  padding: 4px 2px 14px;
}
.wb-dash-title h2 {
  margin: 0;
  font-size: 1.45rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: #f1f3f5;
}
/* .wb-dash-date is gone: the date IS the <h2> now, at every width. */

/* Hidden for now, not deleted — the prev/Today/next control works and its
   delegated handlers are still wired, so re-showing it is a one-line change.
   The counts block keeps the margin-left:auto that used to live here. */
.wb-dash-nav { display: none; }
/* NOT --wb-card-border: that variable is the TEAL ACCENT at 20%
   (schedule-layout.css:20), which read as a green outline around the counts
   block and the button. These are neutral chrome, so they take a neutral
   border; the accent is reserved for things that mean something. */
.wb-dash-step,
.wb-dash-today,
.wb-dash-action {
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 10px;
  color: #cbd5e1;
  cursor: pointer;
  font: inherit;
  font-size: 0.82rem;
  padding: 7px 12px;
  line-height: 1;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.wb-dash-step { padding: 7px 11px; font-size: 1rem; }
.wb-dash-step:hover,
.wb-dash-today:hover,
.wb-dash-action:hover {
  border-color: rgba(255, 255, 255, 0.26);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
}

/* The one real call to action on the page — sized and coloured like it.
   Teal on the LABEL only; the border stays neutral so the accent reads as
   "this is a link somewhere" rather than re-outlining the chrome. Same token
   as .wb-dash-preview and .wb-matchup-title, not a re-picked green. */
.wb-dash-action {
  font-size: 0.86rem;
  font-weight: 600;
  padding: 11px 20px;
  border-radius: 11px;
  color: rgba(var(--theme-accent-rgb, 0, 211, 189), 0.9);
}
.wb-dash-action:hover {
  color: rgb(var(--theme-accent-rgb, 0, 211, 189));
}

.wb-dash-counts {
  margin-left: auto;
  display: inline-flex;
  align-items: stretch;
  gap: 0;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.03);
}
.wb-dash-count {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 8px 16px;
  border-left: 1px solid rgba(255, 255, 255, 0.07);
  min-width: 68px;
}
.wb-dash-count:first-child { border-left: 0; }
.wb-dash-count-n {
  font-size: 1.05rem;
  font-weight: 800;
  color: #f1f3f5;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.wb-dash-count-l {
  font-size: 0.63rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #8b91a0;
}
.wb-dash-count[data-count="live"] .wb-dash-count-n { color: #ef4444; }

/* ── filter bar ─────────────────────────────────────────────────── */
.wb-dash-filter {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 0 2px 18px;
}
.wb-dash-seg {
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.035);
  border: 1px solid rgba(255, 255, 255, 0.07);
}
.wb-dash-tab {
  background: none;
  border: 0;
  border-radius: 9px;
  color: #8b91a0;
  cursor: pointer;
  font: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  padding: 6px 14px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: background 0.15s ease, color 0.15s ease;
}
.wb-dash-tab:hover { color: #e8eaed; }
.wb-dash-tab.is-active {
  background: rgba(255, 255, 255, 0.09);
  color: #fff;
}
.wb-dash-actions { margin-left: auto; display: inline-flex; gap: 8px; }
/* An <a>, so it underlines by default; it reads as a button here. */
.wb-dash-action { text-decoration: none; display: inline-flex; align-items: center; }
/* The hard break in the label is for the mobile 5-cell control only; here there
   is room for one line, and the space before it in the markup survives. */
.wb-dash-action br { display: none; }

.wb-dash-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #ef4444;
  display: inline-block;
  /* flex: none — inside .wb-dash-tab (a flex container) the dot is a flex item,
     and once the tabs had to shrink to fit five in a row it was compressed into
     a thin red bar. */
  flex: none;
  animation: wb-dash-pulse 1.8s ease-out infinite;
}
@keyframes wb-dash-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.55); }
  70%  { box-shadow: 0 0 0 7px rgba(239, 68, 68, 0); }
  100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
}

/* ── rails ──────────────────────────────────────────────────────── */
.wb-dash-rail { margin: 0 0 26px; }
.wb-dash-rail[hidden] { display: none; }
.wb-dash-rail-head h3 {
  margin: 0 0 10px;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #8b91a0;
  display: flex;
  align-items: center;
  gap: 8px;
}
.wb-dash-rail-n { color: #6b7280; font-weight: 600; }

/* Only rendered on MLB's overnight two-day slate, where "Final" is last night
   and "Upcoming" is tonight. Empty (and so invisible) on a normal single day. */
.wb-dash-rail-head { display: flex; align-items: baseline; gap: 10px; }
.wb-dash-rail-day {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: #6b7280;
  margin-bottom: 10px;
}
.wb-dash-rail-day:empty { display: none; }

/* The rail. A WRAPPING grid, not a scroller: on desktop auto-fill + minmax means
   the row breaks into as many rows as the width needs and the cards stretch to
   consume the leftover, so a wide screen shows more games instead of the same
   four plus a hidden overflow. Nothing is reachable only by scrolling sideways.
   Overrides .wb-schedule-list's 2-up grid from schedule-layout.css:639.
   One column is the mobile-first default; the desktop block adds the auto-fill. */
.wb-dash .wb-dash-track {
  display: grid;
  grid-template-columns: 1fr;
  gap: 14px;
  padding: 2px;
  margin: 0;
  list-style: none;
}
/* No width override needed any more. schedule-layout.css:88 forces
   `width: 100% !important` on these items to make them fill their grid track,
   which is exactly right once the rail IS a grid — 100% of the auto-fill track.
   (That !important is why the old horizontal-scroll version had to fight back
   with its own !important; the grid works WITH it instead.)

   border-bottom still has to go — schedule-layout.css:79 adds it as a row
   separator for the VERTICAL list, where it reads as a stray underline. */
.wb-dash .wb-dash-track > li.wb-card {
  margin: 0;
  border-bottom: 0;

  /* .wb-card is deliberately chromeless (schedule-layout.css:99 — "flat
     layout, no container chrome"): in the vertical list the row separator IS
     the visual boundary. Take that away for a horizontal rail and the cards
     float unbounded on the background, so the rail has to supply a real
     surface of its own. */
  background: rgba(255, 255, 255, 0.028);
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 14px;
  padding: 12px 14px;
  height: auto;
  transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
}
.wb-dash .wb-dash-track > li.wb-card:hover {
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.16);
  transform: translateY(-1px);
}
/* Per-rail track density. Only the MINIMUM changes — auto-fill decides the
   count and the cards stretch to fill, so these are "how narrow may a card of
   this kind get before it wraps to a new row", not fixed widths.
   A live card carries an extra live-score column and .wb-card is
   overflow:hidden (schedule-layout.css:102), so it needs the largest floor or
   the last odds cell is silently clipped rather than wrapped. */
/* Uniform sizing across all three rails. The rails used to set their own
   minima (400 live / 230 final) which made a game visibly change size as it
   started and ended, and left finals looking like stubs beside their own
   upcoming cards. One floor for everything; the content differs, the frame
   does not. */

.wb-dash .wb-dash-rail[data-rail="live"] > .wb-dash-track > li.wb-card {
  border-color: rgba(239, 68, 68, 0.34);
  background: rgba(239, 68, 68, 0.045);
}

/* Finals are results, not decisions, so they read dimmer and denser than a
   live or upcoming card — but the SCORE has to survive. The score lives in
   .wb-card-body.has-odds (the settled view renderCardOdds writes on the flip
   to final), and the settled odds beside it are the other half of the story —
   renderCardOdds tints each market green/red/neutral by how it landed. Both
   stay; the card is the same frame as an upcoming one, just filled with
   results instead of prices. */
.wb-dash .wb-dash-rail[data-rail="final"] > .wb-dash-track > li.wb-card {
  opacity: 0.86;
}

/* ── odds column alignment ──────────────────────────────────────────
   schedule-layout.css:364 sets `grid-template-columns: 1.5fr 1fr 1fr 1fr`,
   but `1fr` is shorthand for `minmax(AUTO, 1fr)` — the auto floor means the
   team column can never shrink below its longest name, so "Diamondbacks"
   inflates it to 147px where "Orioles" needs 100px, and the three odds
   columns get whatever is left. Measured across one row of cards, the
   spread cell started at 151px, 192px, 146px and 146px from each card's
   edge — visibly ragged.

   minmax(0, …) removes that floor so every card resolves to identical
   columns; the name then has to be allowed to ellipsis instead of forcing
   the track open.

   Fixed px for the three odds columns rather than fr: they hold short,
   bounded strings ("+1.5 / -200"), so a share-of-width is wasted on them and
   starved the name — at 1.5fr the team column resolved to 111px and every
   long name ellipsised ("Diam…", "Guard…"). Fixing the odds and giving the
   team column the remainder both aligns the cells across cards AND lets the
   name take every spare pixel as the card stretches.

   Where the width came from. In a 402px card the budget was: 28px card
   padding, then .wb-odds-table's own 20px-per-side padding (40px — 10% of the
   card, for nothing), 3x10px grid gaps and 3x72px odds columns, leaving the
   team column 86px. Logo and gap take 38 of that, so the NAME got 48px and
   needed 60 — every club ellipsised, even "Mariners". Trimming the table
   inset, the gaps and the odds columns returns ~50px, all of it to the name. */
/* .wb-card-body.has-odds .wb-odds-table (schedule-layout.css) is THREE classes
   and sets `padding: 8px 20px 16px`, so a two-class `.wb-dash .wb-odds-table`
   loses the cascade outright and the 20px inset survived — which is why every
   club was rendering as a single letter. Matching its depth is the fix; only
   left/right are touched so its vertical padding still applies. */
.wb-dash .wb-card-body.has-odds .wb-odds-table {
  padding-left: 6px;
  padding-right: 6px;
}
.wb-dash .wb-odds-team-row {
  grid-template-columns: minmax(0, 1fr) repeat(3, 58px);
  gap: 8px;
}
/* 36px of logo is a lot of a ~140px column. */
.wb-dash .wb-team-logo { width: 26px; height: 26px; flex: 0 0 26px; }
.wb-dash .wb-team-info { gap: 8px; }

/* A settled card carries a score column the others don't, so the name loses
   ~20px right when it also has to sit beside a result. Settled odds cells
   print the line ALONE — renderCardOdds drops the juice row once a game is
   final — so they need noticeably less width than a live price, and giving
   that back is what keeps "Guardians" from becoming "Gu…". */
.wb-dash .wb-dash-rail[data-rail="final"] .wb-odds-team-row {
  grid-template-columns: minmax(0, 1fr) repeat(3, 52px);
}
/* A LIVE card carries a live-score column the other two states don't, so at
   the shared card width the team name loses ~20px and every club ellipsises
   ("Mar…", "Ran…"). Card widths stay uniform by design — the odds columns
   give the space back instead. */
.wb-dash .wb-dash-rail[data-rail="live"] .wb-odds-team-row {
  grid-template-columns: minmax(0, 1fr) repeat(3, 46px);
}
.wb-dash .wb-team-info { min-width: 0; }
.wb-dash .wb-team-details,
.wb-dash .wb-team-details > * {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── upcoming card ──────────────────────────────────────────────────
   Retuned so the matchup table owns the card. The "AWAY @ HOME" title was
   redundant — both team names are already in the rows below it — so its
   space goes to a Preview affordance and the start time moves up beside it.
   Scoped to the upcoming rail: a final card still leads with its matchup
   line, and a live one needs the score header. */
.wb-dash .wb-dash-rail[data-rail="upcoming"] .wb-matchup-header,
.wb-dash .wb-dash-rail[data-rail="final"] .wb-matchup-header,
.wb-dash .wb-dash-rail[data-rail="live"] .wb-matchup-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}
.wb-dash .wb-dash-rail[data-rail="upcoming"] .wb-matchup-title,
.wb-dash .wb-dash-rail[data-rail="final"] .wb-matchup-title,
.wb-dash .wb-dash-rail[data-rail="live"] .wb-matchup-title { display: none; }
/* The live block supersedes the clock text the 5s poll writes ("Top 2") —
   the same fact, said better and with the count and runners alongside. */
.wb-dash .wb-dash-rail[data-rail="live"] .wb-game-clock { display: none !important; }

/* Top-left status block on a live card. */
.wb-live-head { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* Sized to hold its own against "View Game ›" (0.9rem/700) opposite it —
   at 0.74rem the status of a game in progress lost to a navigation link. */
.wb-live-l1 {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 0.88rem; font-weight: 800; letter-spacing: 0.05em;
  color: #ef4444; text-transform: uppercase;
}
.wb-live-l1 i { font-style: normal; color: #7f1d1d; }
/* The "LIVE •" label, wrapped by liveHeadHtml (scheduleEdgeMLB.js) purely so a
   wide screen can drop the word and lead with the inning. NOT a box of its own:
   .wb-live-l1 is an inline-flex row and its 7px gap is what separates the dot,
   the word, the bullet and the inning, so a real wrapper would swallow one of
   those gaps and re-space the line at EVERY width. display:contents leaves the
   same four items in the same flex row — the wrapper exists only as a handle
   for the desktop block to hang display:none on. */
.wb-live-word { display: contents; }
.wb-live-l2 {
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.05em;
  color: #8b91a0; text-transform: uppercase;
}
.wb-live-l2 i { font-style: normal; color: #4b5563; }
.wb-live-dot {
  width: 7px; height: 7px; border-radius: 50%; background: #ef4444;
  display: inline-block; flex: none;
  animation: wb-dash-pulse 1.8s ease-out infinite;
}
/* MLB renders "Final" as the clock text, not a status pill, so THAT is the
   final card's top-left label — the slot the start time holds on an upcoming
   card. */
.wb-dash .wb-dash-rail[data-rail="final"] .wb-game-clock {
  order: 0;
  margin: 0;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.wb-dash .wb-dash-rail[data-rail="upcoming"] .wb-matchup-time {
  order: 0;
  margin: 0;
  font-size: 0.8rem;
  font-weight: 600;
  color: #8b91a0;
}
.wb-dash-preview {
  order: 2;
  margin-left: auto;
  font-size: 0.9rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  /* Exactly the .wb-matchup-title teal it replaces (schedule-layout.css:498),
     theme variable and all — not a re-picked approximation. */
  color: rgba(var(--theme-accent-rgb, 0, 211, 189), 0.85);
  white-space: nowrap;
}
.wb-dash-preview i { font-style: normal; }
/* Only the upcoming rail restructures its header, so the affordance only
   belongs there — the other two rails keep their own matchup line. */

.wb-dash li.wb-card:hover .wb-dash-preview { color: #fff; }
.wb-dash .wb-dash-rail[data-rail="final"] li.wb-card:hover { opacity: 1; }

/* ── live line movement ─────────────────────────────────────────────
   Set by markOddsMoves() in scheduleCore on each 60s live-odds refresh:
   which cells changed, and which way. Deliberately quiet — a tinted ring and
   a small corner caret, no fill — because a live card already carries a
   score, a status block and six prices.

   box-shadow inset, NOT border: these cells have no border of their own, so
   adding one would grow every marked cell by 2px and jiggle the odds grid
   each refresh. An inset shadow paints inside the existing box.

   Green = the number went UP, red = down. Direction only, not good/bad —
   which of those it is depends on the side you are on.

   Fades after ~18s of the 60s window: long enough to notice on a glance,
   gone well before the next refresh so a stale mark is never mistaken for a
   fresh one. */
.wb-dash .wb-odds-cell { position: relative; }
.wb-dash .wb-odds-cell[data-move="up"]   { --wb-mv: rgba(34, 197, 94, 0.55);  --wb-mv-solid: #22c55e; }
.wb-dash .wb-odds-cell[data-move="down"] { --wb-mv: rgba(239, 68, 68, 0.55); --wb-mv-solid: #ef4444; }
.wb-dash .wb-odds-cell[data-move] { animation: wb-move-ring 18s ease-out forwards; }
/* OUTLINE, not an inset shadow. The shadow is painted inside the padding box
   and was being trimmed where the cell's own background and 6px radius meet,
   which showed as nicks in the ring at the corners. An outline with a
   negative offset is drawn as a single unbroken stroke that follows the
   radius, sits fully inside the box, and still costs no layout. */
@keyframes wb-move-ring {
  0%, 66% { outline-color: var(--wb-mv); }
  100%    { outline-color: rgba(0, 0, 0, 0); }
}
.wb-dash .wb-odds-cell[data-move] {
  outline: 1px solid var(--wb-mv);
  outline-offset: -1px;
  box-shadow: none;
}
/* Top-left, INSET PAST THE CORNER. At 4px/4px the caret sat on the ring's
   6px radius — where the stroke is still curving — and read as a nick in the
   border. 7px from the left and 5px down clears the curve entirely, so the
   caret sits against straight edges on both sides. Fixed offsets rather than
   a centre calculation, so the up and down carets land identically. */
.wb-dash .wb-odds-cell[data-move]::after {
  content: ''; position: absolute; top: 5px; left: 7px; right: auto;
  width: 0; height: 0;
  border-left: 3.5px solid transparent; border-right: 3.5px solid transparent;
  animation: wb-move-caret 18s ease-out forwards;
}
.wb-dash .wb-odds-cell[data-move="up"]::after   { border-bottom: 4.5px solid var(--wb-mv-solid); }
.wb-dash .wb-odds-cell[data-move="down"]::after { border-top: 4.5px solid var(--wb-mv-solid); }
/* Room for the caret so it never crowds the price beneath it. */
.wb-dash .wb-odds-cell[data-move] { padding-top: 11px; }
@keyframes wb-move-caret { 0%, 66% { opacity: 1; } 100% { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  .wb-dash .wb-odds-cell[data-move],
  .wb-dash .wb-odds-cell[data-move]::after { animation-duration: 0.01s; }
}

/* ── status filter ──────────────────────────────────────────────── */
.wb-dash[data-filter="live"]     .wb-dash-rail:not([data-rail="live"]),
.wb-dash[data-filter="upcoming"] .wb-dash-rail:not([data-rail="upcoming"]),
.wb-dash[data-filter="final"]    .wb-dash-rail:not([data-rail="final"]) {
  display: none;
}
/* A filter with no games hides every rail. Say why instead of showing a void. */
.wb-dash-empty { display: none; margin: 4px 2px 26px; color: #8b91a0; font-size: 0.85rem; }
.wb-dash-empty > span { display: none; }
.wb-dash[data-filter="live"][data-empty-rails~="live"]         .wb-dash-empty,
.wb-dash[data-filter="upcoming"][data-empty-rails~="upcoming"] .wb-dash-empty,
.wb-dash[data-filter="final"][data-empty-rails~="final"]       .wb-dash-empty { display: block; }
.wb-dash[data-filter="live"]     .wb-dash-empty > [data-empty-for="live"],
.wb-dash[data-filter="upcoming"] .wb-dash-empty > [data-empty-for="upcoming"],
.wb-dash[data-filter="final"]    .wb-dash-empty > [data-empty-for="final"] { display: inline; }
/* A status with nothing in it is still clickable — it just reads as inactive. */
.wb-dash-tab[data-empty] { opacity: 0.45; }

/* KEEP schedule-layout.css:28's min-height:100vh reservation. Zeroing it made
   the site footer (socials, link list) ride up into the first screen on a
   short slate, which reads as the end of the page when it is really the top
   of one. Filling the viewport costs nothing and keeps the footer where a
   footer belongs — below the fold, reached by scrolling. */
#schedule-container.wb-page-dash { min-height: 100vh; }

@media (prefers-reduced-motion: reduce) {
  .wb-dash-dot { animation: none; }
  .wb-dash .wb-dash-track { scroll-behavior: auto; }
}


/* ═══════════════════════════════════════════════════════════════════════
   >1000px — the only things that are genuinely about having a wide screen.
   ═══════════════════════════════════════════════════════════════════════ */
@media (min-width: 1001px) {
  /* Rails have far more to gain from width than a single table does, so the
     page goes wider than .wb-main's 80rem cap and stays fluid. */
  .wb-dash {
    max-width: min(1720px, 100%);
    padding-left: 1.5rem;
    padding-right: 1.5rem;
  }
  /* auto-fill + minmax turns extra width into MORE CARDS PER ROW rather than
     wider cards, and never into a sideways scroller. */
  .wb-dash .wb-dash-track {
    grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  }
}


/* ═══════════════════════════════════════════════════════════════════════
   ≤1000px — same renderer, same rails, same per-status logic; one column and
   phone-sized type. Everything here is either (a) undoing the flat list's
   touch scale or (b) letting a full-width row be full-width.
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 1000px) {
  /* (a) schedule-layout.css:650+ scales the FLAT LIST up for touch — 24px card
     text, 26px team names, 48px logos. Those rules key off VIEWPORT WIDTH, not
     off which renderer ran, so they are still live under the dashboard, and at
     that size two teams plus six prices do not fit a phone card. Each override
     below is exactly one class deeper than the rule it beats (.wb-dash .x vs
     .x), so specificity settles it and sheet load order does not matter.

     A phone card is ~360px of width showing ONE game, where a desktop card is
     ~350px showing one of four — so the phone has the same width for far less
     content and can afford much larger type than the desktop scale. These are
     sized up from the desktop values accordingly, not down from the 24px flat
     list; the odds columns below are re-fractioned to pay for the wider names. */
  /* EVERY size below is a clamp, not a fixed value. The band this block covers
     is 360px to 1000px — nearly 3x — and one number cannot serve both ends: what
     fits an iPhone SE wastes half a tablet. Floor = the phone size, the vw term
     grows it with the viewport, the ceiling stops it before 1000px hands over to
     the desktop grid. Adding a fixed font-size in here is a regression. */
  .wb-dash .wb-matchup-title,
  .wb-dash .wb-matchup-at,
  .wb-dash .wb-matchup-header .wb-card-status,
  .wb-dash .wb-live-pts,
  .wb-dash .wb-logo-fallback { font-size: clamp(1.06rem, 2.4vw, 1.32rem); font-weight: 600; }
  /* Start time — and the "Final" clock that takes its slot on a settled card —
     climbs FASTER than the rest; it is the top-left anchor of every card. */
  /* These carry the RAIL in the selector on purpose. The ungated rules that
     position this slot are `.wb-dash .wb-dash-rail[data-rail="…"] .wb-matchup-time`
     — three classes, which out-specifies a two-class `.wb-dash .wb-matchup-time`
     no matter where it sits in the file. Matching their depth is the only way a
     mobile size lands here; without it the start time silently stayed at the
     desktop 0.8rem while everything around it grew. */
  .wb-dash .wb-dash-rail[data-rail="upcoming"] .wb-matchup-time,
  .wb-dash .wb-dash-rail[data-rail="final"] .wb-game-clock,
  .wb-dash .wb-matchup-time,
  .wb-dash .wb-matchup-header .wb-game-clock { font-size: clamp(1.2rem, 2.75vw, 1.5rem); font-weight: 600; }
  .wb-dash .wb-team-details h4  { font-size: clamp(1.12rem, 2.7vw, 1.5rem); font-weight: 700; }
  /* 0.95, not 1rem: these are a MONO face, so the widest string on the card is a
     total like "O 12.5" — measured at 58px against a 55px cell at 1rem, which
     overflowed. 0.95 brings it under while still reading far larger than before. */
  .wb-dash .wb-odds-cell .value { font-size: clamp(0.95rem, 2.4vw, 1.3rem); }
  .wb-dash .wb-odds-cell .juice { font-size: clamp(0.85rem, 2vw, 1.1rem); }
  /* The live score was taking 36px + a 12px margin — 48px of a phone card for a
     one- or two-digit number, and it comes straight out of the club name beside
     it. Sized to two digits and no more. */
  .wb-dash .wb-dash-rail[data-rail="live"] .wb-team-info { gap: 6px; }
  .wb-dash .wb-live-pts {
    min-width: 0;
    /* The score was 2px off the run-line box — it read as part of it. The gap
       comes out of the SLACK to its left (the team column is sized for the
       longest club name, so a short one leaves a hole there), not out of the
       odds columns. */
    margin-right: clamp(12px, 2.6vw, 22px);
    padding-left: 0;
    padding-right: 0;
    text-align: right;
  }
  .wb-dash .wb-team-logo     { width: 34px; height: 34px; flex: 0 0 34px; }
  .wb-dash .wb-team-logo img { width: 30px; height: 30px; }
  .wb-dash .wb-dash-preview  { font-size: clamp(1.18rem, 2.3vw, 1.38rem); }
  .wb-dash .wb-dash-track > li.wb-card { padding: 14px 16px; }
  /* The live status block and the edge chips are the two things that were left
     behind at desktop size once everything around them grew. */
  .wb-dash .wb-live-l1   { font-size: clamp(1.12rem, 2.7vw, 1.5rem); }
  .wb-dash .wb-live-l2   { font-size: clamp(0.92rem, 2.2vw, 1.22rem); }
  .wb-dash .wb-edge-chip { font-size: clamp(0.85rem, 2vw, 1.08rem); padding: clamp(6px, 1vw, 9px) clamp(11px, 1.8vw, 16px); }
  /* The live card's "PITCHING: X vs Y" footer. Missed the clamp sweep and so
     stayed at its phone size while everything around it grew with the viewport. */
  .wb-dash .wb-edge-live   { font-size: clamp(0.86rem, 2vw, 1.12rem); }
  .wb-dash .wb-edge-live b { font-size: clamp(0.78rem, 1.85vw, 1.02rem); }

  /* (b) header. The counts block STOPS riding on the title's row and becomes a
     full-width bar of four equal cells. Squeezed beside the title it could only
     ever be as big as the leftover space allowed, which is what made it the
     smallest text on the page; on its own row each cell gets ~a quarter of the
     screen and the numbers can carry real weight. */
  .wb-dash-head     { gap: 10px; padding: 4px 2px 14px; }
  .wb-dash-title h2 { font-size: 1.85rem; }
  /* The counts are off on mobile: the four status tabs directly below already
     name every bucket, and the active tab plus each rail's own head carry the
     same numbers. Desktop keeps them — it has the row to spare. */
  .wb-dash-counts   { display: none; }

  /* ONE segmented control holding FIVE items — the four status tabs plus the
     home-runs link — instead of a 4-up control with a separate bar under it.

     Done with `display: contents` on the two wrapper divs rather than by moving
     the link in the DOM: that collapses .wb-dash-seg and .wb-dash-actions to
     nothing so their children become direct flex items of .wb-dash-filter, which
     takes over as the bordered container. No JS change, `role="tablist"` stays on
     a real element wrapping only real tabs (a link inside a tablist would be an
     invalid child), and renderDashboardActions keeps its .wb-dash-actions target.

     Sizing is content-based, not five equal fifths: at equal widths "All Games"
     and "Upcoming" wrap while "Live" and "Final" waste half their cell. The tabs
     take their natural width and the link takes the remainder, which is what puts
     it on the two lines it wants. */
  .wb-dash-filter {
    display: flex;
    flex-wrap: nowrap;
    align-items: stretch;
    /* Tighter gaps buy the cells themselves more room — the trade asked for. */
    gap: 2px;
    padding: 4px;
    margin-bottom: 16px;
    border-radius: 14px;
    background: rgba(255, 255, 255, 0.035);
    border: 1px solid rgba(255, 255, 255, 0.07);
  }
  .wb-dash-seg,
  .wb-dash-actions { display: contents; }
  /* Tabs GROW (basis auto = natural width, then an equal share of what's left).
     Paired with the link's max-width below this is what balances the row at
     every mobile width: on a phone the link hits its min-width, freezes, and the
     tabs split the little that remains; on a 768px tablet the link hits its
     max-width, freezes, and the tabs absorb the rest instead of leaving one
     enormous teal cell. */
  .wb-dash-tab {
    flex: 1 1 auto;
    min-width: 0;
    justify-content: center;
    padding: clamp(12px, 1.9vw, 20px) clamp(7px, 1.4vw, 14px);
    font-size: clamp(0.85rem, 2.1vw, 1.18rem);
    white-space: nowrap;
  }
  /* Reads as a segment, not a button: no border or fill of its own, teal label,
     and it wraps onto two lines where the tabs stay on one. */
  /* flex-basis 0, NOT auto. On `auto` the basis is the label's full ONE-LINE
     width (~170px), so the link claimed two thirds of the row up front and the
     four tabs shrank around it. At 0 it asks for nothing, the tabs take their
     natural width first, and the link wraps into the two lines it wants. */
  .wb-dash-action br { display: inline; }
  /* IDENTICAL flex and horizontal padding to .wb-dash-tab. That is the whole
     fix for the lopsided row: with a hard-broken label its basis is the longer
     half rather than the full one-line string, so `1 1 auto` hands all five
     cells the same share of the leftover and every label ends up with the same
     air on each side. No min/max clamps — they would re-bias it. */
  .wb-dash-action {
    flex: 1 1 auto;
    min-width: 0;
    justify-content: center;
    text-align: center;
    padding: clamp(8px, 1.2vw, 12px) clamp(7px, 1.4vw, 14px);
    border: 0;
    background: none;
    border-radius: 9px;
    font-size: clamp(0.8rem, 1.85vw, 1.05rem);
    line-height: 1.18;
    white-space: normal;
  }
  .wb-dash-action:hover { background: rgba(255, 255, 255, 0.06); }
  /* No chevron here — it was the affordance for a full-width bar. Inside the
     control the teal label against four grey tabs already reads as the odd one
     out, and a caret on a two-line cell just crowds it. */
  .wb-dash-action::after { content: none; }

  .wb-dash-rail-head h3 { font-size: clamp(0.92rem, 2.1vw, 1.18rem); margin-bottom: 12px; }
  .wb-dash-rail-day     { font-size: 0.8rem; }
  .wb-dash-empty        { font-size: 1rem; }

  /* RAILS order is live, upcoming, final — so exactly two empty rails means
     exactly one populated rail, and then the rail head only repeats what the
     header count and the active tab already say. On a MIXED slate the heads
     come back, because unlabelled groups of cards are worse than a redundant
     label. Attribute values, not :has(), to match how the empty-state copy is
     already selected. */
  .wb-dash[data-empty-rails="live upcoming"] .wb-dash-rail-head,
  .wb-dash[data-empty-rails="live final"]    .wb-dash-rail-head,
  .wb-dash[data-empty-rails="upcoming final"] .wb-dash-rail-head { display: none; }

  .wb-dash-rail { margin-bottom: 18px; }

  /* Fixed-px odds columns exist on desktop to align cells ACROSS cards sitting
     side by side. In one column there is nothing to align across, and fixed
     columns would starve the name on a 360px screen — so the row goes back to
     fractions, which also keeps the three markets even at any phone width. */
  .wb-dash .wb-odds-team-row,
  .wb-dash .wb-odds-table-header,
  .wb-dash .wb-dash-rail[data-rail="final"] .wb-odds-team-row {
    grid-template-columns: minmax(0, 2.1fr) repeat(3, minmax(0, 1fr));
    gap: 6px;
  }
  /* A live card packs the running score INTO the team column beside the logo
     and the club name, so at the shared 1.7fr it was the one card that still
     ellipsised ("Gu…"). The three prices are short enough to fund it.
     The header row has to move WITH it: these are fr units, so widening the
     team column shifts every odds column, and a header left at 1.7fr would sit
     visibly off its own cells. Same selector list, same value. */
  .wb-dash .wb-dash-rail[data-rail="live"] .wb-odds-team-row,
  .wb-dash .wb-dash-rail[data-rail="live"] .wb-odds-table-header {
    grid-template-columns: minmax(0, 2.85fr) repeat(3, minmax(0, 1fr));
    gap: 6px;
  }

  /* Column labels. buildGameCard has ALWAYS emitted this row (Team / Run Line /
     Moneyline / Total) and schedule-layout.css:360 hides it everywhere. A phone
     shows one card at a time with no neighbours to infer the columns from, and
     a full-width card has the room, so it earns its place here — desktop keeps
     it hidden, where four cards per row would repeat it four times.
     "Team" is dropped: the logo and club name under it say so already.

     SIZED BY ITS WIDEST WORD, not by taste. "Moneyline" measured 72px against a
     55px column at 0.68rem/0.07em, which wrapped "Run Line" onto two lines and
     ran "MONEYLINE" straight into "TOTAL". At 0.5rem/0.02em it comes in under
     48px and clears the column at every width that shows it. nowrap so a future
     squeeze fails loudly (overlap) rather than silently reflowing the card. */
  .wb-dash .wb-odds-table-header {
    display: grid;
    align-items: end;
    padding: 0 0 5px;
    /* The widest label is "Moneyline", and at 844px its column is ~148px against
       a ~74px need — the roomiest ratio on the card, which is why this clamp has
       the steepest ramp of the set. */
    font-size: clamp(0.62rem, 1.85vw, 1rem);
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: #6b7280;
  }
  .wb-dash .wb-odds-table-header > span { text-align: center; white-space: nowrap; }
  /* The live rail spends ~30px of its row on the running score, so its odds
     columns are ~6px narrower than upcoming's at the same width — measured
     "Moneyline" at 61px against a 58px column. One notch down clears it. */
  .wb-dash .wb-dash-rail[data-rail="live"] .wb-odds-table-header { font-size: clamp(0.53rem, 1.7vw, 0.94rem); }
  .wb-dash .wb-odds-table-header > span:first-child { visibility: hidden; }
}

/* ── very small phones ────────────────────────────────────────────────
   Below ~400px the odds columns get tight enough that the labels start
   competing with the club names. Only the LIVE card actually loses that fight —
   it spends ~30px of the team column on the running score, so at 360px it had
   36px left for the name once the labels were satisfied. So the labels come off
   THE LIVE CARD ONLY, and the width goes to the name; upcoming and final keep
   theirs, which is what a 390px phone (the most common size there is) sees for
   all but one card on the page. */
@media (max-width: 400px) {
  .wb-dash .wb-dash-rail[data-rail="live"] .wb-odds-table-header { display: none; }
  /* Smaller still down here so "MONEYLINE" clears a ~48px column with margin. */
  .wb-dash .wb-odds-table-header { font-size: 0.5rem; }
  /* .wb-odds-table-header is in this list for the reason given above: re-fraction
     a row without re-fractioning its header and the labels sit off their cells.
     The live rail's header is display:none here so it needs no column value. */
  .wb-dash .wb-odds-team-row,
  .wb-dash .wb-odds-table-header,
  .wb-dash .wb-dash-rail[data-rail="final"] .wb-odds-team-row {
    grid-template-columns: minmax(0, 2.4fr) repeat(3, minmax(0, 1fr));
  }
  .wb-dash .wb-dash-rail[data-rail="live"] .wb-odds-team-row {
    grid-template-columns: minmax(0, 3.5fr) repeat(3, minmax(0, 1fr));
  }
  .wb-dash .wb-team-logo     { width: 30px; height: 30px; flex: 0 0 30px; }
  .wb-dash .wb-team-logo img { width: 26px; height: 26px; }
  .wb-dash-title h2 { font-size: 1.65rem; }
  .wb-dash-tab      { font-size: 0.74rem; padding: 11px 4px; }
}

/* Just above the ≤400 tier the labels switch ON while that tier's extra name
   room switches OFF, so 401–412px was the one band where "Guardians" and
   "White Sox" ellipsised but 390 and 418 did not — and an iPhone 16 Pro is
   402px wide. The 4px reclaimed from the logo is enough to close it, so the
   smaller logo simply extends past the tier boundary. */
@media (max-width: 412px) {
  .wb-dash .wb-team-logo     { width: 30px; height: 30px; flex: 0 0 30px; }
  .wb-dash .wb-team-logo img { width: 26px; height: 26px; }
}

/* ── narrowest phones (≤380px: iPhone SE, older Androids) ─────────────
   Here the row is genuinely over-subscribed and no amount of re-fractioning
   fixes it: a total prints "O 12.5" in a MONO face needing ~55px per odds
   column, three of those plus the logo leaves ~71px for a club name that wants
   88 — so six of them ellipsised. The only real lever left is size, so this
   tier gives back one notch of type and drops the labels entirely. Everything
   from 385px up keeps the full scale; this is the floor, not the design. */
@media (max-width: 380px) {
  .wb-dash .wb-odds-table-header { display: none; }
  .wb-dash .wb-team-details h4  { font-size: 1rem; }
  .wb-dash .wb-odds-cell .value { font-size: 0.88rem; }
  .wb-dash .wb-odds-cell .juice { font-size: 0.78rem; }
}


/* ═══════════════════════════════════════════════════════════════════════
   >1000px — THE APPROVED REDESIGN (scratchpad/schedule-proposed.html).

   A SECOND desktop block, appended rather than merged into the one above.
   The block above is the layout the page shipped with; this one is the
   redesign, and every rule in it is either a straight addition or an
   override of that block at equal specificity, which source order settles.
   Keeping them apart means the redesign can be reverted by deleting from
   this banner to the end of the file, and nothing about the ≤1000px page
   can move when it goes.

   NOTHING here may reach a phone. Every selector in this block lives inside
   `min-width: 1001px`, and the two mobile blocks below the file's original
   desktop block are untouched — their rules are all `.wb-dash …` at the same
   or greater depth as these, but they are gated on `max-width: 1000px`, so
   the two sets can never both apply.

   The artifact is a standalone rebuild with its own class names (.schh-*,
   .rail-*, .sv2-*), so this is a TRANSLATION onto buildGameCard()'s real
   markup, not a copy: no class is renamed and no JS is touched.
   ═══════════════════════════════════════════════════════════════════════ */
@media (min-width: 1001px) {

  /* ── 1. four cards per rail ───────────────────────────────────────────
     A FIXED count, not auto-fill. The block above turns extra width into
     more cards per row (auto-fill + minmax(340px)), which at 1392px of rail
     resolves to three columns; the approved design is four. The trade is
     deliberate and worth naming: with `repeat(4, …)` an ultrawide monitor
     gets WIDER cards rather than a fifth column, so the slate always reads
     as the same four-across shape and a card never changes size because the
     window grew. */
  .wb-dash .wb-dash-track {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 14px;
  }
  /* …but four only FITS on a wide desktop, and the artifact's own 1180px
     boundary does not transfer. The mock's card spends 26px on insets
     (13px of padding a side, content flush); this one spends 40px — 14px of
     card padding plus the 6px the odds table, the matchup header and the
     chip strip each sit in — so at any given width the club name has ~14px
     less to live in here than it does in the mock.

     Measured against the real slate, four-across starves the name well
     above 1180: at 1181px 46 of 48 club names ellipsised, at 1280px 34, at
     1380px 14, and it is only from ~1420px that it settles into the handful
     the mock itself tolerates. 1280 and 1366 are the two commonest laptop
     widths there are, so leaving them on the mock's number would ship
     "Dia…", "Guar…", "Nation…" as the normal state of the page.

     Hence a THREE-up tier between the two: 1181–1420px. It is not invented
     for this — auto-fill resolved to exactly three columns at those widths
     in the layout this replaces, at a ~400px card, which is the same card
     an ultrawide gets at four-across. Two-across is still the floor, on the
     artifact's own boundary, where even three would be too tight.
     Both tiers sit entirely above 1000px, so the phone layout is not
     involved; the narrower must come SECOND to win. */
  @media (max-width: 1420px) {
    .wb-dash .wb-dash-track { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  }
  @media (max-width: 1180px) {
    .wb-dash .wb-dash-track { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }

  /* ── 2. one header row ────────────────────────────────────────────────
     The page ships two stacked bands: .wb-dash-head (title + counts) and
     .wb-dash-filter (status tabs + actions). The redesign is ONE band.

     Done by collapsing both wrappers with `display: contents` so their
     children become direct flex items of .wb-dash, which is exactly the
     trick the ≤1000px block already uses on .wb-dash-seg/.wb-dash-actions —
     same reasoning, one level up. No JS moves, no DOM changes, and
     renderDashboardActions() keeps its .wb-dash-actions target.

     CAUTION: .wb-dash-head is a <header> landmark. `display: contents`
     drops an element from the accessibility tree in older Safari (and in
     Chrome before 89), so the banner/complementary role it carries here can
     go missing. Acceptable: the <h2> inside is the thing that actually
     names this region, and headings are not affected. Do not extend this to
     a wrapper whose ROLE is the only label its contents have. */
  .wb-dash {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    /* Column gap spaces the band's controls; the row gap is what separates
       the band from itself when it wraps at the narrow end of desktop, and
       it is also the first 12 of the 44px above the first rail (see §4). */
    gap: 12px 14px;
    /* The band sat hard against the navbar with only .wb-main's own padding
       above it, which read as cramped once the controls grew to 50px. This
       is added to that padding, not a replacement for it. */
    padding-top: 0.85rem;
  }
  .wb-dash-head,
  .wb-dash-filter { display: contents; }

  /* Anything else that ends up a direct child of .wb-dash gets a full-width
     line of its own. This is not defensive tidying: hrBoardMLB.js PREPENDS
     `<section class="hrb-section">` to .wb-main, and without this it would
     join the header band as an inline flex item at order 0 — i.e. in front
     of the date. Full width + a late order puts it back where it belongs,
     above the rails. keyPlayersNBA.js prepends the same way. */
  .wb-dash > * {
    flex: 1 1 100%;
    min-width: 0;
    order: 9;
  }

  /* The band, left to right: date · day-nav · status tabs ......
     Theater Mode · counts · Home Runs.

     EXACTLY ONE of these may carry `margin-left: auto` — that single auto
     margin eats all the free space and is what splits the band into a left
     group and a right group. Two of them would SHARE the space and pull the
     right group apart. The auto therefore belongs on whichever item leads the
     right group, and that is now Theater Mode. .wb-dash-counts carries one
     from the ungated rules, so it has to be explicitly zeroed here or it
     would open a second gap between the button and the strip. */
  .wb-dash-title   { order: 1; flex: 0 0 auto; min-width: 0; }
  .wb-dash-nav     { order: 2; flex: 0 0 auto; }   /* still display:none above */
  .wb-dash-seg     { order: 3; flex: 0 0 auto; }
  /* Theater Mode is a SIBLING of .wb-dash-actions, not a child of it
     (theaterMLB.js:945 — the actions slot gets .empty()'d whenever its label
     flips, which would eat the button), so once .wb-dash-filter is
     display:contents it is a flex item in its own right and needs its own
     slot in the order. */
  .wb-dash .pbt-open     { order: 4; flex: 0 0 auto; margin-left: auto; }
  .wb-dash-counts        { order: 5; flex: 0 0 auto; margin-left: 0; }
  .wb-dash-actions       { order: 6; flex: 0 0 auto; margin-left: 0; }
  /* Both of these stack full width UNDER the band. Their order is behind
     every control above and their basis is 100%, so they always start a new
     flex line however the band itself wraps. */
  .wb-dash-empty { order: 8; flex: 1 1 100%; margin: 20px 2px 0; }
  .wb-dash-rail  { order: 9; flex: 1 1 100%; }

  /* The date is the title of the band, and it is the only thing in the row
     that is prose rather than a control — so it carries the band's weight.
     It sits a step above the ungated 1.45rem because at this width it is
     competing with a 50px-tall counts strip beside it, not leading a
     stacked phone header. */
  .wb-dash-title h2 {
    font-size: 1.55rem;
    line-height: 1.15;
  }

  /* ── 3. one control height ────────────────────────────────────────────
     The three control groups had drifted apart — segment ~34px, counts
     ~42px, buttons ~38px — which is what stopped the band reading as one
     deliberate row. 46px is the SMALLEST value the counts strip can hold
     without crowding: its two stacked lines measure 18.5px (number at
     1.05rem/1.1) + 12px (label at 0.63rem) = 30.5px, plus the 2px of border,
     leaving ~6.5px of air to split top and bottom. Everything else is sized
     up to meet it rather than the strip being squeezed down. */
  .wb-dash { --wb-ctl-h: 50px; }

  /* The band's type is a step up from the phone scale rather than down from
     it: at 46px and 12.8px the controls read as chrome, when this row is the
     page's primary navigation. Each value below is the ungated one plus
     roughly a point — enough to register as deliberate, not enough to make
     the band compete with the cards. */
  .wb-dash .wb-dash-tab { font-size: 0.86rem; }
  .wb-dash .wb-dash-count-n { font-size: 1.15rem; }
  .wb-dash .wb-dash-count-l { font-size: 0.66rem; }
  .wb-dash .wb-dash-action { font-size: 0.88rem; }

  /* Height only. An `align-items` here would be dead weight in all three
     cases — the two strips below immediately override it with `stretch` so
     their cells can fill the new height, and .wb-dash-action is already
     centred by the ungated rule that makes it an inline-flex. */
  .wb-dash-seg,
  .wb-dash-counts,
  .wb-dash .wb-dash-action {
    height: var(--wb-ctl-h);
  }

  /* Segment: the tabs fill the track instead of floating inside a taller
     shell, so the active tab's fill reads as a segment and not as a pill. */
  .wb-dash-seg {
    align-items: stretch;
    background: #070c11;
    border-color: rgba(255, 255, 255, 0.07);
  }
  .wb-dash .wb-dash-tab { padding: 0 14px; }
  .wb-dash .wb-dash-tab.is-active {
    background: #21262e;
    color: #f3f5f7;
  }

  /* Counts: each cell centres itself now that the strip has a fixed height. */
  .wb-dash-counts {
    align-items: stretch;
    background: #070c11;
    border-color: rgba(255, 255, 255, 0.07);
  }
  .wb-dash .wb-dash-count {
    justify-content: center;
    padding: 0 16px;
  }

  /* Actions: the fixed height drives them, the horizontal padding is kept.
     Nothing here may touch .pbt-open — Theater Mode owns a red tint in
     mlb-theater.css, and the two buttons are a MATCHED PAIR in the artifact
     (.schh-btn--live and .schh-btn--teal): same shape, each tinted in its
     own hue, sitting together at the right edge. So the Home Runs link gets
     the teal twin of theater's red rather than the neutral panel fill the
     counts strip and the segment take — a flat button beside a tinted one
     reads as the disabled half of a pair.

     Both states are restated because `.wb-dash .wb-dash-action:not(.pbt-open)`
     (0,3,0) out-specifies the ungated `.wb-dash-action:hover` (0,2,0);
     without the second rule the button would simply stop responding. */
  .wb-dash .wb-dash-action {
    padding: 0 16px;
    border-color: rgba(255, 255, 255, 0.07);
  }
  /* Home Runs keeps the accent FILL and the accent LABEL, but wears the same
     hairline as the counts strip it now sits beside — an accent-coloured
     border made it read as the odd one out in a row where every other
     container is outlined in white-7%. The border-color declarations are
     simply absent here so the rule above supplies it; hover still lifts the
     fill, which is where the affordance was doing its work anyway. */
  .wb-dash .wb-dash-action:not(.pbt-open) {
    background: rgba(var(--theme-accent-rgb, 0, 211, 189), 0.10);
  }
  .wb-dash .wb-dash-action:not(.pbt-open):hover {
    background: rgba(var(--theme-accent-rgb, 0, 211, 189), 0.16);
  }

  /* ── 4. rail heads as plain text, lowered ─────────────────────────────
     No plate, no border, no rule — the heading has to earn its grouping
     from spacing alone, so the air moves: MORE above each rail to separate
     it from the one before, LESS below so the label reads as belonging to
     the cards under it.

     44px above = the 12px row-gap on .wb-dash plus the 32px here. It works
     out the same between two rails as it does between the band and the
     first one, because a rail is always alone on its flex line.

     The pulsing .wb-dash-dot stays in the LIVE heading on purpose: with the
     dot gone from the cards themselves (§7) this is the one place the live
     group is marked, which is once instead of once per card. */
  .wb-dash-rail {
    margin-top: 32px;
    margin-bottom: 0;
  }
  .wb-dash-rail-head h3 {
    margin-bottom: 9px;
    font-size: 0.84rem;
    font-weight: 800;
    letter-spacing: 0.09em;
    color: #f1f3f5;
  }
  .wb-dash-rail-day { margin-bottom: 9px; }

  /* ── 5. odds column labels ────────────────────────────────────────────
     buildGameCard() has always emitted this row (Team / Run Line /
     Moneyline / Total) and schedule-layout.css:360 hides it everywhere; the
     ≤1000px block already reveals it for the phone. With four cards across,
     the columns are narrow enough that the prices stop being self-evident,
     so the labels earn their place here too.

     The header is its own grid, so it has to be given the SAME track sizes
     as the team rows below it or the labels sit off their own cells — the
     mistake the ≤1000px block calls out for the fr-based rows. Here that is
     easy: §6 gives all three rails one column width.

     The first cell ("Team") stays a spacer — visibility:hidden, not
     display:none, because the grid still needs it to occupy column 1.

     SIZE IS THE ARTIFACT'S (.sv2-oh: 8.5px / 0.03em), and it is not a taste
     call — it is the only size that fits. The labels are nowrap in a 47px
     column, so anything they cannot fit they OVERHANG into the neighbouring
     column rather than wrapping. "Moneyline" is the widest at 9 characters:
     at 9.5px/0.06em it measures 62px, overhanging 7.5px each side, which put
     it 2.5px INTO "Run Line" on its left and left it touching "Total" on its
     right — the row read "RUN LINE MONEYLINETOTAL". At 8.5px/0.03em it comes
     in at ~53px, which still overhangs but restores the ~6px and ~12px of
     air the mock has between the three. Anything larger here silently
     re-collides them. */
  .wb-dash .wb-odds-table-header {
    display: grid;
    grid-template-columns: minmax(0, 1fr) repeat(3, 47px);
    gap: 6px;
    align-items: end;
    padding: 0 0 5px;
    font-size: 8.5px;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: #6b7280;
  }
  .wb-dash .wb-odds-table-header > span {
    text-align: center;
    white-space: nowrap;
  }
  .wb-dash .wb-odds-table-header > span:first-child { visibility: hidden; }

  /* ── 6. odds cells ────────────────────────────────────────────────────
     ONE column width for all three rails. The block above sizes them per
     rail (58 upcoming / 52 final / 46 live) because a live card spends part
     of its team column on the running score and needed the odds to give
     that back. At the smaller cell type below, the columns drop from 58px
     to 47px and hand ~33px back to the name on EVERY card — more than the
     score ever took — so the three rails can share one width again and a
     game stops changing shape as it starts and ends.

     47px is measured, not chosen: the widest string a cell can hold is a
     total like "O 10.5" — 6 characters of JetBrains Mono at 11.5px, which
     is 6 x 0.6em = 41.4px, plus 2px of padding a side. Dropping the cell's
     horizontal padding from 6px to 2px is what buys the last 4px.

     A MISSING price still prints as the renderer's plain hyphen. The design
     asks for an en dash, and CSS cannot substitute a glyph it cannot select
     — the empty cell is `<div class="value">-</div>`, structurally identical
     to a moneyline cell — so that one is a scheduleCore change, not a
     stylesheet change, and is deliberately left undone here rather than
     faked. */
  .wb-dash .wb-odds-team-row,
  .wb-dash .wb-dash-rail[data-rail="final"] .wb-odds-team-row,
  .wb-dash .wb-dash-rail[data-rail="live"]  .wb-odds-team-row {
    grid-template-columns: minmax(0, 1fr) repeat(3, 47px);
    gap: 6px;
  }
  .wb-dash .wb-odds-cell {
    border-radius: 8px;
    padding: 6px 2px;
    min-height: 40px;
  }
  /* The fill is held off the SETTLED cells by name. `.wb-dash .wb-odds-cell`
     and schedule-layout.css's `.wb-odds-cell.settle-win` are both two
     classes, and this sheet loads second (index.html:92 then :96), so a
     bare rule here would quietly repaint every covered/missed market on
     every final card. The green/red wash stays exactly as it is. */
  .wb-dash .wb-odds-cell:not(.settle-win):not(.settle-loss):not(.settle-push) {
    background: #1c2022;
  }
  /* …and the hover affordance has to be restated at the same depth, or the
     :not() chain above out-specifies `.wb-odds-cell:hover` and a live price
     stops responding to the pointer.
     `.wb-odds-table:not(.wb-odds-settled)` is what keeps it off a FINAL card.
     schedule-layout.css:462 deliberately kills hover on a settled table
     because those cells are not placeable bets, but that rule is only
     (0,2,1) — this chain would have beaten it and lit up every market on a
     final card that carries no settle class, which is any market the book
     never priced (`<div class="value">-</div>`). */
  .wb-dash .wb-odds-table:not(.wb-odds-settled)
    .wb-odds-cell:not(.settle-win):not(.settle-loss):not(.settle-push):hover {
    background: #262b2e;
  }
  .wb-dash .wb-odds-cell .value {
    font-size: 11.5px;
    color: #f3f5f7;
  }
  /* The price line is teal whatever its sign — it is the juice, not a
     result, so colouring it by direction would say something untrue. Same
     theme token as .wb-dash-preview and .wb-matchup-title, at full strength
     rather than the 0.85 the flat list uses. */
  .wb-dash .wb-odds-cell .juice {
    font-size: 10px;
    color: rgb(var(--theme-accent-rgb, 0, 211, 189));
  }

  /* The card's own header sat 20px in from each edge (schedule-layout.css:487)
     while the odds table sits 6px in, so the start time and "Preview ›" hung
     14px inside their own columns. At three cards a row that was slack; at
     four it is 28px of a ~296px card spent on nothing. Sides only — the 16px
     of top padding is doing real work. */
  .wb-dash .wb-matchup-header { padding-left: 6px; padding-right: 6px; }
  /* Same 6px for the chip strip, so chips line up with the team logos above
     them. padding does not move a border-top, so the rule above the strip
     still spans the full card. */
  .wb-dash .wb-edge { padding-left: 6px; padding-right: 6px; }

  /* ── 7. live cards lose their red ─────────────────────────────────────
     A live card sits on the SAME hairline as every other card: no red
     border, no tinted fill, no glow, no top bar. The rail heading's dot and
     the red inning line inside the card still carry the state, so the slate
     is readable at a glance without every live card shouting.
     To restore, delete these four rules — the ones they override are intact
     above. */
  .wb-dash .wb-dash-rail[data-rail="live"] > .wb-dash-track > li.wb-card {
    background: #101316;
    border-color: rgba(255, 255, 255, 0.06);
  }
  /* Live cards had no hover response at all: the rail rule above is one
     class deeper than the generic card hover, so it won at every moment
     including hover. Restated here at matching depth. */
  .wb-dash .wb-dash-rail[data-rail="live"] > .wb-dash-track > li.wb-card:hover {
    background: #151a1f;
    border-color: rgba(255, 255, 255, 0.13);
  }
  /* The in-card pulsing dot. The rail heading keeps its own. */
  .wb-dash .wb-live-dot { display: none; }
  /* And the word "LIVE" itself, so the status line is just the inning.
     scheduleEdgeMLB.js wraps it in `<span class="wb-live-word">` (a sibling
     task) and puts the bullet INSIDE that wrapper, which is what makes one
     rule enough: hiding the span takes "LIVE" and the separator together, so
     the line opens on "BOT 7" and never on a stranded "• BOT 7".
     WRITTEN TO SURVIVE A REVERT: if the wrapper is ever removed from the JS,
     this selector matches nothing and the line falls back to the
     "LIVE • BOT 7" it prints today — which is the layout that shipped, not a
     broken one. Never key anything else off .wb-live-word. */
  .wb-dash .wb-live-l1 .wb-live-word { display: none; }

  /* ── 8. palette ───────────────────────────────────────────────────────
     Cards lift off the black page instead of being a white-alpha veil over
     it: opaque values, so a card looks the same wherever it sits and the
     hairline cannot ghost through. Live cards are one notch warmer (§7). */
  .wb-dash .wb-dash-track > li.wb-card {
    background: #0f1317;
    border-color: rgba(255, 255, 255, 0.06);
  }
  .wb-dash .wb-dash-track > li.wb-card:hover {
    background: #151a1f;
    border-color: rgba(255, 255, 255, 0.13);
  }

  /* ── 9. edge chips ────────────────────────────────────────────────────
     Bigger and bolder — at 10px on a grey wash they read as metadata, and
     "WIND OUT 8" is the single most actionable thing on an upcoming card.
     Now an outlined chip: 1px of the tone's own hue over a 12% fill of the
     same hue, which is how every other tinted control in the app is built.
     The tone NAMES are scheduleEdgeMLB.js's own (hot/warm/cold/bad/flat)
     and are not renamed — only their treatment changes. */
  .wb-dash .wb-edge-chip {
    padding: 7px 12px;
    border-radius: 8px;
    border: 1px solid transparent;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    line-height: 1;
  }

  /* TWO CHIPS PER CARD, no more. A third chip wrapped to its own line on a
     4-up card, so one game in a rail grew taller than its neighbours for the
     least important thing on it — and three competing edges is not an edge,
     it is a list. scheduleEdgeMLB.js already emits them strongest-first, so
     hiding the tail keeps the two that earned their place.

     Sibling-combinator rather than :nth-of-type, because .wb-edge also holds
     the .wb-edge-live pitching line: this matches only a chip preceded by two
     other CHIPS, whatever else shares the row. */
  .wb-dash .wb-edge-chip ~ .wb-edge-chip ~ .wb-edge-chip { display: none; }
  .wb-dash .wb-edge-chip[data-tone="hot"] {
    border-color: rgba(34, 197, 94, 0.5);
    background: rgba(34, 197, 94, 0.12);
  }
  .wb-dash .wb-edge-chip[data-tone="warm"] {
    border-color: rgba(251, 191, 36, 0.5);
    background: rgba(251, 191, 36, 0.12);
  }
  .wb-dash .wb-edge-chip[data-tone="cold"] {
    border-color: rgba(96, 165, 250, 0.5);
    background: rgba(96, 165, 250, 0.12);
  }
  .wb-dash .wb-edge-chip[data-tone="bad"] {
    border-color: rgba(239, 68, 68, 0.5);
    background: rgba(239, 68, 68, 0.12);
  }
  .wb-dash .wb-edge-chip[data-tone="flat"] {
    border-color: rgba(139, 145, 160, 0.42);
    background: rgba(139, 145, 160, 0.12);
  }
}
/* ^ END OF THE REDESIGN. Everything from the banner above to this brace can be
   deleted to revert it, and nothing below 1001px moves — the only ungated rule
   the redesign needs (.wb-live-word's display:contents) lives up with its
   siblings near the top of the file, where it is a no-op on its own. */
