/* ============================================================
   Type
   ============================================================
   Self-hosted, because everything else on this site works with the
   network unplugged and the fonts should too — a board that renders in
   Times New Roman the one night your wifi drops is not a board you trust.
   Seven files, 90KB total, and `font-display: swap` means text is
   readable before they land rather than invisible.

   Bodoni Moda for the masthead and entity names, Archivo Narrow for
   headers, nav, labels and prose, IBM Plex Mono for every NUMBER — see the
   note above its @font-face. The pairing is the point: a Didone masthead
   over a condensed grotesque over a fixed-pitch data face is three
   decisions somebody made, where a single neutral sans everywhere is what
   you get when nobody did. It also does real work — it separates "this is
   a section" from "this is a number" without a rule or a box.

   THE NUMBERS ARE NOT THE DISPLAY FACE, and the reason is measured rather
   than felt. This was tried with Instrument Serif and is recorded here so
   the next person to think "the numbers should be the serif" finds the
   result instead of repeating the experiment: it has no `tnum` feature, so
   a column of figures wobbles as the digits change, and its 11 reads as "ll"
   at metric size. Bodoni Moda is a Didone and fails the same way
   harder — high-contrast hairlines vanish at 10px agate, and its figures
   are proportional. It sets names and the masthead. It never sets a price.
*/
@font-face {
  font-family: "Archivo Narrow";
  src: url("../fonts/archivo-narrow.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Archivo Narrow";
  src: url("../fonts/archivo-narrow-700.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
/* Bodoni ships 700 and 900 here: 900 for the masthead, 700 for entity
   names. Nothing asks it for a weight it does not have — a synthesised
   Bodoni at masthead size is a smear, and the old sheet already learned
   that lesson with Instrument Serif. */
@font-face {
  font-family: "Bodoni Moda";
  src: url("../fonts/bodoni-moda-700.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Bodoni Moda";
  src: url("../fonts/bodoni-moda-900.woff2") format("woff2");
  font-weight: 900; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("../fonts/plex-mono.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("../fonts/plex-mono-600.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("../fonts/plex-mono-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* ============================================================
   Qellys Book — design system
   Cohesive dark palette: indigo/blue anchor, cyan + violet accents,
   semantic green/amber/red tuned to the same chroma family.
   ============================================================ */
:root {
  /* Sharp & minimal: neutral near-black, hairline borders, one calm accent.
     Gradient/glow variable names are kept but flattened so every existing
     usage in this sheet resolves to the quiet system. */
  /* THE NEUTRAL RAMP, IN OKLCH, ON ONE WARM HUE.
     Measured before it was touched, because converting hex to oklch()
     notation on its own changes nothing and would be churn. What it found
     was a ramp whose hue FLIPS in the middle:

       bg / bg-2 / panel-2 / panel-3    hue 263-276    cool blue
       border-soft -> border -> text    hue  80- 90    warm amber

     Four dark surfaces were cool while every border and text token above
     them was warm. That is why bone text could sit very slightly "off" on
     a panel without anyone being able to name it — the ground and the ink
     disagreed about temperature.

     It was an oversight rather than a decision: the LIGHT theme is warm end
     to end (hue 83-92 across all nine steps), so only the dark surfaces
     ever went cool.

     The fix holds L and C exactly and moves only the hue to 84 — the median
     of the ramp's own warm cluster. Lightness drifts by at most 0.0007, so
     nothing here can change a contrast ratio; only the temperature moves.
     Written in oklch() so the next person can step L directly instead of
     guessing a hex, which is the actual argument for the notation. */
  --bg: oklch(0.139 0.005 84);          /* was #08090B, hue 263 */
  --bg-2: oklch(0.155 0.007 84);        /* was #0B0C0F, hue 271 */
  --panel: oklch(0.155 0.007 84);
  --panel-2: oklch(0.178 0.008 84);     /* was #101115, hue 274 */
  --panel-3: oklch(0.197 0.010 84);     /* was #14151A, hue 277 */
  --border: #3A352C;
  --border-soft: #23201A;

  /* The quiet ladder, measured rather than picked. APCA on the darkest
     ground, solved against --panel-3 because the lightest panel is where
     contrast is lowest and therefore binds:

       --text        Lc 90    body text, preferred
       --text-dim    Lc 60    larger or secondary text
       --text-mute   Lc 45    large or bold UI
       --text-faint  Lc 30    disabled or decorative

     Those are APCA's own reference targets, one tier apart, with nothing
     between them chosen by eye.

     WHAT THIS REPLACED. --text-mute was #57524A — Lc 15, which APCA calls
     the point of invisibility — across 223 uses, and not on decoration:
     .section-title, .tile .k (the label on every metric tile), .matchup
     .away, .pick .book, .game-sub.starters. --text-dim sat at Lc 51, under
     its own 60 target.

     It could not be repaired by lightening one token. Lc 60 needs
     L 0.761 and --text-dim was L 0.708, so the quiet tier would have
     ended up brighter than the tier above it. One token was doing two
     jobs — decorative furniture, and readable secondary content — and the
     repair is the fourth step, not a brighter third one.

     Each value is solved to its target plus ~0.8 Lc of headroom, so that
     rounding never prints a tier as sitting below its own bar: at exactly
     45.0 the mute tier measured 44.7 on --panel-3 and the audit filed it
     under 'decorative'.

     The split defaults to READABLE. Everything stayed on --text-mute and
     rose to Lc 45; only genuinely non-text marks were moved down to
     --text-faint (the card's 4px grade stripe, two separator glyphs, the
     empty-state icon). Misfiling something that way leaves it too legible
     rather than invisible, which is the failure worth having. */
  /* MOTION. Three durations, two curves, and every transition on the page
     goes through them.

     The default `ease` was doing 21 of the 25 transitions here, and it is
     the wrong curve for a control responding to a tap: it is symmetric,
     so it accelerates slowly at BOTH ends and nothing visibly moves for
     the first few frames. Measured against the replacement, by fraction
     of the duration elapsed:

         elapsed    ease     ease-out
            5%       3.3%      20.2%     6.1x further
           10%       9.5%      39.7%     4.2x
           20%      29.5%      68.2%     2.3x

     An element should leave immediately on the input and settle gently,
     which is what a decelerating curve does. Only one thing overshoots —
     the nav indicator — and it keeps its own spring.

     The durations are the eight that were in use (.12 .14 .15 .18 .2 .24
     .28 .3) collapsed onto the three tiers they were clustering around,
     not three numbers picked fresh. */
  --dur-fast: 120ms;
  --dur-base: 180ms;
  --dur-slow: 280ms;
  --ease-out: cubic-bezier(.2, .8, .2, 1);
  --ease-spring: cubic-bezier(.5, 1.4, .5, 1);

  /* HAIRLINES. 94 rules on this page were drawn as `1px solid`, which on
     a 2x display is TWO device pixels — every divider rendering at double
     the weight it was drawn for. Before retina, "1px border" meant one
     device pixel, and this token means that again.

     Only rules and dividers. The 2px and 3px borders are all `border-left`
     accent stripes — grade colour, warnings, the brand rail — which are
     structural marks rather than hairlines and keep their weight on
     purpose. Halving those would make them read as dividers.

     0.5px is safe: it is exact at 2dppx, and at 3dppx it is 1.5 device
     pixels, which still renders thinner than the 3 it would otherwise
     take. Non-retina never enters the query and keeps a true 1px. */
  --hairline: 1px;

  /* ROW MEASURE — how long a line runs, and how far apart the lines sit.
     The two halves of whether a row of text is comfortable, and both had
     drifted rather than been chosen.

     LEADING was 13 distinct values, 19 of them spread across SIX numbers
     between 1.5 and 1.65 — a range no eye resolves, so the variation was
     cost without effect. Three rungs now, far enough apart to mean
     something:

     LENGTH was six measures from 62ch to 92ch. The comfortable band for
     running text tops out around 75 characters; 78ch and 92ch were past
     it, the 92 on a section subtitle set at --fs-sm where the line is
     longest in characters exactly because the type is smallest.

     Three literals stay off the ladder on purpose:
       line-height: 0    kills inline-block descender space under images;
                         it is a layout trick, not leading.
       line-height: 1    a single line that must not gain height.
       line-height: .96  optical tightening on the masthead Didone, which
                         is nowrap and one line by construction. */
  --lh-tight: 1.25;
  --lh-snug: 1.45;
  --lh-body: 1.6;
  --measure-tight: 62ch;
  --measure: 68ch;
  --measure-wide: 74ch;

  /* Named for a hue this palette no longer has. It measures C 0.024 at
     hue 84.6 — a warm near-neutral, not cyan — left from the indigo/violet
     direction the brand comment describes removing. Four rules still use
     it (.gb-line, .card.gamebet, .chip.books, .val.cyan), so it stays
     until those are re-pointed; the name is the bug, not the colour. */
  --cyan: #A8A090;
  --text: #E8E4D9;
  --text-dim: #BAB3A6;
  --text-mute: #9E988B;
  --text-faint: #7F786C;

  /* The accent is PARCHMENT, not periwinkle. Every colour left in this
     palette after green/amber/red are spoken for is either violet — the
     exact tell we are removing — or a blue one shade off the last one.
     So the active state stops being a colour at all and becomes an
     inversion: warm paper, dark ink, the way a printed tab is marked.
     It also frees the coloured slots to mean only what they mean, which
     on a page about money is worth more than a hue. */
  --brand: #FFB000;
  --brand-2: #4A3B14;
  --brand-ink: #0A0907;            /* text ON the accent — the warm base */
  --good: #29C878;
  /* Amber is ONE colour with one meaning — §1 of the redesign
     decisions: a condition is live or material. --brand is the
     active nav item, which is such a condition, so these were
     always the same hex. Written as an alias so editing one
     cannot silently split them. */
  --warn: var(--brand);
  --bad: #DC5455;
  /* The two status marks the STYLESHEET draws. Same geometry as the check
     and cross in ICON_PATHS (web/js/app.js) — if one moves, move both. Used
     as a mask, so the paint inside the URI is irrelevant; the element's
     background-color is what shows. Kept on :root so the light theme
     inherits them rather than redeclaring the geometry. */
  --ic-check: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="%23000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 8.5l3.2 3.4L13 4.6"/></svg>');
  --ic-cross: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="%23000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4l8 8M12 4l-8 8"/></svg>');
  /* The UFC method bar needs six distinguishable segments and the palette
     above offers five. It used to reach for var(--violet, #a78bfa) — a
     token that does not exist, falling through to Tailwind's violet-400
     verbatim, which is the single swatch this whole palette was built to
     avoid. A muted wine sits far enough from the red to read as a
     different outcome and nowhere near the indigo everything else uses. */
  --plum: #8A8375;

  --grad-brand: #FFB000;
  --grad-violet: #4A3B14;
  --grad-hot: #E5484D;
  --grad-cold: #A8A090;

  --fill-subtle: rgba(232,228,217,.022);
  --fill-hover: rgba(232,228,217,.05);
  --text-body: #A8A090;
  --topbar-bg: rgba(10,9,7,.92);
  --skeleton: linear-gradient(100deg, var(--panel-2) 30%, var(--panel-3) 50%, var(--panel-2) 70%);

  --font-sans: "Archivo Narrow", "Arial Narrow", -apple-system,
               BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-display: "Bodoni Moda", "Iowan Old Style", "Palatino Linotype",
                  Palatino, Georgia, serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* Tighter corners. A 10px radius on every panel is the house style of
     every component library shipped since 2022 — softening everything
     equally is what you get when nothing was decided. 5px still reads as
     a card and stops reading as a template. */
  /* Three steps and a pill, not fourteen. An audit of the rendered pages
     found border-radius values of 1,2,3,4,6,7,8,9,10,11,12,13 and 16px in
     use — nobody chooses both 9 and 10 and 11, that is accretion, and it
     is exactly what "unconsidered" looks like up close. Small controls,
     panels, and the few genuinely large surfaces. Tiny values on bars and
     sparklines are graphics, not containers, and keep their own. */
  /* ---- Type ramp -----------------------------------------------------
     Twenty-three absolute font sizes were in use — 9.5, 10, 10.5, 11,
     11.5, 12, 12.5, 13, 13.5, 14, 14.5, 15, 16, 17, 18, 20, 22, 23, 24,
     26, 30, 34, 38 — which is not a ramp with a few strays in it, it is
     no ramp. Nobody can tell 13px from 13.5px on a card; what they can
     tell is that nothing lines up.

     Nine steps, because the big end is genuinely role-differentiated: a
     stat tile's value, a chart's headline number, the brand, and a
     profile name are four different jobs and collapsing them further
     would be tidiness at the cost of hierarchy.

     Relative sizes are deliberately NOT folded in here. `.5em` on a unit
     suffix and `1.24em` on an emphasised metric are supposed to track
     their parent; they render fractional because they are derived, not
     because anyone typed 8.2px. */
  --fs-2xs: 10px;
  --fs-xs: 11.5px;
  --fs-sm: 12.5px;
  --fs-md: 13.5px;
  --fs-lg: 15px;
  --fs-xl: 17px;
  --fs-2xl: 22px;
  --fs-3xl: 30px;
  --fs-4xl: 34px;
  --radius: 0;
  --radius-sm: 0;
  --radius-lg: 0;
  --shadow: none;
  --shadow-lg: none;
  --ring: 0 0 0 2px rgba(230, 220, 203, 0.30);
}

* { box-sizing: border-box; }

/* Keyboard focus, everywhere, once.
   ============================================================
   Six elements on the whole site had a focus-visible ring, against forty
   buttons — including the sport bar and the page nav, which is how the
   site is used. Tabbing through it showed nothing moving: no way to tell
   where you were, and no way to drive it without a mouse.

   :focus-visible rather than :focus, so a mouse click does not leave a
   ring behind — that is the reason focus outlines got removed from the
   web in the first place, and the modern selector fixes the actual
   complaint without taking the affordance away from anyone using a
   keyboard.

   Square, and offset rather than inset: the design system has no rounded
   containers, and an outline that overlaps its own control reads as a
   border rather than as focus. */
:where(a, button, summary, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}
/* The nav sits tight against its own underline, so its ring needs room to
   clear the indicator rather than sitting on top of it. */
.nav-btn:focus-visible, .sport-btn:focus-visible { outline-offset: 4px; }

html {
  scroll-behavior: smooth;
  /* iOS Safari "text autosizing": it inflates font sizes inside blocks it
     judges wide relative to the viewport, and RE-RUNS that judgement on
     every DOM change — so switching sports with the menu open visibly
     enlarged the header each time. This layout sets its own sizes and is
     responsive already, so the guessing is pure damage. 100% = leave our
     type alone (it does NOT disable the user's own zoom). */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--text);
  line-height: var(--lh-snug);
  -webkit-font-smoothing: antialiased;
  /* Every figure on this site sits in a column next to another figure.
     Proportional digits make those columns wobble as the numbers change,
     which on a board that refreshes every minute reads as the page
     twitching. Tabular everywhere, once, rather than per-component. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

h1, h2, h3 { margin: 0; font-family: var(--font-display); font-weight: 400; }

/* A heading that breaks "Calibration by market and" / "horizon" has a widow,
   and a widow in display type is the loudest amateur tell on a page that is
   otherwise set carefully. `balance` asks the browser to even the lines
   instead, and it is capped around six lines by spec so it cannot become a
   layout cost on something long.

   `pretty` is the body-copy version: it only guards the LAST line against a
   single stranded word, which is why it is safe to apply broadly where
   `balance` would not be. Both degrade to normal wrapping where unsupported,
   which is the whole reason they are worth shipping now rather than later. */
h1, h2, h3, .tile .k { text-wrap: balance; }
p, .note, .tile-sub, .caveat, .reading { text-wrap: pretty; }

/* SVG text does not inherit the page's face the way HTML does, so every
   label inside the ballpark, field and court diagrams — 294 of them across
   the site — was rendering in system-ui while the rest of the page used
   Instrument Sans. Nobody would notice one; the effect of all of them is a
   product that does not quite match itself. Numbers in those diagrams take
   the mono, same as numbers everywhere else. */
svg text { font-family: var(--font-sans); }
svg text.num { font-family: var(--font-mono);
               font-feature-settings: "tnum" 1, "zero" 1; }

/* The serif is a DISPLAY face — it is drawn for size and goes muddy
   small, so it is never used below ~17px. Section headings keep the
   sans, because at 11px in caps a serif is just noise. */

/* No italic serif ships. It was declared for a while and I tried putting
   prose emphasis in it — the obvious home for an editorial italic. A
   screenshot at 390px/DPR3 killed it: our prose runs 13–14.5px, and this
   serif's thin strokes at 13px on a dark panel read LIGHTER than the
   sentence around them, so `<em>` came out looking de-emphasised. The
   floor above is not a style preference, it is where the face stops
   working. Emphasis stays in the sans. */

/* [old top bar — removed 2026-08-11: superseded by the NEW LOOK layer at the end of this file] */
/* §6.11: 1px bone border, transparent fill, uppercase. Hover fills. A
   solid accent slab is the one shape this direction does not have. */
.btn {
  background: none; color: var(--text); border: var(--hairline) solid var(--text); cursor: pointer;
  padding: 7px 14px; font-weight: 700; font-size: var(--fs-xs);
  font-family: var(--font-sans); letter-spacing: .13em; text-transform: uppercase;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
  box-shadow: none;
}
.btn:hover { background: var(--text); color: var(--bg); box-shadow: none; }
.btn:active { transform: translateY(1px); }
.btn.ghost { background: var(--panel-2); border: var(--hairline) solid var(--border); box-shadow: none; color: var(--text-dim); }
.btn.ghost:hover { color: var(--text); border-color: var(--brand-2); }

/* ---------------- View container + transitions ---------------- */
/* The tab transitions slide a view in from ±30px, which pushes the
   DOCUMENT wider for the third of a second the animation runs — so a
   swipe landing mid-transition jogged the whole page sideways on a phone.
   Clipping here contains the slide without touching the page's vertical
   scroll, and without reaching the bracket and the other panels that
   scroll horizontally inside their own boxes. */
main { max-width: 1360px; margin: 0 auto; overflow-x: clip; }
.view { display: none; padding: 22px 26px 60px; }
/* No entrance animation on a view change (§3.4). Tapping a tab should
   change the page, not play a transition at it. */
.view.active { display: block; }

.section-title { font-size: var(--fs-lg); font-weight: 700; margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }

/* ---------------- Sub-tabs: one page, several rooms ----------------
   The Record page composed twenty-two sections into one scroll. These
   group them into five rooms WITHOUT adding a twelfth top-level tab —
   the site already has eleven of those plus a sport switcher, and more
   of them is what made it hard to navigate.

   So this bar must read as SUBORDINATE to `.nav`, never as a peer.
   Three things separate them and none is colour: the main tabs are
   uppercase with .16em tracking and a 2px indicator; these are sentence
   case, unspaced, with a 1px underline on the active one only. Same
   family, obviously lower rank — which is what a reader needs in order
   to know where they are without reading anything. */
.subnav {
  display: flex; flex-wrap: wrap; gap: 4px;
  border-bottom: var(--hairline) solid var(--border);
  margin: 0 0 4px;
}
.subnav-btn {
  border: 0; background: transparent; cursor: pointer;
  font-family: var(--font-sans); font-size: var(--fs-sm); font-weight: 600;
  /* No radius. This redesign is hairlines and square corners throughout —
     a rounded tab is a different design language, and `test_nightform`
     exists to catch exactly that drift. */
  color: var(--text-mute); padding: 7px 11px;
  /* The active underline sits ON the bar's hairline rather than beside
     it — a 1px rule 1px away from another 1px rule reads as a smudge. */
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
.subnav-btn:hover { color: var(--text); }
.subnav-btn.active { color: var(--text); border-bottom-color: var(--brand-2); }
.subnav-btn:focus-visible { outline-offset: 2px; }

/* The hint is the answer to "which tab has what", which is a thing Ethan
   named directly. Tab labels are necessarily short; this line says what
   the room actually holds, and it changes with the tab rather than
   sitting there as static furniture. */
.subnav-hint {
  margin: 0 0 18px; color: var(--text-mute);
  font-size: var(--fs-sm); font-style: italic;
}
.subnav-wrap { margin-bottom: 4px; }

/* A section title directly under a sub-tab bar is redundant with the tab
   you just pressed, so the first one in a room loses its top margin and
   the room breathes instead. */
.subgroup > .section-title:first-child { margin-top: 0; }

@media (max-width: 760px) {
  /* Rooms scroll sideways rather than wrapping to three rows and pushing
     the content off the screen — the same trade `.nav` already makes. */
  .subnav { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .subnav::-webkit-scrollbar { display: none; }
  .subnav-btn { white-space: nowrap; }
}
.section-title .sub { color: var(--text-mute); font-weight: 500; font-size: var(--fs-sm); }

/* THREE RANKS, NOT ONE. Ethan, 2026-08-08: "no visual hierarchy —
   everything looks equally important, so nothing stands out."

   Measured: 81 section titles across the site, every one of them rendered
   at the identical size and weight. "Running P&L" and "Team-form sampler
   — measurement in progress" were typographically the same announcement,
   so a reader had to actually read both to learn which mattered.

   `.lead` is the one thing a page is FOR. `.minor` is the quarantined
   samplers and the not-yet-measured — real, kept visible on purpose, and
   not competing with the number you came to see. Size and weight carry
   it; colour is left alone, because the mute tokens are already at the
   bottom of what stays legible (see the contrast ratchet).  */
.section-title.lead { font-size: calc(var(--fs-lg) * 1.2); letter-spacing: -0.01em; }
.section-title.minor {
  font-size: var(--fs-sm); letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-dim); font-weight: 700; margin-bottom: 8px;
}
.section-title.minor .sub { text-transform: none; letter-spacing: 0; }

/* ---------------- Stat tiles ---------------- */
/* `.stats` is SHARED — the Record page's eight KPI rows, the game-panel
   stats and more all reuse it, with anything from three to five tiles. It
   stays auto-fit for exactly that reason: pinning it to four tracks pushed
   the Record page's fifth tile onto a second row, where it read as an
   orphaned box. The Recommended lead layout below is scoped to #stats,
   which is that one container and nothing else. */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(168px, 100%), 1fr)); gap: 0; margin-bottom: 20px; }

/* The lead column is wider AND its number is bigger — composition first,
   size second. Four equal columns was the shape that made none of them the
   answer, and auto-fit can only ever produce equal columns. */
#stats { grid-template-columns: 1.7fr 1fr 1fr 1fr; }
/* §6.6. No card, no background, no border box — a row of columns divided
   by a single vertical rule. Four equal bordered tiles with a big number
   in each is item 6 on the anti-pattern list, and it was the shape this
   page opened with. */
.tile {
  position: relative;
  background: none;
  border: 0; border-left: var(--hairline) solid var(--border-soft);
  padding: 2px 18px;
}
/* No `overflow: hidden` here. It was left behind when the tile's radial
   wash was removed — nothing clips to the tile any more — and a text block
   that silently truncates is worse than one that pushes its own box taller. */
.stats > .tile:first-child { border-left: 0; padding-left: 0; }
/* The tile's radial wash is gone with the tile. §8.7: gradient anything. */
.tile .k { color: var(--text-mute); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .7px; font-weight: 600; }
.tile .v { font-size: var(--fs-3xl); font-weight: 800; margin-top: 5px; letter-spacing: -0.5px; }
.tile .v.pos { color: var(--good); }
/* The answer, and the context for it. Two steps apart on the existing ramp
   — no new token, because the item is a composition change and inventing a
   bigger size is the way to dodge doing one. */
#stats > .tile.lead .v { font-size: var(--fs-4xl); }
#stats > .tile:not(.lead) .v { font-size: var(--fs-2xl); color: var(--text-dim); }
#stats > .tile:not(.lead) .v.pos { color: var(--good); }

/* The narrow-screen half of this lives in the 760px block further down,
   beside the rule it has to beat. Adding a second breakpoint here made two
   rules fight over the same property with the later one silently winning —
   the phone grid stayed at two columns while this said three. */

/* ---------------- Controls ---------------- */
.controls {
  display: flex; flex-wrap: wrap; gap: 28px; align-items: center;
  padding: 16px 22px; margin-bottom: 20px;
  background: var(--panel); border: var(--hairline) solid var(--border-soft); border-radius: 0;
}
.control { display: flex; flex-direction: column; gap: 7px; min-width: 210px; }
.control label { font-size: var(--fs-sm); color: var(--text-dim); display: flex; justify-content: space-between; }
.control label b { color: var(--text); }
.control.toggle { justify-content: center; }
.control.toggle label { flex-direction: row; align-items: center; gap: 8px; cursor: pointer; }
input[type=range] {
  -webkit-appearance: none; appearance: none; height: 6px; border-radius: 0;
  background: var(--panel-3); outline: none;
}
input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; width: 18px; height: 18px; border-radius: 50%;
  background: var(--brand); cursor: pointer; box-shadow: none;
  transition: transform var(--dur-fast) var(--ease-out);
}
input[type=range]::-webkit-slider-thumb:hover { transform: scale(1.12); }

/* ---------------- Games strip (stadiums + wind) ---------------- */
/* overflow-y is LOCKED: with only overflow-x set, CSS computes the other
   axis to auto, and any pixel of slack turns a phone's vertical swipe into
   scrolling the strip instead of the page. Sideways only — up/down always
   belongs to the page. */
.games-scroller { display: flex; gap: 15px; overflow-x: auto; overflow-y: hidden;
  padding: 4px 2px 14px; scroll-snap-type: x mandatory; }
.games-scroller::-webkit-scrollbar { height: 8px; }
.games-scroller::-webkit-scrollbar-thumb { background: var(--border); border-radius: 0; }
.game-card {
  flex: 0 0 272px; scroll-snap-align: start; overflow: hidden;
  background: var(--panel-2);
  border: var(--hairline) solid var(--border-soft); border-radius: 0; box-shadow: none;
  transition: transform var(--dur-fast) var(--ease-out), border-color var(--dur-base) var(--ease-out);
}
.game-card { transform-style: preserve-3d; transition: border-color var(--dur-base) var(--ease-out); }
.game-card:hover { border-color: var(--brand-2); box-shadow: none; }
.stadium-wrap { line-height: 0; background: #070b16; }
.stadium-wrap .stadium { width: 100%; height: auto; display: block; }
.game-info { padding: 11px 15px 3px; }
.matchup { display: flex; align-items: center; gap: 8px; font-weight: 800; font-size: var(--fs-lg); }
.matchup .at { color: var(--text-mute); font-weight: 500; }
.matchup .away { color: var(--text-dim); }
.game-sub { color: var(--text-mute); font-size: var(--fs-sm); margin-top: 2px; }
/* Probable starters on the MLB ballpark card: one quiet line, the names a
   shade brighter than the park facts so the arms read first. */
.game-sub.starters { color: var(--text); opacity: .85; }
.game-sub.starters .p-detail { color: var(--text-mute); font-size: .92em;
  font-variant-numeric: tabular-nums; }
.game-when { color: var(--text-dim); font-size: var(--fs-sm); font-weight: 600; margin-top: 3px; }
.wind-wrap { display: flex; align-items: center; gap: 11px; padding: 7px 15px 13px; }
.wind-wrap .cond { color: var(--text-dim); font-size: var(--fs-sm); }
.wind { line-height: 0; }

/* wind streamlines */
.wind .stream { animation-name: windflow; animation-timing-function: linear; animation-iteration-count: infinite; opacity: .85; }
.wind .s0 { animation-delay: 0s; } .wind .s1 { animation-delay: .25s; opacity: .6; }
.wind .s2 { animation-delay: .1s; } .wind .s3 { animation-delay: .35s; opacity: .55; }
.wind.low .stream { opacity: .5; } .wind.high .stream { stroke-width: 3; }
@keyframes windflow { from { stroke-dashoffset: 48; } to { stroke-dashoffset: 0; } }

/* ---------------- Pick cards ---------------- */
/* Capped column width + centred tracks. A board with one pick used to
   hug the left edge with two-thirds of the screen empty beside it, which
   reads as broken rather than selective — and sparse IS this product's
   normal state, since the models are built to pass on most things. */
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(346px, 100%), 440px));
  gap: 18px; justify-content: center; }
.loading { color: var(--text-dim); grid-column: 1 / -1; text-align: center; padding: 60px; }
.card {
  position: relative; overflow: hidden;
  /* Flat, not a vertical wash. The gradient was doing nothing a person
     could name — it made every panel look faintly lit from above, which
     across forty panels is the "generated" texture rather than a
     decision. A hairline and a flat field is what a printed table has. */
  background: var(--panel-2);
  border: var(--hairline) solid var(--border-soft); border-radius: 0;
  padding: 18px; box-shadow: none;
  display: flex; flex-direction: column; gap: 14px;
  transition: border-color var(--dur-base) var(--ease-out);
}
.card:hover { border-color: var(--border); box-shadow: none; }
.card.faded { opacity: .6; }
.card::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: var(--grade-color, var(--text-faint)); }

/* space-between distributes LEFTOVER space; it does not make anything
   shrink. Flex items default to min-width:auto, so a long market question
   refuses to go below its content width and shoves the trailing status
   chip past the card's right edge — where .card's overflow:hidden cuts it
   in half. Measured on Polymarket at 320px: "HISTORICAL" lost its tail.
   min-width:0 lets the title wrap; the chip stays whole.

   :not(:first-child) below matters: plenty of card heads hold a title and
   nothing else, and pinning an only child to flex:0 0 auto stops it
   shrinking at all — a 258px head with an 830px child, far worse than the
   bug being fixed. Only a TRAILING sibling gets pinned. And wrap, because
   at 320px a title and a "HISTORICAL" chip genuinely do not share a line;
   the chip drops below rather than off. */
.card-head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-start; gap: 10px; }
.card-head > * { min-width: 0; }
.card-head > :last-child:not(:first-child) { flex: 0 0 auto; }
.card-id { display: flex; gap: 12px; align-items: flex-start; }
.avatar { border-radius: 50%; flex-shrink: 0; box-shadow: none; }
.player { font-size: var(--fs-xl); font-weight: 800; letter-spacing: -0.2px; }
.subtitle { color: var(--text-dim); font-size: var(--fs-sm); margin-top: 2px; }
.pick { font-size: var(--fs-lg); font-weight: 700; color: var(--text); margin-top: 7px; }
.pick .book { color: var(--text-mute); font-weight: 500; }

/* Moneyline (game bet) cards */
.card.gamebet .player .ml-odds {
  font-size: var(--fs-lg); font-weight: 800; color: var(--cyan);
  margin-left: 6px; vertical-align: middle;
}
.card.gamebet { --grade-color: var(--cyan); }
/* A game total's mark: the league's own, with the over/under direction as
   a corner pip. The pip carries the colour the whole badge used to. */
.mark-stack { position: relative; display: inline-block; flex-shrink: 0;
  line-height: 0; }
.mark-pip {
  position: absolute; right: -2px; bottom: -2px;
  display: flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; border-radius: 50%;
  /* The ramp's floor, scaled: this glyph is a mark, not text, and the
     smallest ramp step is still too tall for a 15px disc. */
  font-size: calc(var(--fs-2xs) * 0.8); font-weight: 900; line-height: 1;
  color: #08101f;
  /* An OUTLINE, deliberately: this ring separates the pip from the mark
     under it and must not do the other two jobs. A 2px border would wear
     an accent stripe's weight on something that is not a rule, and a
     box-shadow would be a second light source outside the glow tokens.
     An outline draws outside the box, costs no layout, and this element
     is decorative and never focusable, so it cannot be read as focus. */
  outline: 2px solid var(--panel);
}
.mark-pip.over { background: var(--good); }
.mark-pip.under { background: var(--warn); }

/* Empty state */
/* §6.12. An empty state is a sentence, not a poster.
 *
 * It was a centred block with a 34px emoji on top — the loudest object on a
 * page whose entire message is that there is nothing here, and somebody
 * else's illustration at somebody else's weight. Night Form's answer is the
 * same as its answer everywhere: rule it off, set it left, let the words
 * carry it, and draw the mark at the size of the text it sits beside. */
.empty-slate { text-align: left; padding: 26px 0 30px; margin: 6px 0 4px;
  border-top: var(--hairline) solid var(--border); background: none; border-radius: 0;
  grid-column: 1 / -1; }
.empty-slate .es-icon { color: var(--text-faint); line-height: 1;
  display: block; margin-bottom: 12px; }
.empty-slate .es-icon .ic { width: 22px; height: 22px; }
.empty-slate .es-title { font-size: var(--fs-lg); font-weight: 700;
  margin-top: 0; letter-spacing: .01em; }
.empty-slate .es-sub { color: var(--text-dim); font-size: var(--fs-sm);
  margin-top: 7px; max-width: var(--measure); margin-inline: 0; line-height: var(--lh-body); }
.empty-slate code { background: var(--fill-subtle); padding: 2px 6px;
  border-radius: 0; }
/* A drawn mark on a text baseline. */
.ico-mark { display: inline-flex; vertical-align: -2px; margin-right: 6px;
  color: currentColor; }

/* Game bets grouped by market */
.gamebets-wrap { display: flex; flex-direction: column; gap: 16px; }
.gb-group-label { display: flex; align-items: center; gap: 8px; font-size: var(--fs-sm); font-weight: 800;
  text-transform: uppercase; letter-spacing: .5px; color: var(--text-dim); margin-bottom: 10px; }
.gb-group-label span { background: var(--panel-3); color: var(--text-dim);
  border-radius: 0; padding: 1px 8px; font-size: var(--fs-xs); }

/* A grade is a verdict on QUALITY, so it takes the result ramp, not the
   accent. Ten amber grade badges on one board was the single biggest
   contributor to the 41 amber elements the audit found on Recommended —
   §3.1's own warning is that past ~8, something is being decorated rather
   than encoded, and amber is supposed to mean "this CONDITION is live or
   material".

   Amber survives on `lean` alone, where it means what it means everywhere
   else on the page: proceed with care. Outline rather than fill — nothing
   else in this design is a solid slab of colour. */
.grade { font-family: var(--font-sans); font-size: var(--fs-2xs);
  font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  padding: 3px 9px; border-radius: 0; white-space: nowrap;
  background: none; border: var(--hairline) solid var(--text-mute); color: var(--text-dim); }
.grade.strong { border-color: var(--good); color: var(--good); }
.grade.play { border-color: var(--text); color: var(--text); }
.grade.lean { border-color: var(--warn); color: var(--warn); }
.grade.pass { border-color: var(--border); color: var(--text-mute); }

.metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.metric { background: var(--fill-subtle); border: var(--hairline) solid var(--border-soft); border-radius: 0; padding: 9px 11px; }
.metric .k { font-size: var(--fs-2xs); color: var(--text-mute); text-transform: uppercase; letter-spacing: .5px; font-weight: 600; }
.metric .v { font-size: var(--fs-xl); font-weight: 800; margin-top: 3px; }
.metric .v.pos { color: var(--good); } .metric .v.neg { color: var(--bad); }

/* The SUPPORTING numbers, in rows that HAVE a hero. Scoped through :has()
   on purpose. Not every metrics row is a hierarchy — the NFL game-script
   card shows "CHI implied" beside "GB implied", the same quantity for both
   sides of one game. That is a COMPARISON, and receding half of a
   comparison does not clarify it, it just makes the card quieter. A row
   only recedes when there is something in it to recede behind.

   Where :has() is unsupported these stay at 17px and the hero still leads
   at 22px, so the fallback is a smaller version of the same idea rather
   than a broken layout. */
.metrics:has(.metric.primary) .metric:not(.primary) .v {
  font-size: var(--fs-lg);
  color: var(--text-body);
}
/* A supporting number keeps its SIGN — it is written into the glyph,
   "+39.3%" — but gives up the semantic colour. When all three numbers on a
   card are green, green has stopped saying anything, and the one number the
   card exists to communicate has no way left to stand out. */
.metrics:has(.metric.primary) .metric:not(.primary) .v.pos,
.metrics:has(.metric.primary) .metric:not(.primary) .v.neg {
  color: var(--text-body);
}

.projbar { margin-top: 2px; }
.projbar .labels { display: flex; justify-content: space-between; font-size: var(--fs-sm); color: var(--text-dim); margin-bottom: 6px; }
.track { position: relative; height: 10px; border-radius: 0; background: var(--panel-3); }
.range { position: absolute; height: 100%; border-radius: 0; background: rgba(77, 140, 255, .4); }
.proj-dot, .line-dot { position: absolute; top: 50%; width: 12px; height: 12px; border-radius: 50%; transform: translate(-50%, -50%); }
/* §6.4: bone for the projection, amber for the line. */
.proj-dot { background: var(--text); box-shadow: none; }
.line-dot { background: var(--warn); box-shadow: none; }
.legend { display: flex; gap: 16px; font-size: var(--fs-xs); color: var(--text-dim); margin-top: 8px; }
.legend span::before { content: "●"; margin-right: 4px; }
/* The swatch must be painted from the SAME token as the dot it names, or
   the legend is not a legend. `.proj` read var(--brand) while the dot it
   labels reads var(--text) — and because --brand and --warn are the same
   amber, the legend showed two identical amber bullets for a track
   carrying one amber dot and one bone one. A reader matching them up got
   the projection and the line backwards, on the one control whose entire
   job is saying which is which. Pinned by test_design_tells. */
.legend .proj::before { color: var(--text); } .legend .line::before { color: var(--warn); }

.conf-wrap { display: flex; align-items: center; gap: 10px; }
.conf-meter { flex: 1; height: 8px; background: var(--panel-3); border-radius: 0; overflow: hidden; }
/* scaleX from a full-width base rather than animating width from 0: width
   is a layout property and this meter renders once per card, so a board of
   twenty picks was running twenty simultaneous layout animations.

   .8s was also nearly three times the 300ms the manual puts on UI motion —
   long enough that the number beside the bar has settled while the bar is
   still travelling, which reads as lag rather than as polish. 240ms keeps
   the fill legible as a movement without outlasting the reading of it.

   The bar carries no radius and no content, so scaleX has nothing to
   distort — the usual objection to scaling a box does not apply here. */
.conf-fill { height: 100%; border-radius: 0; width: 100%;
  transform: scaleX(0); transform-origin: left center;
  transition: transform var(--dur-slow) var(--ease-out); }
.conf-num { font-weight: 800; font-size: var(--fs-lg); min-width: 54px; text-align: right; }

.chips { display: flex; gap: 8px; flex-wrap: wrap; }
/* inline-block, not inline: a bordered INLINE box that wraps is drawn as
   two broken half-pills, one per line — which is exactly what the usage
   trend chips did once the column got narrow on a phone. */
.chip { display: inline-block; font-size: var(--fs-xs); padding: 4px 10px; border-radius: 0; border: var(--hairline) solid var(--border); color: var(--text-dim); font-weight: 600; }
.chip.up { color: var(--good); border-color: rgba(47, 213, 140, .4); }
.chip.down { color: var(--bad); border-color: rgba(251, 90, 106, .4); }
/* A stake is a number the model produced, not a condition at the venue.
   It also still carried a blue border from the palette before last. */
.chip.stake { color: var(--text); border-color: var(--border); }
.chip.books { color: var(--cyan); border-color: rgba(55, 205, 234, .35); }
.chip.when { color: var(--text-dim); border-color: rgba(255, 255, 255, .14); }
/* College football. A conditional is amber, never green — the edge is real
   but nothing has been staked, and the two must never look alike. The
   attention chip is the one number that explains why a MAC total is a bet
   and the same edge in a marquee game isn't. */
.chip.cond { color: var(--warn); border-color: rgba(240, 180, 41, .45); }
.cfb-rank { color: var(--warn); font-weight: 800; font-size: .9em; }
.chip.tier-low { color: var(--good); border-color: rgba(47, 213, 140, .35); }
.chip.tier-standard { color: var(--text-dim); }
.chip.tier-marquee { color: var(--text-mute); }

.reasons { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.reasons li { font-size: var(--fs-sm); color: var(--text-body); padding-left: 22px; position: relative; }
/* These two are the most-rendered status marks on the site — one per reason
   bullet, on every card, on every page — and they were text characters, so
   they changed shape on every platform and sat on the text baseline instead
   of on the line they label.

   They are the SAME strokes as icon() in web/js/app.js, carried as a mask
   rather than a background so `background-color` still drives the colour and
   the green/red split survives the theme swap. A mask ignores the SVG's own
   paint, which is why the stroke colour inside the data URI is arbitrary.

   Note for the next audit: a Playwright census that walks TEXT NODES cannot
   see generated content, so these two survived a 176-page sweep that
   reported zero. Grep `content:` in the stylesheet as well. */
.reasons li::before {
  content: ""; position: absolute; left: 0; top: .25em;
  width: 12px; height: 12px; background-color: var(--good);
  -webkit-mask: var(--ic-check) no-repeat center / contain;
  mask: var(--ic-check) no-repeat center / contain;
}
/* Factors working AGAINST the bet: red cross, never a green check. */
.reasons li.neg::before {
  background-color: var(--bad);
  -webkit-mask-image: var(--ic-cross); mask-image: var(--ic-cross);
}
.reasons li.neg { color: var(--text-dim); }
/* §6.8: a 2px left rule, no box, no fill. A tinted rounded panel around
   every caution is the "card containers wrapping every element" tell, and
   at four to a page it stops being read at all. */
.warning { background: none; border: 0; border-left: 2px solid var(--bad);
  color: var(--text-body); font-size: var(--fs-sm); padding: 7px 0 7px 13px;
  border-radius: 0; }

/* ---------------- Sparkline ---------------- */
.spark { display: block; width: 100%; height: auto; }
/* §3.4: 1.1s, and an entrance animation. The line is just drawn. */
.spark-line { stroke-dashoffset: 0; }

/* ---------------- Trending page ---------------- */
.trend-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr)); gap: 18px; }
/* min-width: 0 — grid items default to min-width:auto, which lets a wide
   row inside push the column past its track and the whole page into
   horizontal scroll on phones (the classic grid blowout). */
.trend-col { background: var(--panel-2); border: var(--hairline) solid var(--border-soft); border-radius: 0; padding: 16px; min-width: 0; }
/* 14px is under the serif's floor (see the display-face note near the top),
   and a measurement across every view found this was the only heading that
   broke it. It is a column LABEL, not a headline, so it takes the sans. */
.trend-col h3 { font-family: var(--font-sans); font-weight: 700; font-size: var(--fs-lg); display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.trend-col .colsub { color: var(--text-mute); font-size: var(--fs-sm); margin-bottom: 12px; }
.trow { display: flex; align-items: center; gap: 12px; padding: 10px 8px; border-radius: 0; cursor: pointer; transition: background var(--dur-fast) var(--ease-out); }
.trow:hover { background: var(--fill-hover); }
.trow + .trow { border-top: var(--hairline) solid var(--border-soft); }
.trank { width: 22px; text-align: center; font-weight: 800; color: var(--text-mute); font-size: var(--fs-md); }
.trow .who { flex: 1; min-width: 0; }
.trow .who .nm { font-weight: 700; font-size: var(--fs-lg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.trow .who .mk { color: var(--text-mute); font-size: var(--fs-xs); }
.trow .mini { width: 78px; flex-shrink: 0; }
.trow .val { font-weight: 800; font-size: var(--fs-lg); min-width: 62px; text-align: right; }
.val.pos { color: var(--good); } .val.neg { color: var(--bad); } .val.cyan { color: var(--cyan); }

/* ---------------- Players page ---------------- */
.search-wrap { position: relative; margin-bottom: 22px; max-width: 520px; }
.search-wrap input {
  width: 100%; padding: 14px 16px 14px 44px; font-size: var(--fs-lg); font-family: inherit;
  background: var(--panel); border: var(--hairline) solid var(--border); border-radius: 0; color: var(--text);
  outline: none; transition: border-color var(--dur-base) var(--ease-out);
}
.search-wrap input:focus { border-color: var(--brand-2); box-shadow: none; }
.search-wrap .ico { position: absolute; left: 15px; top: 50%; transform: translateY(-50%); color: var(--text-mute); font-size: var(--fs-xl); }

/* The cap note on an unsearched Players page. Spans the grid rather than
   sitting in a column — it is about the whole list, not the first card. */
.browse-note {
  grid-column: 1 / -1; margin: 0 0 4px;
  color: var(--text-mute); font-size: var(--fs-sm);
}

.player-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(420px, 100%), 1fr)); gap: 18px; }
.profile {
  background: var(--panel-2);
  border: var(--hairline) solid var(--border-soft); border-radius: 0; box-shadow: none;
  overflow: hidden;
}
.profile-head { display: flex; gap: 14px; align-items: center; padding: 18px; position: relative; overflow: hidden; }
.profile-head::after { content: ""; position: absolute; inset: 0; background: var(--profile-grad); opacity: 0.12; pointer-events: none; z-index: 0; }
.profile-head > * { position: relative; z-index: 1; }
.profile-head .meta { flex: 1; }
.profile-head .nm { font-size: var(--fs-2xl); font-weight: 800; }
.profile-head .sub { color: var(--text-dim); font-size: var(--fs-md); margin-top: 2px; }
.profile-head .grade { align-self: flex-start; }

/* minmax(0, 1fr), not 1fr: a bare 1fr is minmax(AUTO, 1fr), so a track
   whose label is wider than its fifth of the row grows instead of sharing,
   and five uneven tracks pushed the last tile out of the card at 320px.
   With the floor at 0 the five columns are genuinely equal and the label
   wraps inside its tile. */
.form-tiles { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 8px; padding: 0 18px 14px; }
.form-tile .k, .form-tile .v { overflow-wrap: break-word; }
.form-tile { background: var(--fill-subtle); border: var(--hairline) solid var(--border-soft); border-radius: 0; padding: 8px 6px; text-align: center; }
.form-tile .k { font-size: var(--fs-2xs); color: var(--text-mute); text-transform: uppercase; letter-spacing: .4px; }
.form-tile .v { font-size: var(--fs-xl); font-weight: 800; margin-top: 2px; }

.profile-spark { padding: 0 14px 6px; }
.profile-pick { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 12px 18px; border-top: var(--hairline) solid var(--border-soft); background: var(--fill-subtle); }
.profile-pick .lbl { font-size: var(--fs-md); font-weight: 700; }
.profile-pick .lbl small { color: var(--text-mute); font-weight: 500; display: block; }

.log-table { width: 100%; border-collapse: collapse; margin: 4px 0 6px; }
.log-table th, .log-table td { text-align: left; padding: 6px 18px; font-size: var(--fs-sm); }
.log-table th { color: var(--text-mute); font-weight: 600; text-transform: uppercase; font-size: var(--fs-2xs); letter-spacing: .4px; }
.log-table td { border-top: var(--hairline) solid var(--border-soft); }
.log-table td.num { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
.log-table .hit { color: var(--good); } .log-table .miss { color: var(--text-dim); }


/* ---------------- Parlay Zone ----------------
   Night Form: no shadows, no gradients, square corners, rules instead of
   fills. The page's hierarchy is deliberate and inverted from every other
   board on the site — the VERDICT is the hero, because on most nights the
   verdict is the whole page, and a "no" that whispers reads as an error. */
.pz-doctrine {
  font-size: var(--fs-md); color: var(--text-dim); line-height: var(--lh-body);
  max-width: var(--measure-tight); padding: 0 0 14px; margin: 2px 0 0;
  border-bottom: var(--hairline) solid var(--border-soft);
}
.pz-verdict {
  display: flex; align-items: baseline; gap: 10px;
  font-size: var(--fs-2xl); font-weight: 700; line-height: var(--lh-tight);
  padding: 18px 0 16px; margin: 0 0 4px;
  border-bottom: 3px solid var(--text);
}
.pz-verdict.pz-no { color: var(--text); }
.pz-verdict.pz-yes { color: var(--good); border-bottom-color: var(--good); }
.pz-verdict .pz-mark { flex: 0 0 auto; transform: translateY(1px); }
.pz-probation, .pz-note {
  display: flex; gap: 9px; align-items: flex-start;
  font-size: var(--fs-sm); color: var(--text-dim); line-height: var(--lh-body);
  max-width: var(--measure-wide); padding: 12px 0; border-bottom: var(--hairline) solid var(--border-soft);
}
.pz-probation .pz-mark, .pz-risk .pz-mark { flex: 0 0 auto; margin-top: 2px; }
/* The structural note answers the verdict directly above it, so it reads
   at full text weight rather than as the dimmed footnotes at the bottom. */
.pz-note.pz-structural { color: var(--text); }
.pz-note.pz-structural .pz-mark { flex: 0 0 auto; margin-top: 2px; color: var(--brand-2); }
.pz-census {
  font-size: var(--fs-xs); color: var(--text-mute); padding: 14px 0 4px;
  letter-spacing: .2px;
}

.pz-ticket { padding: 18px; margin: 16px 0; --grade-color: var(--text-dim); }
.pz-ticket.pz-miss { --grade-color: var(--text-faint); opacity: .92; }
.pz-ticket .card-head { margin-bottom: 12px; }

/* The legs read as an agate block: a fixed grid so the numbers line up in
   columns down the ticket, which is how a bettor scans a slip. */
.pz-legs { list-style: none; margin: 0 0 6px; padding: 0; }
.pz-legs li {
  display: grid; gap: 3px 16px; align-items: baseline;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) auto;
  padding: 10px 0; border-top: var(--hairline) solid var(--border-soft);
  font-size: var(--fs-sm);
}
.pz-legs .pz-leg-name { font-weight: 700; }
.pz-legs .pz-leg-mkt { color: var(--text-dim); }
.pz-legs .pz-leg-nums {
  text-align: right; font-variant-numeric: tabular-nums; color: var(--text-dim);
  white-space: nowrap;
}
.pz-legs .pz-leg-nums i { color: var(--text-faint); font-style: normal; padding: 0 2px; }

/* Correlation reads as a two-line block per pair, not a table. The
   mechanism sentence is the point of the section and a table column
   squeezed it into a four-word-wide gutter on the phone. */
.pz-pairs, .pz-ledger { list-style: none; margin: 0; padding: 0; }
.pz-pairs li, .pz-ledger li {
  padding: 10px 0; border-top: var(--hairline) solid var(--border-soft);
}
.pz-pairs li:first-child, .pz-ledger li:first-child { border-top: 0; padding-top: 4px; }
.pz-pair-head {
  display: flex; flex-wrap: wrap; gap: 6px 16px;
  justify-content: space-between; align-items: baseline;
  font-size: var(--fs-sm);
}
.pz-pair-who { font-weight: 700; }
.pz-pair-rho { font-variant-numeric: tabular-nums; white-space: nowrap; }
.pz-pair-rho i { color: var(--text-faint); font-style: normal; font-weight: 400; }
.pz-pair-why, .pz-kill-why {
  font-size: var(--fs-xs); color: var(--text-dim); line-height: var(--lh-body);
  max-width: var(--measure-wide); margin-top: 3px;
}
.pz-kill-who { font-size: var(--fs-sm); font-weight: 700; }

.pz-sub {
  font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: .6px;
  font-weight: 600; color: var(--text-mute);
  margin: 18px 0 6px; padding-bottom: 4px;
  border-bottom: var(--hairline) solid var(--border-soft);
}
.pz-fine {
  font-size: var(--fs-xs); color: var(--text-dim); line-height: var(--lh-body);
  max-width: var(--measure-wide); margin: 8px 0 2px;
}
.pz-clash { font-size: var(--fs-sm); color: var(--text-dim); padding: 2px 0 4px; }
.pz-clash .ic { color: var(--good); vertical-align: -1px; margin-right: 5px; }

.pz-joint, .pz-price { margin-top: 8px; }
.pz-short { font-size: var(--fs-2xs); color: var(--text-mute); margin-top: 3px;
  letter-spacing: .3px; }
.pz-rank { font-family: var(--font-mono); color: var(--text-mute);
  font-weight: 400; margin-right: 9px; }
.pz-rank-title { margin-top: 24px; }
.chip.pz-g-play { color: var(--good); border-color: rgba(51,199,122,.42); }
.chip.pz-g-marginal { color: var(--brand-2); border-color: rgba(255,176,0,.42); }
.chip.pz-g-short { color: var(--text-mute); }
.pz-ticket:has(.pz-g-play) { --grade-color: var(--good); }
.pz-ticket:has(.pz-g-marginal) { --grade-color: var(--brand-2); }
.pz-runners-title { margin-top: 22px; }
.pz-runners { margin: 0; padding: 0; }
/* Each ranked ticket is its whole card, folded behind its summary row. The
   summary keeps the row's exact layout so opening one does not move the
   numbers a reader was comparing. */
.pz-runner > summary {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 18px;
  padding: 11px 0; border-top: var(--hairline) solid var(--border-soft);
  font-size: var(--fs-sm); cursor: pointer; list-style: none;
}
.pz-runner:first-child > summary { border-top: 0; }
.pz-runner > summary::-webkit-details-marker { display: none; }
/* The affordance, drawn rather than relying on the native triangle, which
   is inconsistent across engines and cannot be coloured. */
.pz-runner > summary::after {
  content: ""; flex: 0 0 auto; width: 7px; height: 7px; margin-left: auto;
  border-right: 1.6px solid var(--text-mute);
  border-bottom: 1.6px solid var(--text-mute);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--dur-base) var(--ease-out);
}
.pz-runner[open] > summary::after { transform: rotate(-135deg) translate(-3px, -3px); }
.pz-runner > summary:hover { color: var(--text); }
/* Square, like every other focus ring here — the design system has no
   rounded containers and a focus state is not an exception to it. */
.pz-runner > summary:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .pz-runner > summary::after { transition: none; }
}
/* A folded card carries the ticket's own margins, so trim the doubling. */
.pz-runner > .pz-ticket { margin-top: 4px; }
.pz-run-legs { flex: 1 1 22ch; min-width: 0; font-weight: 700; }
.pz-run-legs i { color: var(--text-dim); font-style: normal; font-weight: 400; }
.pz-run-num { color: var(--text-dim); font-variant-numeric: tabular-nums;
  white-space: nowrap; }
.pz-run-num.neg { color: var(--bad); }
/* A ticket that cleared every gate and lost only to the one-per-slate cap.
   It sits below the play because it is not tonight's play, but it is not a
   near-miss either — the rail marks it as a ticket that passed, and its
   reason gets a full line rather than a column. */
.pz-runner.pz-demoted > summary {
  border-left: 2px solid var(--good); padding-left: 12px;
}
.pz-run-why {
  color: var(--text-mute); font-size: var(--fs-xs);
  line-height: var(--lh-body); max-width: var(--measure-wide); padding: 8px 0 0;
}
@media (max-width: 640px) { .pz-runner > summary { gap: 3px 14px; } }
.pz-edge { font-size: var(--fs-sm); line-height: var(--lh-body); max-width: var(--measure-wide);
  margin: 10px 0 0; color: var(--text-dim); }
.pz-edge.pos b { color: var(--text); }
.pz-edge.neg b { color: var(--bad); }
.pz-dominance, .pz-verdict-line, .pz-risk, .pz-stake {
  font-size: var(--fs-sm); line-height: var(--lh-body); max-width: var(--measure-wide); margin: 8px 0 0;
}
.pz-verdict-line {
  font-size: var(--fs-md); font-weight: 600; color: var(--text);
  border-left: 3px solid var(--text-dim); padding: 8px 0 8px 12px;
  margin: 16px 0 0;
}
.pz-risk { display: flex; gap: 9px; color: var(--text-dim); }
.pz-stake { color: var(--text-mute); font-size: var(--fs-xs); }
.pz-ledger-title { margin-top: 26px; }

/* Phone: the leg grid cannot hold five columns at 390px, so it folds to a
   name/market row with the numbers beneath it rather than shrinking the
   type past reading size. */
@media (max-width: 640px) {
  .pz-verdict { font-size: var(--fs-xl); }
  .pz-legs li { grid-template-columns: 1fr; }
  .pz-legs .pz-leg-nums { text-align: left; white-space: normal; }
  .pz-pair-head { flex-direction: column; gap: 2px; }
}

.empty { color: var(--text-dim); text-align: center; padding: 50px; grid-column: 1 / -1; }

/* ---------------- Footer ---------------- */
.footer { padding: 22px 26px 44px; color: var(--text-mute); font-size: var(--fs-sm); border-top: var(--hairline) solid var(--border-soft); max-width: 1360px; margin: 0 auto; }
.footer b { color: var(--text-dim); }

@media (prefers-reduced-motion: reduce) {
  .card, .profile, .view.active { animation: none; }
  .spark-line { animation: none; stroke-dashoffset: 0; }
  .wind .stream { animation: none; }
  .conf-fill { transition: none; }
}
@media (max-width: 640px) {
  /* minmax(0, 1fr), not 1fr: a bare 1fr track still refuses to shrink
     below its content's min-content width, so one wide row inside pushed
     the column past a 320px phone and scrolled the whole page sideways. */
  .player-grid, .trend-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================
   Light theme — the surrounding chrome flips to light; the
   stadium/wind "media" SVGs stay dark on purpose (like broadcast
   graphics), which reads as intentional on a light card.
   ============================================================ */
:root[data-theme="light"] {
  --bg: #F2EFE6;
  --bg-2: #EAE6DA;
  --panel: #EFEBE0;
  --panel-2: #EAE6DA;
  --panel-3: #E1DCCC;
  --border: #B9B1A0;
  --border-soft: #D2CBB9;
  /* Paper never had the dark theme's fault: measured, --text-mute here is
     already Lc 52-64 and --text-dim Lc 76-87, both above their targets.
     So the ladder is NOT copied across — lowering --text-dim to the 60
     the dark side needed would make this side worse. Only the fourth
     tier is new, and it is solved the same way, against the darkest
     ground because on paper that is where contrast is lowest. */
  --cyan: #5C564A;
  --text: #14120E;
  --text-dim: #3E3A31;
  --text-mute: #7A7263;
  --text-faint: #A7A194;
  /* On paper the inversion runs the other way: ink on the accent. */
  --brand: #B87400;
  --brand-ink: #F2EFE6;
  --brand-2: #E0C58A;
  --good: #12734B;
  --warn: var(--brand);
  --bad: #C8102E;
  --plum: #7A7263;                 /* darker: it sits on paper, not ink */
  --grad-brand: #B87400;
  --grad-violet: #E0C58A;
  --fill-subtle: rgba(20,18,14,.035);
  --fill-hover: rgba(20,18,14,.07);
  --text-body: #3E3A31;
  --topbar-bg: rgba(242,239,230,.93);
  --skeleton: linear-gradient(100deg, #f4f4f6 30%, #ebebee 50%, #f4f4f6 70%);
  --shadow: none;
  --shadow-lg: none;
  --ring: 0 0 0 2px rgba(58, 111, 216, 0.30);
}
:root[data-theme="light"] body {
  background: var(--bg);
}
:root[data-theme="light"] .nav-btn.active { color: var(--brand-ink); }
:root[data-theme="light"] .grade.pass { color: var(--text-dim); }

/* theme toggle button */
.theme-toggle {
  width: 40px; height: 40px; border-radius: 0; cursor: pointer;
  background: var(--panel); border: var(--hairline) solid var(--border); color: var(--text-dim);
  display: grid; place-items: center; font-size: var(--fs-xl); font-family: inherit;
  transition: color var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.theme-toggle:hover { color: var(--text); border-color: var(--brand-2); transform: translateY(-1px); }

/* ---------------- Loading skeletons ---------------- */
.skeleton-card {
  height: 300px; border-radius: 0; border: var(--hairline) solid var(--border-soft);
  background: var(--skeleton); background-size: 220% 100%;
  animation: shimmer 1.3s ease-in-out infinite;
}
@keyframes shimmer { from { background-position: 140% 0; } to { background-position: -40% 0; } }
@media (prefers-reduced-motion: reduce) { .skeleton-card { animation: none; } }

/* ============================================================
   Motion — scroll reveal, tilt, projection-bar & sparkline pings.
   All gated behind prefers-reduced-motion at the bottom.
   ============================================================ */

/* §3.4 names scroll reveals and entrance animations as FORBIDDEN, and this
   was the biggest source of both: every card faded up 16px on a stagger, so
   a page of forty cards spent most of a second assembling itself.

   The class and its IntersectionObserver stay — removing them would mean
   touching the renderer, and a card that never gets `.in` must still be
   visible — but they now resolve to no motion at all. A betting board is
   read in a hurry; content that arrives late is content you scrolled past.

   Kept: the live-dot pulse, the wind stream, hover feedback. All micro, all
   permitted. */
.reveal, .reveal.in { opacity: 1; transform: none; transition: none; }

/* pointer tilt on stadium cards */
.tilt { transition: transform var(--dur-slow) var(--ease-out); will-change: transform; }
/* `none`, said plainly. This rule exists to STOP the transform easing
   while the pointer is driving the tilt — without it every mouse move
   queues a 300ms animation and the card lags the cursor. It used to say
   that by transitioning box-shadow instead, which reads as "animate the
   shadow" and animates nothing, since no rule in this sheet sets a
   shadow value at all. */
.tilt.tilting { transition: none; }

/* projection bar draws in on each render */
/* §3.4: entrance animations are forbidden and .7s is over the ceiling. A
   projection bar that grows from zero is telling you a story about itself
   rather than showing you a number. */
.range { transform-origin: left center; }
/* Same: the markers are simply where they are. */
.proj-dot, .line-dot { animation: none; }
@keyframes dotPop {
  from { opacity: 0; transform: translate(-50%, -50%) scale(0); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

/* radar "ping" behind the latest sparkline point */
/* 27 of these on the Recommended page, each pinging forever at 1.9s. §3.4
   permits exactly one perpetual animation — the live dot, at 1.6s, because
   a game in progress is a real state that changes. The last point of a
   finished sparkline is not; it just sat there flashing. */
.spark-pulse { transform-box: fill-box; transform-origin: center; animation: none; }
@keyframes sparkPing {
  0%   { transform: scale(.5); opacity: .55; }
  100% { transform: scale(2.6); opacity: 0; }
}

/* animated brand shimmer on the logo */
.logo { position: relative; overflow: hidden; }
.logo::after {
  content: ""; position: absolute; inset: 0;
  background: none;
  transform: translateX(-120%); animation: sheen 5.5s ease-in-out infinite;
}
@keyframes sheen { 0%, 70% { transform: translateX(-120%); } 85%, 100% { transform: translateX(120%); } }

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; }
  .reveal.in { transition: none; }
  .tilt { transition: none; }
  .range { animation: none; }
  .proj-dot, .line-dot { animation: none; }
  .spark-pulse { animation: none; opacity: 0; }
  .logo::after { animation: none; }
}

/* ============================================================
   Headshots, team marks, directional transitions, refresh spin
   ============================================================ */

/* real photo layered over the SVG helmet; photo removes itself on error */
.avatar-stack { position: relative; display: inline-block; flex-shrink: 0; }
.avatar-stack .avatar { display: block; }
.avatar-photo {
  position: absolute; inset: 0; width: 100%; height: 100%;
  border-radius: 50%; object-fit: cover; object-position: top;
  box-shadow: none;
}
/* Same rule as the team marks, same reason: a headshot is a cut-out on
   transparent ground, so the drawn helmet was showing through every real
   face. It leaves on load — and the stack keeps a plain disc, because a
   cut-out with nothing behind it floats on the page. */
.avatar-stack.art-on > .avatar { display: none; }
.avatar-stack.art-on { background: var(--panel-3); border-radius: 50%; }

/* procedural team monogram */
.team-mark { display: inline-block; vertical-align: -4px; }
.matchup .mt { display: inline-flex; align-items: center; gap: 6px; }
.profile-head .sub { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }

/* direction-aware tab transitions */
.view.active.from-right, .view.active.from-left { animation: none; }

/* Loading feedback moved to the brand when the Refresh button retired:
   a tap on the logo visibly pulses while the new numbers land. */
#brand-home.loading .logo { animation: pulseLogo .9s ease-in-out infinite; }
@keyframes pulseLogo {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .55; transform: scale(.94); }
}
@keyframes spinGlyph { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .view.active.from-right, .view.active.from-left { animation: none; }
  #brand-home.loading .logo { animation: none; opacity: .6; }
}

/* [old sport switcher — removed 2026-08-11: superseded by the NEW LOOK layer at the end of this file] */

/* ============================================================
   Live games — badges, pulsing dot, scores, in-play ribbon
   ============================================================ */
.stadium-wrap { position: relative; }

/* Live is a CONDITION, which in this palette is amber — not the red of a
   loss. The old badge was a red gradient lozenge, which said "bad" on a
   game that is merely in progress. Outline, not fill: nothing else on the
   page is a solid slab of colour. */
.status-badge {
  position: absolute; top: 9px; left: 9px; z-index: 2;
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-sans);
  font-size: var(--fs-2xs); font-weight: 700; letter-spacing: .18em;
  text-transform: uppercase;
  padding: 3px 8px; border-radius: 0; color: var(--text-dim);
  background: var(--bg); border: var(--hairline) solid var(--border);
}
.status-badge.live { background: var(--bg); border-color: var(--warn);
  color: var(--warn); box-shadow: none; }
.status-badge.final { background: var(--bg); color: var(--text-mute); }
.status-badge .per { font-weight: 700; opacity: .92; }

.live-dot {
  width: 6px; height: 6px; border-radius: 0; background: var(--warn);
  box-shadow: none;
  animation: livePulse 1.4s ease-out infinite; flex-shrink: 0;
}
.status-badge.live .live-dot { background: var(--warn); }
.live-dot.sm { width: 6px; height: 6px; }
@keyframes livePulse {
  0%   { box-shadow: none; }
  70%  { box-shadow: none; }
  100% { box-shadow: none; }
}

.game-card.is-live { border-color: rgba(251, 44, 70, .55); }
.game-card.is-live:hover { box-shadow: none; }

.matchup .score { margin-left: 6px; font-size: var(--fs-xl); font-weight: 900; color: var(--text); }
.matchup .mt { flex-wrap: wrap; }

.live-detail {
  display: flex; align-items: center; gap: 7px; margin-top: 6px;
  font-size: var(--fs-sm); color: #ff8a97;
}
.live-detail .live-dot { background: #fb2c46; }

/* pick card in-play ribbon */
.live-ribbon {
  display: inline-flex; align-items: center; gap: 7px; align-self: flex-start;
  font-size: var(--fs-xs); font-weight: 800; letter-spacing: .4px; color: #fff;
  padding: 4px 10px; border-radius: 0;
  background: var(--bg); border-color: var(--warn); color: var(--warn);
  box-shadow: none;
}
.live-ribbon .live-dot { background: #fff; }

@media (prefers-reduced-motion: reduce) { .live-dot { animation: none; } }

/* The live count sits inline in the footer, breathing room around it.
   The bases moved onto the park art; what is left here is the count and
   the outs, so this is a text row now rather than a 46px drawing. */
.wind-wrap.live-footer { justify-content: flex-start; gap: 12px; flex-wrap: wrap; }
.live-detail.count { display: inline-flex; align-items: center; gap: 10px; margin-left: auto; }
.cnt {
  font-size: var(--fs-sm); font-weight: 800; color: var(--text);
  font-variant-numeric: tabular-nums; letter-spacing: .5px; white-space: nowrap;
}
/* The dash is punctuation between two figures, not a third figure. */
.cnt i { font-style: normal; color: var(--text-mute); margin: 0 1px; }
.cnt-outs { display: inline-flex; align-items: center; gap: 6px; }
.outdots { display: block; flex-shrink: 0; }
.count-label { font-size: var(--fs-xs); color: var(--text-dim); font-weight: 600; white-space: nowrap; }

/* Live auto-refresh indicator in the header.
   The healthy state used to be RED — a red pill, red border, red text
   reading "Updated 1m ago" — while the DEGRADED state below was amber.
   That is exactly backwards, and it cost a real morning: a board that was
   working perfectly was read as broken because the one chip describing its
   health was the loudest alarm on the page. Fresh is quiet; the pulsing dot
   alone carries "this is live". */
.live-refresh {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-sm); font-weight: 700; color: var(--text-dim);
  padding: 5px 10px; border-radius: 0;
  background: var(--fill-subtle); border: var(--hairline) solid var(--border-soft);
}
.live-refresh .live-dot { background: var(--good); box-shadow: none; }
/* Amber, not red: the board isn't wrong, it's old. Red here would compete
   with the live-game dot, which is the one colour that has to keep meaning
   "a game is running right now". */
.live-refresh.stale {
  color: var(--warn); background: rgba(217, 165, 72, 0.12);
  border-color: rgba(217, 165, 72, 0.4);
}

.data-source {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-sm); font-weight: 700; padding: 5px 10px; border-radius: 0;
  border: var(--hairline) solid transparent; cursor: default;
}
.data-source .src-dot { width: 7px; height: 7px; border-radius: 50%; }
.data-source.live { color: var(--good); background: rgba(48, 209, 88, 0.12); border-color: rgba(48, 209, 88, 0.32); }
.data-source.live .src-dot { background: var(--good); box-shadow: none; }
.data-source.sample { color: var(--warn); background: rgba(255, 179, 64, 0.12); border-color: rgba(255, 179, 64, 0.30); }
.data-source.sample .src-dot { background: var(--warn); }

/* ---------------- Bankroll control ---------------- */
.control.bankroll { min-width: 230px; }
.bankroll-inputs {
  display: flex; align-items: center; gap: 6px;
  background: var(--panel-3); border: var(--hairline) solid var(--border); border-radius: 0;
  padding: 6px 10px;
}
.bankroll-inputs .pfx, .bankroll-inputs .sfx { color: var(--text-mute); font-size: var(--fs-sm); font-weight: 600; }
.bankroll-inputs input {
  background: transparent; border: 0; outline: none; color: var(--text);
  font-family: inherit; font-size: var(--fs-lg); font-weight: 700; width: 70px;
  -moz-appearance: textfield;
}
.bankroll-inputs input::-webkit-outer-spin-button,
.bankroll-inputs input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.bankroll-inputs input#unit-pct { width: 40px; text-align: right; color: var(--text-dim); }
.control.bankroll label b { color: var(--good); font-weight: 700; }

.tile-sub { color: var(--text-mute); font-size: var(--fs-sm); font-weight: 600; margin-top: 2px; }

/* Long Shots */
.ls-note { color: var(--text-dim); font-size: var(--fs-md); margin-bottom: 14px; }
.card.longshot .ls-primary {
  margin-top: 10px; padding: 9px 11px; border-radius: 0;
  background: var(--panel-2); border: var(--hairline) solid var(--border);
  font-size: var(--fs-sm); font-weight: 600; color: var(--text);
}
.card.longshot .metrics { grid-template-columns: repeat(4, 1fr); }


/* ============================================================
   Sharp & minimal overrides — flat surfaces, hairline borders,
   tabular numbers, quiet chrome. Appended last so it wins.
   ============================================================ */
body { font-size: var(--fs-lg); }

/* Numbers line up like a terminal. */
.tile .v, .metric .v, .stat-v, .ml-odds, .book, .spark, td, .v {
  font-variant-numeric: tabular-nums;
}

/* …and are SET like one. Deliberately a narrower list than the rule above:
   `td` and `.book` carry player names and "FanDuel", and a proportional
   word in a fixed-pitch face looks like a bug rather than a choice.

   Weight 500, not 800. The mono ships 400 and 500; asking for 800 would
   have the browser fake it, and a smeared outline at 30px is the loudest
   thing on the page. The tracking goes back to 0 too — negative letter
   spacing on a fixed-pitch face fights the one property it has. */
.tile .v, .metric .v, .stat-v, .ml-odds, .conf-num, .trow .val,
.form-tile .v, .rec-chart .rc-net, .lf-region-n,
.brk-score, .rec-scope-n,
/* Found by sweeping every view for text that is mostly digits and still in
   the sans. A half-converted board reads as an accident; the price, the
   score, the P&L and the win-loss record are the same KIND of thing as the
   KPI above them and have to be set like it. */
.book, .score, .rb-net, .rb-wl, .rl-pnl, .rl-odds, .rl-date,
td.num, .dl-num, .std-n, .pm-avatar, .odds,
.rb-clv, .dl-rank, .ros-n {
  font-family: var(--font-mono);
  font-weight: 500;
  letter-spacing: 0;
  font-feature-settings: "tnum" 1, "zero" 1;   /* slashed zero, on purpose */
}
/* A unit is not part of the figure. Set at full display size it competes
   with the number and, in a monospace — which is wider per character than
   the sans it replaced — it pushed "-106.50 pts" onto a second line at
   every width. Smaller, lighter, and the value never breaks mid-number. */
/* NOT white-space: nowrap. That stopped the wrap and started a clip — an
   unbreakable "-106.50 pts" ran 56px out of a 320px phone tile, which is
   worse than two lines. The break opportunity stays; the unit is just made
   small enough that a desktop tile no longer needs it. */
.tile .v .unit {
  font-size: .5em; font-weight: 400; opacity: .7; letter-spacing: .04em;
}
/* The space before it is load-bearing: without one there is no break
   opportunity in the run, so a phone tile clipped "pts" clean off instead
   of dropping it to the next line. The gap is that space, not a margin. */

/* A wallet address is a string you compare character by character — the one
   place a monospace is not a style choice but a functional requirement. */
.pm-wallet, code,
/* Polymarket renders a trader handle as a bare link and the wallet
   under it as .subtitle, so both are addressed by where they sit. */
.wallet { font-family: var(--font-mono); }
/* Both weights get a job. A face nobody matches is a file the browser never
   fetches and a line of CSS that quietly means nothing — the same dead
   declaration the italic serif was deleted for. 400 carries the small
   supporting figures, 500 the ones you read first. */
.form-tile .v, .lf-region-n, .rec-scope-n, .brk-score {
  font-weight: 400;
}
/* The lead KPI carried its own size and tracking; keep the size, drop the
   tracking with it. */
.tile .v { letter-spacing: 0; }

/* Chrome: flat topbar, and the nav is a row of labels — not a box.
 *
 * This block used to paint `.nav` with a panel fill and a full border, and
 * `.nav-indicator` with a filled slab. Both directly contradict the rule
 * they override, whose own comment reads: "No pill backgrounds, no boxes:
 * the bar is a row of labels over a single rule ... a section nav is not a
 * row of buttons." Two bordered boxes stacked inside a 157px topbar is what
 * made the whole header read as one smooshed block — and the league
 * switcher's box stretched to 1144px around 380px of buttons, so most of it
 * was an empty outline. The underline treatment was already there
 * underneath; these overrides were sitting on top of it. */
.btn, .btn:hover { box-shadow: none; }
.btn { background: var(--panel-2); border: var(--hairline) solid var(--border); color: var(--text); }
.btn:hover { border-color: var(--brand-2); filter: none; }
.theme-toggle { box-shadow: none; }

/* Cards: flat, hairline, tight.
   `.tile` is deliberately NOT in this list. It declares its own treatment
   ~950 lines up (§6.6: no background, no border box, just a single vertical
   rule between columns) and this rule has the same specificity, so being
   later it silently won and put the box back. The visible symptom was on
   the Record page: every tile got a full border, `.stats > .tile:first-child`
   stripped only the LEFT side, and the ROI tile ended up in a
   three-sided box whose bottom edge sat 2px under its own sub-line —
   inside `overflow: hidden`, so the text read as cut off. */
.card, .game-card, .stat, .topplay {
  background: var(--panel);
  border: var(--hairline) solid var(--border-soft);
  box-shadow: none;
  border-radius: 0;
}
.card:hover, .game-card:hover { box-shadow: none; border-color: var(--border); }

/* Section titles read as kickers, not headlines. */
/* Density contrast, deliberately. An audit of the rendered pages found the
   gap between every top-level block was 14px on seven of eleven views —
   standard deviation ZERO. Even rhythm reads as machine-set because it is:
   a human laying out a page gives a new SECTION more air than a
   continuation of the one above, and that difference is most of what makes
   a page feel composed rather than emitted.
   So a section heading opens a gap; blocks inside a section keep the tight
   14px. First-child gets none, or every page starts with a hole. */
.section-title {
  font-size: var(--fs-sm); text-transform: uppercase; letter-spacing: .09em;
  color: var(--text-dim); font-weight: 700;
  margin-top: 34px;
}
/* Fifty-four call sites set this inline, at 0, 4, 8, 14, 16, 18, 20, 22, 24
   and 26px — ten values inside an 18px band, which is not a rhythm anyone
   chose and reads exactly like the single value it replaced. They are gone;
   the class governs. `tight` is the one real decision among them: this
   heading opens its container and must not push it down. */
.section-title.tight,
.view > .section-title:first-child,
.section-title:first-child { margin-top: 0; }
.section-title .sub { text-transform: none; letter-spacing: 0; font-weight: 500; }

/* Grades: outline chips, color as a whisper. */
.grade { background: transparent; border: var(--hairline) solid currentColor; box-shadow: none;
         font-weight: 700; letter-spacing: .02em; }
/* Class-specific so these outrank the legacy filled-pill rules — otherwise
   the old solid background wins and the text goes green-on-green invisible. */
.grade.strong { background: transparent; color: var(--good); }
/* Bone, not --brand: --brand is the amber accent now, and a grade is a
   verdict on quality, not a live condition. Eight amber PLAY badges on
   one board is decoration. */
.grade.play { background: transparent; color: var(--text); }
.grade.lean { background: transparent; color: var(--warn); }
.grade.pass { background: transparent; color: var(--text-mute); }
:root[data-theme="light"] .nav-btn.active { color: var(--text); }

/* Chips + sliders: quiet. */
.chip { background: var(--fill-subtle); border: var(--hairline) solid var(--border-soft); box-shadow: none; }
input[type="range"]::-webkit-slider-thumb { box-shadow: none; }

/* Motion: fast + subtle. */
.reveal { transition-duration: .25s; }
.tilting { transition: none; }


/* Floating tooltip for graph dots. */
.graph-tip {
  position: fixed; z-index: 1000; pointer-events: none;
  background: var(--panel-3); color: var(--text);
  border: var(--hairline) solid var(--border); border-radius: 0;
  padding: 5px 9px; font-size: var(--fs-sm); font-weight: 600;
  font-variant-numeric: tabular-nums; white-space: nowrap;
  box-shadow: none;
}

/* ---------------- Polymarket — polished cards ---------------- */
.score-ring {
  width: 48px; height: 48px; border-radius: 50%; flex: 0 0 auto;
  display: grid; place-items: center;
}
.score-ring span {
  width: 38px; height: 38px; border-radius: 50%; background: var(--panel);
  display: grid; place-items: center; font-weight: 800; font-size: var(--fs-md);
}
.pm-avatar {
  width: 44px; height: 44px; border-radius: 50%; flex: 0 0 auto;
  background: var(--panel-3); border: var(--hairline) solid var(--border);
  display: grid; place-items: center; font-weight: 800; font-size: var(--fs-lg);
  color: var(--brand-2); text-transform: uppercase;
}
.pm-status { font-size: var(--fs-xs); font-weight: 800; letter-spacing: .06em;
  border: var(--hairline) solid currentColor; border-radius: 0; padding: 3px 10px; }

/* --- Why Us: open math-layer calculators --- */
.calc-in {
  background: var(--panel-3); border: var(--hairline) solid var(--border); border-radius: 0;
  color: var(--text); font-family: inherit; font-size: var(--fs-lg); font-weight: 700;
  padding: 6px 10px; width: 92px; outline: none;
}
.calc-in:focus { border-color: var(--brand-2); }
select.calc-in { width: auto; font-weight: 600; }
#view-why label { color: var(--text-mute); font-size: var(--fs-sm); font-weight: 600;
  display: inline-flex; align-items: center; gap: 6px; }


/* --- The stadium strip as the hero ------------------------------------
   This is the one thing on the site nobody else has: real park geometry,
   roof state, a live wind gauge and the HR factor the park is playing to.
   It sat below the stat tiles, the top-plays row and a section header —
   four blocks of chrome before the only genuinely distinctive thing on
   the page. Now it leads, and it is sized to be looked at rather than
   scrolled past. */
.hero-title { margin-top: 4px; }
.hero-games { padding-bottom: 6px; }
.hero-games .game-card { min-width: 380px; }
@media (max-width: 900px) { .hero-games .game-card { min-width: 300px; } }


/* --- Typographic hierarchy -------------------------------------------
   Section titles, their subtitles and body copy all sat within a couple
   of points and one weight of each other, so nothing told the eye where
   a section began or which text was the point. Titles step up in size
   and brightness; subtitles step down and lose their bold. */
/* This one wins on order over the block above, so it is where the section
   gap actually lives. Two rules, one property, 300 lines apart is its own
   small lesson — measured 26px while the earlier rule said 34 and the JS
   said ten other things. */
.section-title {
  font-size: var(--fs-md); color: var(--text-dim); letter-spacing: .1em;
  margin-top: 34px; margin-bottom: 14px;
}
.section-title.tight, .section-title:first-child { margin-top: 0; }
/* Three levels of air, which is the whole of the rhythm: 34px says "new
   section", 14px says "still the same section, new group" (the market
   subheads that repeat down a card grid), 0 says "top of the page". The
   ten inline values this replaced said nothing — at 14 vs 16 vs 18px the
   eye cannot tell them apart, so every break read the same. */
.section-title.subhead { margin: 14px 0 0; }
.section-title .sub {
  font-size: var(--fs-sm); color: var(--text-mute); font-weight: 400;
  line-height: var(--lh-body); max-width: var(--measure-wide);
}
/* The hero keeps its own rhythm — it is the first thing on the page. */
.hero-title { font-size: var(--fs-lg); color: var(--text); margin-top: 4px; }
.hero-title .sub { color: var(--text-dim); }


/* --- Progressive disclosure on section explanations -------------------
   The reasoning stays available and is never deleted; it just stops
   being the first thing on every section. See enhanceSectionSubs(). */
.section-title .sub.sub-collapsed { display: none; }
/* §6.7. A grey outlined box next to a heading reads as a DISABLED CONTROL,
   which is the opposite of what these are — they are the site's core
   differentiator, the place it shows its work. So the box goes and they
   become a footnote marker: amber, tracked, with a dagger. That is a
   promotion. Explanation should look like editorial apparatus, not like a
   button somebody switched off. */
.why-toggle {
  background: none; border: 0; padding: 0 0 0 2px;
  color: var(--brand-2); border-radius: 0;
  font-family: var(--font-sans); font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: .1em; text-transform: lowercase; cursor: pointer;
  line-height: var(--lh-snug); opacity: 1;
}
.why-toggle::after { content: "\2020"; font-size: .85em; vertical-align: super;
  margin-left: 1px; }
.why-toggle:hover { color: var(--text); }
.why-toggle:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
/* Expanded copy drops onto its own line so it never squeezes the title. */
.section-title { flex-wrap: wrap; }
.section-title .sub { flex-basis: 100%; }
/* §6.7: expanded, it is set as prose behind a 2px amber rule — the same
   apparatus the probation banner uses, so an explanation looks like an
   explanation wherever it appears. */
.section-title .sub:not(.sub-collapsed) {
  border-left: 2px solid var(--brand); padding: 6px 0 6px 13px;
  margin-top: 8px; text-transform: none; letter-spacing: 0;
  font-weight: 400; color: var(--text-body); max-width: var(--measure-wide);
}


/* --- One number leads on a card --------------------------------------
   Hit prob, edge and EV sat in three identical grey boxes, so a card
   presented three equally-weighted numbers and left the eye to pick.
   Edge is the one the whole site is organised around — the AVG EDGE
   tile, the min-edge slider, the Edge Board — so it gets the emphasis
   and the other two become the context they actually are. The same shape
   repeats on the other card types: MODEL / BOOK IMPLIED / *EDGE* on a game
   bet, POSITION / ENTRY / *NOW* on a wallet flag.

   That much was already true. What this rule USED to do about it was a
   tint and a border — and measurement says the type barely moved:

       supporting numbers   17.0px
       the hero             18.6px      a 9% difference

   `font-size: 1.24em` was written to mean "24% bigger than its siblings",
   but `em` on font-size resolves against the PARENT — `.metric`, at the
   card's 15px — not against the 17px the sibling values are set in. So the
   rule said "1.24x the card's body text" and landed 1.6px above the things
   it was supposed to lead. The box was carrying the whole idea, which is
   the one tool this item rules out: size, weight and colour, not borders.

   Weight cannot be the lever here. These numbers are set in the mono face,
   which ships 400 and 500 only — asking for 800 gets a synthesised smear.
   So the emphasis is size and colour, and the tint and border-colour that
   were standing in for them are gone: all three tiles are now the same
   container, and only the type inside them differs. */
.metric.primary .k { color: var(--text-dim); }
.metric.primary .v {
  /* 22px against the supporting 15px. 30px was tried and screenshotted:
     it leads harder, but the hero fills its tile edge to edge while the two
     supporting tiles become mostly empty box, and the row reads lopsided
     rather than ordered. Whether the number should also outrank the card's
     TITLE is a composition question, and composition is the next item. */
  font-size: var(--fs-2xl);
  letter-spacing: -0.2px;
}
/* Signed heroes keep the semantic colour; an unsigned one takes the
   brightest text so it still leads. Written as :not() rather than as a
   plain `color:` because this rule and `.metric .v.pos` have the SAME
   specificity, and being later in the file would silently win — which
   would paint every edge figure white and delete the one colour on the
   card that means something. */
.metric.primary .v:not(.pos):not(.neg) { color: var(--text); }

/* ======================================================================
   Record page
   The receipts page was built out of bare `.stat` divs and inline styles
   that shared nothing with the rest of the site — no gradient, no accent,
   no type scale — so the page that carries the whole credibility claim
   looked like a spreadsheet someone pasted in. It now uses the same
   `.tile` / `.card` vocabulary as every other page, with two additions:
   a `lead` tile for the numbers that decide whether the process works,
   and real grid rows for the splits (the old flex rows with five fixed
   columns clipped their own net-units figure off the right edge).
   ====================================================================== */
.tile .v.neg { color: var(--bad); }

.rec-kpis .tile.lead {
  border-color: color-mix(in srgb, var(--brand) 30%, var(--border-soft));
  background: none;
}
.rec-kpis .tile.lead::after { opacity: .16; }
.rec-kpis .tile.lead .v { font-size: var(--fs-4xl); }
@supports not (background: color-mix(in srgb, red 10%, transparent)) {
  .rec-kpis .tile.lead { border-color: rgba(90, 140, 255, .35); }
}

/* Progressive disclosure for the page's explanatory prose: right to have,
   wrong to lead with. */
.rec-disclose { margin: 12px 0 4px; }
.rec-disclose > summary {
  cursor: pointer; list-style: none; display: inline-flex; align-items: center; gap: 6px;
  color: var(--text-mute); font-size: var(--fs-sm); font-weight: 600;
  border: var(--hairline) solid var(--border-soft); border-radius: 0; padding: 5px 12px;
}
.rec-disclose > summary::-webkit-details-marker { display: none; }
.rec-disclose > summary::before { content: "?"; opacity: .75; font-weight: 800; }
.rec-disclose > summary:hover { color: var(--text-dim); border-color: var(--border); }
.rec-disclose[open] > summary::before { content: "×"; }
.rec-disclose > div {
  color: var(--text-dim); font-size: var(--fs-md); line-height: var(--lh-body);
  max-width: var(--measure-wide); margin-top: 8px; padding-left: 12px;
  border-left: 2px solid var(--border-soft);
}

/* P&L chart */
.rec-chart { padding: 14px 10px 8px; gap: 0; }
.rec-chart .rc-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 0 8px 8px;
}
.rec-chart .rc-net { font-size: var(--fs-3xl); font-weight: 800; letter-spacing: -.5px; }
.rec-chart .rc-net.pos { color: var(--good); }
.rec-chart .rc-net.neg { color: var(--bad); }
.rec-chart .rc-span { color: var(--text-mute); font-size: var(--fs-sm); }
.rec-chart .rc-foot { color: var(--text-mute); font-size: var(--fs-xs); padding: 4px 8px 4px; }

/* Splits */
/* Explicitly two columns, not auto-fit. Four buckets across a 1440 laptop
   gave each track 336px, of which 278 is fixed number columns — the market
   names ellipsed down to "t." and "s.". Two columns fits all four in a
   clean 2x2 with room for the labels they exist to show. */
.rec-buckets { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
@media (max-width: 700px) { .rec-buckets { grid-template-columns: 1fr; } }
.rec-bucket {
  min-width: 0; background: var(--panel-2);
  border: var(--hairline) solid var(--border-soft); border-radius: 0; overflow: hidden;
}
.rb-head {
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .7px; text-transform: uppercase;
  color: var(--text-mute); padding: 11px 14px 8px;
}
/* The label column is the one that gives; every number column is fixed so
   the columns line up down the table and nothing runs off the edge. */
.rb-row {
  display: grid; align-items: center; gap: 10px;
  grid-template-columns: minmax(0, 1fr) 60px 58px 70px;
  padding: 7px 14px; border-top: var(--hairline) solid var(--border-soft);
}
.rb-rows.has-clv .rb-row { grid-template-columns: minmax(0, 1fr) 60px 58px 50px 70px; }
.rb-labels {
  border-top: none; color: var(--text-mute); font-size: var(--fs-2xs);
  text-transform: uppercase; letter-spacing: .5px; padding-bottom: 4px;
}
.rb-labels .rb-net, .rb-labels .rb-wl, .rb-labels .rb-clv { color: var(--text-mute); font-weight: 600; }
.rb-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-md); }
.rb-bar { display: block; height: 6px; border-radius: 0; background: var(--fill-subtle);
  overflow: hidden; border: var(--hairline) solid var(--border-soft); }
.rb-bar > i { display: block; height: 100%; background: var(--brand); opacity: .8; border-radius: 0; }
.rb-wl { text-align: right; font-size: var(--fs-sm); color: var(--text-dim); font-variant-numeric: tabular-nums; }
.rb-clv { text-align: right; font-size: var(--fs-sm); color: var(--text-mute); font-variant-numeric: tabular-nums; }
.rb-net { text-align: right; font-weight: 700; font-size: var(--fs-md); font-variant-numeric: tabular-nums; }
.rb-net.pos { color: var(--good); } .rb-net.neg { color: var(--bad); }

/* Recent settled picks */
.rec-list { padding: 0; gap: 0; }
.rl-row {
  display: grid; align-items: center; gap: 12px;
  grid-template-columns: 20px 86px minmax(0, 1fr) 90px 56px 72px;
  padding: 9px 16px 9px 14px;
  border-bottom: var(--hairline) solid var(--border-soft);
  border-left: 3px solid transparent;
}
.rl-row:last-child { border-bottom: none; }
.rl-row.won { border-left-color: color-mix(in srgb, var(--good) 65%, transparent); }
.rl-row.lost { border-left-color: color-mix(in srgb, var(--bad) 45%, transparent); }
.rl-row.push { border-left-color: var(--border); }
@supports not (background: color-mix(in srgb, red 10%, transparent)) {
  .rl-row.won { border-left-color: rgba(47, 213, 140, .65); }
  .rl-row.lost { border-left-color: rgba(251, 90, 106, .45); }
}
.rl-icon { font-weight: 800; font-size: var(--fs-md); text-align: center; }
.rl-row.won .rl-icon { color: var(--good); }
.rl-row.lost .rl-icon { color: var(--bad); }
.rl-date { color: var(--text-mute); font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.rl-main { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-md); }
.rl-bet { color: var(--text-mute); }
/* min-width:0 + clip, for the same reason .rl-main has them. This column
   holds free text — a Polymarket trader handle, which can be a 42-character
   wallet address — inside a fixed 90px track. Without them the grid item
   refuses to shrink below its content and the name renders straight across
   the price and the P&L beside it. Measured at up to 225px of overhang. */
.rl-proc {
  text-align: right; font-size: var(--fs-xs); white-space: nowrap;
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
/* The Polymarket table puts a NAME in this column, where every other table
   puts a two-word verdict ("beat the close"). Clipping alone turned
   "0x3DFb…eea1" into "0x3…", which identifies nobody — the last four
   characters are how a wallet is recognised and they are exactly what the
   ellipsis eats. So this one table gets a wider track, taken from the
   1fr column, which has slack the fixed ones do not. */
.pm-rows .rl-row { grid-template-columns: 20px 86px minmax(0, 1fr) 168px 56px 72px; }
.rl-proc.good { color: var(--good); } .rl-proc.bad { color: var(--bad); }
.rl-proc.warn { color: var(--warn); } .rl-proc.none { color: var(--text-mute); opacity: .55; }
.rl-odds { text-align: right; font-size: var(--fs-sm); color: var(--text-dim); font-variant-numeric: tabular-nums; }
.rl-pnl { text-align: right; font-weight: 700; font-size: var(--fs-md); font-variant-numeric: tabular-nums; }
.rl-pnl.pos { color: var(--good); } .rl-pnl.neg { color: var(--bad); }
/* Parlay tickets reuse .rl-row, but a ticket has something a single bet
   does not: legs. .rl-row is a six-column GRID, so a legs element added as
   a sixth child lands in the 72px P&L column and renders each leg as a
   vertical stack of one-word lines. It spans the full row instead, set
   under the ticket line at the same indent as .rl-main. */
.rl-row.has-legs { row-gap: 4px; }
.pl-legs {
  grid-column: 1 / -1; padding-left: 32px;
  display: flex; flex-wrap: wrap; gap: 4px 14px;
  font-size: var(--fs-xs); color: var(--text-mute);
}
.pl-leg { white-space: nowrap; }
.pl-leg.lost { opacity: .6; }
.pl-mark { font-weight: 800; margin-right: 3px; }
.pl-leg.won .pl-mark { color: var(--good); }
.pl-leg.lost .pl-mark { color: var(--bad); }
.pl-verdict { padding: 10px 16px 10px 14px; font-size: var(--fs-sm);
  border-bottom: var(--hairline) solid var(--border-soft); }
.pl-codes { padding: 9px 16px 9px 14px;
  border-top: var(--hairline) solid var(--border-soft); }
.pl-conds { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.pl-cond { font-size: var(--fs-xs); color: var(--text-mute);
  border: var(--hairline) solid var(--border); padding: 3px 8px; }
.pl-cond.met { color: var(--good); border-color: color-mix(in srgb, var(--good) 40%, transparent); }

/* ---------------- Futures ----------------
   A season table, so it is set as one: fixed columns, tabular figures, and
   the rule doing the dividing rather than a box per row. The two probability
   columns a reader actually scans (title, and the edge when we have a price)
   are the only ones that carry weight. */
.fx-prior {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 12px 14px; margin-bottom: 14px;
  border-left: 3px solid var(--warn); background: var(--panel);
  font-size: var(--fs-sm); color: var(--text-dim); line-height: var(--lh-body);
}
.fx-prior b { color: var(--warn); }

/* Capped, because the slack in a grid has to land somewhere and a full-width
   table put it all in the group column — the abbreviation alone at the far
   left, a canyon, then the numbers. A season table wants its figures near
   their names. */
.fx-table { overflow-x: auto; max-width: 860px; }
.fx-row {
  display: grid; align-items: center; gap: 12px;
  grid-template-columns: 54px minmax(0, 1fr) 62px 88px 54px 62px 58px;
  padding: 8px 16px 8px 14px;
  border-bottom: var(--hairline) solid var(--border-soft);
  font-variant-numeric: tabular-nums; font-size: var(--fs-sm);
  min-width: 560px;
}
.fx-table.priced .fx-row {
  grid-template-columns: 54px minmax(0, 1fr) 62px 88px 54px 62px 58px 66px 54px;
  min-width: 680px;
}
.fx-row:last-child { border-bottom: none; }
.fx-head {
  color: var(--text-mute); font-size: var(--fs-xs); text-transform: uppercase;
  letter-spacing: .7px; font-weight: 600; border-bottom: var(--hairline) solid var(--border);
}
.fx-team { font-weight: 700; font-size: var(--fs-md); }
.fx-grp { color: var(--text-mute); font-size: var(--fs-xs);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fx-rec, .fx-proj, .fx-div, .fx-po, .fx-ttl, .fx-odds, .fx-edge { text-align: right; }
.fx-rec { color: var(--text-dim); }
.fx-proj { color: var(--text); }
/* The band is the honest half of a projection — one number for a season is a
   forecast pretending to be a fact — so it sits with the number, quieter. */
.fx-band { color: var(--text-mute); font-size: var(--fs-xs); margin-left: 4px; }
.fx-div, .fx-po { color: var(--text-dim); }
/* The title number is what the page is for; the other two are context. */
.fx-ttl { color: var(--text-dim); }
.fx-row:not(.fx-head) .fx-ttl { color: var(--text); font-weight: 700; }
.fx-odds { color: var(--text-dim); }
.fx-edge { font-weight: 700; }
.fx-edge.pos { color: var(--good); } .fx-edge.neg { color: var(--bad); }
.fx-note { color: var(--text-mute); font-size: var(--fs-sm);
  margin: 10px 0 0; line-height: var(--lh-body); max-width: var(--measure); }

.fx-market { margin-top: 14px; border-top: var(--hairline) solid var(--border-soft); }
.fx-market-head {
  font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .7px;
  font-weight: 700; color: var(--text-dim); padding: 12px 0 6px;
}
.fx-prow {
  display: grid; align-items: center; gap: 12px;
  grid-template-columns: minmax(0, 1fr) 48px 52px 96px 48px 92px;
  /* The one border deliberately left off --hairline: a DOTTED rule needs
     width to still read as dots, and half a pixel of one renders as a
     smudge or vanishes outright. Solid rules have no such floor. */
  padding: 6px 0; border-bottom: 1px dotted var(--border-soft);
  font-variant-numeric: tabular-nums; font-size: var(--fs-sm);
}
.fx-pname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fx-pteam { color: var(--text-mute); font-size: var(--fs-xs); }
.fx-pnow, .fx-pproj, .fx-pav, .fx-pp { text-align: right; }
.fx-pnow { color: var(--text-mute); }
.fx-pav { color: var(--text-mute); font-size: var(--fs-xs); }
.fx-pp { color: var(--text-dim); font-size: var(--fs-xs); }
.fx-pp.pos { color: var(--good); }

/* The narrow-screen rules for this table sit in the 760px block further
   down, beside the other phone rules, rather than opening one more
   breakpoint of the same width for it. */

.rec-stamp { color: var(--text-mute); font-size: var(--fs-sm); margin-top: 14px; }


/* ======================================================================
   Single-game page — reached by clicking a card in the stadium strip.
   ====================================================================== */
.game-card[data-gid] { cursor: pointer; }
.game-card[data-gid]:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.gp-back { margin-bottom: 14px; }
.gp-hero {
  display: grid; grid-template-columns: minmax(220px, 380px) minmax(0, 1fr);
  gap: 22px; align-items: center; margin-bottom: 20px;
  background: var(--panel-2);
  border: var(--hairline) solid var(--border-soft); border-radius: 0;
  padding: 18px; overflow: hidden;
}
/* The stadium art is drawn for a card slot; given a half-page column it
   grows to ~370px and pushes the hero taller than the content beside it.
   Capped, so the header stays a header. */
.gp-art {
  position: relative; min-width: 0; border-radius: 0; overflow: hidden;
  max-height: 260px; display: flex; align-items: center; justify-content: center;
}
.gp-art svg { width: 100%; height: auto; max-height: 260px; display: block; }

/* auto-FILL keeps empty tracks, so a section holding one card leaves it
   parked in the first track well left of centre. auto-FIT collapses them
   and the lone card centres — which matters here, where a game often has
   exactly one prop in a market. */
.gp-cards { grid-template-columns: repeat(auto-fit, minmax(min(346px, 100%), 440px)); }


/* ======================================================================
   Data-heavy pages: Polymarket, Fantasy, NBA, UFC
   ====================================================================== */

/* `.cards` caps each track at 440px, which is right for a prop card you
   read one at a time. auto-fill counts repetitions off that MAX, so at a
   1440 laptop it resolves to exactly two 440px columns and leaves 410px —
   31% of the row — empty on pages that are lists of comparable things.
   Counting off the MIN instead gives three columns that share the width.

   auto-FILL rather than auto-fit on purpose: auto-fit collapses the
   tracks it doesn't use, so a section holding two cards stretched them to
   660px each — a card twice the width of the same card one section down.
   auto-fill keeps the column width fixed and lets a short row end early,
   which is what a grid of comparable things should do.

   The min() is not decoration. A bare minmax(340px, 1fr) is a FLOOR, so in
   a container narrower than 340px the track stays 340px and overflows it.
   `.cards` is overridden to 1fr on phones, but `.cards.wide` outranks that
   rule on specificity, so Polymarket, Fantasy and the UFC board kept a
   340px track inside a 296px column at 320px wide — and `.card`'s
   overflow:hidden then CHOPPED the right 44px off every card rather than
   letting it scroll. min(340px, 100%) lets the floor collapse to the
   container when the container is smaller, which is the whole trick. */
.cards.wide {
  grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr));
  justify-content: stretch;
}

/* Fighter comparison. Two flex columns with min-width:190px inside a
   ~430px card cannot sit side by side, so they wrapped and left the "vs"
   divider stranded at the end of the first line. A grid states the
   intent: two corners with the divider between them, stacking only when
   the card is genuinely too narrow. */
.ufc-vs { display: grid; grid-template-columns: 1fr auto 1fr; gap: 10px; align-items: start; margin-top: 10px; }
.ufc-vs > .vs-sep {
  align-self: center; color: var(--text-mute); font-size: var(--fs-xs); font-weight: 800;
  letter-spacing: .5px; text-transform: uppercase;
}
.ufc-corner { min-width: 0; }
.ufc-corner .fc-name { font-weight: 700; font-size: var(--fs-md); }
.ufc-corner .fc-meta { color: var(--text-mute); font-size: var(--fs-xs); margin-top: 2px; }
.ufc-corner .fc-stats { color: var(--text-body); font-size: var(--fs-xs); margin-top: 3px; line-height: var(--lh-snug); }
.ufc-corner .fc-cover { color: var(--text-mute); font-size: var(--fs-xs); margin-top: 2px; }
@media (max-width: 520px) {
  .ufc-vs { grid-template-columns: 1fr; }
  .ufc-vs > .vs-sep { justify-self: start; }
}

/* Shared list-row grid for the plain tables these pages render: the
   near-miss report, the flag report card, and the top-markets list.
   Same alignment rules as the Record page's rows. */
.dl-row {
  display: grid; align-items: center; gap: 12px;
  padding: 9px 16px; border-bottom: var(--hairline) solid var(--border-soft);
}
.dl-row:last-child { border-bottom: none; }
.dl-rank { color: var(--text-mute); font-size: var(--fs-sm); text-align: right; font-variant-numeric: tabular-nums; }
.dl-main { min-width: 0; }
.dl-main > strong { font-size: var(--fs-md); }
.dl-sub { display: block; color: var(--text-mute); font-size: var(--fs-xs); margin-top: 2px; }
.dl-num { text-align: right; font-variant-numeric: tabular-nums; font-size: var(--fs-sm); color: var(--text-dim); }
.dl-num.strong { font-weight: 700; font-size: var(--fs-md); color: var(--text); }
.dl-num.pos { color: var(--good); } .dl-num.neg { color: var(--bad); }

.pm-band { grid-template-columns: minmax(0, 1fr) 64px 150px 96px; }
.pm-wallet { grid-template-columns: minmax(0, 1fr) 64px 130px 62px; }
@media (max-width: 700px) {
  .pm-band, .pm-wallet { grid-template-columns: minmax(0, 1fr) 56px 90px; }
  .pm-band .dl-num.implied, .pm-wallet .dl-num.implied { display: none; }
}

/* Usage movers is the Fantasy page's lead content and it ran 36 rows
   deep — about 3400px of near-identical lines before anything else was
   reachable. Capped, with the rest one click away. */
.ff-more {
  display: block; width: 100%; text-align: center; cursor: pointer;
  background: var(--fill-subtle); border: none; border-top: var(--hairline) solid var(--border-soft);
  color: var(--text-dim); font-family: inherit; font-size: var(--fs-sm); font-weight: 600;
  padding: 11px; letter-spacing: .2px;
}
.ff-more:hover { color: var(--brand-2); }
.ff-hidden { display: none; }

.ff-table { padding: 0; gap: 0; }
.ff-row {
  display: grid; align-items: center; gap: 12px;
  grid-template-columns: minmax(0, 1fr) minmax(70px, 210px) 58px 58px 58px 118px 76px 68px;
  padding: 8px 16px; border-bottom: var(--hairline) solid var(--border-soft);
}
.ff-row:last-child { border-bottom: none; }
.ff-head {
  color: var(--text-mute); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: .06em; font-weight: 600; padding: 9px 16px;
}
.ff-head .ff-n, .ff-head .ff-bar-h { text-align: right; }
.ff-head .ff-bar-h { text-align: left; }
.ff-who { display: flex; align-items: center; gap: 10px; min-width: 0; }
.ff-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-md); }
.ff-pos { color: var(--text-mute); }
.ff-n { text-align: right; font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.ff-n.lead { font-weight: 700; font-size: var(--fs-md); }
.ff-n.dim { color: var(--text-dim); }
.ff-n.mute { color: var(--text-mute); }
/* Last week's share as a bar; the tick marks the season average, so the
   gap between them IS the trend the delta chip states in words. */
.ff-bar {
  position: relative; display: block; height: 7px; border-radius: 0;
  background: var(--fill-subtle); border: var(--hairline) solid var(--border-soft); overflow: hidden;
}
.ff-bar > i { display: block; height: 100%; border-radius: 0; opacity: .85; }
.ff-bar > b {
  position: absolute; top: -2px; bottom: -2px; width: 2px;
  background: var(--text); opacity: .5; border-radius: 0;
}
@media (max-width: 1100px) {
  .ff-row { grid-template-columns: minmax(0, 1fr) 56px 56px 56px 110px 66px; }
  .ff-bar, .ff-bar-h, .ff-n.rz { display: none; }
}
@media (max-width: 700px) {
  .ff-row { grid-template-columns: minmax(0, 1fr) 56px 100px; }
  .ff-row > .ff-n:not(.lead):not(.trend) { display: none; }
  .ff-head .ff-n:not(.lead):not(.trend) { display: none; }
  /* "▲ +55pt vs 4wk" does not fit 100px, and the column header already
     says TREND with "season vs 4-week vs last week" right above it — so
     drop the words the layout is already saying and keep the number. */
  .chip-suffix { display: none; }
  .ff-n.trend .chip { white-space: nowrap; }
}

/* Draft kit */
.dk-row { grid-template-columns: 26px minmax(0, 1fr) 40px 58px 58px; }
.dk-slrow { grid-template-columns: minmax(0, 1fr) 92px 110px; }
.dk-tier { font-weight: 800; font-size: var(--fs-sm); text-align: right; }
.dk-poscard { padding: 14px 0 6px; gap: 8px; }
.dk-poscard .card-head { padding: 0 16px; }
.dk-tierlabel {
  font-size: var(--fs-2xs); font-weight: 800; text-transform: uppercase; letter-spacing: .8px;
  padding: 7px 16px 2px; border-top: var(--hairline) solid var(--border-soft); margin-top: 4px;
}
.dk-tierlabel:first-child { border-top: none; margin-top: 0; }
.dk-posrow {
  display: grid; grid-template-columns: 22px minmax(0, 1fr) 44px;
  gap: 8px; align-items: baseline; padding: 3px 16px; font-size: var(--fs-md);
}
.dk-pr { color: var(--text-mute); font-size: var(--fs-xs); text-align: right; }
.dk-pn { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.dk-pt { color: var(--text-mute); font-weight: 400; font-size: var(--fs-xs); }
.dk-pp { text-align: right; font-variant-numeric: tabular-nums; color: var(--text-dim); font-size: var(--fs-sm); }
/* Live-draft strikeout: enough to read as "gone" at a glance without
   making the name illegible if you want to double-check it. */
.dk-taken { opacity: .32; }
.dk-taken .dl-main strong, .dk-taken .dk-pn { text-decoration: line-through; }
.dk-draftday { --grade-color: var(--brand-2); }
.dk-bestrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.dk-bl { color: var(--text-mute); font-size: var(--fs-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: .6px; min-width: 96px; }
@media (max-width: 700px) {
  .dk-row { grid-template-columns: 22px minmax(0, 1fr) 34px 50px; }
  .dk-row .dl-num:not(.strong) { display: none; }
}
.dk-moved { color: var(--warn); font-weight: 700; }

/* Offseason panel */
.os-card { padding: 14px 16px; gap: 8px; }
.os-body { display: grid; gap: 4px; }
.os-row {
  display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) 16px minmax(0, 1fr);
  gap: 8px; align-items: center; font-size: var(--fs-sm); padding: 3px 0;
}
.os-team { display: flex; align-items: center; gap: 6px; font-weight: 600;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.os-before { color: var(--text-mute); text-decoration: line-through;
  text-decoration-color: color-mix(in srgb, var(--bad) 55%, transparent);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@supports not (background: color-mix(in srgb, red 10%, transparent)) {
  .os-before { text-decoration-color: rgba(251, 90, 106, .55); }
}
.os-arrow { color: var(--text-mute); text-align: center; }
.os-now { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Rookie rows have no before/arrow — two columns. */
.os-rookies { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 2px 22px; }
.os-rookies .os-row { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); }
.os-rookies .os-now { color: var(--text-dim); font-weight: 500; text-align: right; }
@media (max-width: 700px) {
  .os-row { grid-template-columns: minmax(0, 1.1fr) 16px minmax(0, 1fr); }
  .os-before { display: none; }
}
.gp-teams {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  font-size: var(--fs-2xl); font-weight: 800; letter-spacing: -.3px;
}
.gp-teams > span { display: inline-flex; align-items: center; gap: 8px; }
.gp-at { color: var(--text-mute); font-weight: 600; font-size: var(--fs-lg); }
.gp-sub { color: var(--text-dim); font-size: var(--fs-md); margin-top: 6px; }
.gp-chips { margin-top: 12px; }
.gp-stats { margin-bottom: 22px; }

/* Venue panel — fills the column beside the stadium art with the reason
   the park carries the factors it does. */
.pk-panel { margin-top: 14px; border-top: var(--hairline) solid var(--border-soft); padding-top: 12px; }
.pk-head { font-size: var(--fs-md); font-weight: 700; display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline; }
.pk-facts { color: var(--text-mute); font-size: var(--fs-xs); font-weight: 500; }
/* Capped, not 1fr: three numbers stretched across a half-page column read
   as three empty boxes with a digit lost in each. */
.pk-dims { display: grid; grid-template-columns: repeat(3, minmax(0, 108px));
  gap: 8px; margin-top: 10px; }
.pk-dim {
  background: var(--fill-subtle); border: var(--hairline) solid var(--border-soft);
  border-radius: 0; padding: 7px 10px; min-width: 0;
}
.pk-dim .k { display: block; font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: .5px;
  color: var(--text-mute); font-weight: 600; }
.pk-dim .v { display: block; font-size: var(--fs-xl); font-weight: 800; letter-spacing: -.3px; margin-top: 2px;
  font-variant-numeric: tabular-nums; }
.pk-dim .s { display: block; font-size: var(--fs-2xs); color: var(--text-mute); min-height: 13px; }

.pk-factors { margin-top: 10px; display: grid; gap: 6px; }
.pk-factor { display: grid; grid-template-columns: 74px minmax(60px, 240px) 44px;
  gap: 10px; align-items: center; }
.pk-fk { font-size: var(--fs-xs); color: var(--text-dim); }
/* Bar is centred on league average, so the eye reads direction before it
   reads the number: right of the tick is above average, left is below. */
.pk-fbar { position: relative; display: block; height: 7px; border-radius: 0;
  background: var(--fill-subtle); border: var(--hairline) solid var(--border-soft); }
.pk-fbar > i { position: absolute; top: 0; bottom: 0; background: var(--brand); opacity: .85; }
.pk-fbar > b { position: absolute; left: 50%; top: -2px; bottom: -2px; width: 1px;
  background: var(--text); opacity: .35; }
.pk-fv { font-size: var(--fs-xs); text-align: right; font-variant-numeric: tabular-nums; color: var(--text-dim); }
.pk-fv.up { color: var(--good); } .pk-fv.down { color: var(--bad); }

.pk-plays { color: var(--text-body); font-size: var(--fs-sm); line-height: var(--lh-body); margin: 10px 0 0; }
.pk-note { color: var(--text-mute); font-size: var(--fs-xs); line-height: var(--lh-body); margin: 8px 0 0; }
@media (max-width: 860px) {
  .gp-hero { grid-template-columns: 1fr; }
}


/* [old tablet header wrap — removed 2026-08-11] */

@media (max-width: 760px) {
  /* [old phone menu — removed 2026-08-11: the sidebar drawer replaces it] */
  /* iOS Safari zooms the WHOLE PAGE in when a focused field is smaller
     than 16px — and it never zooms back out, which leaves every later
     tap looking enlarged. Range sliders don't trigger it and keep their
     own sizing. */
  input:not([type="range"]), select, textarea { font-size: var(--fs-xl); }
  /* Data rows (.drow) — Edge Board, Scanner pairs, stale lines, sharp
     anchors, fantasy sleepers. Each is a flex row whose fixed numeric
     columns sum well past a phone viewport (the Edge Board's alone need
     ~460px), so the card's overflow:hidden silently CLIPPED the
     right-hand numbers: the EV, the grade, the gap. Let them wrap and let
     the fixed widths collapse — a wrapped number is readable, a clipped
     one is invisible. */
  /* Thumb-sized tap targets. The pill toggles were 21px tall — under
     every mobile guideline and a real miss-rate on a moving bus. This
     block sits AFTER the base .why-toggle rule, so it actually wins. */
  .why-toggle { padding: 8px 14px; line-height: var(--lh-body); position: relative; }
  /* …and the pill is still only ~34px of ink. Rather than inflate a 10px
     label into a 44px lozenge, the REACH grows without the pill growing:
     an invisible child stretches the hit area to Apple's 44px minimum.
     Its siblings in .section-title are plain text, so the few pixels of
     overhang can't steal a tap from another control. */
  .why-toggle::after {
    content: ""; position: absolute; left: 0; right: 0;
    top: 50%; transform: translateY(-50%);
    height: 44px; min-width: 44px;
  }
  .drow { flex-wrap: wrap; row-gap: 4px; }
  .drow > * { min-width: 0 !important; }
  /* The label column gets its OWN line. Sharing one with the numeric
     columns squeezed it to a ~45px stub whose text was ellipsed away to
     nothing ("Yank…"); wrapped, it reads in full and the numbers follow
     underneath. */
  .drow > [style*="flex:1"] { flex: 1 1 100% !important; }
  /* …but a full-width line is no use if the text can't use it. Several of
     these rows set white-space:nowrap inline (on the row, or on the label
     itself) so their columns hold one line on a desktop. On a phone that
     turned the label into a 577px sentence inside a 390px card, silently
     clipped by the row's overflow:hidden — "Aaron Judge · Yankees vs
     Rockies · Elite barrel rate" ended somewhere around "Elite ba".
     Only the LABEL column is released; the numeric columns keep the row's
     nowrap so "48% vs 41%" can't break across two lines. */
  .drow > [style*="flex:1"],
  .drow > [style*="flex:1"] * { white-space: normal !important; }
  .drow > [style*="flex:1"] {
    overflow: visible !important; text-overflow: clip !important;
  }
  .drow > span[style*="text-align:right"] { text-align: left !important; }
  /* Record page. The settled-pick row needs 414px of fixed columns before
     the label gets a pixel, so on a phone it clips its own P&L figure
     inside the card's overflow:hidden. Split into two lines: what the bet
     was, then how it did. The splits drop their CLV column for the same
     reason — the market name is what the table is for. */
  /* Each meta span gets its OWN area — several grid items assigned to a
     shared area don't flow side by side, they stack on top of each other
     (the overlapping-text bug). */
  /* `.pm-rows .rl-row` is listed explicitly because it is a two-class
     selector and this one is a single class — a media query adds no
     specificity, so the desktop six-column template won here and fought a
     five-area layout: the 1fr column computed to 0px and the trader name
     landed on top of the price. Matching its specificity is the fix. The
     alternative — wrapping the desktop rule in a min-width block of its
     own — opens the duplicate-breakpoint trap that test_header_density
     slices on. */
  /* The FLEXIBLE track is proc, not odds. With `auto auto minmax(0,1fr)`
     across date/proc/odds, a long trader handle took everything an `auto`
     track would give it and squeezed odds to a measured 0px — so the price
     rendered underneath the name. The price is three characters and never
     needs to flex; the name is the only thing here whose length is
     unbounded, so it gets the track that can shrink. */
  .rl-row, .pm-rows .rl-row {
    grid-template-columns: 20px auto minmax(0, 1fr) auto 72px;
    grid-template-areas: "icon main main main pnl"
                         ".    date proc odds .";
    row-gap: 3px; padding: 10px 12px 10px 11px;
  }
  .rl-icon { grid-area: icon; }
  .rl-main { grid-area: main; white-space: normal; }
  .rl-pnl { grid-area: pnl; }
  .rl-date { grid-area: date; }
  /* Wrapping, not clipping — a phone row has vertical room and no hover to
     reveal a title. `overflow-wrap: anywhere` because a wallet address or a
     handle like "monkeymashingkeyboard" is a single unbreakable token, and
     `white-space: normal` alone will not break one: it overflowed the track
     intact and ran across the price. */
  .rl-proc {
    grid-area: proc; white-space: normal;
    overflow: visible; overflow-wrap: anywhere;
  }
  .rl-odds { grid-area: odds; }
  .rl-date, .rl-proc, .rl-odds { text-align: left; justify-self: start; }
  .rl-date::after { content: " ·"; opacity: .4; }
  .rl-odds::before { content: "· "; opacity: .4; }
  .rb-rows.has-clv .rb-row { grid-template-columns: minmax(0, 1fr) 46px 54px 66px; }
  .rb-clv { display: none; }
  .rec-chart .rc-net { font-size: var(--fs-2xl); }

  .view { padding: 14px 12px 48px; }
  .hero-games .game-card { min-width: 262px; }
  /* The lead keeps its own full-width row here; the three that give it
     context share the row below. Two equal columns would put the answer
     beside a supporting number at the same width, which is the shape this
     layout exists to stop. */
  .stats { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .tile .v { font-size: var(--fs-2xl); }
  #stats { grid-template-columns: repeat(3, 1fr); gap: 10px 0; }
  #stats > .tile.lead .v { font-size: var(--fs-3xl); }
  #stats > .tile:not(.lead) .v { font-size: var(--fs-lg); }
  #stats > .tile.lead {
    grid-column: 1 / -1; border-left: 0; padding-left: 0;
    padding-bottom: 12px; border-bottom: var(--hairline) solid var(--border-soft);
  }
  #stats > .tile.lead + .tile { border-left: 0; padding-left: 0; }
  /* Three narrow columns, and one of the labels wraps to two lines —
     "Suggested exposure" did, and its number then sat 17px below the other
     two, so the row of context read as a staircase. Reserving both lines
     puts every value back on one baseline. */
  #stats > .tile:not(.lead) .k { min-height: 2.9em; }

  /* The group and the raw record are the first things to go — a phone reader
     scanning a futures table wants the projection and the title number. */
  /* HIDE EXACTLY AS MANY CELLS AS THE TRACK COUNT DROPS. The columns are
     named rather than selected by position: every cell here is a <span>, so
     :nth-of-type counted tags and matched the GROUP cell instead of the
     second percentage — the playoff column never hid, seven cells went into
     five tracks, and every row's edge wrapped onto a line of its own. */
  .fx-row { grid-template-columns: 46px minmax(0, 1fr) 56px 56px; min-width: 0; }
  .fx-table.priced .fx-row {
    grid-template-columns: 44px minmax(0, 1fr) 52px 50px; min-width: 0;
  }
  .fx-grp, .fx-rec, .fx-po { display: none; }
  .fx-table.priced .fx-odds, .fx-table.priced .fx-div { display: none; }
  .fx-prow { grid-template-columns: minmax(0, 1fr) 46px 84px 44px; }
  .fx-pteam, .fx-pp { display: none; }
  .cards { grid-template-columns: 1fr; }
  .controls { gap: 16px; padding: 14px; }
  .control { min-width: 100%; }
}

/* ============================================================
   Touch targets, keyed to the INPUT DEVICE rather than the viewport
   ============================================================ */

/* Standalone links that happen to be inline — a trader handle, a market
   question, a wallet address. Each already owns its line, so it is a row's
   main tap target while measuring only its own cap height (16–20px). The
   padding grows the hit box; the matching negative margin keeps the line
   box the size it was, so nothing on the page moves. */
@media (pointer: coarse) {
  .player > a, .dl-main > a, .pm-title > a {
    display: inline-block; padding: 8px 0; margin: -8px 0;
  }
}

/* A touch device wide enough to be served the DESKTOP layout: a phone held
   sideways, or a tablet. Every tap-target rule above lives in a
   max-width media query, so rotating the phone silently handed back the
   21px "why?" pill and the 29px sport buttons — the fingers did not get
   any smaller. Width decides which LAYOUT; pointer decides which HAND. */
@media (pointer: coarse) and (min-width: 761px) {
  .why-toggle { padding: 8px 14px; line-height: var(--lh-body); position: relative; }
  .why-toggle::after {
    content: ""; position: absolute; left: 0; right: 0;
    top: 50%; transform: translateY(-50%);
    height: 44px; min-width: 44px;
  }
  .sport-btn, .nav-btn { min-height: 36px; }
}

/* ============================================================
   Retracting header on touch devices
   ============================================================ */

/* The movement itself is inline style written by initHeaderTuck(), which
   offsets the header by however far you have scrolled since your last
   change of direction — the YouTube search-bar feel.

   There is deliberately NO transition on that transform. A transition
   would animate on its own clock while your finger is on the glass, so the
   bar would trail the page instead of moving with it. The only thing left
   here is the guarantee that an open menu is never off-screen: the panel
   lives inside this element, and !important because it has to beat the
   inline transform. */
@media (max-width: 760px), (pointer: coarse) and (max-width: 1024px) {
  body.menu-open .topbar { transform: none !important; }
}

/* ============================================================
   Active rosters
   ============================================================ */

/* Collapsed teams are a grid of buttons; one expands at a time. 32 teams
   x ~53 players is 1,700 rows, and rendering them all was never going to
   scroll well on a phone — so the list stays a list until you pick. */
.ros-teams { display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr)); }
.ros-card { grid-column: 1 / -1; padding: 0; overflow: hidden; }
.ros-team {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 10px 12px; background: var(--panel-2); color: var(--text);
  border: var(--hairline) solid var(--border); border-radius: 0;
  font: inherit; font-size: var(--fs-md); font-weight: 700; cursor: pointer;
  text-align: left;
}
.ros-team:hover { border-color: var(--brand-2); }
.ros-team.open { border-radius: 0;
  border-color: var(--brand-2); }
/* The team's real logo, not a coloured square with the abbreviation in
   it. Ethan, 2026-08-13: "we are not showing player headshots or logos on
   the roster page". `teamMark` draws a monogram underneath and steps
   aside when the logo paints, so this slot must not paint a ground of its
   own — a background here shows through every transparent logo PNG. */
.ros-mark {
  width: 30px; height: 30px; flex: 0 0 auto;
  display: grid; place-items: center;
}
.ros-mark svg, .ros-mark img { display: block; }
/* The face sits on the baseline of the name, and never squeezes it: the
   name column is the one that gives, so the avatar is fixed. */
.ros-name .avatar, .ros-name .avatar-stack { flex: 0 0 auto; }
.ros-team-name { flex: 1 1 auto; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.ros-team-n { color: var(--text-mute); font-size: var(--fs-xs); font-weight: 600;
  flex: 0 0 auto; }

/* The roster table. Same column idiom as the fantasy rows: one column
   gives, the rest are fixed, so nothing can clip its own numbers. */
.ros-head, .ros-row {
  display: grid; align-items: center; gap: 10px;
  grid-template-columns: 52px minmax(0, 1fr) 44px 40px 40px;
  padding: 7px 14px; border-bottom: var(--hairline) solid var(--border-soft);
}
.ros-head {
  color: var(--text-mute); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: .06em; font-weight: 700;
}
.ros-row:last-child { border-bottom: none; }
.ros-row.out { opacity: .62; }
.ros-slot { color: var(--text-mute); font-size: var(--fs-xs); font-weight: 700;
  font-variant-numeric: tabular-nums; }
.ros-name { min-width: 0; font-size: var(--fs-md); font-weight: 600;
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ros-name .chip { font-size: var(--fs-2xs); padding: 2px 7px; }
.ros-pos { color: var(--text-dim); font-size: var(--fs-xs); font-weight: 700; }
.ros-n { text-align: right; font-size: var(--fs-sm); color: var(--text-dim);
  font-variant-numeric: tabular-nums; }
.ros-group {
  padding: 9px 14px 4px; font-size: var(--fs-2xs); font-weight: 800;
  text-transform: uppercase; letter-spacing: .06em; color: var(--text-dim);
  border-top: var(--hairline) solid var(--border-soft); display: flex; gap: 8px;
}
.ros-group:first-of-type { border-top: none; }
.ros-group-n { color: var(--text-mute); font-weight: 600; }

/* Transactions */
.ros-move {
  display: grid; align-items: center; gap: 10px;
  grid-template-columns: minmax(0, 1fr) 130px 90px;
  padding: 9px 14px; border-bottom: var(--hairline) solid var(--border-soft); font-size: var(--fs-md);
}
.ros-move:last-child { border-bottom: none; }
.ros-move-name { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-weight: 600; }
.ros-move-teams { color: var(--text-dim); font-size: var(--fs-sm); }
.ros-move-teams b { color: var(--good); }
.ros-move-date { color: var(--text-mute); font-size: var(--fs-xs); text-align: right;
  font-variant-numeric: tabular-nums; }

@media (max-width: 700px) {
  /* AGE and EXP are the first things to go: a name that fits beats two
     numbers you can get from the player page. */
  .ros-head .ros-n, .ros-row .ros-n { display: none; }
  .ros-head, .ros-row { grid-template-columns: 46px minmax(0, 1fr) 40px; }
  .ros-move { grid-template-columns: minmax(0, 1fr) 104px; }
  .ros-move-date { display: none; }
}

/* The slate date swings ~80px between a one-day MLB card ("Fri, Jul 31")
   and a two-day NFL week ("Sun, Jan 4 – Mon, Jan 5"). That was slack the
   header could absorb until the Rosters button took the last of it, and
   then the whole top row wrapped on one sport and not the other — a 50px
   jump on every switch. Content must not decide layout: the field is a
   fixed width, so the header is the same height whatever is in it. */
@media (min-width: 761px) {
  #slate-date {
    display: inline-block; min-width: 21ch; text-align: right;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
}

/* The fight-by-fight edge board is a 640px-wide scroller, which is fine on
   a laptop and hides the most important column on a phone: a PASS row's
   REASON. That reason now carries the weigh-in gate ("missed weight by
   1.5 lb"), so a verdict you have to scroll sideways to read is a verdict
   that doesn't get read. On a phone the row wraps instead. */
@media (max-width: 700px) {
  .ufc-edge-row { flex-wrap: wrap; min-width: 0 !important; row-gap: 4px; }
  .ufc-edge-row > span:first-child { min-width: 52px !important; text-align: left !important; }
  .ufc-edge-row > span:nth-child(2) { flex: 1 1 100% !important; order: 3; }
  .ufc-edge-row > span:last-child { text-align: left !important; white-space: normal !important; }
}

/* UFC — the market board. Books derive method props off the moneyline, so
   the fight's other markets are where the distribution usually finds its
   edge; the ones our feed didn't price still show a fair number to shop. */
.ufc-shop { margin-top: 10px; border-top: var(--hairline) solid var(--border-soft); padding-top: 8px; }
.ufc-shop summary { cursor: pointer; color: var(--text-dim); font-size: var(--fs-sm); font-weight: 600; }
.ufc-shop-row { display: grid; grid-template-columns: 74px 1fr auto 62px; gap: 8px;
  align-items: center; padding: 5px 0; font-size: var(--fs-sm); border-bottom: var(--hairline) solid var(--border-soft); }
.ufc-shop-row:last-child { border-bottom: 0; }
.ufc-shop-row .sr-m { color: var(--text-mute); text-transform: uppercase; font-size: var(--fs-2xs); letter-spacing: .04em; }
.ufc-shop-row .sr-s { color: var(--text-body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ufc-shop-row .sr-o { font-variant-numeric: tabular-nums; color: var(--text); }
.ufc-shop-row .sr-e { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
.ufc-shop-row .sr-e.pos { color: var(--good); }
.ufc-shop-row .sr-e.mute { color: var(--text-mute); font-weight: 600; }
@media (max-width: 560px) {
  .ufc-shop-row { grid-template-columns: 1fr auto 58px; }
  .ufc-shop-row .sr-m { display: none; }
}

/* About — a reading page, not a dashboard. Longer measure, looser leading
   and real paragraph rhythm, because this is the one page on the site
   somebody actually reads top to bottom. */
.about-lede { max-width: var(--measure); margin: 0 0 4px; }
.about-lede p { color: var(--text-body); font-size: var(--fs-lg); line-height: var(--lh-body); margin: 0 0 12px; }
.about-lede strong { color: var(--text); }
.about-card .about-body { margin-top: 6px; max-width: var(--measure-tight); }
.about-card .about-body p { color: var(--text-body); font-size: var(--fs-md); line-height: var(--lh-body); margin: 0 0 10px; }
.about-card .about-body p:last-child { margin-bottom: 0; }
.about-card .about-body strong { color: var(--text); }
.about-card .about-body em { color: var(--text-dim); font-style: normal; }
.about-list { margin: 4px 0 0; padding-left: 18px; }
.about-list li { color: var(--text-body); font-size: var(--fs-md); line-height: var(--lh-body); margin-bottom: 7px; }
.about-list a { color: var(--brand-2); }
/* One column: these cards are prose, and prose in two narrow columns on a
   wide screen is harder to read, not denser. */
#about-body .cards.wide { grid-template-columns: minmax(0, 1fr); }
@media (max-width: 560px) {
  .about-lede p { font-size: var(--fs-lg); }
  .about-card .about-body p, .about-list li { font-size: var(--fs-md); }
}

/* The help links are the most important thing on the About page and were
   the hardest to hit — 15px tall inline text. On a touch screen they get
   a real target. Someone reaching for a helpline should not have to aim. */
.about-list a {
  display: inline-block; padding: 3px 2px; text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
@media (pointer: coarse) {
  .about-list a {
    padding: 12px 10px; margin: 2px 0; min-height: 24px; line-height: 20px;
    border: var(--hairline) solid var(--border-soft); border-radius: 0;
    background: var(--fill-subtle); text-decoration: none; font-weight: 600;
  }
}

/* [old More menu — removed 2026-08-11] */

/* Which EDGE of the More button the menu hangs from, decided by whether
   the button ends its row.
   ------------------------------------------------------------------
   The rule above anchors left, on the reasoning that "More" is the
   switcher's last item so there is always at least the menu's width to
   its right. Measured, that was not true even before this file changed:
   at 1280px the left-anchored menu ran from 1199 to 1399 — 119px past the
   viewport, and enough to give the whole PAGE a horizontal scrollbar. The
   widths in that comment's checklist (800/1024/1280/1440/1800) include the
   one it fails at.

   Making the switcher full-width below 1248px moved "More" to the far
   right of the bar, which added the same overflow at 960 (7px) and 900
   (67px). So the anchor has to follow the button.

   847px is where the switcher stops fitting on one line, and the number is
   a measurement rather than a round figure: the eleven buttons need 795px
   and the content box is the viewport less 52px of bar padding, so 847 is
   the narrowest width that still holds them (795 fits in exactly 795) and
   846 is the first that wraps. Both 848 and 861 were tried first and both
   clipped, by 120px and 107px, because they left-anchored widths where
   "More" still ended the row. At or above 847 "More" always ends it, so
   the menu's RIGHT edge belongs on the button's and it can never reach the
   viewport edge. Below it "More" can be the FIRST item of a wrapped second
   row — measured at 820 it sits at x 26 — and there the original reasoning
   holds exactly: anchor left, with the whole bar's width to the right.

   THE BREAKPOINT HAS TO TRACK THE WRAP POINT, and it is wrong in both
   directions. Set too high, the widths between the wrap point and it
   left-anchor while "More" still ends the row — 861 was the first attempt
   and clipped 107px at 860. Set too low, they right-anchor while "More"
   leads a wrapped row, and the menu hangs off the LEFT instead (at 820
   that would be -120). Adding a sport moves the wrap point and this number
   with it; test_headerwrap pins the pair together.

   The old note against right-anchoring ("it opened under WNBA and UFC and
   looked like it belonged to them — at 1440, button 630-685, menu
   485-685") was measured when the switcher was narrower and "More" sat
   mid-row. It now ends the row at 1199-1240, so the menu lands under
   Record and The Lab, beside the button rather than adrift under a
   league. */
@media (min-width: 847px) {
  .sport-more .sport-group[data-group="tools"] { left: auto; right: 0; }
}

/* On a phone the wrapper must not exist: the slide-down menu already
   groups these as their own card under a "More" heading, and a second
   toggle inside it would be a control that opens a control. */
@media (max-width: 760px) {
  .sport-more { display: contents; }
  .more-toggle { display: none !important; }
}

/* [old header sizing — removed 2026-08-11] */

/* [old phone toggle lift — removed 2026-08-11] */

/* [old status-row wrap — removed 2026-08-11] */

/* [old status-row wrap — removed 2026-08-11] */

/* ============================================================
   Standings + the postseason bracket
   ============================================================
   Same column idiom as the roster table: one column gives and the rest
   are fixed, so nothing can clip its own numbers. The wide columns
   (per-game scoring, home/away, L10) are the ones that drop on a phone —
   the record, the percentage and the differential are what a standings
   table is FOR, and they stay at every width. */
.std-card { grid-column: 1 / -1; padding: 0; overflow: hidden; }
.std-group {
  padding: 10px 14px 6px; font-size: var(--fs-xs); font-weight: 800;
  text-transform: uppercase; letter-spacing: .06em; color: var(--text);
  border-bottom: var(--hairline) solid var(--border-soft); background: var(--panel-2);
}
.std-head, .std-row {
  display: grid; align-items: center; gap: 8px;
  grid-template-columns:
    24px 34px minmax(0, 1fr) 58px 46px 48px 74px 52px 52px 42px 52px;
  padding: 7px 14px; border-bottom: var(--hairline) solid var(--border-soft);
}
.std-head {
  color: var(--text-mute); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: .06em; font-weight: 700;
}
.std-row:last-child { border-bottom: none; }
.std-rank { color: var(--text-mute); font-size: var(--fs-xs); font-weight: 700;
  font-variant-numeric: tabular-nums; text-align: right; }
.std-mark {
  width: 26px; height: 26px; border-radius: 0;
  display: grid; place-items: center; font-size: var(--fs-2xs); font-weight: 800;
  color: #fff; text-shadow: 0 1px 2px rgba(0, 0, 0, .5);
}
.std-name { min-width: 0; font-size: var(--fs-md); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.std-n { text-align: right; font-size: var(--fs-sm); color: var(--text-dim);
  font-variant-numeric: tabular-nums; }
.std-n.std-rec { font-weight: 700; color: var(--text); }
.std-n.good { color: var(--good); }
.std-n.bad { color: var(--bad); }

/* The bracket. Rounds are columns and scroll sideways on their own — a
   four-round chart cannot fit a phone, and squeezing it would make the
   one thing it exists to show unreadable. */
.brk { display: flex; gap: 14px; overflow-x: auto; padding: 2px 2px 10px;
  margin-bottom: 14px; }
/* The round NAME sits on one line across the whole chart; only the
   matchups spread out to meet the next round. Letting the name share the
   space-around distribution walked the headings diagonally down the
   page — the later the round, the lower its label. */
.brk-round { flex: 0 0 auto; min-width: 168px;
  display: flex; flex-direction: column; }
.brk-matches { flex: 1 1 auto; display: flex; flex-direction: column;
  justify-content: space-around; gap: 8px; }
.brk-round-name {
  font-size: var(--fs-2xs); font-weight: 800; text-transform: uppercase;
  letter-spacing: .06em; color: var(--text-dim); text-align: center;
  padding-bottom: 2px;
}
.brk-match {
  border: var(--hairline) solid var(--border); border-radius: 0;
  background: var(--panel-2); overflow: hidden;
}
.brk-side {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 8px 11px; font-size: var(--fs-sm); font-weight: 600;
  color: var(--text-dim);
}
.brk-side + .brk-side { border-top: var(--hairline) solid var(--border-soft); }
/* The winner is the whole point of a bracket, so it is the only thing
   with full contrast — the loser is legible, not shouting. */
.brk-side.won { color: var(--text); font-weight: 800; }
.brk-side.won .brk-score { color: var(--good); }
.brk-team { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.brk-score { font-variant-numeric: tabular-nums; flex: 0 0 auto; }

@media (max-width: 760px) {
  .std-head .std-wide, .std-row .std-wide { display: none; }
  .std-head, .std-row {
    grid-template-columns: 20px 30px minmax(0, 1fr) 54px 44px 46px 40px;
    gap: 6px; padding: 7px 10px;
  }
}

/* ============================================================
   The page nav at tablet widths
   ============================================================
   Ten tabs is more than 761-1023px can lay out in a row, so the nav
   wrapped and the header grew to 244px on an iPad in portrait — a
   quarter of the screen handed to chrome, which is the exact complaint
   the header work already answered once at other widths.

   One row that scrolls itself, rather than two rows that push the board
   down. Every tab stays reachable and the header stays one line tall.
   The indicator is positioned inside .nav, so it scrolls with the tabs
   instead of drifting off the active one. */
@media (min-width: 761px) and (max-width: 1023px) {
  .nav {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;             /* the row is short; a bar would
                                          eat more height than it earns */
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    max-width: 100%;
  }
  .nav::-webkit-scrollbar { display: none; }
  .nav-btn {
    flex: 0 0 auto;                    /* never squeeze a label to a stub */
    padding: 9px 12px;
    scroll-snap-align: start;
  }
}

/* The sport switcher at tablet widths.
   Seven buttons (six leagues + More) total ~497px of ink, and at 761-1023
   they wrapped to a second row — which took the brand row from 46px to 88
   and the whole header to 244. Scrolling is not an option here: the More
   dropdown is absolutely positioned, and a scroll container would clip it
   open. So the buttons get tighter instead. Nothing is hidden and nothing
   is squeezed to a stub — the emoji and the league name both survive.

   UPDATE, since Polymarket and Fantasy joined the row: nine buttons no
   longer fit here even tightened, and the band wraps to two rows again.
   That is accepted rather than fixed. Measured at 900px with this block
   already applied: 508px of buttons plus 140px of gaps against a 492px box
   — 156px short, where taking eight gaps from 18px to 14px returns 32px.
   Closing a gap that size means either shrinking the labels, which makes
   the nav least readable on exactly the device that needed help, or taking
   the width off the brand block, which re-opens the masthead layout this
   section spent a whole pass settling.

   And the wrap is good here: it breaks at the leagues/markets group
   boundary, so the second row starts with Polymarket rather than orphaning
   "UFC". It reads as grouping, not overflow. Checked by screenshot at
   900px, not only by counting rows. */
@media (min-width: 761px) and (max-width: 1023px) {
  .sport-switch .sport-btn { padding: 7px 8px; font-size: var(--fs-sm); gap: 4px; }
  .sport-group { gap: 3px; }
}

/* ============================================================
   Live fight — body diagram
   ============================================================ */
.lf-age { font-size: var(--fs-xs); color: var(--text-mute); margin: -4px 0 10px;
  font-variant-numeric: tabular-nums; }
.lf-age.warn { color: var(--warn); font-weight: 700; }
.lf-card { padding: 14px; margin-bottom: 12px; }
.lf-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-size: var(--fs-sm); font-weight: 800; text-transform: uppercase;
  letter-spacing: .05em; color: var(--text-dim); margin-bottom: 12px; }
/* The LIVE badge pulses. It is the one element on the site allowed to,
   because it is the one making a claim about right now. */
.chip.live { color: var(--good); border-color: var(--good);
  animation: lfPulse 1.6s ease-in-out infinite; }
@keyframes lfPulse { 0%, 100% { opacity: 1 } 50% { opacity: .45 } }
@media (prefers-reduced-motion: reduce) { .chip.live { animation: none } }

/* Two fighters side by side — the comparison IS the graphic, so they must
   never stack on a phone unless there is genuinely no room. */
.lf-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.lf-fighter { min-width: 0; }
.lf-name { font-size: var(--fs-md); font-weight: 800; margin-bottom: 8px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bodyfig { width: 100%; max-width: 132px; height: auto; display: block;
  margin: 0 auto 10px; }
.lf-absorbed { text-align: center; font-size: var(--fs-xs); color: var(--text-mute);
  margin-bottom: 10px; }
.lf-region { display: grid; grid-template-columns: 42px minmax(0, 1fr) 28px;
  align-items: center; gap: 8px; margin-bottom: 5px; }
.lf-region-name { font-size: var(--fs-xs); color: var(--text-dim); font-weight: 600; }
.lf-bar { height: 7px; border-radius: 0; background: var(--panel-3);
  overflow: hidden; }
/* No transition here on purpose. The span is written by innerHTML with its
   width already inline, so it is a freshly-inserted element whose width
   never changes — a transition on it cannot fire, and the .4s ease that
   used to sit here never once ran. A declaration that cannot do anything
   is decoration wearing the costume of craft, the same reason the
   slashed-zero rule was refused. If this bar is ever wanted as a movement,
   it wants transform like .conf-fill, not width. */
.lf-bar > span { display: block; height: 100%; border-radius: 0; }
.lf-region-n { text-align: right; font-size: var(--fs-sm); font-weight: 700;
  font-variant-numeric: tabular-nums; }
.lf-chips { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 9px; }
.lf-chips .chip { font-size: var(--fs-2xs); padding: 2px 7px; }

@media (max-width: 420px) {
  .lf-pair { gap: 8px; }
  .bodyfig { max-width: 96px; }
  .lf-region { grid-template-columns: 34px minmax(0, 1fr) 24px; gap: 5px; }
  .lf-region-name { font-size: var(--fs-2xs); }
  /* A fighter's name is how you know which figure you are looking at, so
     on a narrow screen it WRAPS rather than truncating. "Alexander
     Volkan…" identifies nobody, and there is vertical room here. */
  .lf-name { white-space: normal; overflow: visible; font-size: var(--fs-sm);
             line-height: var(--lh-tight); min-height: 2.5em; }

  /* A fixed column count stops working somewhere, and for these it is the
     small phone. Measured at 320px: the four-up long shot row leaves 33px
     of usable label width once padding is taken out, so "chances" — one
     unbreakable word about 45px wide — ran straight out of the card, which
     clips. Two-up gives ~71px and the labels fit. The three-up metric row
     was doing a smaller version of the same thing (4.5px over). */
  .metrics, .card.longshot .metrics { grid-template-columns: repeat(2, 1fr); }
  /* Three tiles into two columns leaves the hero stranded on a half-empty
     second row — which is worse than the flat version it replaced, because
     now it looks like a layout bug rather than a choice. Give it the whole
     first row instead: on the screen with the least room, the number the
     card exists to communicate is read first and the context follows. */
  .metrics .metric.primary { grid-column: 1 / -1; order: -1; }
}

/* ============================================================
   Record scope — which model you are judging
   ============================================================
   The combined page answers "is the system making money". A per-sport
   page answers "is THIS model any good", which is what the next change to
   it depends on. Scrolls on one row rather than wrapping, because seven
   scopes above a table should not cost two lines of header. */
.rec-scopes {
  display: flex; gap: 6px; margin-bottom: 14px; padding-bottom: 2px;
  overflow-x: auto; scrollbar-width: none;
}
.rec-scopes::-webkit-scrollbar { display: none; }
/* §8.9: pill-shaped filter chips with filled backgrounds are on the
   anti-pattern list, and a filled amber slab for the active scope is the
   loudest thing on a page whose subject is a P&L curve. Same treatment as
   the nav — labels over a rule, active marked by a 2px underline — so the
   two rows of tabs on this page stop being two different components. */
.rec-scope {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 0; margin-right: 18px; border-radius: 0; cursor: pointer;
  background: none; border: 0; border-bottom: 2px solid transparent;
  color: var(--text-mute); font-family: var(--font-sans);
  font-size: var(--fs-sm); font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase;
  min-height: 34px;
}
.rec-scope:hover { color: var(--text); }
.rec-scope.active {
  background: none; border-bottom-color: var(--brand-2);
  color: var(--text);
}
/* The count is the honest part: a scope with nothing in it still appears,
   because hiding it would make "no bets yet" and "no such board" look
   identical. */
.rec-scope-n {
  font-size: var(--fs-2xs); font-weight: 600; opacity: .75;
  font-variant-numeric: tabular-nums;
}
.rec-scope.active .rec-scope-n { opacity: .9; }

@media (pointer: coarse) { .rec-scope { min-height: 42px; } }


/* Drawn icons sit ON the text baseline. An emoji is a character and rides
   the baseline for free; an inline <svg> is a replaced element and sits on
   it, which puts it a couple of pixels low next to its own label. */
.ic { vertical-align: -0.14em; flex-shrink: 0; }


/* ======================================================================
   Venue art — engraving
   ======================================================================
   The stadium, ballpark and court diagrams are the site's most distinctive
   asset, and they were coloured illustration: team-tinted radial washes,
   green mow stripes, oak-plank courts, gradient roofs. Night Form re-cuts
   them as line engraving — §5 promotes the venue mark from decoration to a
   data-bearing component.

   This is done from the STYLESHEET, not by rewriting web/js/visuals.js,
   for one specific reason: SVG presentation attributes (`fill="..."`,
   `stroke="..."` written on the element) sit at the very bottom of the
   cascade, so any CSS rule beats them. Re-skinning here changes every
   painted surface while leaving every <text>, every label, every score and
   every geometry untouched — which is exactly the guarantee the
   preservation contract needs. A rewrite of three 60-line drawing
   functions could silently drop a label; this cannot.

   The one exception the spec grants is team colour (§6.13). It survives on
   the endzone and paint-area rects as a flat block behind the monogram, so
   you can still tell whose building you are looking at.
   ------------------------------------------------------------------- */
.stadium, .field { background: none; }

/* COLOUR IS BACK ON THE SURFACES — Ethan, 2026-08-08.
   ----------------------------------------------------------------------
   "i feel like we should have the color for the stadiums and arenas and
   shit instead of these lines we are calling stadiums."

   This reverses the outline-only line language and the four-ink palette
   that docs/THE_OVERHEAD.md §1 had marked LOCKED. That document is not
   wrong and has not been deleted — it was written against real research
   (Ehrenberg-Bass on repetition; Tropicana losing ~20% of unit sales in
   seven weeks after replacing a distinctive asset), and it contains the
   warning that fits this exact moment: "the temptation to redraw this
   because you have looked at it a thousand times is the single biggest
   risk to it ever working." The lock names Ethan as the one who can open
   it, and he did, with three variants rendered side by side in front of
   him. §1 is updated rather than ignored.

   WHAT CAME BACK: the fills. Grass, infield dirt, hardwood, the team's
   own colour on the stands and the end zones — all of it was still in
   `visuals.js` the whole time, because the engraving was a stylesheet
   re-skin and never touched the drawing code. Deleting the strip rules
   restores it exactly.

   WHAT STAYED, and this is the difference between this and simply
   reverting: the art sits on the SITE'S ground, not its own. The rules
   below still flatten the navy sky wash to `--bg-2`, still set every
   label in mono at one weight, still zero every radius, and still draw
   the plaques as hairline boxes. Rendered side by side, the full revert
   read as three cards pasted in from a different website — a radial navy
   gradient behind each venue on a near-black page, with system-ui labels
   among mono ones. Keeping the ground and the type is what lets the
   colour arrive without the discontinuity.
   ---------------------------------------------------------------------- */

/* The sky wash — RE-DECIDED 2026-08-11 with the rest of the New Look.
   The rule that lived here flattened every venue's first rect to the
   page ground, which is what made the strip read as diagrams. Ethan's
   render draws the stadiums as NIGHT SCENES — deep sky, floodlight
   bloom, the two teams' colours in the air — and visuals.js now paints
   exactly that (nightFx). The art owning its backdrop is the point now:
   the card frame + vignette are what tie it to the page instead. */

/* Plaques (HR factor, altitude) and the bases sit inside <g>. Hairline
   box, square corners — a filled navy pill next to coloured grass reads
   as a leftover, and these carry NUMBERS, which belong to the page's
   type system rather than to the illustration. `rx` is a CSS geometry
   property, the only way to unset an rx="7.5" written on the element
   without editing the drawing code. */
.stadium g rect { fill: none; stroke: var(--border); stroke-width: 1; opacity: 1; }
/* ...WITH ONE EXCEPTION, and it is the exception that proves the rule was
   about plaques rather than about rects. An occupied base is not chrome,
   it is the live state of the game — the single most time-sensitive fact
   on the card. Hollowing it makes an empty base and a runner on second
   the same drawing.

   THIS RULE IS LOAD-BEARING IN THE LITERAL SENSE: the fill is written on
   the element as well, and a presentation attribute loses to any CSS
   selector, so the rule above erased it outright. Measured, not guessed —
   `getComputedStyle(rect).fill` returned "none" with `fill="#ffd24a"`
   sitting in the markup. Two classes to one, so this wins. */
.stadium g rect.on-base { fill: #ffd24a; stroke: #ffffff; stroke-width: 1; }
/* Zero radius is a house rule everywhere else in Night Form; the art was
   the one place still drawing rounded corners (rx="6" on the NFL field
   and end zones, rx="7.5" on the plaques). */
.stadium rect, .field rect { rx: 0; ry: 0; }

/* Every label on the art is bone mono at one weight. The pre-redesign art
   set them in white, in the team's ideal-contrast colour, and at four
   opacities — three type systems on one card.
   ----------------------------------------------------------------------
   THE HALO IS NOT DECORATION. `--text-dim` was chosen when the ground
   under these labels was flat `--bg-2` and identical on every card. With
   the colour back, the same label now sits on Vikings purple, Chiefs red
   or Marlins teal — and "MIN" across a purple dome disappeared outright in
   the first render. Restoring the old per-team ideal-contrast fill would
   solve it and bring back the third type system with it.
   A ground-coloured outline under the glyphs solves it once, for every
   surface, without asking what colour is underneath: `paint-order` draws
   the stroke first so the fill stays crisp on top. It inverts with the
   theme for free — bone text with a near-black halo on dark, ink text with
   a near-white halo on light — because both ends are tokens. */
.stadium text, .field text {
  fill: var(--text); font-family: var(--font-mono); font-weight: 400;
  opacity: 1;
  paint-order: stroke fill;
  stroke: var(--bg-2); stroke-width: 2.4px; stroke-linejoin: round;
}
/* The plaques sit on the flat ground inside a hairline box, so they need
   no halo — and a stroked figure inside a 1px rule reads as bold. */
.stadium g text { stroke-width: 0; }
.stadium text.num, .field text.num { fill: var(--text); }


/* The wind gauge, engraved. §5.5 is explicit that it survives the redesign
   — "it is the only place the exact bearing is legible" — but as a drawn
   dial rather than a filled navy disc with a green needle. Same cascade
   trick as the venue art: presentation attributes lose to CSS, so the
   drawing keeps every number and label it had. */
.wind circle { fill: none; stroke: var(--border); stroke-width: 1; }
/* The streams are STROKED lines that show flow, not filled shapes — the
   first pass filled them and the dial grew a scatter of amber dashes. The
   needle (polygon) is the only filled thing here. */
/* Graphite, not amber. Amber-stroked streams put eight accent elements
   inside one 92px dial and crossed the mph reading — §3.1's own warning is
   that more than ~8 amber elements on a screen means something is being
   decorated rather than encoded. The NEEDLE carries the bearing in amber;
   the flow lines are texture behind it. */
.wind .stream { fill: none; stroke: var(--border); stroke-width: 2; }
.wind polygon { fill: var(--warn); stroke: none; }
.wind path:not(.stream) { fill: none; stroke: var(--text-mute); stroke-width: 1.2; }
.wind line { stroke: var(--border); }
.wind text { fill: var(--text-dim); font-family: var(--font-mono); }
.wind .num, .wind text[font-weight="800"], .wind text[font-weight="700"] {
  fill: var(--text);
}
.wind.dome path { fill: none; stroke: var(--text-mute); stroke-width: 1.2; }

/* Team identity badges — §6.13. A white monogram on a filled colour chip
   is the one shape this palette does not have. The colour survives as a
   block; the letters go bone so they read at any tint. */
.team-mark rect, .team-mark circle { rx: 0; }
.team-mark text { fill: var(--text); font-family: var(--font-mono);
  font-weight: 500; }


/* [old masthead / standing / masthead-brief — removed 2026-08-11] */

/* The Kalshi board. Six cells: sport chip, the market's own title (the one
   unbounded column, so it takes the flexible track and the ellipsis),
   Kalshi's probability, ours, the gap, volume. Numbers right-aligned and
   tabular so a column of them reads as a column. */
.kx-row {
  display: grid; align-items: center; gap: 10px;
  grid-template-columns: 52px minmax(0, 1fr) 52px 52px 74px 84px;
  padding: 8px 14px; border-bottom: var(--hairline) solid var(--border-soft);
}
.kx-row:last-child { border-bottom: 0; }
/* The venue column: which exchange the row is priced on, now that both
   share one table. Deliberately NOT colour-coded — amber is the one
   accent and it means "live or material" (§1), which a venue name is
   not. The word does the work; the model and edge columns already say
   the thing that actually differs between the two feeds. */
.kx-venue { display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--fs-2xs); font-weight: 800; letter-spacing: .04em;
  padding: 4px 6px; }
.kx-title { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: var(--fs-sm); }
.kx-match { color: var(--text-mute); }
.kx-num { text-align: right; font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm); }
.kx-vol { text-align: right; color: var(--text-mute);
  font-variant-numeric: tabular-nums; font-size: var(--fs-xs); }
@media (max-width: 760px) {
  /* The title gets its own line; the numbers share a second. Volume drops —
     on a phone the edge is the story and the row was six columns of 60px. */
  .kx-row { grid-template-columns: 52px 1fr 1fr 1fr;
    grid-template-areas: "sport title title title" ".  k m e"; row-gap: 4px; }
  .kx-sport { grid-area: sport; }
  .kx-title { grid-area: title; white-space: normal; }
  /* Named, never positional: every cell here is a <span>, and
     :nth-of-type counts TAGS — the sport chip is span number one, so
     positional rules grab the wrong cells or none. The futures table
     already paid for this exact lesson. */
  .kx-k { grid-area: k; text-align: left; }
  .kx-m { grid-area: m; text-align: left; }
  .kx-e { grid-area: e; text-align: left; }
  .kx-vol { display: none; }
}

/* --- The Lab -------------------------------------------------------------
   The backtest page. Its one visual job is keeping the reader from
   confusing "the model forecasts well" with "we beat a book": the basis
   chip is the loudest thing on each card, and a naive-basis card wears
   the warning colour rather than the good one. */
.lab-sport { margin-bottom: 26px; }
.lab-card { padding: 14px; margin-bottom: 12px; }
.lab-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.lab-head .mini { margin-left: auto; opacity: .7; }
.chip.good { color: var(--good); border-color: rgba(47, 213, 140, .4); }
.chip.warn { color: var(--warn); border-color: rgba(240, 180, 41, .45); }
.lab-bins { margin-top: 12px; }
/* The reliability bar: how far a bucket's real hit rate sat from what the
   model claimed. Width is the gap, not the value — a long bar is always
   bad news, in either direction. */
.lab-bar { display: inline-block; height: 8px; min-width: 1px; border-radius: 0; background: var(--good); opacity: .55; }
.lab-bar.neg { background: var(--bad); }
.lab-bins td, .lab-bins th { white-space: nowrap; }


/* The Overhead, where it sits outside a venue card.
   ------------------------------------------------------------------
   Same drawing, same 240x150, on the two surfaces that are not the
   Recommended scroller: a UFC pick card and the Kalshi board's header.
   Both get the art's own hairline frame rather than the card's, so the
   Overhead reads as a plate set into the page — the same relationship
   the venue cards already have. */
.ufc-overhead, .kx-overhead {
  margin-top: 10px; border: var(--hairline) solid var(--border-soft);
  background: var(--bg-2); line-height: 0;
}
.ufc-overhead svg, .kx-overhead svg { width: 100%; height: auto; display: block; }
/* On the board it is a header, so it gets the width of one tile rather
   than the whole table — a 900px-wide probability rule is a chart, and
   this is a mark. */
.kx-overhead { max-width: 280px; margin-bottom: 10px; }

/* Rocket Radar (meme coins).
   ------------------------------------------------------------------
   The honesty card renders ABOVE every score on the page and borrows
   the warn colour for its rule: the base rates are the one thing a
   visitor must not scroll past. Everything else on the page reuses the
   shared kit (.stats, .card, .chip, .agate) — the only new furniture is
   the rates strip, the exit-reason list, and the gated-row treatment. */
/* One line closed, the whole card on demand — the base rates must be
   unskippable without owning the fold ("you can barely see the charts").
   The summary line carries all five numbers itself. */
.mc-honesty {
  border-left: 3px solid var(--warn);
  margin-bottom: 12px; padding: 9px 14px;
}
.mc-honesty summary { cursor: pointer; display: flex; flex-wrap: wrap;
  align-items: baseline; gap: 6px 14px; list-style: none; }
.mc-honesty summary::-webkit-details-marker { display: none; }
.mc-honesty-head {
  font-size: var(--fs-xs); font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: var(--warn); white-space: nowrap;
}
.mc-honesty-line { color: var(--text-dim); font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums; }
.mc-honesty[open] .mc-honesty-line { display: none; }
.mc-rates { display: grid; margin-top: 10px;
  grid-template-columns: repeat(auto-fit, minmax(min(170px, 100%), 1fr));
  gap: 6px 14px; }
.mc-rate { display: flex; align-items: baseline; gap: 7px;
  font-size: var(--fs-xs); color: var(--text-dim); }
.mc-rate-n { font-weight: 800; font-size: var(--fs-md); color: var(--text);
  font-variant-numeric: tabular-nums; white-space: nowrap; }
.mc-honesty-foot { margin-top: 8px; color: var(--text-mute);
  font-size: var(--fs-xs); }
/* The terminal: a strip of coin buttons over one live chart. ONE ROW,
   scrolling sideways — twenty-seven buttons wrapping to three rows put
   another 100px between the reader and the candles. Active pick carries
   the brand ring; the rocket tag says who cleared the risk gate. */
.mc-picker { display: flex; flex-wrap: nowrap; overflow-x: auto;
  gap: 6px; margin-bottom: 12px; padding-bottom: 4px;
  scrollbar-width: thin; }
.mc-picker .mc-pick { flex: none; white-space: nowrap; }
.mc-pick { display: inline-flex; align-items: baseline; gap: 7px;
  padding: 6px 10px; background: none; cursor: pointer;
  border: var(--hairline) solid var(--border); color: var(--text-dim);
  font: inherit; font-size: var(--fs-sm); }
.mc-pick b { color: var(--text); font-weight: 700; }
.mc-pick:hover { border-color: var(--text-mute); }
.mc-pick.active { border-color: var(--brand-2); color: var(--text);
  background: var(--fill-subtle); }
.mc-pick-tag { font-size: var(--fs-xs); font-weight: 800;
  letter-spacing: .06em; text-transform: uppercase; color: var(--good); }
.mc-pick-pct { font-variant-numeric: tabular-nums; }
/* "Just landed" — first sighting on our tape under ten minutes ago. */
.mc-new { font-size: var(--fs-xs); font-weight: 800; color: var(--brand-2);
  letter-spacing: .06em; text-transform: uppercase; margin-left: 6px; }
.mc-exit-why { margin: 10px 0 0; padding-left: 18px;
  font-size: var(--fs-sm); color: var(--text-body); }
.mc-exit-why li { margin-top: 4px; }
.mc-exit-why li::marker { color: var(--bad); }
/* Gated rows stay READABLE — hiding them would make "filtered out" and
   "never seen" look identical, and the reasons live in the hover. */
.mc-board tr.mc-gated td { opacity: .5; }
.mc-gate-tag { font-size: var(--fs-xs); font-weight: 800; color: var(--bad);
  letter-spacing: .06em; text-transform: uppercase; margin-left: 6px; }
/* The sparkline is OUR tape (poll-resolution, honest about it); the live
   candle chart is the venue's own embed, one at a time in a dock — sixty
   live iframes is a tab-killer, one where you asked for it is a terminal. */
.mc-spark-wrap { margin-top: 10px; border-top: var(--hairline) solid
  var(--border-soft); padding-top: 8px; }
.mc-spark { display: block; width: 100%; height: 44px; }
/* The chart IS the product — give it the screen. 78vh with floors and
   ceilings: DexScreener's embed carries its own txns panel at the
   bottom, so anything under ~520px leaves the candles a letterbox
   ("you can barely see the charts, it's so crunched"). */
.mc-chart-card iframe { display: block; width: 100%;
  height: clamp(520px, 78vh, 1000px);
  border: 0; margin-top: 8px; background: var(--bg-2); }
/* The dock's numbers ride the TITLE line — a separate stats band was
   ~60px between the reader and the candles. Wraps on a phone. */
.mc-dock-head { flex-wrap: wrap; gap: 8px 18px; align-items: baseline; }
.mc-dock-stats { display: flex; flex-wrap: wrap; gap: 2px 16px;
  margin: 0; flex: 1; justify-content: flex-end; }
.mc-dock-stats .metric { padding: 0; border: 0; background: none; }
.mc-dock-stats .metric .k { font-size: var(--fs-xs); }
.mc-dock-stats .metric .v { font-size: var(--fs-md); margin-top: 0; }
.mc-chart-link { background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--brand-2); font: inherit; font-size: var(--fs-xs);
  font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.mc-chart-link:hover { text-decoration: underline; }
.mc-board td, .mc-board th { white-space: nowrap; }
.mc-board td:nth-child(2) { max-width: 220px; overflow: hidden;
  text-overflow: ellipsis; }

/* My Bets — the user's own sportsbook log.
   ------------------------------------------------------------------
   Reuses .stats/.agate/.card/.btn; the only new furniture is the entry
   form, the safety banner, and the small per-row action buttons. */
.mb-safety { border-left: 3px solid var(--good); font-size: var(--fs-sm);
  color: var(--text-dim); margin-bottom: 16px; }
.mb-safety b { color: var(--text); }
.mb-form { margin-bottom: 18px; }
.mb-form-row { display: flex; flex-wrap: wrap; gap: 10px 14px;
  align-items: flex-end; margin-bottom: 10px; }
.mb-form-row label { display: flex; flex-direction: column; gap: 4px;
  font-size: var(--fs-xs); color: var(--text-mute); font-weight: 600;
  text-transform: uppercase; letter-spacing: .5px; }
.mb-form-row.mb-grow, .mb-form label.mb-grow { flex: 1; min-width: 200px; }
.mb-form input, .mb-form select { font: inherit; font-size: var(--fs-md);
  padding: 7px 9px; background: var(--bg-2); color: var(--text);
  border: var(--hairline) solid var(--border); text-transform: none;
  letter-spacing: 0; }
.mb-form input[type="number"] { max-width: 100px; }
.mb-add { align-self: flex-end; }
.mb-warn { color: var(--warn); font-size: var(--fs-sm); min-height: 1em; }
.mb-io { font-size: var(--fs-xs); padding: 4px 10px; margin-left: 6px; }
.mb-actions { display: flex; gap: 4px; white-space: nowrap; }
.mb-act { font: inherit; font-size: var(--fs-xs); font-weight: 700;
  padding: 3px 8px; cursor: pointer; background: none;
  border: var(--hairline) solid var(--border); color: var(--text-dim); }
.mb-act:hover { border-color: var(--text-mute); color: var(--text); }
.mb-act.win:hover { border-color: var(--good); color: var(--good); }
.mb-act.loss:hover { border-color: var(--bad); color: var(--bad); }
.mb-act.del:hover { border-color: var(--bad); color: var(--bad); }
.mb-import { margin-bottom: 18px; }
.mb-import summary { cursor: pointer; font-weight: 700;
  color: var(--text-dim); }
.mb-import summary:hover { color: var(--text); }
.mb-import textarea { width: 100%; font: var(--fs-sm)/1.5 ui-monospace,
  SFMono-Regular, Menlo, monospace; background: var(--bg-2);
  color: var(--text); border: var(--hairline) solid var(--border);
  padding: 8px 10px; margin: 8px 0; resize: vertical; }
.mb-import-note { color: var(--text-mute); font-size: var(--fs-sm);
  margin: 8px 0; }
.mb-bulk-summary { font-weight: 700; margin-top: 10px; }
.mb-bulk-cols { display: block; font-weight: 400; color: var(--text-mute);
  font-size: var(--fs-xs); margin-top: 2px; }

/* Injury report.
   ------------------------------------------------------------------
   All shared kit (.stats, .agate, .section-title); the only new
   furniture is the player identity cell (face beside name), the
   team-group header, and the small position/side tag. Status colour is
   inline because it is DATA — availability — not decoration. */
.inj-id { display: inline-flex; align-items: center; gap: 8px; }
.inj-face { width: 24px; height: 24px; border-radius: 50%;
  object-fit: cover; background: var(--fill-subtle); flex: none; }
.inj-pos { color: var(--text-mute); font-size: var(--fs-xs);
  font-weight: 600; }
.inj-team-head { display: flex; align-items: baseline; gap: 8px;
  font-weight: 800; margin: 18px 0 8px; }
.inj-team-n { color: var(--text-mute); font-weight: 600;
  font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.inj-table td, .inj-table th { white-space: nowrap; }
.inj-table td:nth-last-child(3) { max-width: 260px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.inj-team-cell { max-width: 170px; overflow: hidden;
  text-overflow: ellipsis; }

/* Motion, switched off for anyone who has asked the OS to stop it.

   There were two narrow blocks doing this — one for a disclosure chevron,
   one for the confidence bars — which left every other transition and
   animation on the page running. A per-element opt-out cannot keep up with
   a stylesheet; the rule has to be the default for everyone who asked.

   Not `none`: a zeroed duration still FIRES transitionend, so anything
   waiting on that event keeps working instead of hanging. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* An empty hint reserves no space — on standalone pages the nav (and
   its hint) is hidden, but the empty <p> still held its margins, part
   of the dead band between the header and the first real content. */
.nav-hint:empty { display: none; margin: 0; padding: 0; }

/* [compact navigation — removed 2026-08-11: the sidebar IS the menu now] */

/* One device pixel, on the screens where 1px stopped meaning that. */
@media (min-resolution: 2dppx) {
  :root { --hairline: 0.5px; }
}

/* ============================================================
   NFL preseason — the August block
   ============================================================
   A fixture list and its scores, on the board for about five weeks a
   year. It is a block rather than a nav tab because a permanent
   destination that says nothing for eleven months costs more than it
   gives, and it retires itself off the last fixture's date so nobody has
   to remember to remove it.

   Deliberately quieter than a pick. Nothing here is priced — that is the
   whole boundary the module and the renderer both hold — so it must not
   wear the visual weight the picks do, or the page implies a claim it is
   not making. Agate rows, muted text, no grade badges and no accent
   except the live dot. */
.pre-card { padding: 14px 16px; }
.pre-note { margin: 0 0 12px; font-size: var(--fs-sm); color: var(--text-mute);
  line-height: var(--lh-body); max-width: var(--measure); }
.pre-note b { color: var(--text-dim); font-weight: 700; }

.pre-week { margin-top: 12px; }
.pre-week:first-of-type { margin-top: 0; }
/* A <details>, so three of the four weeks fold away. The marker is the
   site's own chevron rather than the UA triangle, which does not inherit
   colour and sat black-on-black in the dark theme. */
.pre-week > summary { cursor: pointer; list-style: none; }
.pre-week > summary::-webkit-details-marker { display: none; }
.pre-week > summary::after {
  content: "+"; margin-left: auto; color: var(--text-mute);
  font-weight: 700; font-size: var(--fs-sm);
  transition: transform var(--dur-fast) var(--ease-out);
}
.pre-week[open] > summary::after { content: "−"; }
.pre-week > summary:hover { color: var(--text-dim); }
.pre-week > summary:focus-visible { outline: 2px solid var(--brand);
  outline-offset: 3px; }
.pre-week-head {
  display: flex; align-items: baseline; gap: 10px;
  font-size: var(--fs-2xs); font-weight: 800; letter-spacing: .14em;
  text-transform: uppercase; color: var(--text-mute);
  padding-bottom: 6px; border-bottom: var(--hairline) solid var(--border-soft);
}
.pre-week-n { color: var(--text-mute); letter-spacing: .06em; font-weight: 600; }

/* One row per fixture. A grid, not a flex row: the date, the two marks
   and the score have to line up down the column or 16 games a week read
   as a heap. */
.pre-game {
  display: grid; align-items: center; gap: 0 10px;
  grid-template-columns: 46px 46px 14px 46px minmax(0, 1fr) auto;
  padding: 7px 0; border-bottom: var(--hairline) solid var(--border-soft);
  font-size: var(--fs-sm);
}
.pre-game:last-child { border-bottom: 0; }
.pre-date { color: var(--text-mute); font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs); }
.pre-team {
  display: grid; place-items: center; padding: 3px 0;
  border: var(--hairline) solid; border-radius: 0;
  font-size: var(--fs-2xs); font-weight: 800; letter-spacing: .06em;
  color: var(--text-dim);
}
.pre-at { color: var(--text-mute); font-size: var(--fs-2xs); text-align: center; }
.pre-score { font-variant-numeric: tabular-nums; color: var(--text-dim);
  letter-spacing: .04em; }
/* The winner reads louder than the loser; neither is good/bad coloured,
   because we did not bet it. */
.pre-score b { font-weight: 500; color: var(--text-mute); }
.pre-score b.won { font-weight: 700; color: var(--text); }
.pre-state { color: var(--text-mute); font-size: var(--fs-xs);
  text-align: right; letter-spacing: .08em; text-transform: uppercase; }
.pre-state.live { color: var(--warn); font-weight: 700; }
.pre-roof { color: var(--text-mute); font-size: var(--fs-2xs); }

@media (max-width: 560px) {
  /* The date leads its own line and the fixture sits under it; six
     columns in 320px is four characters a cell. */
  .pre-game { grid-template-columns: 44px 14px 44px minmax(0, 1fr) auto;
    row-gap: 2px; }
  .pre-date { grid-column: 1 / -1; }
  .pre-roof { display: none; }
}

/* ============================================================
   Real team logos
   ============================================================
   The logo is layered OVER the monogram chip rather than replacing it, so
   `onerror="this.remove()"` on the <img> falls back to the chip that
   shipped before — a wrong ESPN abbreviation, a dead CDN or no network at
   all lands on exactly the previous behaviour. Nothing here can be worse
   than what it replaced.

   Sized by the wrapper, not the image, so the fallback and the logo take
   identical space and no row reflows when one becomes the other. */
.team-mark-wrap {
  position: relative; display: inline-block; flex: 0 0 auto;
  vertical-align: middle; line-height: 0;
}
.team-mark-wrap > .team-mark,
.team-mark-wrap > .team-logo {
  position: absolute; inset: 0; width: 100%; height: 100%;
}
/* `contain`, because these marks are not one aspect ratio — a Boston B is
   near-square and a Texans bull is wide. Cropping to fill would cut the
   part of a logo people recognise. */
.team-mark-wrap > .team-logo { object-fit: contain; }

/* THE DRAWING IS A FALLBACK, SO IT LEAVES WHEN THE REAL ONE ARRIVES.
   Every logo and headshot the CDNs serve is a transparent PNG, and this
   layer sat UNDER them permanently — so the monogram's diagonal band,
   colour block and abbreviation showed through the artwork on every row
   (Ethan, 2026-08-12, circling exactly that on the picks list and the
   live board). `art-on` is set by the image's own onload, which means the
   chip is visible in precisely the case it exists for: no image. */
.team-mark-wrap.art-on > .team-mark { display: none; }
/* One pick, one face: the identity column on the recommended list —
   a player's face for a prop, a team's mark for a side, the league's
   for a game total. */
.pick-id { flex: 0 0 auto; display: inline-flex; align-items: center; }
.pick-id .avatar-photo, .pick-id .avatar { border-radius: 50%; }

/* --- Is there an edge at all? -------------------------------------------
   The Record page's frame, above the ROI rather than beside it. A -28% ROI
   next to "the edge signal carries no information" is one story with a
   cause; the same ROI alone invites a hunt for the bad week that explains
   it, and there is no bad week.

   Deliberately quiet. The verdict is doing the work, and a panel that
   shouts undermines a number whose whole authority is that it was measured
   carefully. One hairline accent on the left edge carries the tone. */
.rec-edge {
  margin: 0 0 18px; padding: 14px 16px 12px;
  background: var(--panel-2);
  border: var(--hairline) solid var(--border-soft);
  border-left: 3px solid var(--text-faint);
}
.rec-edge.tone-neg { border-left-color: var(--bad); }
.rec-edge.tone-pos { border-left-color: var(--good); }
.rec-edge .section-title { margin-top: 0; }
.rec-edge-verdict {
  font-size: var(--fs-md); line-height: var(--lh-snug); color: var(--text);
  margin: 6px 0 12px; max-width: var(--measure-tight);
}
.rec-edge-tbl { width: auto; min-width: min(100%, 34rem); margin-bottom: 10px; }
.rec-edge-tbl td, .rec-edge-tbl th { padding: 4px 14px 4px 0; }
/* The claimed edge is the row that decides things; the other three are
   context for it. */
.rec-edge-tbl tr.lead-row td { font-weight: 700; color: var(--text); }
.rec-edge-tbl tr.lead-row td:first-child::after {
  content: " \2190"; color: var(--text-faint); font-weight: 400;
}
.rec-edge .mini { max-width: var(--measure); }
@media (max-width: 640px) {
  .rec-edge { padding: 12px 12px 10px; }
  .rec-edge-tbl { min-width: 0; width: 100%; font-size: var(--fs-xs); }
  .rec-edge-tbl td, .rec-edge-tbl th { padding: 4px 8px 4px 0; }
}

/* Knowledge tiers on a reason (NFL_MODEL §2.3). Deliberately the quietest
   thing on the card: up to five reasons each carry one, so anything with
   a background or a colour of its own would out-shout the sentence it is
   labelling. It reads as an aside, which is what it is — until a bet
   loses, when it is the first thing you want. */
.r-tier {
  margin-left: .5ch;
  font-size: var(--fs-xs);
  letter-spacing: .04em;
  text-transform: lowercase;
  color: var(--text-mute);
  white-space: nowrap;
}
.r-tier::before { content: "· "; }
/* Only INFERENCE gets a colour, and only a faint one. It is the tier that
   means the model itself was wrong, so it is the one worth finding on a
   card you are re-reading after a loss. The other two are facts about the
   world and need no emphasis. */
.r-inference { color: var(--text-dim); font-style: italic; }

/* ============================================================
   NEW LOOK — 2026-08-11, from Ethan's render, shipped faithfully.
   ============================================================
   "go new look … copy my render and ship it fully."

   The dialect: near-black with a violet cast, one electric accent
   (--brand violet), gold reserved for the Qellys mark, green for
   money-good and red for money-bad, soft 12-14px radii, and light
   built from GLOW (violet shadow tokens) instead of the old
   hairline-and-bone newspaper look. Gradients and shadows are now
   legal — but only through the tokens below, so the page stays one
   system instead of fifty opinions (tests/test_newlook.py).

   Deliberately not shipped, per Ethan and per what this site is:
   no balance chip, no bet slip, no "Place Bet", no deposits. The
   slip-shaped hole is filled by the honest equivalents — My Bets
   (your own log) and Bankroll (your unit sizing).
   ============================================================ */
:root {
  --bg: #07070C;
  --bg-2: #0A0A13;
  --panel: #10101B;
  --panel-2: #161626;
  --panel-3: #1D1D30;
  --border: #272741;
  --border-soft: #1C1C2F;
  --text: #F1F1FA;
  --text-dim: #BCBCD4;
  --text-body: #D6D6E8;
  --text-mute: #9999B3;
  --text-faint: #7B7B91;
  --brand: #8D5BF2;
  --brand-2: #A88FF7;
  --brand-ink: #F1F1FA;
  --gold: #F5B93B;
  --good: #42C268;
  --bad: #DF5953;
  --warn: #F79D00;
  --radius-sm: 8px;
  --radius: 12px;
  --radius-lg: 14px;
  --topbar-bg: rgba(7, 7, 12, .82);
  /* Light lives in tokens so every glow on the site is the SAME glow. */
  --glow: 0 0 0 1px rgba(141, 91, 242, .28), 0 6px 28px rgba(141, 91, 242, .16);
  --glow-soft: 0 10px 30px rgba(0, 0, 0, .45);
  --grad-brand: linear-gradient(135deg, #8D5BF2, #6B2BD6);
  --grad-card: radial-gradient(130% 100% at 50% 0%, rgba(141, 91, 242, .18), rgba(7, 7, 12, 0) 62%);
  --grad-chip: linear-gradient(180deg, rgba(141, 91, 242, .22), rgba(141, 91, 242, .08));
  --font-display: "Archivo Narrow", var(--font-sans);
}
:root[data-theme="light"] {
  --bg: #F4F4FB;
  --bg-2: #ECECF6;
  --panel: #FFFFFF;
  --panel-2: #F6F5FD;
  --panel-3: #EEECF9;
  --border: #D9D7EC;
  --border-soft: #E6E4F4;
  --text: #17172A;
  --text-dim: #43436A;
  --text-body: #2B2B4A;
  --text-mute: #6E6E92;
  --text-faint: #A9A9C6;
  --brand: #7D3BEE;
  --brand-2: #8D5BF2;
  --brand-ink: #FFFFFF;
  --gold: #B87E10;
  /* Amber stays the material-conditions colour — but it can no longer
     alias --brand (violet since the pivot). Its own value, both themes. */
  --warn: #B45309;
  /* The original light block declares its own --grad-brand (old amber)
     at higher specificity than the base :root — re-pin the violet here
     or TOP GAME and the active league chip go amber in light. */
  --grad-brand: linear-gradient(135deg, #7D3BEE, #8D5BF2);
  --topbar-bg: rgba(244, 244, 251, .88);
  --glow: 0 0 0 1px rgba(125, 59, 238, .22), 0 6px 24px rgba(125, 59, 238, .12);
  --glow-soft: 0 10px 26px rgba(23, 23, 42, .10);
  --grad-card: radial-gradient(130% 100% at 50% 0%, rgba(125, 59, 238, .10), rgba(244, 244, 251, 0) 62%);
  --grad-chip: linear-gradient(180deg, rgba(125, 59, 238, .16), rgba(125, 59, 238, .05));
}

/* The page never scrolls sideways — Ethan's screen recording
   (2026-08-10): the whole site panned left-right on his phone because
   the header cluster ran past the viewport and nothing clipped it.
   Two fixes, and this is the seatbelt: even if some future element
   overflows again, the DOCUMENT refuses to pan. `clip` rather than
   `hidden` because hidden makes body a scroll container and silently
   kills every position:sticky descendant. Wide content (tables, the
   stadium strip, charts) already scrolls inside its own overflow-x
   boxes, which clip does not touch. */
html, body { overflow-x: clip; }

/* The bar's real height, notch included. Four other rules pin themselves
   to the bottom of the bar (the sidebar, the rail, the mobile drawer and
   its scrim) and every one of them said a bare 64px. Once the bar grew by
   the safe-area inset on an installed iPhone, all four would have sat that
   many pixels too high — a gap under the header on exactly the devices
   this feature exists for. One token, so they cannot drift again. */
:root {
  --topbar-h: calc(64px + env(safe-area-inset-top));
}

/* ---------------- The bar ---------------- */
/* THE NOTCH. Installed to the home screen, this runs with no Safari
   chrome and `apple-mobile-web-app-status-bar-style: black-translucent`,
   so the page paints UNDER the status bar and the clock sits on top of
   the wordmark. `env(safe-area-inset-top)` is zero everywhere else — in
   a browser tab, on desktop, on Android — so this costs nothing there
   and is not behind a media query.
   The bottom tab bar has had its own inset rule for a while, but it was
   inert until `viewport-fit=cover` landed in index.html alongside this:
   without cover the insets are all reported as 0. */
.topbar {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: 18px;
  height: var(--topbar-h);
  padding: env(safe-area-inset-top) 20px 0;
  background: var(--topbar-bg);
  backdrop-filter: blur(16px);
  border-bottom: var(--hairline) solid var(--border-soft);
}
.menu-toggle { display: none; }
.brand-home { display: flex; align-items: center; gap: 11px; color: inherit;
  text-decoration: none; flex: 0 0 auto; }
.logo { width: 38px; height: 38px; display: grid; place-items: center;
  background: none; border: 0; }
.qmark { width: 100%; height: 100%; stroke: var(--gold); stroke-width: 3.4; }
.brand-words h1 { font-family: var(--font-display); font-size: var(--fs-xl);
  font-weight: 700; letter-spacing: .34em; line-height: 1; white-space: nowrap;
  text-transform: uppercase; }
.brand-words h1 span { color: var(--text-mute); font-weight: 600; }
.tagline { color: var(--text-mute); font-size: var(--fs-2xs); margin-top: 3px;
  font-weight: 700; letter-spacing: .22em; text-transform: uppercase;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 34ch; }

.nav { display: flex; align-items: center; gap: 4px; margin: 0 auto; min-width: 0; }
.nav-indicator { display: none; }
.nav-btn {
  border: 0; background: transparent; cursor: pointer; color: var(--text-mute);
  font: inherit; font-size: var(--fs-sm); font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
  padding: 9px 13px; border-radius: var(--radius-sm); white-space: nowrap;
  transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.nav-btn:hover { color: var(--text); background: var(--panel-2); }
.nav .nav-btn.active {
  color: var(--text); background: var(--panel-2); box-shadow: inset 0 -2px 0 var(--brand); }
.live-count { display: inline-grid; place-items: center; min-width: 17px; height: 17px;
  margin-left: 6px; padding: 0 4px; border-radius: 999px; background: var(--bad);
  color: #fff; font-size: var(--fs-2xs); font-weight: 800; letter-spacing: 0; }
.live-count[hidden] { display: none; }

.slate-meta { display: flex; align-items: center; gap: 10px; color: var(--text-dim);
  font-size: var(--fs-sm); flex: 0 0 auto; margin-left: auto; min-width: 0; }
.meta-ico { display: grid; place-items: center; width: 34px; height: 34px;
  border: 0; border-radius: var(--radius-sm); background: transparent; color: var(--text-mute);
  cursor: pointer; transition: color var(--dur-fast) var(--ease-out),
  background var(--dur-fast) var(--ease-out); }
.meta-ico:hover { color: var(--text); background: var(--panel-2); }
.meta-ico svg { width: 17px; height: 17px; }
.nav-hint { display: none; }

/* ---------------- The shell: sidebar · content · rail ---------------- */
.shell { display: grid; grid-template-columns: 240px minmax(0, 1fr);
  max-width: 1760px; margin: 0 auto; align-items: start; }
body.has-rail .shell { grid-template-columns: 240px minmax(0, 1fr) 304px; }
.shell main { max-width: none; margin: 0; min-width: 0; }

.sidebar { position: sticky; top: var(--topbar-h); height: calc(100vh - var(--topbar-h));
  overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: 14px;
  padding: 18px 14px 20px; border-right: var(--hairline) solid var(--border-soft);
  background: var(--bg-2); scrollbar-width: thin; }

.sb-greet { padding: 2px 10px 0; line-height: var(--lh-tight); }
.sb-greet span { display: block; color: var(--text-mute); font-size: var(--fs-sm); }
.sb-greet b { display: block; font-size: var(--fs-xl); font-weight: 800;
  color: var(--text); overflow: hidden; text-overflow: ellipsis; }

.sb-sec { display: flex; flex-direction: column; gap: 4px; }
.sb-label { margin: 12px 10px 4px; font-size: var(--fs-2xs); font-weight: 800;
  letter-spacing: .16em; text-transform: uppercase; color: var(--text-mute); }
.sb-group { display: flex; flex-direction: column; gap: 2px; }

.sb-item { display: flex; align-items: center; gap: 11px; width: 100%;
  border: 0; background: transparent; cursor: pointer; text-align: left;
  color: var(--text-dim); font: inherit; font-size: var(--fs-md); font-weight: 600;
  padding: 9px 10px; border-radius: var(--radius-sm);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
.sb-item:hover { background: var(--panel-2); color: var(--text); }
.sb-item.active { background: var(--grad-chip); color: var(--text);
  box-shadow: inset 2px 0 0 var(--brand); }
.sb-ico { width: 17px; height: 17px; flex: 0 0 auto; color: var(--text-mute); }
.sb-item.active .sb-ico, .sb-item:hover .sb-ico { color: var(--brand-2); }
.sb-badge { margin-left: auto; display: inline-grid; place-items: center;
  min-width: 18px; height: 18px; padding: 0 5px; border-radius: 999px;
  background: var(--bad); color: #fff; font-size: var(--fs-2xs); font-weight: 800; }
.sb-badge[hidden] { display: none; }

.sb-chips { flex-direction: row; flex-wrap: wrap; gap: 6px; padding: 0 4px; }
.sb-chips .sport-btn { flex: 1 1 calc(33.3% - 6px); border: var(--hairline) solid var(--border);
  background: var(--panel); color: var(--text-mute); cursor: pointer; font: inherit;
  font-size: var(--fs-xs); font-weight: 800; letter-spacing: .08em;
  padding: 8px 2px; border-radius: var(--radius-sm); text-align: center; text-transform: uppercase;
  transition: all var(--dur-fast) var(--ease-out); }
.sb-chips .sport-btn:hover { color: var(--text); border-color: var(--brand-2); }
.sb-chips .sport-btn.active { background: var(--grad-brand); border-color: transparent;
  color: #fff; box-shadow: var(--glow); }

/* High Confidence — the render's toggle card, wired to a real filter. */
.sb-hcm { margin-top: auto; display: flex; flex-direction: column; gap: 10px;
  padding: 13px 13px 12px; border-radius: var(--radius);
  border: var(--hairline) solid var(--border);
  background: var(--panel) }
.sb-hcm { background-image: var(--grad-card); }
.sb-hcm-words { display: flex; gap: 10px; align-items: flex-start; }
.sb-hcm-words svg { width: 20px; height: 20px; color: var(--brand-2); flex: 0 0 auto; }
.sb-hcm-words b { display: block; font-size: var(--fs-md); }
.sb-hcm-words span { display: block; color: var(--text-mute); font-size: var(--fs-xs);
  margin-top: 2px; letter-spacing: .06em; text-transform: uppercase; font-weight: 700; }
.sb-hcm-switch { display: flex; align-items: center; gap: 9px; border: 0;
  background: transparent; cursor: pointer; color: var(--text-mute);
  font: inherit; font-size: var(--fs-xs); font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; padding: 0; }
.sb-hcm-switch .knob { position: relative; width: 40px; height: 22px;
  border-radius: 999px; background: var(--panel-3); border: var(--hairline) solid var(--border);
  transition: background var(--dur-base) var(--ease-out); }
.sb-hcm-switch .knob::after { content: ""; position: absolute; top: 2px; left: 2px;
  width: 16px; height: 16px; border-radius: 50%; background: var(--text-mute);
  transition: transform var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out); }
.sb-hcm-switch.on .knob { background: var(--grad-brand); border-color: transparent; }
.sb-hcm-switch.on .knob::after { transform: translateX(18px); background: #fff; }
.sb-hcm-switch.on { color: var(--text); }

.sb-foot { display: flex; flex-direction: column; gap: 10px; padding: 2px 6px 0; }
.standing { display: flex; flex-wrap: wrap; gap: 6px 12px; align-items: baseline;
  color: var(--text-mute); font-size: var(--fs-2xs); font-family: var(--font-mono);
  text-decoration: none; line-height: var(--lh-snug); }
.standing:hover { color: var(--text-dim); }
.standing b { font-weight: 600; color: var(--good); }
.standing b.neg { color: var(--bad); }
.standing .lbl { text-transform: uppercase; letter-spacing: .14em; font-weight: 700; }
.standing:empty { display: none; }
.sb-foot-links { display: flex; gap: 4px; }
.sb-foot-links .sport-btn { border: 0; background: transparent; color: var(--text-mute);
  cursor: pointer; font: inherit; font-size: var(--fs-xs); font-weight: 700;
  padding: 5px 8px; border-radius: var(--radius-sm); letter-spacing: .06em; }
.sb-foot-links .sport-btn:hover { color: var(--text-dim); background: var(--panel-2); }
.sb-foot-links .sport-btn.active { color: var(--text); }

/* ---------------- The rail ---------------- */
.rail { position: sticky; top: var(--topbar-h); max-height: calc(100vh - var(--topbar-h)); overflow-y: auto;
  display: flex; flex-direction: column; gap: 14px; padding: 22px 18px 22px 4px;
  scrollbar-width: thin; }
.rail-card { border: var(--hairline) solid var(--border); border-radius: var(--radius);
  background: var(--panel); background-image: var(--grad-card); padding: 15px 16px; }
.rail-title { display: flex; align-items: center; gap: 8px;
  font-size: var(--fs-xs); font-weight: 800; letter-spacing: .14em;
  text-transform: uppercase; color: var(--text-dim); }
.rail-list { list-style: none; margin: 11px 0 0; padding: 0; display: flex;
  flex-direction: column; gap: 9px; }
.rail-list li { position: relative; padding-left: 15px; color: var(--text-body);
  font-size: var(--fs-sm); line-height: var(--lh-body); }
.rail-list li::before { content: ""; position: absolute; left: 0; top: .52em;
  width: 6px; height: 6px; border-radius: 50%; background: var(--brand); }
.rail-list b { color: var(--text); }
.rail-more { display: inline-block; margin-top: 12px; color: var(--brand-2);
  font-size: var(--fs-xs); font-weight: 700; text-decoration: none;
  letter-spacing: .04em; }
.rail-more:hover { color: var(--text); }
.rail-quiet { margin: 10px 0 0; color: var(--text-mute); font-size: var(--fs-sm); }

/* ---------------- Home: stadium strip dressing ---------------- */
.hero-games .game-card { border: var(--hairline) solid var(--border); border-radius: var(--radius-lg);
  background-color: var(--panel); background-image: var(--grad-card);
  overflow: hidden; transition: border-color var(--dur-fast) var(--ease-out),
  box-shadow var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out); }
.hero-games .game-card:hover { border-color: var(--brand-2); box-shadow: var(--glow);
  transform: translateY(-2px); }
.stadium-wrap { position: relative; }
.top-game-tag { position: absolute; top: 10px; right: 10px; z-index: 3;
  padding: 4px 9px; border-radius: var(--radius-sm); background: var(--grad-brand); color: #fff;
  font-size: var(--fs-2xs); font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
  box-shadow: var(--glow); }
.game-time-chip { position: absolute; top: 10px; left: 10px; z-index: 3;
  padding: 4px 9px; border-radius: var(--radius-sm); background: rgba(7, 7, 12, .72);
  border: var(--hairline) solid var(--border); color: var(--text-dim);
  font-size: var(--fs-2xs); font-weight: 800; letter-spacing: .1em; font-family: var(--font-mono); }
:root[data-theme="light"] .game-time-chip { background: rgba(255, 255, 255, .78);
  color: var(--text-dim); }

/* ---------------- Home: Top Picks strip ---------------- */
.tp-title { align-items: baseline; flex-wrap: wrap; }
.tp-more { margin-left: auto; color: var(--brand-2); font-size: var(--fs-xs);
  font-weight: 700; text-decoration: none; }
.tp-more:hover { color: var(--text); }
#top-picks { margin: 26px 0 6px; }
.tp-strip { display: grid; grid-auto-flow: column; grid-auto-columns: 292px;
  gap: 12px; overflow-x: auto; padding: 4px 2px 12px;
  scrollbar-width: thin; scroll-snap-type: x proximity; }
.tp-card { scroll-snap-align: start; border: var(--hairline) solid var(--border);
  border-radius: var(--radius); background-color: var(--panel);
  background-image: var(--grad-card); padding: 13px 14px 12px;
  display: flex; flex-direction: column; gap: 10px;
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }
.tp-card:hover { border-color: var(--brand-2); box-shadow: var(--glow); }
.tp-top { display: flex; align-items: center; gap: 10px; min-width: 0; }
.tp-who { min-width: 0; flex: 1; line-height: var(--lh-tight); }
.tp-who b { display: block; font-size: var(--fs-md); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.tp-who span { display: block; color: var(--text-dim); font-size: var(--fs-xs);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tp-who .tp-when { color: var(--text-mute); }
.tp-foot { display: flex; align-items: baseline; gap: 10px;
  border-top: var(--hairline) solid var(--border-soft); padding-top: 9px; }
.tp-odds { font-family: var(--font-mono); font-size: var(--fs-lg); color: var(--text); }
.tp-book { color: var(--text-mute); font-size: var(--fs-2xs); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; }

/* ---------------- Home: performance panels ---------------- */
#home-perf { margin: 8px 0 26px; }
.perf-grid { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: 12px; }
.perf-card { border: var(--hairline) solid var(--border); border-radius: var(--radius);
  background-color: var(--panel); background-image: var(--grad-card);
  padding: 15px 16px; }
.perf-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; }
.perf-window { margin-left: auto; color: var(--text-mute); font-size: var(--fs-2xs);
  font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
.perf-tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px; margin-bottom: 8px; }
.perf-tile .k { display: block; color: var(--text-mute); font-size: var(--fs-2xs);
  font-weight: 800; letter-spacing: .1em; text-transform: uppercase; }
.perf-tile b { display: block; margin-top: 4px; font-family: var(--font-mono);
  font-size: var(--fs-2xl); font-weight: 600; }
.perf-tile .sub2 { display: block; color: var(--text-mute); font-size: var(--fs-2xs);
  margin-top: 2px; }
.perf-spark { width: 100%; height: 84px; display: block; margin-top: 6px; }
.perf-donut-row { display: flex; align-items: center; gap: 18px; }
.perf-donut { width: 118px; height: 118px; flex: 0 0 auto;
  transform: rotate(-90deg); }
.perf-donut text { transform: rotate(90deg); transform-origin: 50px 50px;
  fill: var(--text); }
.donut-n { font-size: var(--fs-2xl); font-weight: 700; font-family: var(--font-mono); }
.donut-k { font-size: var(--fs-2xs); fill: var(--text-mute); letter-spacing: .14em;
  text-transform: uppercase; }
.perf-legend { display: flex; flex-direction: column; gap: 8px; font-size: var(--fs-sm);
  color: var(--text-dim); min-width: 0; }
.perf-legend i { display: inline-block; width: 9px; height: 9px; border-radius: 3px;
  margin-right: 8px; }
.perf-legend b { color: var(--text); margin: 0 3px; }
.perf-legend span { color: var(--text-mute); }
.perf-link { display: inline-block; margin-top: 12px; color: var(--brand-2);
  font-size: var(--fs-xs); font-weight: 700; text-decoration: none; }
.perf-link:hover { color: var(--text); }

/* ---------------- Bankroll view ---------------- */
.bk-card { max-width: 620px; padding: 18px; border-radius: var(--radius);
  background-image: var(--grad-card); }
.bk-note { margin: 14px 0 0; color: var(--text-mute); font-size: var(--fs-sm);
  line-height: var(--lh-body); max-width: var(--measure); }

/* ---------------- Soft radii for the system's shared shapes ---------------- */
.card, .controls, .stats .tile, .btn, .empty-slate { border-radius: var(--radius); }
.btn { border-color: var(--border); }
.btn:hover { background: var(--grad-brand); border-color: transparent; color: #fff; }

/* ---------------- Responsive: rail folds, sidebar becomes drawer ------- */
@media (max-width: 1279px) {
  body.has-rail .shell { grid-template-columns: 240px minmax(0, 1fr); }
  .rail { position: static; max-height: none; grid-column: 2;
    display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    align-items: start; padding: 0 26px 26px; }
  .perf-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 900px) {
  .shell, body.has-rail .shell { display: block; }
  .rail { padding: 0 16px 22px; }

  .menu-toggle { display: grid; place-items: center; width: 42px; height: 42px;
    border: var(--hairline) solid var(--border); border-radius: var(--radius-sm); background: transparent;
    color: var(--text); cursor: pointer; flex: 0 0 auto; }
  .mt-bars { position: relative; width: 16px; height: 2px; border-radius: 1px;
    background: currentColor; }
  .mt-bars::before, .mt-bars::after { content: ""; position: absolute; left: 0;
    width: 16px; height: 2px; border-radius: 1px; background: currentColor;
    transition: transform var(--dur-base) var(--ease-out); }
  .mt-bars::before { top: -5px; }
  .mt-bars::after { top: 5px; }
  .menu-open .mt-bars { background: transparent; }
  .menu-open .mt-bars::before { transform: translateY(5px) rotate(45deg); }
  .menu-open .mt-bars::after { transform: translateY(-5px) rotate(-45deg); }
  .mt-label { display: none; }

  /* The drawer: the SAME sidebar, slid in from the left under the bar.
     Nothing is a second copy of anything — one rail, two positions. */
  .sidebar { position: fixed; top: var(--topbar-h); left: 0; bottom: 0; z-index: 50;
    width: min(84vw, 330px); height: auto;
    transform: translateX(-102%); transition: transform var(--dur-base) var(--ease-spring);
    border-right: var(--hairline) solid var(--border); box-shadow: none; }
  body.menu-open .sidebar { transform: translateX(0); box-shadow: var(--glow-soft); }
  body.menu-open { overflow: hidden; }
  body.menu-open::after { content: ""; position: fixed; inset: var(--topbar-h) 0 0 0;
    background: rgba(3, 3, 8, .55); backdrop-filter: blur(2px); z-index: 45; }
}

@media (max-width: 760px) {
  .topbar { gap: 10px; padding: 0 12px; }
  .nav { display: none; }           /* the drawer carries every destination */
  .tagline { display: none; }
  .brand-words h1 { font-size: var(--fs-md); letter-spacing: .26em; }
  .brand-home { flex-shrink: 0; }
  #data-source { display: none; }
  /* The root cause of the sideways pan: this cluster was flex:0 0 auto
     from the desktop rule, so at 297-375px wide it OVERFLOWED a 390px
     bar instead of shrinking. It now takes what is left and truncates. */
  .slate-meta { gap: 6px; flex: 1 1 auto; min-width: 0; overflow: hidden;
    justify-content: flex-end; }
  .meta-ico, .theme-toggle { flex-shrink: 0; }
  #slate-date { flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; max-width: 34vw; font-size: var(--fs-xs); }
  .perf-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  .tp-strip { grid-auto-columns: 84vw; }
}

/* Sidebar items are sentence-case whatever class machinery they carry —
   .nav-btn's uppercase is top-bar clothing, not the item's. */
.sidebar .sb-item { text-transform: none; letter-spacing: .01em;
  font-size: var(--fs-md); font-weight: 600; }
/* Phone: the freshness chip must truncate, never push past the edge. */
.lr-short { display: none; }
@media (max-width: 760px) {
  /* The bar was cutting its own chips off (Ethan's screenshot): the
     date is on the board already, so it leaves; the freshness chip
     drops to its bare age ("3m"), colour still saying updated/stale,
     the full sentence in the hover title. */
  #slate-date { display: none; }
  .lr-full { display: none; }
  .lr-short { display: inline; }
  .live-refresh { max-width: none; overflow: visible; }
  #data-source { max-width: 30vw; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; }
}

/* ---------------- Render-fidelity pass (2026-08-11 evening) ------------
   "i want our stadiums and page too look exactly like this page
   visually." The night art lives in visuals.js (nightFx); this block is
   the card column, the strip's working controls, and the green My Bets
   action the render's slip button became. */
.strip-head { display: flex; align-items: flex-start; gap: 14px;
  flex-wrap: wrap; margin-bottom: 4px; }
.strip-head .hero-title { flex: 1 1 auto; margin-bottom: 8px; }
.strip-controls { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.strip-controls select {
  background: var(--panel-2); color: var(--text-dim);
  border: var(--hairline) solid var(--border); border-radius: var(--radius-sm);
  font: inherit; font-size: var(--fs-xs); font-weight: 700;
  padding: 8px 9px; cursor: pointer; }
.strip-controls select:hover { color: var(--text); border-color: var(--brand); }
.mode-btn { border: var(--hairline) solid var(--border); }
.mode-btn[aria-pressed="true"] { color: var(--brand-2); border-color: var(--brand);
  background: var(--grad-chip); }

.games-outer { position: relative; }
.strip-arrow { position: absolute; top: 40%; z-index: 5;
  width: 36px; height: 36px; border-radius: 50%;
  display: grid; place-items: center; cursor: pointer;
  background: var(--panel-2); border: var(--hairline) solid var(--border);
  color: var(--text-dim); box-shadow: var(--glow-soft); }
.strip-arrow:hover { color: var(--text); border-color: var(--brand); }
.strip-arrow svg { width: 16px; height: 16px; }
.strip-arrow.prev { left: -8px; }
.strip-arrow.next { right: -8px; }
@media (max-width: 760px) { .strip-arrow { display: none !important; } }

/* Grid mode: the same cards, wrapped instead of scrolled. The cards must
   also release the strip's min-width — a 380px card in a ~326px grid cell
   paints over its neighbour — and the scroller's overflow-y clip. */
#games.games-grid { display: grid !important;
  grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr));
  overflow: visible; }
#games.games-grid .game-card { min-width: 0; }

/* The card column, centred like the render. */
.hero-games .game-card { text-align: center; }
.gc-teams { display: flex; align-items: center; justify-content: center;
  gap: 12px; margin-top: 2px; }
.gc-side { display: inline-flex; align-items: center; gap: 7px; }
.gc-side .score { font-family: var(--font-mono); font-size: var(--fs-lg); }
.gc-vs { color: var(--text-mute); font-size: var(--fs-2xs); font-weight: 800;
  letter-spacing: .12em; text-transform: uppercase; }
.gc-name { margin-top: 7px; font-size: var(--fs-md); font-weight: 700; }
.gc-venue { margin-top: 3px; color: var(--text-mute); font-size: var(--fs-xs); }
.hero-games .game-sub, .hero-games .game-when, .hero-games .starters {
  justify-content: center; text-align: center; }
.hero-games .wind-wrap { justify-content: center; }
.gc-picks { position: absolute; bottom: 10px; left: 50%;
  transform: translateX(-50%); z-index: 3;
  padding: 4px 9px; border-radius: var(--radius-sm);
  background: rgba(7, 7, 12, .72); border: var(--hairline) solid var(--brand);
  color: var(--brand-2); font-size: var(--fs-2xs); font-weight: 800;
  letter-spacing: .08em; text-transform: uppercase; }
:root[data-theme="light"] .gc-picks { background: rgba(255, 255, 255, .8); }

/* Top Picks: tile + green action, the render's geometry. */
.tp-tile { display: grid; place-items: center; width: 46px; height: 46px;
  flex: 0 0 auto; border-radius: var(--radius-sm);
  background: var(--panel-3); border: var(--hairline) solid var(--border-soft); }
.tp-add { margin-left: auto; border: 0; cursor: pointer;
  background: var(--good); color: #05230F;
  font: inherit; font-size: var(--fs-xs); font-weight: 800;
  letter-spacing: .02em; padding: 7px 11px; border-radius: var(--radius-sm);
  transition: filter var(--dur-fast) var(--ease-out); }
.tp-add:hover { filter: brightness(1.12); }
.perf-window { border: var(--hairline) solid var(--border);
  border-radius: var(--radius-sm); padding: 4px 9px; }
.rail-list li::before { background: var(--good); }

/* ---------------- Mobile system (Ethan's phone renders, 2026-08-11) ----
   Bottom tab bar, the LIVE NOW board, and the phone home order:
   performance first, then the stadiums two-up, then the picks. */
.tabbar { display: none; }

.lb-chips { display: flex; gap: 8px; flex-wrap: wrap; margin: 2px 0 14px; }
.lb-chip { display: inline-flex; align-items: center; gap: 7px;
  border: var(--hairline) solid var(--border); border-radius: 999px;
  background: var(--panel); color: var(--text-dim); cursor: pointer;
  font: inherit; font-size: var(--fs-xs); font-weight: 800;
  letter-spacing: .06em; padding: 7px 13px; text-transform: uppercase; }
.lb-chip b { font-family: var(--font-mono); font-weight: 600;
  color: var(--text-mute); }
.lb-chip.active { background: var(--grad-brand); border-color: transparent;
  color: #fff; box-shadow: var(--glow); }
.lb-chip.active b { color: #fff; }

.lb-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
  gap: 12px; margin-bottom: 30px; }
.lb-card { border: var(--hairline) solid var(--border); border-radius: var(--radius);
  background-color: var(--panel); background-image: var(--grad-card);
  padding: 13px 15px; cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }
.lb-card:hover { border-color: var(--brand); box-shadow: var(--glow); }
.lb-head { display: flex; align-items: center; gap: 10px; }
.lb-live { display: inline-flex; align-items: center; gap: 6px;
  color: var(--good); font-size: var(--fs-2xs); font-weight: 800;
  letter-spacing: .12em; text-transform: uppercase; }
.lb-sit { color: var(--text-mute); font-size: var(--fs-xs); }
.lb-league { margin-left: auto; color: var(--text-faint); font-size: var(--fs-2xs);
  font-weight: 800; letter-spacing: .1em; }
.lb-score { display: grid; grid-template-columns: 1fr auto auto auto 1fr;
  align-items: center; gap: 12px; margin: 12px 0 10px; }
.lb-team { display: flex; flex-direction: column; align-items: center; gap: 5px; }
.lb-team em { font-style: normal; color: var(--text-dim); font-size: var(--fs-xs);
  font-weight: 700; }
.lb-score > b { font-family: var(--font-mono); font-size: var(--fs-3xl);
  font-weight: 600; }
.lb-mid { min-width: 48px; display: grid; place-items: center; }
.lb-diamond { width: 48px; height: 40px; }
/* .lb-lines/.lb-cell retired 2026-08-11: the card grew the render's
   per-team SPREAD | TOTAL | ML grid (.lb-table, later in this file). */

@media (max-width: 760px) {
  /* The tab bar replaces the top-bar hamburger: same drawer, thumb reach. */
  .menu-toggle { display: none !important; }
  .tabbar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 56;
    display: grid; grid-template-columns: repeat(5, 1fr); align-items: end;
    padding: 6px 4px calc(8px + env(safe-area-inset-bottom));
    background: var(--topbar-bg); backdrop-filter: blur(16px);
    border-top: var(--hairline) solid var(--border-soft); }
  .tb-item { display: flex; flex-direction: column; align-items: center; gap: 3px;
    border: 0; background: transparent; color: var(--text-mute); cursor: pointer;
    font: inherit; font-size: var(--fs-2xs); font-weight: 700; padding: 4px 2px;
    letter-spacing: .02em; position: relative; text-transform: none; }
  .tb-item svg { width: 21px; height: 21px; }
  .tb-item.active { color: var(--brand-2); }
  .tb-center { color: var(--text-dim); }
  .tb-center-disc { display: grid; place-items: center; width: 46px; height: 46px;
    margin-top: -22px; border-radius: 50%; background: var(--grad-brand);
    color: #fff; box-shadow: var(--glow); }
  .tb-center-disc svg { width: 21px; height: 21px; }
  .tb-center.active { color: var(--text); }
  .tb-center-disc { position: relative; }
  .tb-badge { position: absolute; top: -3px; right: -3px;
    min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px;
    display: inline-grid; place-items: center; background: var(--bad);
    color: #fff; font-size: var(--fs-2xs); font-weight: 800; }
  .tb-badge[hidden] { display: none; }
  body { padding-bottom: 84px; }        /* room for the bar */
  body.menu-open .sidebar { z-index: 60; }

  /* RE-ORDERED 2026-08-11 (Ethan: "top bets, the stadiums, then roi")
     — the order lives in the DOM now, one order on every screen, so
     the phone-only flex reordering retired. */
  .tp-strip { grid-auto-columns: 86vw; }
  .hero-games .game-card { min-width: 66vw; }
  .lb-grid { grid-template-columns: 1fr; }
}

/* ---------------- Rocket Radar, given direction (2026-08-10) -----------
   Ethan: "Meme coin page has no direction. I have no clue what I'm
   looking at. It just shows coin names with 'rocket' next to it."
   The hero says what the page is; the watchlist says why each coin is
   listed; the legend says how the three systems work. */
.mc-hero { display: flex; flex-direction: row; gap: 18px 26px;
  align-items: center; flex-wrap: wrap;
  padding: 18px 20px; margin-bottom: 12px; background-image: var(--grad-card); }
.mc-hero-words { flex: 1 1 420px; min-width: 0; }
.mc-hero-title { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  font-family: var(--font-display); font-size: var(--fs-2xl); font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; }
.mc-hero-live { color: var(--good); font-family: var(--font-sans);
  font-size: var(--fs-2xs); font-weight: 800; letter-spacing: .12em;
  text-transform: uppercase; display: inline-flex; align-items: center; gap: 6px; }
.mc-hero-words p { margin: 8px 0 0; color: var(--text-body); font-size: var(--fs-sm);
  line-height: var(--lh-body); max-width: var(--measure); }
.mc-hero-words b { color: var(--text); }
.mc-hero-stats { display: grid; grid-template-columns: repeat(5, auto);
  gap: 8px 18px; align-items: center; align-content: center;
  flex: 0 0 auto; margin-left: auto; }
.mc-hs { text-align: center; }
.mc-hs b { display: block; font-family: var(--font-mono); font-size: var(--fs-xl); }
.mc-hs span { display: block; margin-top: 2px; color: var(--text-mute);
  font-size: var(--fs-2xs); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; white-space: nowrap; }

.mc-legend { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px; margin-top: 12px; }
.mc-leg b { display: block; font-size: var(--fs-sm); margin-bottom: 3px; }
.mc-leg span { color: var(--text-mute); font-size: var(--fs-xs);
  line-height: var(--lh-snug); display: block; }

/* The terminal: watchlist beside the chart, stacked on phones. */
.mc-term { display: grid; grid-template-columns: 348px minmax(0, 1fr);
  gap: 14px; align-items: start; }
.mc-watch { border: var(--hairline) solid var(--border); border-radius: var(--radius);
  background: var(--panel); overflow: hidden; }
.mc-watch-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 11px 13px; border-bottom: var(--hairline) solid var(--border-soft);
  font-size: var(--fs-xs); font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-dim); }
.mc-watch-head span { color: var(--text-mute); font-weight: 600;
  letter-spacing: .02em; text-transform: none; }
.mc-picker { display: flex; flex-direction: column; max-height: 640px;
  overflow-y: auto; overflow-x: hidden; scrollbar-width: thin;
  padding: 4px; gap: 2px; background: none; border: 0; }
.mc-pick { display: flex; align-items: center; gap: 10px; width: 100%;
  border: 0; background: transparent; cursor: pointer; text-align: left;
  color: var(--text-dim); font: inherit; padding: 8px 9px;
  border-radius: var(--radius-sm);
  transition: background var(--dur-fast) var(--ease-out); }
.mc-pick:hover { background: var(--panel-2); }
.mc-pick.active { background: var(--grad-chip); box-shadow: inset 2px 0 0 var(--brand); }
.mc-rank { flex: 0 0 16px; color: var(--text-mute); font-family: var(--font-mono);
  font-size: var(--fs-2xs); text-align: right; }
.mc-tile { display: inline-grid; place-items: center; flex: 0 0 auto;
  border-radius: var(--radius-sm); font-weight: 800; font-size: var(--fs-2xs);
  letter-spacing: .04em; }
.mc-pick-id { min-width: 0; flex: 1 1 auto; line-height: var(--lh-tight); }
.mc-pick-id b { display: block; color: var(--text); font-size: var(--fs-sm);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mc-pick-id em { display: block; font-style: normal; color: var(--text-mute);
  font-size: var(--fs-2xs); white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.mc-pick-nums { flex: 0 0 auto; text-align: right; line-height: var(--lh-tight); }
.mc-pick-nums b { display: block; font-family: var(--font-mono);
  font-size: var(--fs-xs); color: var(--text); font-weight: 600; }
.mc-pick-pct { font-family: var(--font-mono); font-size: var(--fs-2xs); }
.mc-mom { flex: 0 0 58px; display: inline-flex; align-items: center; gap: 6px; }
.mc-mom i { display: block; flex: 1 1 auto; height: 4px; border-radius: 2px;
  background: var(--text-mute); max-width: 34px; min-width: 2px; }
.mc-mom b { font-family: var(--font-mono); font-size: var(--fs-2xs); font-weight: 600; }
.mc-riskb { display: inline-block; border: var(--hairline) solid; border-radius: 999px;
  padding: 2px 8px; font-size: var(--fs-2xs); font-weight: 800;
  letter-spacing: .06em; white-space: nowrap; }
.mc-dock-id { display: flex; align-items: center; gap: 9px; }

@media (max-width: 1000px) {
  .mc-term { grid-template-columns: minmax(0, 1fr); }
  .mc-picker { max-height: 300px; }
  .mc-hero-stats { grid-template-columns: repeat(5, 1fr); gap: 6px; width: 100%; }
}

/* Parlay Mode rides directly under High Confidence — the first card
   carries the margin-top:auto that pins the pair to the sidebar's
   bottom; the second must not repeat it or they drift apart. */
#sb-pz { margin-top: 0; }

/* The venue photo covers the drawn scene when present; every chip and
   badge rides above it. Missing files remove themselves (onerror). */
.venue-photo { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 1; }
.stadium-wrap .status-badge, .stadium-wrap .top-game-tag,
.stadium-wrap .game-time-chip, .stadium-wrap .gc-picks { z-index: 3; }
/* The live base diamond, over the photo. Bottom-left is the one corner
   nothing else on this card claims: the LIVE badge is top-left, the time
   and weather chips are top-right, and the picks chip is bottom-right. */
.venue-runners { position: absolute; left: 8px; bottom: 8px; z-index: 3; }
/* THE OVERLAY IS A CHIP, NOT ART. `.gp-art svg { width: 100% }` stretches
   every SVG in the hero to the container so the drawn ballpark fills it —
   and the runner overlay is an SVG in the same box, so on the game page it
   inflated to three bases the size of the outfield. Caught on the live
   game page rather than the strip card, because the card's box is small
   enough that the difference did not read as wrong. Pinned in both
   containers at the size it is drawn at. */
.gp-art .venue-runners, .stadium-wrap .venue-runners {
  width: 40px; height: 32px; max-height: 32px; flex: none;
}
/* Occupied bases carry a class because `.stadium g rect { fill: none }`
   hollows out every rect in the art and a presentation attribute loses to
   any CSS rule — the exact trap the drawn version documented, and this
   overlay lives close enough to inherit it. */
.venue-runners .on-base { fill: #ffd24a; }

/* The UFC card banner — one octagon render per event, hash-picked in
   renderUFC. Wide and shallow so it sets the scene without pushing the
   numbers below the fold. */
.ufc-banner { display: block; width: 100%; aspect-ratio: 3 / 1;
  object-fit: cover; border-radius: var(--radius-lg);
  border: var(--hairline) solid var(--border); margin-bottom: 14px; }

/* ---- Render-sheet pass (Ethan, 2026-08-11): My Bets cards, Results
   analytics, Live line grid, Props market tiles, art weather chip. ---- */

/* My Bets — status chips, sport filter, bet cards. */
.mbc-filters { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 2px 0 12px; }
.mbc-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.mbc-chip { background: var(--panel-2); color: var(--text-mute);
  border: var(--hairline) solid var(--border); border-radius: var(--radius);
  padding: 6px 13px; font-size: var(--fs-sm); font-weight: 600; cursor: pointer; }
.mbc-chip.active { background: var(--brand); border-color: var(--brand);
  color: #fff; }
.mbc-sport { margin-left: auto; }
.mbc-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(330px, 100%), 1fr));
  gap: 10px; margin-bottom: 18px; }
.mbc { background: var(--panel); border: var(--hairline) solid var(--border);
  border-radius: var(--radius-lg); padding: 12px 14px; }
.mbc.open { border-left: 3px solid var(--warn); }
.mbc.win { border-left: 3px solid var(--good); }
.mbc.loss { border-left: 3px solid var(--bad); }
.mbc.push { border-left: 3px solid var(--border); }
.mbc-head { display: flex; align-items: baseline; gap: 10px; }
.mbc-title { flex: 1; min-width: 0; font-weight: 700; overflow-wrap: anywhere; }
.mbc-odds { color: var(--brand-2); }
.mbc-tag { font-size: var(--fs-xs); letter-spacing: .06em; }
.mbc-legs { margin: 6px 0 0; padding-left: 18px; color: var(--text-body);
  font-size: var(--fs-sm); }
.mbc-legs li { margin: 2px 0; }
.mbc-sub { color: var(--text-mute); font-size: var(--fs-xs); margin-top: 5px; }
.mbc-foot { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-top: 9px; font-size: var(--fs-sm); }
.mbc-stake { color: var(--text-mute); }
.mbc-outcome { color: var(--text-mute); }
.mbc-foot .mb-actions { margin-left: auto; display: flex; gap: 5px; }

/* Results — the range-scoped analytics block. */
.ra-ranges { float: right; display: inline-flex; gap: 4px; }
.ra-range { background: none; color: var(--text-mute);
  border: var(--hairline) solid var(--border); border-radius: var(--radius-sm);
  padding: 3px 9px; font-size: var(--fs-xs); font-weight: 700; cursor: pointer; }
.ra-range.active { background: var(--brand); border-color: var(--brand); color: #fff; }
.ra-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;
  margin: 0 0 10px; }
.ra-stat { background: var(--panel); border: var(--hairline) solid var(--border);
  border-radius: var(--radius); padding: 10px 14px; }
.ra-stat .k { display: block; color: var(--text-mute); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: .07em; }
.ra-stat .v { font-size: var(--fs-xl); }
.ra-stat .v.good { color: var(--good); }
.ra-stat .v.bad { color: var(--bad); }
.ra-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;
  margin: 10px 0 18px; }

/* Live board — the SPREAD | TOTAL | ML grid, one row per team. */
.lb-table { display: grid; grid-template-columns: 1fr auto auto auto;
  gap: 3px 16px; margin-top: 10px; padding-top: 9px;
  border-top: var(--hairline) solid var(--border); }
.lb-table .lb-th { color: var(--text-mute); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: .07em; text-align: right; }
.lb-table .lb-th:first-child { text-align: left; }
.lb-table .lb-tm { color: var(--text-mute); font-size: var(--fs-sm); }
.lb-table b { text-align: right; font-family: var(--font-mono);
  font-size: var(--fs-sm); }

/* Live line track — how the market has moved since first pitch. The chart
   is a plain sparkline; everything here is the frame around it. */
.lb-track { margin-top: 10px; padding-top: 9px;
  border-top: var(--hairline) solid var(--border); }
.lb-track-head { display: flex; justify-content: space-between; align-items: baseline;
  gap: 10px; color: var(--text-mute); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: .07em; }
.lb-track .spark { width: 100%; height: 52px; margin-top: 4px; display: block; }
.lb-move { font-family: var(--font-mono); letter-spacing: 0; }
.lb-move.up { color: var(--good); }
.lb-move.down { color: var(--bad); }
.lb-move.flat { color: var(--text-mute); }
/* Readable tier, not faint: this sentence says whose number the chart is
   showing, which is the difference between reading the market's opinion
   and reading ours. Text a reader must read is never demoted. */
.lb-track-foot { margin: 3px 0 0; color: var(--text-mute);
  font-size: var(--fs-xs); line-height: var(--lh-snug); }

/* Props — one tile per market priced tonight. */
.pm-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 9px; margin: 4px 0 16px; }
.pm-tile { background: var(--panel); border: var(--hairline) solid var(--border);
  border-radius: var(--radius); padding: 11px 13px; text-align: left;
  cursor: pointer; color: var(--text); }
.pm-tile.active { border-color: var(--brand); background: var(--panel-2); }
.pm-tile .pm-name { display: block; font-weight: 700; font-size: var(--fs-sm); }
.pm-tile .pm-count { color: var(--text-mute); font-size: var(--fs-xs); }
.pm-tile.hue0 { border-left: 3px solid var(--brand); }
.pm-tile.hue1 { border-left: 3px solid var(--good); }
.pm-tile.hue2 { border-left: 3px solid var(--cyan); }
.pm-tile.hue3 { border-left: 3px solid var(--warn); }
.pm-tile.hue4 { border-left: 3px solid var(--plum); }
.pm-tile.hue5 { border-left: 3px solid var(--bad); }

/* The temp · wind chip on the card art, opposite the time chip. */
.game-wx-chip { position: absolute; left: 10px; bottom: 10px; z-index: 3;
  background: rgba(7, 7, 12, 0.72); color: var(--text-mute);
  border-radius: var(--radius-sm); padding: 3px 8px; font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums; }

/* Home — the render's profit/loss headline over the perf tiles. */
.perf-day { display: flex; align-items: baseline; gap: 10px; margin: 2px 0 8px; }
.perf-day .k { color: var(--text-mute); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: .07em; }
.perf-day b { font-size: var(--fs-3xl); font-weight: 800; letter-spacing: -.5px; }

@media (max-width: 640px) {
  .ra-stats, .ra-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 7px; }
  .mbc-list { grid-template-columns: 1fr; }
}

/* ---- Desktop render-sheet pass (Ethan, 2026-08-11): event page, live
   rows, analytics rail, props polish, avatar chip, bankroll goal. ---- */

/* Event page — lines + insights side by side above the stats. */
.gp-panel-title { font-weight: 800; letter-spacing: .02em; margin-bottom: 8px; }
.gp-panel-sub { color: var(--text-mute); font-weight: 400; font-size: var(--fs-sm); }
.gp-row { display: grid; grid-template-columns: 1fr; gap: 12px; margin: 14px 0; }
@media (min-width: 900px) { .gp-row { grid-template-columns: 1fr 1fr; } }
.gp-lines .lb-table { border-top: none; padding-top: 0; }
.gp-note-list { margin: 0; padding-left: 18px; color: var(--text-body);
  font-size: var(--fs-sm); }
.gp-note-list li { margin: 4px 0; }
/* The hero art holds the photo the same way a strip card does. */
.gp-art { position: relative; overflow: hidden; }
.gp-art .venue-photo { border-radius: var(--radius); }

/* Live board on desktop — the render's full-width rows: score story on
   the left, the line grid on the right. */
@media (min-width: 900px) {
  .lb-grid { grid-template-columns: 1fr; }
  .lb-card { display: grid; grid-template-columns: 1fr auto; column-gap: 26px;
    align-items: center; }
  .lb-card .lb-head { grid-column: 1 / -1; }
  .lb-card .lb-table { border-top: none; padding-top: 0; margin-top: 0;
    min-width: 300px; }
}

/* Analytics on desktop — chart left, the three stats as a right rail. */
.ra-main { display: grid; grid-template-columns: 1fr; gap: 10px; margin-bottom: 10px; }
.ra-main .rec-chart { margin-bottom: 0; }
@media (min-width: 1000px) {
  .ra-main { grid-template-columns: 1fr 235px; align-items: stretch; }
  .ra-main .ra-stats { grid-template-columns: 1fr; margin: 0; align-content: start; }
}
.ra-alltime { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 640px) { .ra-alltime { grid-template-columns: repeat(2, 1fr); } }

/* Props tiles grow into the render's desktop grid. */
@media (min-width: 900px) {
  .pm-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 11px; }
  .pm-tile { padding: 15px 16px; }
  .pm-tile .pm-name { font-size: var(--fs-md); }
  .pm-tile.active { box-shadow: var(--glow); }
}

/* The avatar chip — initials when signed in, no balance beside it ever. */
.nav-acct-init { display: inline-grid; place-items: center; width: 22px; height: 22px;
  border-radius: 50%; background: var(--brand); color: #fff;
  font-size: var(--fs-xs); font-weight: 800; letter-spacing: .02em; }

/* Bankroll — the goal bar and the logged-P&L chart. */
.bk-goal, .bk-curve { margin-top: 12px; }
.bk-goal-row { display: flex; align-items: center; gap: 8px; }
.bk-goal-row input { width: 130px; }
.bk-goal-pct { margin-left: auto; font-weight: 800; color: var(--brand-2); }
.bk-goal-bar { height: 8px; border-radius: var(--radius-sm); background: var(--panel-2);
  overflow: hidden; margin: 10px 0 8px; }
.bk-goal-bar i { display: block; height: 100%; background: var(--brand); }
.bk-curve-net { font-size: var(--fs-3xl); font-weight: 800; letter-spacing: -.5px;
  margin: 2px 0 6px; }
.bk-curve-span { display: flex; justify-content: space-between;
  color: var(--text-mute); font-size: var(--fs-xs); margin-top: 4px; }

/* ---- The rail, per Ethan's crop (2026-08-11): dot insights, the rich
   LIVE NOW box, and the desk in the slip's slot. ---- */
.rail-list li { list-style: none; position: relative; padding-left: 16px; }
.rail-list li::before { content: ""; position: absolute; left: 0; top: 0.55em;
  width: 6px; height: 6px; border-radius: 50%; background: var(--good); }
.rail-see { float: right; color: var(--brand-2); font-size: var(--fs-xs);
  font-weight: 700; text-decoration: none; }
.rlv { border: var(--hairline) solid var(--border); border-radius: var(--radius);
  padding: 10px 11px; margin-top: 9px; background: var(--panel-2); }
.rlv-head { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; }
.rlv-live { color: var(--bad); font-size: var(--fs-xs); font-weight: 800;
  letter-spacing: .08em; display: inline-flex; align-items: center; gap: 4px; }
.rlv-body { display: flex; align-items: center; gap: 10px; }
.rlv-teams { flex: 1; min-width: 0; display: grid; gap: 5px; }
.rlv-row { display: flex; align-items: center; gap: 7px; font-size: var(--fs-sm); }
.rlv-row span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.rlv-row b { font-family: var(--font-mono); font-size: var(--fs-lg); }
.rlv-side { text-align: center; }
.rlv-side em { display: block; color: var(--text-mute); font-style: normal;
  font-size: var(--fs-xs); line-height: var(--lh-tight); }
.rlv-lines { display: flex; justify-content: space-between; gap: 10px;
  margin-top: 8px; padding-top: 7px;
  border-top: var(--hairline) solid var(--border-soft);
  color: var(--text-mute); font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums; }
.rail-paper { margin-left: 6px; }
.rail-rec { display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  padding: 7px 0; border-bottom: var(--hairline) solid var(--border-soft); }
.rail-rec:last-of-type { border-bottom: none; }
.rail-rec-t { flex: 1 1 100%; font-size: var(--fs-sm); font-weight: 600; }
.rail-rec em { color: var(--text-mute); font-style: normal; font-size: var(--fs-xs); }

/* ---- The Zeno-style sidebar (Ethan, 2026-08-12): model entries with
   their spec sub-line, and the weather page's rows. ---- */
.sb-model { align-items: center; }
.sb-model-words { display: flex; flex-direction: column; line-height: var(--lh-tight); }
.sb-model-words b { font-size: var(--fs-sm); }
.sb-model-words span { color: var(--text-mute); font-size: var(--fs-xs); }
.wx-row { display: flex; align-items: center; gap: 14px; padding: 11px 16px;
  border-bottom: var(--hairline) solid var(--border-soft); cursor: pointer; }
.wx-row:last-child { border-bottom: none; }
.wx-teams { display: flex; align-items: center; gap: 6px; font-weight: 700;
  min-width: 150px; }
.wx-teams em { color: var(--text-mute); font-style: normal; }
.wx-park { flex: 1; min-width: 0; color: var(--text-mute); font-size: var(--fs-sm);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wx-chips { margin: 0; }
@media (max-width: 640px) {
  .wx-row { flex-wrap: wrap; gap: 8px; }
  .wx-park { flex-basis: 100%; }
}

/* --- Prop analysis (Ethan's render, 2026-08-16) -------------------------
   Face on the left, chart on the right, stat row under it. The card the
   render shows is this block inside the prop card that already carries
   the grade, the reasons and the stake — those are not repeated here. */
/* ONE COLUMN. The identity panel is gone — every card that renders this
   block already introduced the player in its own head, so a 208px column
   repeating the face and name was both the duplication Ethan flagged and
   the reason the plot was squeezed. The chart takes the full card. */
.prop-analysis { margin-top: 10px; border-top: var(--hairline) solid var(--border-soft);
                 padding-top: 10px; }
/* The tint is the card's verdict at a glance — whether recent form backs
   the side we took. It is a stripe on the leading edge, which is why it
   may be heavier than a hairline; see propAnalysis for what it means. */
.prop-analysis.pa-good { border-left: 3px solid var(--good); padding-left: 12px; }
.prop-analysis.pa-bad { border-left: 3px solid var(--bad); padding-left: 12px; }
/* The prop bar: what the chart below is measured against, on one line.
   It replaces the identity column — the reader already knows who this
   is, what they need here is the threshold and the price. */
.pa-bar { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px 20px;
          margin-bottom: 8px; }
.pa-cell { min-width: 0; overflow-wrap: anywhere;
           display: inline-flex; align-items: baseline; gap: 6px; }
.pa-who2 { font-size: var(--fs-2xs); letter-spacing: .06em; color: var(--text-mute);
           text-transform: uppercase; margin-left: auto; }
.pa-m { font-weight: 700; font-size: var(--fs-sm); text-transform: uppercase; }
.pa-big { font-weight: 800; font-size: var(--fs-lg); font-variant-numeric: tabular-nums; }
.pa-big.pos { color: var(--good); } .pa-big.neg { color: var(--bad); }
.pa-head { display: flex; justify-content: space-between; align-items: center;
           font-size: var(--fs-2xs); letter-spacing: .08em; color: var(--text-mute); }
.pa-legend { display: inline-flex; align-items: center; gap: 5px; }
.pa-legend i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }
.pa-legend i.ok { background: var(--good); }
.pa-legend i.no { background: var(--bad); margin-left: 8px; }
.pa-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px;
            border-top: var(--hairline) solid var(--border-soft); padding-top: 8px; margin-top: 4px; }
.pa-tile { text-align: center; min-width: 0; }
.pa-v { font-weight: 800; font-size: var(--fs-lg); font-variant-numeric: tabular-nums; }
.pa-v.pos { color: var(--good); } .pa-v.neg { color: var(--bad); }
.pa-k { font-size: var(--fs-2xs); letter-spacing: .08em; color: var(--text-mute); }
/* The chart sizes from its own viewBox ratio rather than a fixed height:
   a hard height letterboxes it inside its column on a narrow screen —
   the chart shrinks to fit the width and the leftover height is dead
   band. Ratio-sizing removes the band at every width. */
.pa-chart svg { display: block; width: 100%; height: auto; }
/* The bar wraps on a phone rather than scrolling; the plot is already
   full width at every size now that nothing shares the row with it. */
@media (max-width: 720px) { .pa-who2 { margin-left: 0; } }


/* --- One prop, its own page (Ethan, 2026-08-13) ------------------------
   Reached by clicking a recommended pick. The card says it is a door the
   way the game cards do — a cursor and a lift on hover — and the rows
   below use the same agate idiom as the rest of the site's tables. */
/* Every surface that lists a prop is a door, per Ethan 2026-08-13:
   "anywhere we show a prop, we need to offer the option to click on it".
   Cards, dashboard rows, edge-board rows and Top Picks tiles all wear the
   same class, so the affordance is identical wherever it appears — and
   only where `propOpenable` says there is a page behind it. */
.openable { cursor: pointer; }
.card.openable:hover { border-color: var(--brand-2); }
.openable:focus-visible { outline: 2px solid var(--brand-2); outline-offset: -2px; }
/* The list rows are not cards, so they say "clickable" the way rows do. */
.ls-row.openable:hover, div.openable:not(.card):not(.tp-card):hover {
  background: var(--panel-3); }
.tp-card.openable:hover { border-color: var(--brand-2); }
.pp-card { padding: 14px; }
.pp-log, .pp-form {
  display: grid; align-items: center; gap: 10px;
  padding: 9px 12px; border-bottom: var(--hairline) solid var(--border-soft);
}
.pp-log { grid-template-columns: 62px 96px minmax(0, 1fr) 62px 84px; }
.pp-form { grid-template-columns: minmax(0, 1fr) 70px 120px; }
.pp-log:last-child, .pp-form:last-child { border-bottom: none; }
.pp-when, .pp-fk { color: var(--text-mute); font-size: var(--fs-xs); font-weight: 700; }
.pp-opp { font-size: var(--fs-sm); font-weight: 700; }
/* --text-mute, not --text-faint: the faint token is on an 8-use budget
   precisely so every new column cannot argue itself into being the
   exception. Size carries this column's rank instead of colour. */
.pp-extra { color: var(--text-mute); font-size: var(--fs-2xs); min-width: 0;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pp-val, .pp-fv { text-align: right; font-weight: 800; font-size: var(--fs-md);
                  font-variant-numeric: tabular-nums; }
.pp-hit, .pp-fd { text-align: right; font-size: var(--fs-2xs); font-weight: 800;
                  letter-spacing: .06em; font-variant-numeric: tabular-nums; }
.pp-val.pos, .pp-hit.pos, .pp-fd.pos { color: var(--good); }
.pp-val.neg, .pp-hit.neg, .pp-fd.neg { color: var(--bad); }
.pp-fv { color: var(--text); }
/* On a phone the context column is the one that gives: who and how much
   are the two facts that must survive. */
@media (max-width: 720px) {
  .pp-log { grid-template-columns: 54px minmax(0, 1fr) 54px 72px; }
  .pp-extra { display: none; }
}


/* --- Preregistered tests (Ethan, 2026-08-13) ---------------------------
   Deliberately not styled like a result. A test still collecting gets a
   progress bar and a count and NO percentage, because a running total
   that reads like a finding is the thing preregistration exists to
   prevent. */
.pr-row { display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center; gap: 12px; padding: 10px 14px;
  border-bottom: var(--hairline) solid var(--border-soft); }
.pr-row:last-child { border-bottom: none; }
.pr-claim { min-width: 0; font-size: var(--fs-sm); font-weight: 700; }
.pr-claim .sub { display: block; font-weight: 400; color: var(--text-mute);
  font-size: var(--fs-2xs); margin-top: 2px; }
.pr-prog { display: block; width: 120px; height: 6px; border-radius: 3px;
  background: var(--panel-3); overflow: hidden; }
.pr-prog i { display: block; height: 100%; background: var(--brand); }
.pr-n { font-size: var(--fs-2xs); color: var(--text-mute); font-weight: 700;
  font-variant-numeric: tabular-nums; white-space: nowrap; }

/* The slate-horizon note: when the board above is not tonight. Amber rail
   because it is a condition that is live, not a link to a page. */
.slate-horizon { display: flex; align-items: baseline; gap: 7px;
  margin: 0 0 12px; padding: 8px 12px; border-left: 3px solid var(--warn);
  background: var(--panel-3); border-radius: 0 var(--radius) var(--radius) 0;
  color: var(--text-body); font-size: var(--fs-sm); line-height: var(--lh-snug); }
.slate-horizon b { color: var(--text); }
.slate-jump { color: var(--brand); font-weight: 700; text-decoration: none;
  white-space: nowrap; }
.slate-jump:hover { text-decoration: underline; }

/* Preseason starter scan — what each side has historically done. */
/* OWN ROW, for the same reason `.pre-line` takes one: `.pre-game` is a
   six-column grid and this landed in the 1fr cell, which was fine while
   the text read "21 att" and overlapped the kickoff time once it read
   "played 4 of 4, 21 att when he did". */
.pre-scan { grid-column: 1 / -1;
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin-top: 6px; padding-top: 6px;
  border-top: var(--hairline) solid var(--border); }
.pre-scan-side { display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-sm); }
.pre-scan-n { color: var(--text-mute); font-size: var(--fs-xs); }
/* Who we expect to see, set apart from the club's record beside it. */
.pre-scan-qb { color: var(--text-dim); font-size: var(--fs-xs); }

/* --- Rankings, side by side ---------------------------------------------
   The table is agate: many columns of small numbers, so tabular figures
   and a horizontal scroll of its own rather than a page that scrolls
   sideways. The DISAGREEMENT strip above it is the part with colour,
   because it is the part worth reading. */
/* A PANE, NOT A PAGE. Two hundred rows below a disagreement strip pushes
   everything else off the bottom; the board is for scrolling THROUGH at a
   draft table, so it scrolls inside itself and the header stays put. */
.rank-scroll { overflow: auto; margin-top: 10px; max-height: 62vh; }
.rank-table { width: 100%; border-collapse: collapse;
  font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.rank-table th, .rank-table td { padding: 5px 10px; text-align: right;
  border-bottom: var(--hairline) solid var(--border-soft); white-space: nowrap; }
.rank-table th { color: var(--text-mute); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: .05em; font-weight: 700;
  position: sticky; top: 0; background: var(--panel); z-index: 1; }
.rank-table th:first-child, .rank-table td:first-child { text-align: left; }
.rank-name { color: var(--text); }
/* Names read left, numbers read right — and the name is not always the
   FIRST column (the lineup table leads with the slot), so the alignment
   rides the class. Qualified with the table, or `.rank-table td` wins on
   specificity and every name stays right-aligned. */
.rank-table .rank-name { text-align: left; }
.rank-none { color: var(--text-mute); }
.rank-fight-head { margin-top: 12px; font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: .06em; color: var(--text-mute); }
.rank-fight-rows { display: flex; flex-direction: column; gap: 4px;
  margin-top: 6px; }
.rank-fight-row { display: flex; flex-wrap: wrap; align-items: center;
  gap: 8px; font-size: var(--fs-sm); }
.rank-spread { margin-left: auto; color: var(--text-mute);
  font-size: var(--fs-xs); font-variant-numeric: tabular-nums; }
.rank-import { margin-top: 14px; padding-top: 10px;
  border-top: var(--hairline) solid var(--border); }
.rank-import summary { cursor: pointer; font-size: var(--fs-sm);
  color: var(--text-dim); }
.rank-help { color: var(--text-mute); font-size: var(--fs-xs); }
.rank-import textarea { width: 100%; margin-top: 8px; padding: 9px 12px;
  background: var(--panel-2); color: inherit; font-family: inherit;
  font-size: var(--fs-sm); border: var(--hairline) solid var(--border);
  border-radius: var(--radius); }
.rank-btns { display: flex; align-items: center; gap: 10px; margin-top: 8px;
  font-size: var(--fs-xs); color: var(--text-mute); }
.ff-dim { opacity: .5; }

/* --- Accounts: email and password ----------------------------------------
   Wraps rather than scrolls: on a phone the email, the password and the
   two buttons stack, which is the only arrangement where all four stay
   tappable at 390px. */
.acct-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 10px; }
.acct-row input { flex: 1 1 200px; min-width: 0; padding: 9px 12px;
  background: var(--panel-2); color: inherit; font-family: inherit;
  font-size: var(--fs-sm); border: var(--hairline) solid var(--border);
  border-radius: var(--radius); }
.acct-btns { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.acct-note { margin-top: 8px; color: var(--text-mute);
  font-size: var(--fs-xs); }
.acct-more { margin-top: 12px; padding-top: 10px;
  border-top: var(--hairline) solid var(--border); }
.acct-more summary { cursor: pointer; font-size: var(--fs-sm);
  color: var(--text-dim); }
/* The age + terms acknowledgment on sign-up. A real label so the whole
   line is a tap target — a bare checkbox on a phone is a 16px target
   people miss and then blame the form for. */
.acct-confirm { display: flex; align-items: flex-start; gap: 9px;
  margin-top: 10px; cursor: pointer; font-size: var(--fs-xs);
  color: var(--text-mute); line-height: var(--lh-snug); }
.acct-confirm input { margin-top: 2px; flex: 0 0 auto; }

/* --- Terms and Privacy ---------------------------------------------------
   Plain documents. Measure capped for reading rather than filling the
   window: legal text is the one thing on this site nobody skims, and a
   140-character line is where people lose their place. */
.legal { max-width: var(--measure); margin: 32px auto 64px;
  padding: 0 20px; }
.legal h1 { font-size: var(--fs-2xl); margin-bottom: 4px; }
.legal h2 { font-size: var(--fs-lg); margin: 28px 0 8px; }
.legal p, .legal li { font-size: var(--fs-sm); line-height: var(--lh-body);
  color: var(--text-dim); }
.legal li { margin: 5px 0; }
.legal-date { color: var(--text-mute); font-size: var(--fs-xs); }
.legal-draft { padding: 12px 14px; margin-bottom: 24px;
  border-left: 3px solid var(--warn); background: var(--panel);
  border-radius: var(--radius); }
.legal-todo { color: var(--warn); }
.legal-table { width: 100%; border-collapse: collapse; margin: 10px 0; }
.legal-table th, .legal-table td { text-align: left; vertical-align: top;
  padding: 8px 10px 8px 0; font-size: var(--fs-sm);
  border-bottom: var(--hairline) solid var(--border-soft); }
.legal-table th { width: 34%; color: var(--text); font-weight: 600; }
.legal-table td { color: var(--text-dim); }
.legal-back { margin-top: 36px; }

.bill-state { font-size: var(--fs-sm); color: var(--text-dim); }
.bill-state.on { color: var(--good); font-weight: 600; }

/* --- In-page anchor targets ---------------------------------------------
   The masthead is position:sticky at 64px (measured, both 1280 and 390),
   so a block jumped to with block:"start" lands with its heading BEHIND
   it — which reads as the link having gone somewhere wrong, on a link
   whose whole job is getting you somewhere. Both the browser's native
   hash jump and scrollIntoView honour scroll-margin-top.

   Add an id here when a new in-page link appears; `revealAnchor` in
   app.js is what makes such a link work at all when its target sits in a
   sub-tab. */
#preseason-board { scroll-margin-top: 76px; }

/* --- League desk: the lineup and the trades ------------------------------ */
.ld-total { font-size: var(--fs-xl); font-weight: 800;
  font-variant-numeric: tabular-nums; }
.ld-swaps { margin-top: 12px; padding-top: 10px;
  border-top: var(--hairline) solid var(--border); }
.ld-trade { padding: 10px 0;
  border-bottom: var(--hairline) solid var(--border-soft); }
.ld-trade:last-of-type { border-bottom: 0; }
.ld-trade-head { display: flex; flex-wrap: wrap; align-items: center;
  gap: 8px; }
.ld-trade-body { margin-top: 6px; font-size: var(--fs-sm); }
/* The Yahoo connect row. Wraps rather than scrolls: on a phone the code
   box and its button end up stacked, which is the only arrangement where
   both stay tappable. */
.ld-connect { display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 8px; }
.ld-connect .rank-input { flex: 1 1 220px; min-width: 0; }
.rank-input { padding: 8px 11px; background: var(--panel-2); color: inherit;
  font-family: inherit; font-size: var(--fs-sm);
  border: var(--hairline) solid var(--border);
  border-radius: var(--radius); }

/* --- Pick-by-pick advice, in the draft room ------------------------------ */
.dk-advice { margin-top: 12px; padding-top: 10px;
  border-top: var(--hairline) solid var(--border); }
.dk-advice-head { display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 10px; font-size: var(--fs-xs); text-transform: uppercase;
  letter-spacing: .06em; color: var(--text-mute); }
.dk-window { margin-left: auto; text-transform: none; letter-spacing: 0; }
.dk-take { display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 8px; font-size: var(--fs-lg); }
.dk-wait { display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 8px; font-size: var(--fs-sm); }
.dk-advice-note { margin-top: 8px; color: var(--text-mute);
  font-size: var(--fs-xs); }
.pre-scan-note { margin-left: auto; color: var(--text-mute);
  font-size: var(--fs-xs); font-style: italic; }
/* The MARKET's number, and styled to read as one: tabular figures, muted,
   no accent colour anywhere. A posted price given the same emphasis as a
   recommendation is how a page starts making claims it has not earned. */
/* SPANS THE WHOLE ROW. `.pre-game` is a six-column grid; without this the
   line lands in whichever cell is left over — the wide 1fr when there is
   no starter scan, and the narrow trailing `auto` when there is, where it
   stacked one number per line and looked like a different component. */
.pre-line { grid-column: 1 / -1;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px;
  margin-top: 4px; font-size: var(--fs-xs); color: var(--text-mute);
  font-variant-numeric: tabular-nums; }
.pre-line-lab { text-transform: uppercase; letter-spacing: .06em;
  font-size: var(--fs-xs); opacity: .8; }
.pre-line-n { color: var(--text); }
.pre-line-books { margin-left: auto; }
