/*
 * LoW Pick'Em — stylesheet.
 *
 * ★ THE PALETTE IS VERBATIM FROM THE DESIGN MOCKUP.
 *   Source: /opt/agents/server/pickem-mockups/mockup-desktop.html (md5 b6a100c342186635688303f72140441f)
 *   Every token value below is copied, not re-derived. If the mockup is revised, re-copy the
 *   token block rather than hand-editing hexes here.
 *
 * ★ THEME MECHANISM — and where the DEFAULT lives.
 *   Three layers, exactly as the mockup defines them:
 *     :root                                    the DARK palette + color-scheme: dark
 *     :root[data-theme="light"]                the LIGHT palette, explicit override
 *     @media (prefers-color-scheme: light)     light for a viewer whose OS says light,
 *       :root:not([data-theme])                 as long as they have not chosen explicitly
 *
 *   So the shipped behaviour is FOLLOW THE OS, FALL BACK TO DARK. That is the mockup's own
 *   default and is deliberately unchanged here.
 *
 *   The brief for this wave said "default LIGHT", which conflicts with the file the render
 *   compare runs against. That conflict is unresolved at time of writing, so the default is
 *   expressed in exactly ONE place and is a one-line change either way:
 *     - to force a default regardless of the OS, set DEFAULT_THEME in public/app.js
 *     - to make LIGHT the fallback in CSS instead, swap the token blocks of `:root` and
 *       `:root[data-theme="light"]` and flip the media query to prefers-color-scheme: dark
 *   Nothing else in this file or the app reads the theme, so there is no second site to miss.
 *
 * ★ COLOUR LITERALS. Everything this app added references a custom property, so a restyle is
 *   a token swap rather than a rewrite. The inherited mockup CSS is not quite pure, and it is
 *   kept verbatim rather than tidied, because a mockup edited on the way in stops being the
 *   thing a render-compare compares against. The six exceptions, all from the mockup:
 *     line  70  rgba(245,185,61,.06)  the accent wash behind <body>
 *     line 141  rgba(0,0,0,.2)        .pill hairline
 *     line 188  #fff                  .pill text over a team-colour swatch
 *     line 266  rgba(0,0,0,.2)        .seg.on shadow
 *     line 283  #fff                  .ti text over a team-colour swatch
 *     line 329  rgba(0,0,0,.2)        .rseg.on shadow
 *   The two #fff sit on API-supplied team colours rather than on a themed surface, so they do
 *   not track the palette by design. The four rgba() are shadow/wash alphas over black.
 *
 *   The team-colour swatches in the markup are likewise data, not design — they arrive from
 *   the API, not from this file.
 */

:root{
  color-scheme: dark;
  --bg:#0A0D13; --surface:#141922; --surface-2:#1D2430; --raise:#232C3A;
  --line:#28303D; --line-soft:#1E2530;
  --ink:#EAEEF5; --muted:#8B95A7; --faint:#5C6576;
  --accent:#F5B93D; --accent-deep:#E0A21F; --accent-ink:#1A1206;
  --win:#35C88A; --loss:#F0605A; --pending:#8B95A7;
  --win-bg:rgba(53,200,138,.12); --loss-bg:rgba(240,96,90,.12);
  --shadow:0 10px 30px rgba(0,0,0,.45);
  --mono:ui-monospace,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
  --ui:system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
}
:root[data-theme="light"]{
  color-scheme: light;
  --bg:#EEF1F6; --surface:#FFFFFF; --surface-2:#F5F7FB; --raise:#EAEEF4;
  --line:#DCE2EC; --line-soft:#E7ECF3;
  --ink:#111722; --muted:#5A6474; --faint:#939EAE;
  --accent:#C98A12; --accent-deep:#B37B0C; --accent-ink:#FFFFFF;
  --win:#149A63; --loss:#CE3A34; --pending:#5A6474;
  --win-bg:rgba(20,154,99,.10); --loss-bg:rgba(206,58,52,.10);
  --shadow:0 10px 28px rgba(30,45,70,.12);
}
@media (prefers-color-scheme: light){
  :root:not([data-theme]){
    color-scheme: light;
    --bg:#EEF1F6; --surface:#FFFFFF; --surface-2:#F5F7FB; --raise:#EAEEF4;
    --line:#DCE2EC; --line-soft:#E7ECF3;
    --ink:#111722; --muted:#5A6474; --faint:#939EAE;
    --accent:#C98A12; --accent-deep:#B37B0C; --accent-ink:#FFFFFF;
    --win:#149A63; --loss:#CE3A34; --pending:#5A6474;
    --win-bg:rgba(20,154,99,.10); --loss-bg:rgba(206,58,52,.10);
    --shadow:0 10px 28px rgba(30,45,70,.12);
  }
}
*{box-sizing:border-box}
html,body{margin:0;padding:0}
body{
  background:
    radial-gradient(120% 40% at 50% -6%, rgba(245,185,61,.06), transparent 60%),
    var(--bg);
  color:var(--ink); font-family:var(--ui);
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  min-height:100vh; overflow-x:hidden;
}
@media (prefers-reduced-motion: reduce){*{animation:none!important;transition:none!important}}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:6px}

/* ================= APP SHELL ================= */
.app{display:grid;grid-template-columns:220px 1fr;min-height:100vh}

/* ---------- sidebar ---------- */

/* ══════════════ RESERVING THE iOS NOTCH (env(safe-area-inset-top)) ══════════════
 *
 * THE BUG: installed to an iPhone home screen, the wordmark sat behind the status bar clock
 * and clipped. `apple-mobile-web-app-capable=yes` makes the home-screen app TRUE standalone,
 * and with `viewport-fit=cover` on the shells the web content then fills the display
 * edge-to-edge — including the strip behind the status bar. Nothing reserved room for it.
 *
 * ★ status-bar-style="black" DOES NOT SAVE IT. That controls what the bar looks like, not
 *   whether the content runs under it; viewport-fit=cover puts the content there regardless.
 *   And it stays "black" on purpose — see the note in the shells' <head>: "black-translucent"
 *   forces WHITE status-bar text always, which is unreadable over this app's LIGHT palette
 *   (--bg #EEF1F6). An opaque black strip reads correctly in both themes.
 *
 * ★★ WHICH ELEMENT IS AT THE TOP DEPENDS ON THE BREAKPOINT, AND THAT IS THE WHOLE SUBTLETY.
 *    The DOM is .app > (.side, .colwrap > .topbar), and the grid re-lays it:
 *
 *      >900px   .app is TWO COLUMNS. .side is column 1 and .topbar is the top of column 2,
 *               so BOTH start at y=0 and both need the inset. (An installed iPad.)
 *      <=900px  .app is TWO ROWS. .side is ROW 1 — and on a phone .side is the horizontal
 *               band that CARRIES THE WORDMARK. .topbar is inside .colwrap, which is row 2,
 *               so it never reaches the top of the viewport.
 *
 *    So on a phone the inset belongs to .side ALONE. Putting it on .topbar there would leave
 *    the logo exactly as clipped as before and open a stray gap above the week label instead
 *    — a fix that looks right in the stylesheet and changes nothing on the phone. The mobile
 *    .topbar rule below re-states `padding` as a shorthand, which resets padding-top and
 *    drops the inset on its own; that is deliberate, and there is a test on it.
 *
 * ★ NOT SCOPED TO standalone, AND IT DOES NOT NEED TO BE. env(safe-area-inset-top) is 0 on
 *   Android, on desktop, in an ordinary Safari tab and on any phone without a notch, so these
 *   declarations are inert everywhere except the case they exist for. Scoping them to
 *   (display-mode: standalone) would add a second thing to keep in step for no behaviour.
 *
 * ★ IT SURVIVES STICKINESS. .topbar is position:sticky;top:0 inside .colwrap: padding is part
 *   of the sticky box, so the clearance rides with it and holds both at rest and when the bar
 *   pins on scroll. .side is position:static on mobile and never scrolls at all.
 *
 * ★ EACH env() CARRIES AN EXPLICIT `0px` FALLBACK. Without one, a UA that supports env()
 *   but leaves the variable undefined makes the whole declaration invalid at computed-value
 *   time — which drops padding-top to 0 and pulls the header UP, the opposite of the fix and
 *   worse than never having touched it. The fallback makes the failure mode "unchanged".
 *
 * ★ THE LITERAL IN EACH calc() MUST TRACK THE `padding` SHORTHAND ON THE LINE ABOVE IT. CSS
 *   cannot read its own shorthand back, so this is a duplicated number in three places —
 *   test_structural.js pins each pair so the two cannot drift apart silently.
 *
 * ★ REVERSAL: delete the two `padding-top:calc(...)` declarations below and the third in the
 *   mobile .side rule. Nothing else changed.
 */
.side{
  position:sticky;top:0;align-self:start;height:100vh;
  background:var(--surface);border-right:1px solid var(--line);
  display:flex;flex-direction:column;gap:6px;padding:20px 14px;
  padding-top:calc(20px + env(safe-area-inset-top, 0px));   /* notch; tracks the 20px above */
}
.wordmark{display:flex;align-items:center;gap:9px;padding:6px 8px 18px}
/* The mark beside the wordmark. Was three CSS bars, which read as a bar chart — a wrong
   signal on a football app. Coloured through the accent token via currentColor, so it follows
   the palette like every other icon. */
.logomark{width:20px;height:20px;flex:0 0 auto;color:var(--accent)}
.wordmark b{font-size:18px;font-weight:800;letter-spacing:-.01em}
.wordmark b span{color:var(--accent)}

.navlabel{font-size:9.5px;text-transform:uppercase;letter-spacing:.18em;color:var(--faint);
  font-weight:700;padding:2px 10px 8px}
.navb{
  display:flex;align-items:center;gap:11px;padding:11px 12px;border-radius:11px;
  background:transparent;border:1px solid transparent;cursor:pointer;color:var(--muted);
  font-family:var(--ui);font-size:13.5px;font-weight:700;letter-spacing:.01em;text-align:left;width:100%;
  transition:background .15s,color .15s
}
.navb svg{width:19px;height:19px;flex:0 0 auto}
.navb:hover{background:var(--surface-2);color:var(--ink)}
.navb.on{color:var(--accent);background:color-mix(in srgb,var(--accent) 10%, transparent);
  border-color:color-mix(in srgb,var(--accent) 28%, transparent)}
.side .spacer{flex:1 1 auto}
.sidefoot{font-size:10.5px;color:var(--faint);line-height:1.5;padding:10px 10px 2px;border-top:1px solid var(--line-soft)}

/* ---------- powered-by credit ----------
   A de-emphasised footer attribution: the same micro-caps type as .navlabel and .eyebrow, so
   it reads as a credit rather than competing with the LoW Pick'Em wordmark. */
/* Sits under the wordmark in the sidebar column, and beside it when .side becomes a row at
   the mobile breakpoint — no separate mobile rule needed, the container does the work. */
.poweredby{display:flex;align-items:center;gap:8px;flex-wrap:nowrap;
  margin:0 0 16px;padding:0 8px;
  font-size:9.5px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
/* Two short words that must stay two short words: wrapped, "POWERED / BY" reads as broken. */
.poweredby .pb-label{white-space:nowrap}
.poweredby img{height:14px;width:auto;display:block;flex:0 0 auto}

/* ★ THE BRAND MARK DOES NOT FOLLOW THE THEME, and must not be made to. Its fills are brand
     colours, not tokens, so there is no token to swap and a CSS filter would invent a colour
     the brand does not have. What IS available is two official variants of the same lockup:

       accent-logo-white.png  #FFFFFF lettering + #EB9034 mark   19.45:1 on --bg #0A0D13
       accent-logo-black.png  #111111 lettering + #EB9034 mark   16.68:1 on --bg #EEF1F6

     Using either one on both palettes fails badly in one of them: the white lettering scores
     1.13:1 on the light background — not faded, INVISIBLE, leaving an orange dot beside the
     words "Powered by". Sized on HEIGHT alone, because the two crops are 5.48:1 and 5.25:1 —
     close but not identical, so a fixed width would stretch one of them.

     Mirrors the three-layer mechanism the palette itself uses, so an explicit choice still
     beats the operating system's. */
.poweredby .brand-light{display:none}
:root[data-theme="light"] .poweredby .brand-dark{display:none}
:root[data-theme="light"] .poweredby .brand-light{display:block}
@media (prefers-color-scheme: light){
  :root:not([data-theme]) .poweredby .brand-dark{display:none}
  :root:not([data-theme]) .poweredby .brand-light{display:block}
}

/* ---------- mobile bottom nav (see the RESPONSIVE block for the design it comes from) ----------
   Hidden by default: above the breakpoint .side IS the navigation, and the two must never
   both be on screen. Everything that makes this a bottom bar lives in the media query. */
.nav{display:none}

/* ---------- top bar ---------- */
.colwrap{display:flex;flex-direction:column;min-width:0}
.topbar{
  position:sticky;top:0;z-index:20;
  display:flex;align-items:center;gap:14px;
  padding:14px 26px;border-bottom:1px solid var(--line-soft);
  /* Column 2 starts at y=0 too, so the notch note above .side applies here as well. This is
     the >900px case only: the mobile rule re-states the shorthand and drops it. */
  padding-top:calc(14px + env(safe-area-inset-top, 0px));
  background:linear-gradient(180deg, color-mix(in srgb,var(--accent) 5%, var(--surface)), var(--surface));
  backdrop-filter:saturate(1.1)
}
.weeklabel{display:flex;align-items:baseline;gap:9px}
.weeklabel .wk{font-size:14px;font-weight:800;letter-spacing:.01em}
.weeklabel .season{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.14em}
.lockchip{
  display:inline-flex;align-items:center;gap:6px;font-family:var(--mono);font-size:11.5px;
  color:var(--accent);background:color-mix(in srgb,var(--accent) 12%, transparent);
  border:1px solid color-mix(in srgb,var(--accent) 34%, transparent);
  padding:6px 11px;border-radius:999px;font-variant-numeric:tabular-nums
}
/* ★ display:inline-flex above OUTRANKS the user agent's [hidden]{display:none}, and this
   chip is unwired — nothing in app.js fills or toggles it — so the hidden attribute in
   the markup is all that keeps it off the header. Same companion rule as .weekpick,
   .notice, .screen and .nav .navb. */
.lockchip[hidden]{display:none}
.lockchip svg{width:12px;height:12px}
.topbar .push{flex:1 1 auto}
.themebtn{
  font-family:var(--ui);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);background:transparent;border:1px solid var(--line);
  padding:7px 12px;border-radius:999px;cursor:pointer
}
.themebtn:hover{color:var(--ink);border-color:var(--accent)}
.avatar{
  width:34px;height:34px;border-radius:50%;flex:0 0 auto;
  background:conic-gradient(from 210deg,var(--accent),var(--accent-deep));
  color:var(--accent-ink);font-weight:800;font-size:13px;
  display:grid;place-items:center;border:1px solid rgba(0,0,0,.2)
}

/* ---------- main / screens ---------- */
.main{padding:26px;display:flex;justify-content:center}
.screen{width:100%;max-width:1080px;display:none}
.screen.active{display:block;animation:fade .28s ease both}
@keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}

.eyebrow{font-size:10.5px;text-transform:uppercase;letter-spacing:.18em;color:var(--faint);
  font-weight:700;margin:2px 2px 14px;display:flex;align-items:center;gap:10px}
.eyebrow .r{color:var(--muted);letter-spacing:.02em;font-weight:600;text-transform:none;font-size:11px}
.h1{font-size:20px;font-weight:800;letter-spacing:-.01em;margin:0 0 3px}
.sub1{font-size:12.5px;color:var(--muted);margin:0 0 18px}

/* ================= PICKS ================= */
.picksgrid{display:grid;grid-template-columns:1fr 312px;gap:22px;align-items:start}
.gamegrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:13px}

/* game card (reused) */
.card{
  background:var(--surface-2);border:1px solid var(--line);border-radius:15px;
  overflow:hidden;transition:border-color .18s
}
.card:hover{border-color:color-mix(in srgb,var(--accent) 22%, var(--line))}
.card.locked{opacity:.86}
.card .meta{display:flex;align-items:center;justify-content:space-between;
  padding:9px 13px;border-bottom:1px solid var(--line-soft)}
.kick{font-family:var(--mono);font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums;
  display:flex;align-items:center;gap:6px}
.kick .lg{color:var(--faint);text-transform:uppercase;letter-spacing:.1em;font-size:9.5px}
/* A game in progress. Brighter than a kickoff time and than a final, because it is the one
   line on the card that is changing while you look at it. Both tokens track the palette, so
   this reads correctly in light and dark without a second definition. */
.kick.inplay{color:var(--ink);font-weight:600}
.kick.inplay::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--win);
  flex:0 0 auto}
.status{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.1em;
  padding:3px 8px;border-radius:6px}
.status.open{color:var(--accent);background:color-mix(in srgb,var(--accent) 13%,transparent)}
.status.final{color:var(--muted);background:var(--raise)}
.status.lockstat{color:var(--muted);background:var(--raise);display:inline-flex;gap:5px;align-items:center}
.status.lockstat svg{width:10px;height:10px}

.matchup{display:flex;align-items:stretch}
.teams{flex:1 1 auto;display:flex;flex-direction:column;min-width:0}
.team{
  display:flex;align-items:center;gap:11px;padding:11px 13px;cursor:pointer;
  border-bottom:1px solid var(--line-soft);transition:background .15s
}
.team:last-child{border-bottom:none}
.team:hover{background:var(--raise)}
.team .ti{width:26px;height:26px;border-radius:7px;flex:0 0 auto;display:grid;place-items:center;
  font-size:11px;font-weight:800;color:#fff;font-family:var(--mono)}
/* The team crest, inside the SAME 26px box the initials use, so a card that has one and a
   card that does not line up. `contain` rather than `cover`: ESPN serves 500x500 transparent
   PNGs and some are wide wordmarks, which `cover` would crop the sides off. */
.team .ti .tlogo{width:100%;height:100%;object-fit:contain;display:block}
.team .nm{flex:1 1 auto;min-width:0}
.team .nm b{font-size:14px;font-weight:700;display:block;line-height:1.15}
.team .nm .sub{font-size:10.5px;color:var(--faint)}
/* ★ THE NCAAF AP #N CHIP, an accent pill before the team name (NCAAF only; NFL has no poll).
   Inline so it rides the name line and wraps with it in a narrow column; sized like the small
   status chip so a card with a rank and one without still line up. */
.rankbadge{display:inline-flex;align-items:center;vertical-align:middle;margin-right:5px;
  font-family:var(--ui);font-size:9.5px;font-weight:800;letter-spacing:.02em;
  font-variant-numeric:tabular-nums;color:var(--accent);
  background:color-mix(in srgb,var(--accent) 13%,transparent);
  border-radius:999px;padding:1px 5px;line-height:1.35}
.team .ln{font-family:var(--mono);font-size:13px;font-weight:600;color:var(--muted);
  font-variant-numeric:tabular-nums;letter-spacing:.02em}
.team .ln.fav{color:var(--ink)}
.team .pickdot{width:20px;height:20px;border-radius:50%;border:2px solid var(--line);flex:0 0 auto;
  display:grid;place-items:center}
.team.picked{background:color-mix(in srgb,var(--accent) 9%, transparent)}
.team.picked .pickdot{border-color:var(--accent);background:var(--accent)}
.team.picked .pickdot::after{content:"";width:7px;height:7px;border-radius:50%;background:var(--accent-ink)}
.team.picked .nm b{color:var(--ink)}
.team.winner .ln{color:var(--win)}
/* ★ THE GAME'S WINNER — a badge, not a colour shift, so it is obvious rather than noticed.
 * Distinct from .res.win/.res.loss below, which answer whether the PLAYER'S PICK was right. */
/* Your Card's per-pick result: did MY pick win. Its own class, because the two other marks in
 * this app answer different questions — .res.win/.loss is the same fact in the confidence
 * column, and .wintag is which TEAM covered, which is not about this player at all. */
.yc-res{font-family:var(--ui);font-size:12px;font-weight:800;line-height:1;flex:0 0 auto}
.yc-res.win{color:var(--win)}
.yc-res.loss{color:var(--loss)}

/* ★ IT FLOWS INSIDE .nm, IT IS NOT A ROW ITEM. As a flex child of .team this competed with a
   wrapping team name and the score, which is what mashed the card on a phone. There is
   deliberately no `flex` declaration left: .nm is a plain block, so this is inline content
   sitting after the Home/Away line, free to wrap onto its own line in a narrow column and
   unable to take width from the name or the number. */
.wintag{display:inline-flex;align-items:center;gap:3px;margin:3px 0 0 6px;vertical-align:middle;
  font-family:var(--ui);font-size:9.5px;font-weight:750;letter-spacing:.06em;
  text-transform:uppercase;color:var(--win);background:var(--win-bg);
  border:1px solid var(--win);border-radius:999px;padding:2px 7px;line-height:1.35}
.team.winner .nm b{color:var(--win)}

.confcol{
  flex:0 0 62px;border-left:1px solid var(--line-soft);display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:3px;background:
  linear-gradient(180deg,transparent, color-mix(in srgb,var(--accent) 5%, transparent))
}
.confcol .cl{font-size:8.5px;text-transform:uppercase;letter-spacing:.12em;color:var(--faint)}
.confval{font-family:var(--mono);font-size:26px;font-weight:700;color:var(--accent);
  font-variant-numeric:tabular-nums;line-height:1}
.confcol.empty .confval{color:var(--faint)}
.confcol .res{font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:.08em;margin-top:2px}
.res.win{color:var(--win)} .res.loss{color:var(--loss)}

/* Your Card sticky panel */
.yourcard{position:sticky;top:82px;background:var(--surface);border:1px solid var(--line);
  border-radius:16px;box-shadow:var(--shadow);overflow:hidden}
.yc-h{display:flex;align-items:center;justify-content:space-between;padding:14px 15px 12px;
  border-bottom:1px solid var(--line-soft)}
.yc-h b{font-size:14px;font-weight:800;letter-spacing:.01em}
.yc-count{font-family:var(--mono);font-size:12px;color:var(--accent);font-variant-numeric:tabular-nums;
  background:color-mix(in srgb,var(--accent) 12%,transparent);border:1px solid color-mix(in srgb,var(--accent) 30%,transparent);
  padding:3px 9px;border-radius:999px}
.yc-body{padding:14px 15px}
.tray{background:var(--surface-2);border:1px solid var(--line);border-radius:14px;
  padding:12px 13px;display:flex;flex-direction:column;gap:10px}
.tray .th{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}
.tray .th span{font-size:11.5px;color:var(--muted);line-height:1.35}
.tray .th b{font-family:var(--mono);font-size:12px;color:var(--ink);white-space:nowrap}
.chips{display:flex;gap:7px;flex-wrap:wrap}
.chip{
  width:30px;height:30px;border-radius:9px;display:grid;place-items:center;
  font-family:var(--mono);font-size:14px;font-weight:600;font-variant-numeric:tabular-nums;
  background:var(--raise);color:var(--faint);border:1px solid var(--line);
  text-decoration:line-through;text-decoration-color:var(--faint)
}
.chip.open{color:var(--accent-ink);background:var(--accent);border-color:var(--accent);
  text-decoration:none;box-shadow:0 3px 10px color-mix(in srgb,var(--accent) 40%, transparent)}
.yc-lines{margin:14px 0 4px;display:flex;flex-direction:column;gap:2px}
.yc-line{display:flex;align-items:center;justify-content:space-between;padding:7px 2px;
  border-bottom:1px solid var(--line-soft);font-size:12px}
.yc-line:last-child{border-bottom:none}
.yc-line span{color:var(--muted)}
.yc-line b{font-family:var(--mono);font-variant-numeric:tabular-nums;color:var(--ink)}
.yc-line b.warn{color:var(--accent)}
.btn{
  width:100%;border:none;border-radius:13px;padding:14px;font-family:var(--ui);
  font-size:14.5px;font-weight:800;letter-spacing:.02em;cursor:pointer;margin-top:14px;
  background:var(--accent);color:var(--accent-ink);box-shadow:0 6px 18px color-mix(in srgb,var(--accent) 34%,transparent)}
.btn:hover{background:var(--accent-deep)}
/* ★ THE COMPANION RULE, same trap as .notice, .screen, .nav .navb and .weekpick: an author
   rule setting `display` outranks the user agent's [hidden]{display:none}, so the moment .btn
   grows a display the Save button would stop hiding and nothing would look wrong in the CSS.
   Declared rather than relied upon — renderCard() drives this button from .hidden. */
#savePicks[hidden]{display:none}
.btn.ghost{background:transparent;color:var(--muted);border:1px solid var(--line);box-shadow:none;font-weight:700}
.btn .sub{font-weight:600;opacity:.75;font-size:12px}

/* ================= STANDINGS ================= */
.potline{display:flex;align-items:center;justify-content:space-between;background:var(--surface-2);
  border:1px solid var(--line);border-radius:13px;padding:14px 18px;margin-bottom:16px}
.potline .lbl{font-size:11px;text-transform:uppercase;letter-spacing:.13em;color:var(--muted)}
.potline .amt{font-family:var(--mono);font-size:22px;font-weight:700;color:var(--accent);font-variant-numeric:tabular-nums}
.potline .r{text-align:right}
.potline .r .s{font-size:11px;color:var(--faint);font-family:var(--mono);font-variant-numeric:tabular-nums}

.segwrap{display:inline-flex;background:var(--surface-2);border:1px solid var(--line);
  border-radius:11px;padding:3px;margin-bottom:16px;gap:3px}
.seg{text-align:center;padding:8px 18px;border-radius:8px;font-size:12px;font-weight:700;
  color:var(--muted);cursor:pointer;letter-spacing:.01em;user-select:none}
.seg.on{background:var(--raise);color:var(--ink);box-shadow:0 1px 4px rgba(0,0,0,.2)}

.tablewrap{overflow-x:auto;background:var(--surface-2);border:1px solid var(--line);border-radius:15px}
table.tbl{width:100%;border-collapse:collapse;min-width:620px}
table.tbl thead th{
  font-size:10px;text-transform:uppercase;letter-spacing:.13em;color:var(--faint);font-weight:700;
  text-align:left;padding:13px 16px;border-bottom:1px solid var(--line);background:var(--surface-2);
  position:sticky;top:0
}
table.tbl th.num,table.tbl td.num{text-align:right;font-variant-numeric:tabular-nums;font-family:var(--mono)}
/* Sortable columns. The pointer is the only cue a <th> gives that it does anything, and the
   arrow marks which column the board is currently ordered by. The focus ring is the global
   :focus-visible rule, so a keyboard user can see where they are. */
table.tbl thead th.sortable{cursor:pointer;user-select:none}
table.tbl thead th.sortable:hover{color:var(--ink)}
table.tbl thead th[aria-sort=ascending],table.tbl thead th[aria-sort=descending]{color:var(--accent)}
.sortind{margin-left:5px;font-size:9px;letter-spacing:0}
table.tbl tbody td{padding:12px 16px;border-bottom:1px solid var(--line-soft);font-size:13.5px;vertical-align:middle}
table.tbl tbody tr:last-child td{border-bottom:none}
table.tbl tbody tr:hover td{background:var(--raise)}
.rank{font-family:var(--mono);font-size:13px;font-weight:700;color:var(--faint);font-variant-numeric:tabular-nums;width:34px}
tr.lead .rank{color:var(--accent)}
.player{display:flex;align-items:center;gap:11px;min-width:0}
.pill{width:30px;height:30px;border-radius:9px;flex:0 0 auto;display:grid;place-items:center;
  font-weight:800;font-size:12px;color:#fff;font-family:var(--mono)}
.player b{font-size:14px;font-weight:700;line-height:1.2}

/* ── a standings name is TWO lines, always ──
 *
 * First name on line one, everything after it on line two. The point is uniform row height:
 * long names used to wrap and short ones did not, so the board was a ladder of two heights.
 *
 * ★ min-height IS THE WHOLE MECHANISM. A one-token player renders an EMPTY .pn-last, and an
 *   empty block box is zero pixels tall — without a reserved line their rows would be short
 *   and the board uneven again, just with the other half of the names. 1.2em matches the
 *   line-height above so a filled line and a reserved one are exactly the same size.
 *
 * ★ nowrap stops a two-word surname ("Jo Smith") re-wrapping to a THIRD line in a narrow
 *   frozen column, which would break the guarantee this rule exists to make. The columns are
 *   auto-width, so a long name widens its column rather than being clipped; the ellipsis is
 *   only there for the case where something upstream does constrain the width.
 *
 * ★ REVERSAL: delete this block and playerName()/splitName() in app.js, and put
 *   `el('b', null, r.name)` back at the two render sites.
 */
.pname{display:block;min-width:0}
.pn-first,.pn-last{display:block;line-height:1.2;min-height:1.2em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rec{font-family:var(--mono);font-size:12px;color:var(--faint);font-variant-numeric:tabular-nums}
.cov{font-family:var(--mono);font-size:12.5px;color:var(--muted);font-variant-numeric:tabular-nums;text-align:right}
.ptsn{font-family:var(--mono);font-size:15px;font-weight:700;color:var(--ink);font-variant-numeric:tabular-nums;text-align:right}
.movew{font-family:var(--mono);font-size:12px;text-align:center;width:34px}
.movew.up{color:var(--win)} .movew.dn{color:var(--loss)} .movew.eq{color:var(--faint)}
tr.me td{background:color-mix(in srgb,var(--accent) 8%, transparent)}
.youtag{font-size:8.5px;font-weight:800;color:var(--accent-ink);background:var(--accent);
  padding:1px 5px;border-radius:5px;margin-left:7px;letter-spacing:.06em;vertical-align:middle}
.stand.hidden{display:none}

/* ── the week picker (By Week scope) ── */
.weekpick{display:flex;align-items:center;gap:9px;margin:0 0 14px}
/* ★ display:flex above OUTRANKS the user agent's [hidden]{display:none} — author origin
   beats it outright — so syncWeekPicker() setting .hidden would have no visual effect and
   the picker would show under Overall and All-Time. Same companion rule as .notice,
   .screen and .nav .navb, each added for this same trap. */
.weekpick[hidden]{display:none}
.weekpick .wplabel{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.1em;
  color:var(--faint)}

/* ── the season grid ──
   The grid is wide by nature — one column per week — so it scrolls INSIDE its wrapper and
   never makes the page body scroll sideways. The player column is sticky, so a row stays
   identifiable once it has been scrolled; it carries an opaque background of its own, or the
   week cells would slide visibly underneath it. */
.gridwrap{overflow-x:auto}
table.tbl.grid{min-width:520px}
.tbl.grid th.gname,.tbl.grid td.gname{position:sticky;left:0;z-index:2;background:var(--surface-2);
  min-width:158px}
.tbl.grid thead th.gname{background:var(--surface-2)}
.tbl.grid tbody tr:hover td.gname{background:var(--raise)}
.tbl.grid tr.me td.gname{background:color-mix(in srgb,var(--accent) 8%, var(--surface-2))}
.tbl.grid th.wk,.tbl.grid td.wk{text-align:right;font-family:var(--mono);font-size:12.5px;
  font-variant-numeric:tabular-nums;min-width:46px}
.tbl.grid th.wk.live{color:var(--accent)}
.tbl.grid td.wk.none{color:var(--faint)}
.tbl.grid th.gwins,.tbl.grid td.gwins,.tbl.grid th.gtotal,.tbl.grid td.gtotal,
.tbl.grid th.gstat,.tbl.grid td.gstat{
  text-align:right;font-family:var(--mono);font-variant-numeric:tabular-nums}
.tbl.grid td.gtotal{font-weight:700;color:var(--ink)}
.tbl.grid td.gstat{font-weight:500;color:var(--muted)}
.tbl.grid td.gwins{color:var(--accent);font-weight:700}

/* ── Rank Journey: an Overall row expands inline to a per-week "overall place" chart ── */
.tbl.grid tr.rjrow{cursor:pointer}
.tbl.grid tr.rjrow:hover td.gname,
.tbl.grid tr.rjrow.open td.gname{background:var(--raise)}
.rjcaret{margin-left:6px;color:var(--muted);font-size:11px;display:inline-block;
  transition:transform .2s ease}
.tbl.grid tr.rjrow.open .rjcaret{transform:rotate(180deg);color:var(--accent)}
/* The drawer is a second table row, shown only when its opener is open. */
.rjdrawer{display:none}
.rjdrawer.open{display:table-row}
.tbl.grid td.rjcell{padding:0;background:var(--surface-2);border-bottom:1px solid var(--line)}
/* Pinned to the visible width so it never sits off-screen behind the scrolled week columns;
   JS refines the width to the exact scroll viewport where a real layout exists (see app.js). */
.rj-box{position:sticky;left:0;box-sizing:border-box;width:min(100%,92vw);
  padding:10px 14px 16px}
/* Chart on the left, the six season stat tiles on the right; they wrap (chart on top, tiles
   below) once the drawer is too narrow to hold both — which is what a phone gets. */
.rj-in{display:flex;gap:18px;flex-wrap:wrap;align-items:flex-start}
.rj-left{flex:1 1 430px;min-width:280px;max-width:480px}
.rj-right{flex:1 1 300px;min-width:250px}
.rj-cap{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:8px}
.rj-cap .rj-t{font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);
  font-weight:700}
.rj-cap .rj-sum{font-size:12.5px;color:var(--ink);font-weight:700;text-align:right}
.rj-sum .rj-up{color:var(--win)} .rj-sum .rj-dn{color:var(--loss)}
.rj-chart{background:var(--bg);border:1px solid var(--line);border-radius:12px;padding:10px 6px 4px}
.rj-svg{display:block;width:100%;height:auto}
.rj-svg text{font-family:var(--mono);font-variant-numeric:tabular-nums}
/* Season stat tiles: a 2×3 grid. Sit on --bg like the chartbox so both read as one panel. */
.rj-tiles{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}
.rj-tile{background:var(--bg);border:1px solid var(--line);border-radius:11px;padding:12px 13px;
  display:flex;flex-direction:column;justify-content:center;min-height:74px}
.rj-tile.hi{background:color-mix(in srgb,var(--accent) 12%,var(--bg));
  border-color:color-mix(in srgb,var(--accent) 45%,var(--line))}
.rj-tile .rj-k{font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);
  font-weight:700;margin-bottom:5px}
.rj-tile .rj-v{font-size:24px;font-weight:800;line-height:1;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums}
.rj-tile.hi .rj-v{color:var(--accent)}
.rj-tile .rj-v .rj-up{color:var(--win)}
.rj-tile .rj-s{font-size:11.5px;color:var(--muted);font-weight:600;margin-top:5px}
@media (prefers-reduced-motion:reduce){ .rjcaret{transition:none} }

/* The weekly high scorer, on the By Week board. */
.weekhigh{font-size:8.5px;font-weight:800;color:var(--accent-ink);background:var(--accent);
  border-radius:5px;padding:2px 6px;margin-left:7px;white-space:nowrap}
tr.wonweek .rank{color:var(--accent)}

/* ================= ADMIN ================= */
.adminnote{font-size:12px;color:var(--muted);line-height:1.55;background:var(--surface-2);
  border:1px solid var(--line);border-left:3px solid var(--accent);border-radius:10px;padding:12px 15px;margin-bottom:16px;max-width:760px}
.asub.hidden{display:none}

/* slate table */
.lineval{font-family:var(--mono);font-size:13.5px;font-weight:700;color:var(--ink);font-variant-numeric:tabular-nums;
  background:var(--raise);border:1px solid var(--line);border-radius:8px;padding:5px 10px;display:inline-flex;align-items:center;gap:7px}
.lineval.manual{color:var(--accent);border-color:color-mix(in srgb,var(--accent) 40%, transparent)}
.srcflag{font-family:var(--mono);font-size:9px;text-transform:uppercase;letter-spacing:.06em;
  padding:2px 6px;border-radius:5px;background:var(--raise);color:var(--faint)}
.srcflag.man{color:var(--accent);background:color-mix(in srgb,var(--accent) 12%,transparent)}
.sportflag{margin-left:6px;font-family:var(--mono);font-size:9px;text-transform:uppercase;letter-spacing:.06em;
  padding:1px 5px;border-radius:5px;background:var(--raise);color:var(--faint);vertical-align:middle}
.pen{width:32px;height:32px;border-radius:8px;display:inline-grid;place-items:center;background:var(--raise);
  border:1px solid var(--line);color:var(--muted);cursor:pointer}
.pen:hover{color:var(--accent);border-color:var(--accent)}
.pen svg{width:14px;height:14px}
.toggle{width:42px;height:25px;border-radius:999px;flex:0 0 auto;background:var(--raise);
  border:1px solid var(--line);position:relative;cursor:pointer;transition:background .18s;display:inline-block}
.toggle::after{content:"";position:absolute;top:2px;left:2px;width:19px;height:19px;border-radius:50%;
  background:var(--faint);transition:transform .18s,background .18s}
.toggle.on{background:color-mix(in srgb,var(--accent) 30%, transparent);border-color:var(--accent)}
.toggle.on::after{transform:translateX(17px);background:var(--accent)}
tr.slate-off td{opacity:.5}
.gcell b{font-size:13.5px;font-weight:700}
.gcell .gs{font-family:var(--mono);font-size:10.5px;color:var(--faint);display:block;margin-top:2px}

/* users table */
.uinfo{display:flex;align-items:center;gap:11px;min-width:0}
.uinfo b{font-size:13.5px;font-weight:700;line-height:1.2}
.em{font-family:var(--mono);font-size:11.5px;color:var(--faint)}
.roleseg{display:inline-flex;flex:0 0 auto;background:var(--raise);border:1px solid var(--line);
  border-radius:9px;padding:2px;gap:2px}
.rseg{padding:6px 12px;border-radius:7px;font-family:var(--ui);font-size:11px;font-weight:700;
  color:var(--muted);cursor:pointer;letter-spacing:.02em;user-select:none}
.rseg.on{background:var(--surface-2);color:var(--ink);box-shadow:0 1px 4px rgba(0,0,0,.2)}
.rseg.on[data-role="admin"]{color:var(--accent)}
.uactions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end}
.ubtn{background:var(--raise);border:1px solid var(--line);color:var(--muted);font-family:var(--ui);
  font-size:11px;font-weight:700;padding:7px 11px;border-radius:8px;cursor:pointer;letter-spacing:.01em;white-space:nowrap}
.ubtn:hover{color:var(--accent);border-color:var(--accent)}
.uconf{font-family:var(--mono);font-size:10px;color:var(--win);text-align:right;line-height:1.3;
  opacity:0;transition:opacity .18s;flex-basis:100%;margin-top:2px}
.uconf.show{opacity:1}
/* A player who has not picked this week: present on the board, visibly not in the running. */
.tbl tr.nopicks{opacity:.62}
.tbl .rec.muted{color:var(--faint);font-style:italic}
.urow.inactive{opacity:.55}
.urow.inactive .pill{filter:grayscale(1)}
.ubtn.danger:hover{color:var(--loss);border-color:var(--loss)}
.uconf .why{color:var(--muted);font-family:var(--ui);font-size:11px;line-height:1.45;
  flex-basis:100%;text-align:left;margin-bottom:6px;opacity:1}
/* The invite, sized to be read and selected by hand if the clipboard is unavailable. */
.invite-text{white-space:pre-wrap;font-family:var(--mono);font-size:11px;line-height:1.5;
  color:var(--ink);background:var(--surface-2);border:1px solid var(--line);border-radius:9px;
  padding:10px 12px;margin:6px 0 8px;flex-basis:100%;text-align:left;user-select:all}
.invite-sent{font-size:11px;color:var(--win);flex-basis:100%;text-align:left;margin-bottom:2px}
.invite-unsent{font-size:11px;color:var(--loss);flex-basis:100%;text-align:left;margin-bottom:2px}
.invite-note{font-size:10.5px;color:var(--faint);flex-basis:100%;margin-top:4px;text-align:left}
/* ---------- the week-slate panel ----------
   Mirrors .picksgrid/.yourcard deliberately: same 312px column, same sticky offset, same
   mobile collapse. Reuses .yc-h/.yc-count/.yc-body/.yc-lines outright rather than restyling
   them, so the two sticky panels in this app cannot drift apart. */
.slategrid{display:grid;grid-template-columns:1fr 312px;gap:22px;align-items:start}
.slatecard{position:sticky;top:82px;background:var(--surface);border:1px solid var(--line);
  border-radius:16px;box-shadow:var(--shadow);overflow:hidden}
.slatenote{margin:0;font-size:11px;line-height:1.45;color:var(--faint)}
/* The rule players most need and nowhere stated: picks stay editable, per game, until that
   game kicks off. Same voice as the panel's other explanatory lines. */
.picknote{margin:10px 0 0;font-size:11px;line-height:1.5;color:var(--faint)}
.picknote b{color:var(--muted);font-weight:700}
.slate-line{display:flex;align-items:center;gap:8px;justify-content:space-between;padding:8px 2px;
  border-bottom:1px solid var(--line-soft);font-size:12px}
.slate-line:last-child{border-bottom:none}
.slate-line .gm{min-width:0;display:flex;flex-direction:column;gap:2px}
.slate-line .gm b{font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.slate-line .gm span{color:var(--faint);font-size:10.5px}
/* ★ Selected but filtered out of the table. Stated, not hidden: this panel is the only place
     an admin can see what a forgotten filter is concealing before they save. */
.slate-line.offview .gm span{color:var(--accent)}
.slate-rm{flex:0 0 auto;background:var(--raise);border:1px solid var(--line);color:var(--muted);
  width:24px;height:24px;border-radius:7px;cursor:pointer;font-size:14px;line-height:1;
  display:grid;place-items:center;font-family:var(--ui)}
.slate-rm:hover{color:var(--loss);border-color:var(--loss)}

/* ---------- games in this week (published-slate controls) ---------- */
.weekgames{margin-top:30px}
.addgame{margin-top:14px}
.addgame summary{cursor:pointer;font-size:12px;font-weight:700;color:var(--muted);
  padding:8px 2px;letter-spacing:.01em;list-style:none}
.addgame summary::-webkit-details-marker{display:none}
.addgame summary:hover{color:var(--accent)}
.addgame[open] summary{color:var(--accent);margin-bottom:8px}
.activenote{font-size:11.5px;color:var(--muted);margin:-6px 0 16px;font-family:var(--mono);
  font-variant-numeric:tabular-nums}
.activenote b{color:var(--accent);font-weight:700}
.activenote .warn{color:var(--loss)}
/* ★ A PULLED LINE MUST NOT LOOK LIKE A LOCKED ONE. The locked line is the graded record; this
   is a preview of a market that moves all week. Different border, different colour, and a
   literal "CURRENT" flag beside it — mistaking one for the other is a money error. */
.lineval.current{border-color:color-mix(in srgb,var(--accent) 45%,transparent);
  color:var(--accent);background:color-mix(in srgb,var(--accent) 8%,transparent)}
.srcflag.live{color:var(--accent);background:color-mix(in srgb,var(--accent) 14%,transparent)}
/* The reason a control beside this note is greyed. Visible, because `title` is a hover
   affordance that does not exist on touch — see setControlReason() in app.js. */
.flagrow .why{color:var(--muted);font-size:11px;line-height:1.45;flex-basis:100%;margin-top:2px}
.flagrow .ubtn{margin-left:auto;white-space:nowrap}
.activenote .why,.filternote .why{color:var(--muted);font-family:var(--ui);font-size:11.5px;
  display:inline-block;margin-top:4px;line-height:1.45}

.dangerzone summary{color:var(--faint)}
.dangerzone summary:hover,.dangerzone[open] summary{color:var(--loss)}
.dangerzone .btn.ghost{border-color:var(--line);color:var(--muted)}
.dangerzone .btn.ghost:not(:disabled):hover{color:var(--loss);border-color:var(--loss)}
.dangerzone .btn.ghost:disabled{opacity:.45;cursor:not-allowed}
.gameact{display:flex;gap:7px;justify-content:flex-end;align-items:center;flex-wrap:wrap}
/* ★ "Void", never "Delete": players hold picks against this row and they are RELEASED, not
     scored a miss. The word is part of the behaviour, not a label choice. */
.voidbtn{background:var(--raise);border:1px solid var(--line);color:var(--muted);font-family:var(--ui);
  font-size:11px;font-weight:700;padding:6px 11px;border-radius:8px;cursor:pointer;white-space:nowrap}
.voidbtn:hover{color:var(--loss);border-color:var(--loss)}
.lineedit{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.lineedit .confpick{width:auto;min-width:74px}
.gm-void{color:var(--loss);font-family:var(--mono);font-size:11px}
.gm-settled{color:var(--win);font-family:var(--mono);font-size:11px}

/* The active-filter readout. Deliberately prominent enough to notice: an admin who forgets a
   range is set and sees an empty table will conclude the feed is broken. */
.filternote{font-family:var(--mono);font-size:11px;color:var(--muted);align-self:center;
  font-variant-numeric:tabular-nums}
.filternote b{color:var(--accent);font-weight:700}
/* The slate card's two actions, sized like .yourcard's #savePicks rather than by the inline
   attributes they used to carry for the old full-width bar. */
.slatecard .btn{width:100%}
.slatecard .btn.ghost{margin-top:14px}
.slatecard .btn:not(.ghost){margin-top:8px}

/* ================= RESPONSIVE ================= */
@media (max-width:900px){
  /* ★ grid-template-ROWS, and it is load-bearing. .app is `min-height:100vh` and at this
       breakpoint it collapses to ONE column, which makes .side and .colwrap two AUTO rows —
       and align-content defaults to stretch, so an auto row absorbs free space. On a short
       page (an empty week, say) the leftover viewport height was split between them and .side
       inflated into a ~100px band of bare --surface under the wordmark. It never showed on
       desktop because there the two are one row, side by side, and there is no free height to
       share. Pinning the rows makes .side content-sized and gives the slack to the content. */
  .app{grid-template-columns:1fr;grid-template-rows:auto 1fr}
  /* ★ ROW 1 — this is the band the wordmark sits in on a phone, so THIS is the element the
       status bar was clipping. The inset goes here, not on .topbar. See the notch note above
       the base .side rule. */
  .side{position:static;height:auto;flex-direction:row;flex-wrap:wrap;align-items:center;gap:6px;
    border-right:none;border-bottom:1px solid var(--line);padding:12px 16px;
    padding-top:calc(12px + env(safe-area-inset-top, 0px))}
  .wordmark{padding:0 10px 0 4px}
  .navlabel,.side .spacer,.sidefoot{display:none}
  /* ★ .side keeps the wordmark only. Its nav buttons are replaced below, not reflowed:
       mockup-mobile.html navigates from a bottom bar, and leaving these visible would put
       the same three destinations on the screen twice. */
  .side .navb{display:none}

  /* =====================================================================
     BOTTOM TAB BAR — mockup-mobile.html, a SECOND design rather than the
     desktop one at a narrow width.

     The declarations below are the mobile mockup's, verbatim. Two things are
     added because that mockup draws inside a fixed-height .phone frame where a
     flex child pins itself to the bottom, and this app scrolls a real document:

       position:fixed + inset      pins the bar to the VIEWPORT bottom, which is
                                   what `flex:0 0 auto` achieves inside .phone.
       .main padding-bottom        keeps the last card clear of the bar.

     env(safe-area-inset-bottom) is load-bearing and not decoration: without it
     the bar sits under the home indicator on a notched iPhone. It is the tell
     that this is a phone design and not a narrow desktop.
     ===================================================================== */
  .nav{flex:0 0 auto;display:flex;border-top:1px solid var(--line);background:var(--surface);
    padding:8px 6px calc(8px + env(safe-area-inset-bottom));
    position:fixed;left:0;right:0;bottom:0;z-index:30}
  .nav .navb{flex:1;background:transparent;border:none;cursor:pointer;display:flex;flex-direction:column;
    align-items:center;gap:4px;padding:6px 4px;color:var(--faint);font-family:var(--ui)}
  .nav .navb svg{width:22px;height:22px}
  .nav .navb span{font-size:10px;font-weight:700;letter-spacing:.02em}
  .nav .navb.on{color:var(--accent)}
  /* ★ .nav .navb sets display:flex, which OUTRANKS the user agent's [hidden]{display:none}.
       Without this the Commissioner tab would be visible to every player on a phone — the
       markup's `hidden` attribute would be silently ignored. */
  .nav .navb[hidden]{display:none}
  /* The desktop sidebar's hover fill has no place on a tab bar; the mobile mockup has none. */
  .nav .navb:hover{background:transparent}
  /* ★ NO safe-area-inset-top HERE, DELIBERATELY. At this breakpoint .topbar is inside
       .colwrap, which is grid ROW 2 — it sits below .side and never touches the status bar.
       This shorthand resets the base rule's padding-top, which is exactly what should happen;
       adding the inset back would open a gap above the week label and fix nothing. */
  .topbar{padding:12px 16px;flex-wrap:wrap}
  .main{padding:18px 14px}
  /* Clearance for the fixed tab bar. Re-checked when the credit moved out of this column:
     still required, because .main's own last card would otherwise sit under the bar. */
  .colwrap{padding-bottom:calc(74px + env(safe-area-inset-bottom))}
  /* A row item beside the wordmark. flex-wrap on .side means a very narrow phone drops it to
     a second line rather than crushing it; .pb-label keeps "Powered by" itself intact. */
  .poweredby{margin:0;padding:0 4px}
  .picksgrid{grid-template-columns:1fr}
  .yourcard{position:static;order:-1}
  .slategrid{grid-template-columns:1fr}
  .slatecard{position:static;order:-1}
  .gamegrid{grid-template-columns:1fr}
}

/* Avatar/initials ink: white on the JS-set hued gradient — the same "text over a non-themed
   colour" case the app's .pill/.ti already treat as a sanctioned literal. Defined here as a
   token, ahead of the additions marker below, so History's later styles reference it not a hex. */
:root{ --avatar-ink:#fff; }

/* ==========================================================================
   APP ADDITIONS — states the mockup had no reason to draw.
   A mockup shows the happy path fully populated. A running app spends real
   time empty, loading, failing and refusing, and a screen with no design for
   those states invents one at runtime. All of it is built from the same
   tokens, so a restyle still reaches it.
   ========================================================================== */

/* ---------- login ---------- */
.authpage{min-height:100vh;display:grid;place-items:center;padding:24px}
.authcard{width:100%;max-width:380px;background:var(--surface);border:1px solid var(--line);
  border-radius:16px;padding:28px 24px;box-shadow:var(--shadow)}
.authcard .wordmark{padding:0 0 18px}
.authcard h1{font-size:19px;margin:0 0 4px;letter-spacing:-.01em}
.authcard p.sub1{margin:0 0 18px}
.field{display:flex;flex-direction:column;gap:6px;margin-bottom:14px}
.field label{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
.field input{background:var(--surface-2);border:1px solid var(--line);border-radius:9px;
  padding:11px 12px;color:var(--ink);font-family:var(--ui);font-size:15px}
.field input:focus-visible{border-color:var(--accent)}

/* ---------- messages ---------- */
.notice{border-radius:10px;padding:11px 13px;font-size:13px;line-height:1.45;margin:0 0 14px;
  border:1px solid var(--line);background:var(--surface-2);color:var(--muted)}
.notice.err{border-color:var(--loss);background:var(--loss-bg);color:var(--ink)}
.notice.ok{border-color:var(--win);background:var(--win-bg);color:var(--ink)}
.notice[hidden]{display:none}

.toast{position:fixed;left:50%;bottom:22px;transform:translateX(-50%);z-index:50;
  background:var(--raise);border:1px solid var(--line);border-radius:10px;
  padding:11px 16px;font-size:13px;color:var(--ink);box-shadow:var(--shadow);
  max-width:min(560px,92vw);opacity:0;pointer-events:none;transition:opacity .18s}
.toast.show{opacity:1}
.toast.err{border-color:var(--loss);background:var(--loss-bg)}

/* ---------- empty / loading ---------- */
.empty{border:1px dashed var(--line);border-radius:12px;padding:26px 20px;text-align:center;
  color:var(--muted);font-size:13px;line-height:1.55;background:var(--surface)}
.empty b{display:block;color:var(--ink);font-size:14px;margin-bottom:5px}
.loading{color:var(--faint);font-size:13px;padding:18px 2px}

/* ---------- pick card additions ---------- */
/* The confidence value is a real control here, not a printed number. */
.confpick{background:var(--surface-2);border:1px solid var(--line);border-radius:8px;
  color:var(--ink);font-family:var(--mono);font-size:15px;font-weight:700;
  padding:6px 8px;min-width:56px;text-align:center;cursor:pointer}
.confpick:disabled{opacity:.5;cursor:not-allowed}
.card.locked .confpick{display:none}
.flagrow{display:flex;gap:8px;align-items:flex-start;padding:9px 11px;border-radius:9px;
  border:1px solid var(--loss);background:var(--loss-bg);font-size:12.5px;margin-bottom:8px}
.flagrow b{font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.04em}

/* ---------- admin slate additions ---------- */
.slateform{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end;margin-bottom:16px}
.slateform .field{margin-bottom:0;min-width:130px}
.slateform .btn{margin-top:0;width:auto;flex:0 0 auto;padding-left:16px;padding-right:16px}
.srcflag.warn{border-color:var(--loss);color:var(--loss)}

/* A screen is only "active" once its data has landed; until then it shows .loading. */
.screen[hidden]{display:none}

/* ── the modal primitive ──────────────────────────────────────────────────────────
 * ONE overlay, reused by every screen that needs a dialog (matchup today; onboarding
 * and the help agent next). Nothing below is matchup-specific except the .m* content
 * classes at the end, which are the matchup modal's own body layout.
 *
 * ★ EVERY COLOUR IS A TOKEN. A hex here would look correct in whichever theme it was
 *   written against and wrong in the other, and the palette-completeness test could not
 *   see it — that test compares token DEFINITIONS, and a hardcoded colour is a token
 *   that was never defined. test_ui.js asserts this block contains no hex literals.
 */
.modal-backdrop{position:fixed;inset:0;z-index:60;display:flex;align-items:center;
  justify-content:center;padding:18px;
  /* The scrim is DERIVED FROM A TOKEN, not a literal rgba: the app's own styles must all
     follow a restyle, and test_ui.js enforces that on everything after APP ADDITIONS. */
  background:color-mix(in srgb,var(--bg) 78%, transparent)}
.modal-backdrop[hidden]{display:none}
.modal{background:var(--surface);border:1px solid var(--line);border-radius:15px;
  box-shadow:var(--shadow);width:min(720px,100%);max-height:min(86vh,900px);
  display:flex;flex-direction:column;overflow:hidden}
.modal-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;
  padding:14px 16px;border-bottom:1px solid var(--line-soft);background:var(--surface-2)}
.modal-title{margin:0;font-size:15px;color:var(--ink);font-weight:650;line-height:1.3}
.modal-sub{margin:4px 0 0;font-family:var(--mono);font-size:11px;color:var(--muted);
  font-variant-numeric:tabular-nums}
.modal-x{background:none;border:1px solid var(--line);border-radius:9px;color:var(--muted);
  font-size:17px;line-height:1;cursor:pointer;padding:4px 9px;flex:none}
.modal-x:hover{color:var(--ink);border-color:var(--accent)}
/* ★ The BODY scrolls VERTICALLY, never the page behind it — and never SIDEWAYS. A horizontal
 *   scrollbar here was a reported bug: content that does not fit must wrap, stack or scroll
 *   inside its own container, because a modal that pans left-right hides half of itself. */
.modal-body{padding:14px 16px;overflow-y:auto;overflow-x:hidden}

/* ── the matchup modal's body: a HEAD-TO-HEAD comparison ──
 *
 * ★ ONE ROW PER STAT — away value | label | home value — so the two teams line up by
 *   construction. Two independent stat stacks drift apart the moment one team has a section
 *   the other does not, and no CSS fixes that reliably.
 *
 * ★ NOTHING HERE IS PINNED TO A FIXED WIDTH. The grid is fractional and every cell may
 *   shrink (min-width:0), which is what keeps the modal from overflowing sideways on a phone.
 */
.mvs{display:flex;flex-direction:column;gap:10px;min-width:0}
.mvs-head{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:8px;
  padding-bottom:9px;border-bottom:1px solid var(--line-soft)}
.mvs-at{font-family:var(--mono);font-size:11px;color:var(--faint);text-align:center}
.mcol{min-width:0;display:flex;flex-direction:column;align-items:center;gap:3px;text-align:center}
.mcol-side{font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
.mcol-team{font-size:12.5px;color:var(--ink);font-weight:620;line-height:1.2;
  overflow-wrap:anywhere}
/* ★ THE MODAL CREST, SIZED EXPLICITLY. `.team .ti` is a descendant selector on the pick
 *   cards and cannot reach here; without this the raw 500x500 ESPN PNG rendered at natural
 *   size, swallowed the modal and forced the horizontal scrollbar. */
.ti-sm{width:44px;height:44px;border-radius:9px;display:grid;place-items:center;flex:0 0 auto;
  font-size:14px;font-weight:800;font-family:var(--mono);color:var(--muted);
  background:var(--surface-2);border:1px solid var(--line-soft)}
.ti-sm .tlogo{width:100%;height:100%;object-fit:contain;display:block}
.mgroup{display:flex;flex-direction:column;gap:1px}
.mgroup-h{font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint);
  text-align:center;margin-bottom:2px}
.mrow{display:grid;grid-template-columns:1fr auto 1fr;align-items:baseline;gap:8px;
  padding:3px 0;border-top:1px solid var(--line-soft)}
.mgroup .mrow:first-of-type{border-top:none}
.mrow .k{font-size:10.5px;color:var(--faint);text-align:center;white-space:nowrap}
.mrow .v{min-width:0;font-family:var(--mono);font-size:12.5px;color:var(--ink);
  font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
.mrow .v[data-side="away"]{text-align:left}
.mrow .v[data-side="home"]{text-align:right}
.mrow .v .mform{display:inline-flex}
.mrow .v[data-side="home"] .mform{justify-content:flex-end}
.mform{display:flex;gap:4px;flex-wrap:wrap}
.mform span{font-family:var(--mono);font-size:10.5px;width:17px;height:17px;border-radius:5px;
  display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--line)}
.mform .w{color:var(--win);background:var(--win-bg);border-color:var(--win)}
.mform .l{color:var(--loss);background:var(--loss-bg);border-color:var(--loss)}
.mform .p{color:var(--pending)}
/* ★ The muted "why it is absent" line. An unexplained blank reads as a bug, so every section
 *   that could not be filled says so in the space it would have occupied. */
/* ★ COMPACT, but never removed. An honest "no data" line that sprawls over a phone screen
 *   crowds out the stats that DID arrive; one centred line per section says the same thing. */
.mnote{font-size:11px;color:var(--faint);font-style:italic;padding:2px 0;text-align:center}
.mpool{margin-top:12px;border:1px solid var(--line-soft);border-radius:12px;padding:10px 11px;
  background:var(--surface-2);font-size:12px;color:var(--ink)}
.mpool .mgroup-h{margin-bottom:6px}
.mpool-line{padding:2px 0;color:var(--muted)}
.mpool-line b{color:var(--ink);font-family:var(--mono);font-variant-numeric:tabular-nums}
/* Who picked that side. Small and quiet — the counts are the headline, the roster is the
 * detail — and it must wrap rather than push the strip past the modal on a phone. */
.mpool-who{font-size:11.5px;line-height:1.5;color:var(--muted);overflow-wrap:anywhere;
  padding:1px 0 5px}
.mats{margin-top:12px;border:1px solid var(--line-soft);border-radius:12px;padding:10px 11px;
  background:var(--surface-2)}
/* The ATS block reuses the same three-column comparison row. */
.mats .mrow{border-top-color:var(--line-soft)}
.modal-error{color:var(--loss);font-size:12.5px;padding:6px 0}
.modal-retry{background:var(--surface-2);border:1px solid var(--line);border-radius:9px;
  color:var(--ink);font-size:12px;cursor:pointer;padding:6px 12px;margin-top:6px}
.modal-retry:hover{border-color:var(--accent)}
/* The info control on a game card. Sits in the meta row beside the lock status, and is a real
   <button> so it is tabbable and announced — matchup info is not a pick. */
/* The matchup trigger: the WORD plus a small bar chart, in the accent colour.
 * It was a lone faint glyph and players were not finding it. */
.infob{display:inline-flex;align-items:center;gap:6px;background:none;border:none;
  color:var(--accent);font-family:var(--ui);font-size:13px;font-weight:650;line-height:1;
  cursor:pointer;padding:5px 8px;border-radius:9px;white-space:nowrap}
.infob:hover,.infob:focus-visible{background:color-mix(in srgb,var(--accent) 13%, transparent)}
.infob:active{opacity:.72}
.infob-t{letter-spacing:.01em}
/* Three bars, bottom-aligned, drawn from the control's own colour so they follow every state
 * (hover, focus, active) without a second declaration to keep in step. */
.infob-i{display:inline-flex;align-items:flex-end;gap:2px;height:12px}
.infob-i .bar{display:block;width:3px;border-radius:1px;background:currentColor}
.infob-i .b1{height:6px}
.infob-i .b2{height:9px}
.infob-i .b3{height:12px}

/* ── the first-login walkthrough (rendered inside the modal primitive above) ──
 * Tokens only, like everything after APP ADDITIONS — test_ui.js enforces it.
 */
.ob{display:flex;flex-direction:column;gap:11px}
.ob-lead{margin:0;font-size:13px;color:var(--ink)}
.ob-hint{margin:0;font-size:11.5px;color:var(--faint)}
.ob-field{display:flex;flex-direction:column;gap:4px}
.ob-label{font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
.ob-field input{background:var(--surface-2);border:1px solid var(--line);border-radius:9px;
  color:var(--ink);font-family:var(--ui);font-size:13px;padding:8px 10px;width:100%}
.ob-field input:focus{outline:none;border-color:var(--accent)}
/* Inline, under the field it is about — a toast would vanish mid-correction. */
.ob-error{color:var(--loss);font-size:11.5px;margin-top:4px}
.ob-panel{border:1px solid var(--line-soft);border-radius:12px;padding:12px 13px;
  background:var(--surface-2)}
.ob-panel-h{margin:0 0 6px;font-size:14px;color:var(--ink);font-weight:650}
.ob-panel-p{margin:0;font-size:13px;line-height:1.55;color:var(--muted)}
.ob-actions{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:2px}
.ob-step{font-family:var(--mono);font-size:10.5px;color:var(--faint);
  letter-spacing:.08em;font-variant-numeric:tabular-nums}
.ob-next{margin-left:auto;background:var(--accent);border:1px solid var(--accent-deep);
  border-radius:9px;color:var(--accent-ink);font-size:13px;font-weight:620;cursor:pointer;
  padding:8px 16px}
.ob-next:hover{background:var(--accent-deep)}
.ob-next:disabled{opacity:.6;cursor:default}

/* ── the AI help affordance + chat panel (inside the modal primitive) ──
 * Tokens only, like everything after APP ADDITIONS.
 */
.helpfab{position:fixed;right:16px;bottom:78px;z-index:40;display:flex;align-items:center;gap:7px;
  background:var(--surface-2);border:1px solid var(--line);border-radius:999px;
  box-shadow:var(--shadow);color:var(--ink);font-family:var(--ui);font-size:12.5px;
  font-weight:600;cursor:pointer;padding:9px 14px}
.helpfab:hover{border-color:var(--accent);color:var(--accent)}
@media (min-width:900px){ .helpfab{bottom:20px} }

.help{display:flex;flex-direction:column;gap:10px;min-height:0;flex:1 1 auto}
/* ★ The disclaimer. Muted, but always present and never collapsed away. */
.help-note{margin:0;font-size:11.5px;line-height:1.5;color:var(--faint);
  border-left:2px solid var(--line);padding-left:9px}
/**
 * ★ THE LIST TAKES WHATEVER HEIGHT IS LEFT AND SCROLLS INSIDE ITSELF — it is no longer capped
 *   at a fraction of the viewport. On a phone a fixed 46vh cap left the panel with almost no
 *   room to read in while the modal itself had plenty.
 *
 * ★ min-height:0 IS THE WHOLE TRICK, and it is the thing that is easy to leave out: a flex
 *   child will not shrink below its content by default, so without it the list grows past the
 *   modal and the whole panel overflows instead of scrolling.
 */
.help-msgs{display:flex;flex-direction:column;gap:8px;flex:1 1 auto;min-height:0;
  overflow-y:auto;overflow-x:hidden;padding:2px 1px}
.help-empty{color:var(--faint);font-size:12.5px;padding:6px 2px}
.help-msg{max-width:86%;border-radius:12px;padding:8px 11px;font-size:13px;line-height:1.5;
  white-space:pre-wrap;overflow-wrap:anywhere}
.help-msg.user{align-self:flex-end;background:var(--accent);color:var(--accent-ink);
  border:1px solid var(--accent-deep)}
.help-msg.bot{align-self:flex-start;background:var(--surface-2);color:var(--ink);
  border:1px solid var(--line-soft)}
.help-typing{color:var(--faint);letter-spacing:.14em}
.help-error{align-self:stretch;color:var(--loss);background:var(--loss-bg);
  border:1px solid var(--loss);border-radius:10px;font-size:12px;padding:7px 10px}
/* Pinned at the bottom of the panel: a sibling of the scroller, and never shrunk away. */
.help-form{display:flex;gap:8px;align-items:center;flex:0 0 auto;
  padding-bottom:env(safe-area-inset-bottom,0px)}
/* ★ 16px, deliberately. iOS Safari ZOOMS the whole page in on focus for any input under 16px,
 * which on a phone is indistinguishable from the layout being broken. */
.help-input{flex:1 1 auto;min-width:0;background:var(--surface-2);border:1px solid var(--line);
  border-radius:9px;color:var(--ink);font-family:var(--ui);font-size:16px;padding:10px 11px}
.help-input:focus{outline:none;border-color:var(--accent)}
.help-send{flex:0 0 auto;background:var(--accent);border:1px solid var(--accent-deep);
  border-radius:9px;color:var(--accent-ink);font-size:12.5px;font-weight:620;cursor:pointer;
  padding:10px 15px}
.help-send:hover{background:var(--accent-deep)}
.help-send:disabled,.help-input:disabled{opacity:.6;cursor:default}

/* ── live scores, inline on the team rows (scoreboard style) ──
 * Tokens only, like everything after APP ADDITIONS.
 */
.team .scr{
  /* ★ TABULAR + a fixed min-width, so a 7 and a 17 occupy the same box and the two rows of a
     card line up vertically the way a real scoreboard does. */
  font-family:var(--mono);font-variant-numeric:tabular-nums;
  font-size:17px;font-weight:700;color:var(--ink);
  min-width:30px;text-align:right;flex:0 0 auto;letter-spacing:-.01em
}
/* An empty slot holds the space without drawing anything — a pre-game row shows no number. */
.team .scr.none{min-height:1px}
/* The leader's number is the one a glance should land on. */
.card.live .team .scr,.team.winner .scr{color:var(--accent)}
.team.winner .scr{color:var(--win)}

/* ── the LIVE card edge: this game is running right now ── */
.card.live{
  border-color:var(--accent);
  box-shadow:0 0 0 1px var(--accent), 0 6px 22px color-mix(in srgb,var(--accent) 18%, transparent)
}
/* The hover rule below must not wash the live edge back out. */
.card.live:hover{border-color:var(--accent)}

/* ★ THE SAFETY NET UNDER THE CREST SIZING. `.team .ti .tlogo` and `.ti-sm .tlogo` size the two
 * places a crest is rendered today; this bare rule means the NEXT place cannot reproduce the
 * bug that started this — a 500x500 ESPN PNG rendering at natural size and blowing out its
 * container — even if whoever adds it forgets a size class.
 */
.tlogo{max-width:100%;max-height:100%;object-fit:contain}

/* ── the modal at PHONE width ──
 * A centred dialog with margins wastes the only screen a phone has, and the chat panel then
 * has almost nothing to scroll in. Below 560px the modal takes the viewport.
 */
@media (max-width:560px){
  .modal-backdrop{padding:0;align-items:stretch}
  .modal{width:100%;max-width:none;height:100%;max-height:none;border-radius:0;border:none}
  .modal-head{padding:12px 13px}
  .modal-body{padding:12px 13px}
  /* The floating help control sits above the bottom nav rather than on top of it. */
  .helpfab{right:12px;bottom:74px;padding:8px 12px;font-size:12px}
  /* The head-to-head rows stay three columns — they are already fractional — but the crest
     and the labels tighten so two team names fit a narrow header. */
  .ti-sm{width:36px;height:36px}
  .mrow .k{font-size:10px}
  .mrow .v{font-size:12px}
}
/* The chat panel's body is a bounded flex column, so .help-msgs can take the space that is
   left. Opted into by openHelp via bodyClass, never imposed on the other modals. */
.modal-body.help-body{display:flex;flex-direction:column;min-height:0;overflow:hidden}

/* ══════════════ HOME-SCREEN (STANDALONE) APP SHELL ══════════════
 *
 * THE BUG: added to an iPhone home screen, the bottom tab bar FLOATS instead of docking. In
 * an ordinary Safari tab it is fine. `.nav` is position:fixed;bottom:0 and there is no
 * transform/filter ancestor to break it — `backdrop-filter` sits on `.topbar`, which is inside
 * `.app`, and `.nav` is a SIBLING of `.app`, so it is not a descendant. The rule is correct.
 * What is not correct is the surrounding model: the DOCUMENT is the scroll container, and the
 * iOS standalone WebView mishandles a fixed bottom element over a scrolling body.
 *
 * THE FIX: stop the body scrolling. The shell becomes a fixed-height, non-scrolling box and
 * the CONTENT scrolls inside it, so the bar is pinned over something that no longer moves.
 * `.nav` itself is untouched — no DOM change, no new wrapper, no index.html edit.
 *
 * ★★ SCOPED TO NON-BROWSER DISPLAY MODES, WHICH IS THE WHOLE SAFETY ARGUMENT. Desktop,
 *    Android Chrome and iOS Safari-in-a-tab all work today and all report
 *    `display-mode: browser`, so none of them can match this block — the fix cannot regress
 *    them, structurally rather than by care. standalone/fullscreen/minimal-ui are the three
 *    non-browser modes; matching all three costs nothing and covers a launcher that reports
 *    one of the other two.
 *
 * ★ `100dvh` AFTER `100vh`, in that order: the fallback first, the dynamic-viewport unit
 *   second so it wins where supported. dvh is the right unit even in standalone, where there
 *   is no collapsing toolbar, because it is the one that means "the viewport as it is now".
 *
 * ★ THE SAFE-AREA HANDLING IS UNCHANGED. `.nav`'s padding still runs the bar's BACKGROUND
 *   down under the home indicator with the buttons above it, and `.colwrap`'s
 *   `padding-bottom:calc(74px + env(...))` still clears the last card — that padding now sits
 *   inside the scroller, which is exactly where it needs to be.
 *
 * ★ REVERSAL: delete this entire block. Nothing outside it changed.
 */
@media (display-mode: standalone) and (max-width:900px),
       (display-mode: fullscreen) and (max-width:900px),
       (display-mode: minimal-ui) and (max-width:900px){
  html,body{height:100%;overflow:hidden}
  .app{height:100vh;height:100dvh;min-height:0}
  /* The one region that scrolls. min-height:0 is load-bearing: a grid/flex child will not
     shrink below its content without it, so the internal scroller would never engage and the
     shell would grow past the viewport again — reintroducing the very thing being fixed. */
  .colwrap{min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain}
}

/* ══════════════ TWO SCREENSHOT CLEANUPS ══════════════
 *
 * Both are CSS-only on purpose: public/app.js was being edited elsewhere when these landed,
 * and neither needs a markup change to work.
 */

/* ── 1. no initials monogram in the standings ──
 *
 * app.js renders `div.pill` with the player's initials as the first child of `.player`.
 * Hidden rather than removed because the removal would be a change to a file this cleanup
 * does not own — and `display:none` is the better mechanism anyway: a display:none child is
 * not a flex item at all, so `.player`'s `gap:11px` does not reserve a slot for it and the
 * name slides fully left with no leading gap. (`visibility:hidden` WOULD have left the
 * 30px hole, which is the trap here.)
 *
 * ★ SCOPED TO `.player`, WHICH IS STANDINGS-ONLY — the descendant selector is load-bearing,
 *   not decoration. The admin user list renders the SAME `.pill` class for its own avatars
 *   (app.js: `tr.urow > td > div.uinfo > div.pill`), so a bare `.pill{display:none}` would
 *   silently blank the commissioner's user table too. There is a test on exactly that.
 *
 * ★ ONLY THE BY-WEEK TABLE ACTUALLY HAS ONE. The season/all-time table builds `.player` with
 *   a `span.rank` first and no pill, so it is untouched — the two tables end up consistent
 *   (no monograms anywhere) but only one of them changes. Expect the by-week rows to get
 *   shorter: the 30px pill was setting their height, and they will now match the season
 *   table's text-only rows.
 *
 * ★ REVERSAL: delete the one declaration below.
 */
.player .pill{display:none}

/* ── 2. the Your Card result marks, in a column instead of adrift ──
 *
 * `.yc-line` is a flex row with `justify-content:space-between` over
 * [span name][span.yc-res ✓/✗][b confidence] — and the ✓/✗ is CONDITIONAL, appended only
 * once the pick is graded. So a row has two children or three, and with space-between the
 * middle one is parked wherever the name happens to end. Every row put the mark at a
 * different x, which is what reads as ragged.
 *
 * Giving the NAME the free space collapses the problem: it absorbs the slack, space-between
 * has nothing left to distribute, and the mark and the number pack against the right edge in
 * a fixed order. The number's right edge is then the row's right edge whether or not a mark
 * is present, so graded and ungraded rows line up with each other too.
 *
 * ★ `min-width:0` IS NOT OPTIONAL. A flex item will not shrink below its own min-content
 *   width without it, so a long "Away Team @ Home Team" label would refuse to give ground and
 *   push the mark and the number off the right edge — the alignment would hold for short
 *   names and break for exactly the long ones that made it look ragged in the first place.
 *
 * ★ THE FIRST CHILD SPECIFICALLY, never `.yc-line span`. That would flex `.yc-res` as well
 *   and stretch the mark's own box across the row, putting the glyph back where it started.
 *   `.yc-res` keeps its `flex:0 0 auto` (see its rule above) and there is a test on that.
 *
 * ★ THE `min-width` ON THE NUMBER IS WHAT MAKES IT A COLUMN rather than merely right-aligned.
 *   Confidence runs 1..n, so a one-digit and a two-digit value are different widths and the
 *   mark beside them would still shuffle by a character between rows. Two mono characters is
 *   the widest a confidence gets; `— unassigned` is far wider and simply ignores the minimum.
 *
 * ★ REVERSAL: delete the three declarations below.
 */
.yc-line{gap:10px}
.yc-line > span:first-child{flex:1 1 auto;min-width:0}
.yc-line > b{min-width:2ch;text-align:right}

/* ══════════════ FREEZING THE IDENTITY COLUMNS ON THE STANDINGS TABLES ══════════════
 *
 * By Week and All-Time both render through renderStandings -> sortableTable as a plain `.tbl`
 * inside `.tablewrap{overflow-x:auto}`, so on a phone they scroll sideways and the player
 * scrolls away with the stats — you end up reading a row of numbers with no idea whose they
 * are. This pins the two identity columns (# and Player) the way `.tbl.grid td.gname` already
 * pins the season grid's.
 *
 * Not breakpoint-scoped, exactly like the grid: sticky is inert while the table fits its
 * wrapper and engages only once .tablewrap actually scrolls, so the desktop layout is
 * untouched by construction rather than by a media query someone has to maintain.
 *
 * ★★ THE BACKGROUNDS ARE THE WHOLE TRICK, AND THE BASE ONE IS NOT WHAT IT LOOKS LIKE.
 *    `table.tbl tbody td` sets NO background — the cells are transparent and what you see is
 *    `.tablewrap`'s own var(--surface-2) showing through. So the frozen cells must be painted
 *    var(--surface-2), NOT var(--surface): those are two different greys — --surface is the
 *    card colour and --surface-2 is the wrapper's — so painting the frozen cells with the
 *    wrong one draws a two-tone stripe down the left of every table. A frozen cell
 *    with no background at all lets the scrolled stat columns slide visibly underneath it.
 *
 * ★ ALL THREE ROW STATES NEED PAINTING, and the cascade order between them is load-bearing:
 *      normal   var(--surface-2)                                     (0,2,1)
 *      tr.me    accent 8% over var(--surface-2)                      (0,3,2)
 *      hover    var(--raise)                                         (0,3,3)
 *    `tr.me td` (line ~441) mixes the accent over TRANSPARENT, which is fine for an ordinary
 *    cell sitting on the wrapper and see-through for a frozen one — so it is restated here
 *    over an opaque base, exactly as the grid does for .gname. And the hover rule has to
 *    outrank the me rule, or hovering your own row would leave the frozen cells tinted while
 *    the rest of the row went grey: a seam down the middle of the row you look at most.
 *
 * ★ tr.lead / tr.wonweek recolour only the rank TEXT and tr.nopicks only sets opacity, so
 *   none of them need a frozen-cell variant. (The opacity is safe here: it composites the
 *   whole row as a unit, so the frozen cell still covers its siblings inside that group.)
 *
 * ★ NO z-index CHANGE TO THE SHARED HEADER RULE, deliberately. The frozen th's z-index:2
 *   already lifts it over its scrolling neighbours, which are positioned but z-index:auto.
 *   Bumping `table.tbl thead th` would also hit `.tbl.grid`, whose own .gname layering sits
 *   at 2 — the grid's other headers would start painting over its frozen column. Not this
 *   feature's table, not this feature's change.
 *
 * ★ THE OFFSET IS A TOKEN, NOT A REPEATED NUMBER. The player column must start exactly where
 *   the rank column ends; writing the same literal twice is how those two silently drift.
 *   --rank-w is the single source and a test pins the pair.
 *
 * ★ WHY 52px, AND WHAT WOULD BREAK IT. `width` on a table cell is a suggestion the layout may
 *   override upward, so it is only honoured when it EXCEEDS the column's min-content width.
 *   That natural width is ~49px here: 32px of padding plus the wider of a two-digit rank or
 *   the "#" header with its sort arrow. 52px clears it with a little slack. A league of 100+
 *   players would push a three-digit rank past it and the column would grow, leaving the
 *   player column overlapping it by the difference — bump this value if that ever happens.
 *   (24 players today.)
 *
 * ★ REVERSAL: delete this whole block, and the two `cls:` keys plus the 'plyr' td class in
 *   renderStandings. Nothing else changed.
 */
table.tbl{--rank-w:52px}

.tbl th.rank,.tbl td.rank{position:sticky;left:0;z-index:2;
  width:var(--rank-w);min-width:var(--rank-w);max-width:var(--rank-w)}
.tbl th.plyr,.tbl td.plyr{position:sticky;left:var(--rank-w);z-index:2}

/* Reusing `.rank` on the <th> drags that cell rule's cosmetics onto the header too. Only one
   of them actually shows — the mono face on the "#" — so it is put back; the rest either
   already match the header or are outranked by `table.tbl thead th`. */
.tbl thead th.rank{font-family:var(--ui)}

.tbl td.rank,.tbl td.plyr{background:var(--surface-2)}
.tbl tr.me td.rank,.tbl tr.me td.plyr{background:color-mix(in srgb,var(--accent) 8%, var(--surface-2))}
.tbl tbody tr:hover td.rank,.tbl tbody tr:hover td.plyr{background:var(--raise)}

/* ── no "YOU" badge on the standings ──
 *
 * The row already says whose it is: tr.me tints it amber, and the frozen identity columns
 * carry an opaque version of that same tint, so the badge was repeating in text what the row
 * says in colour.
 *
 * ★ HIDDEN, NOT DELETED, and scoped by the fact that `.youtag` has exactly two render sites —
 *   renderStandings and the season grid, both standings. It appears in no shell and no other
 *   module, so one rule reaches every view it exists in and cannot blank anything else. There
 *   is a test on that count, because the safety of a blanket hide is entirely a claim about
 *   how many places render the class.
 *
 * ★ display:none, NOT visibility:hidden — a display:none child is not a flex item at all, so
 *   `.player`'s gap:11px reserves no slot for it and the name keeps its spacing. Same
 *   mechanism as the standings monogram above.
 *
 * ★ tr.me IS DELIBERATELY UNTOUCHED. Only the badge goes; the row tint is what makes the
 *   badge redundant in the first place, so removing both would take away the thing that
 *   justified removing either.
 *
 * ★ REVERSAL: delete the one declaration below.
 */
.youtag{display:none}

/* ── form signals on the season board: hot/cold + rank movement ──
 *
 * These ride inside the frozen .gname column beside the name, so the one thing that stays on
 * screen while the week columns scroll sideways also says how that player is travelling.
 *
 * ★ ONE INLINE-FLEX WRAPPER, at a 4px internal gap. .player spaces its children at 11px, which
 *   is right for name-sized things and far too much for two small marks; the wrapper takes one
 *   of those gaps and handles its own contents. flex:0 0 auto so the marks never shrink when a
 *   long name squeezes the row.
 *
 * ★ COLOURS ARE THE PALETTE'S EXISTING VERDICT TOKENS — --win for a climb, --loss for a fall,
 *   --faint for holding — the same three the mockup's own (still unused) .movew.up/.dn/.eq
 *   rule picked for exactly this signal. Tokens only, so both themes follow.
 *
 * ★ THE TREND GLYPHS ARE EMOJI and are deliberately NOT tinted: colouring 🔥 would fight the
 *   glyph's own colour and achieve nothing. They sit at 11px, the same size as the arrow, so
 *   the pair reads as one mark rather than a picture next to some text. Precedent for emoji in
 *   this UI is the 🏆 on the week-high badge.
 *
 * ★ REVERSAL: delete this block and trendMark() plus its two call lines in renderGrid.
 */
.trendmark{display:inline-flex;align-items:center;gap:4px;flex:0 0 auto;line-height:1;
  font-family:var(--mono);font-size:11px;font-variant-numeric:tabular-nums}
.trend-hot,.trend-cold{font-size:11px;line-height:1}
.trend-up{color:var(--win)}
.trend-dn{color:var(--loss)}
.trend-eq{color:var(--faint)}

/* ================= RECAPS =================
 * Built ONLY from the existing :root tokens + components (Chris, explicit): no new palette, no
 * new fonts. Both themes come free through the tokens. Layout mirrors the recap mockup; every
 * colour is a var(). */
.recap{max-width:760px}
.rc-mast{border-bottom:2px solid var(--ink);padding-bottom:14px;margin-bottom:6px}
.rc-eyebrow{font-size:11px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--accent)}
.rc-mast h2{margin:4px 0 2px;font-size:clamp(30px,7vw,46px);line-height:.96;font-weight:800;letter-spacing:-.02em}
.rc-dateline{color:var(--muted);font-size:13px}
.rc-strip{display:flex;flex-wrap:wrap;gap:10px;margin-top:14px}
.rc-stat{flex:1 1 150px;background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:11px 13px;box-shadow:var(--shadow)}
.rc-stat .k{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint);font-weight:700}
.rc-stat .v{font-size:24px;font-weight:800;letter-spacing:-.02em;margin-top:2px;font-variant-numeric:tabular-nums}
.rc-stat .v small{font-size:12px;font-weight:600;color:var(--muted)}
.rc-sec{margin-top:26px}
.rc-sech{display:flex;align-items:baseline;gap:9px;margin-bottom:10px}
.rc-tag{font-size:10px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:var(--accent);background:color-mix(in srgb,var(--accent) 16%,transparent);padding:3px 8px;border-radius:6px}
.rc-sech h3{margin:0;font-size:16px;font-weight:750;letter-spacing:-.01em}
.rc-card{background:var(--surface);border:1px solid var(--line);border-radius:14px;padding:14px 16px;box-shadow:var(--shadow)}
.rc-winner{background:linear-gradient(180deg,color-mix(in srgb,var(--accent) 14%,var(--surface)),var(--surface));border:1px solid var(--accent);border-radius:16px;padding:18px;display:flex;align-items:center;gap:16px;box-shadow:var(--shadow)}
.rc-winner .medal{font-size:34px;line-height:1}
.rc-winner .who{font-size:22px;font-weight:800;letter-spacing:-.02em}
.rc-winner .sub{color:var(--muted);font-size:13px;margin-top:2px}
.rc-winner .pts{margin-left:auto;text-align:right}
.rc-winner .pts b{font-size:32px;font-weight:800;display:block;line-height:1;font-variant-numeric:tabular-nums}
.rc-winner .pts span{font-size:11px;text-transform:uppercase;letter-spacing:.1em;color:var(--muted);font-weight:700}
.rc-tablewrap{overflow-x:auto}
.rc-table{width:100%;border-collapse:collapse;font-size:14px}
.rc-table th,.rc-table td{text-align:left;padding:8px;border-bottom:1px solid var(--line-soft);white-space:nowrap}
.rc-table th{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--faint);font-weight:700}
.rc-table td.r,.rc-table th.r{text-align:right;font-variant-numeric:tabular-nums}
.rc-table tr:last-child td{border-bottom:none}
.rc-rk{color:var(--faint);font-weight:700}
.rc-delta{font-weight:700;font-size:12px}
.rc-up{color:var(--win)}
.rc-down{color:var(--loss)}
.rc-flat{color:var(--faint)}
.rc-two{display:grid;grid-template-columns:1fr 1fr;gap:12px}
@media(max-width:560px){.rc-two{grid-template-columns:1fr}}
.rc-super .role{font-size:11px;font-weight:800;letter-spacing:.06em;text-transform:uppercase}
.rc-super.good .role{color:var(--win)}
.rc-super.bad .role{color:var(--loss)}
.rc-super .line{margin-top:6px;font-size:15px;font-weight:700;letter-spacing:-.01em}
.rc-super .detail{color:var(--muted);font-size:13px;margin-top:4px}
.rc-chip{display:inline-flex;align-items:center;font-size:11px;font-weight:700;padding:2px 8px;border-radius:999px;font-variant-numeric:tabular-nums;margin-left:4px}
.rc-chip.win{background:var(--win-bg);color:var(--win)}
.rc-chip.loss{background:var(--loss-bg);color:var(--loss)}
.rc-game{font-weight:750;letter-spacing:-.01em}
.rc-score{font-family:var(--mono);font-weight:700;color:var(--muted);font-variant-numeric:tabular-nums}
.rc-verdict{margin-top:10px;padding-top:10px;border-top:1px dashed var(--line);color:var(--muted);font-size:13px}
.rc-verdict b{color:var(--ink)}
.rc-meter{height:8px;border-radius:999px;background:var(--surface-2);overflow:hidden;margin:9px 0 5px;display:flex}
.rc-meter i{display:block;height:100%}
.rc-meter .a{background:var(--accent)}
.rc-meter .b{background:var(--line)}
.rc-meterlab{display:flex;justify-content:space-between;font-size:11px;color:var(--muted);font-weight:600}
.rc-cashrow{display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid var(--line-soft)}
.rc-cashrow:last-child{border-bottom:none}
.rc-cashrow .amt{margin-left:auto;font-weight:800;color:var(--win);font-variant-numeric:tabular-nums}
.rc-cashrow .season{color:var(--muted);font-size:12px;font-variant-numeric:tabular-nums}
.rc-empty{color:var(--muted);padding:24px 0;font-size:14px}

/* ═══════════════════ History (Pool Picks Archive) — scoped under #s-history ═══════════════════
 * Ported from docs/superpowers/specs/2026-09-09-history-pool-picks-mockup.html, namespaced so
 * the generic class names (.cell, .avatar, .side, .pchip, .modal…) cannot collide with the rest
 * of the app. Uses the shell's existing theme tokens, so light/dark come for free. */
#s-history .hist-statline{display:flex;gap:16px;flex-wrap:wrap;color:var(--muted);font-size:12.5px;font-family:var(--mono);margin:2px 0 10px}
#s-history .hist-statline span::before{content:"";}
#s-history .hist-legend{display:flex;flex-wrap:wrap;gap:8px 16px;margin:4px 0 14px;font-size:12px;color:var(--muted)}
#s-history .hist-legend .k{display:inline-flex;align-items:center;gap:6px}
#s-history .hist-legend .sw{width:22px;height:16px;border-radius:4px;display:inline-grid;place-items:center;font-size:10px;font-weight:800;font-family:var(--mono)}
#s-history .sw.hit{background:color-mix(in srgb,var(--win) 22%,transparent);color:var(--win);border:1px solid color-mix(in srgb,var(--win) 45%,transparent)}
#s-history .sw.miss{background:color-mix(in srgb,var(--loss) 16%,transparent);color:var(--loss);border:1px solid color-mix(in srgb,var(--loss) 40%,transparent)}
#s-history .sw.lock{background:var(--surface-2);color:var(--faint);border:1px solid var(--line)}
#s-history .sw.live{background:color-mix(in srgb,var(--accent) 16%,transparent);color:var(--accent-deep);border:1px solid color-mix(in srgb,var(--accent) 40%,transparent)}

/* desktop matrix */
#s-history .hist-gridwrap{overflow-x:auto;border:1px solid var(--line);border-radius:14px;background:var(--surface);box-shadow:var(--shadow)}
#s-history .hist-table{border-collapse:separate;border-spacing:0;width:100%;font-variant-numeric:tabular-nums}
#s-history .pfoot,#s-history .poolsum{font-family:var(--mono);font-size:12px;color:var(--muted);font-weight:500}
#s-history .hist-table .pfoot{padding:12px 10px;text-align:center;border-top:1px solid var(--line-soft)}
#s-history .hist-table .pfoot.lbl{text-align:left}
#s-history .gcard .pfoot{margin-top:6px}
#s-history .poolsum{padding:12px 0;border-top:1px solid var(--line-soft)}
#s-history .hist-table th,#s-history .hist-table td{border-bottom:1px solid var(--line-soft);white-space:nowrap}
#s-history thead th{position:sticky;top:0;z-index:3;background:var(--surface-2);padding:9px 10px;font-size:11.5px;color:var(--muted);font-weight:700;text-align:center;vertical-align:bottom}
#s-history thead th .mu{font-family:var(--mono);font-size:12px;color:var(--ink);font-weight:800}
#s-history thead th .mu .x{color:var(--faint);margin:0 1px}
#s-history thead th .sc{font-family:var(--mono);font-size:11px;color:var(--muted);margin-top:2px}
#s-history thead th .sc.live{color:var(--accent-deep)}
#s-history thead th .at{font-size:10px;margin-top:2px;color:var(--faint)}
#s-history .pcol{position:sticky;left:0;z-index:2;background:var(--surface);text-align:left;padding:7px 12px;min-width:206px;border-right:1px solid var(--line)}
#s-history thead .pcol{z-index:4;background:var(--surface-2)}
#s-history .hplayer{display:flex;align-items:center;gap:10px;min-width:0}
#s-history .rk{font-family:var(--mono);font-size:12px;color:var(--faint);width:18px;text-align:right;flex:0 0 auto}
#s-history .avatar{width:28px;height:28px;flex:0 0 auto;border-radius:8px;display:grid;place-items:center;font-weight:800;font-size:11px;color:var(--avatar-ink)}
#s-history .pname{font-weight:700;font-size:13.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#s-history .ppts{margin-left:auto;font-family:var(--mono);font-weight:800;color:var(--accent-deep);flex:0 0 auto}
#s-history tbody tr{cursor:pointer}
#s-history tbody tr:hover .pcol{background:var(--surface-2)}
#s-history tr.top .pcol{box-shadow:inset 3px 0 0 var(--accent)}
#s-history tr.me .pcol{background:color-mix(in srgb,var(--accent) 8%,transparent)}
#s-history td.cell{text-align:center;padding:7px 8px;position:relative}
#s-history .pk{display:inline-flex;flex-direction:column;align-items:center;gap:2px;min-width:46px;padding:4px 6px;border-radius:8px;line-height:1.1}
#s-history .pk .ab{font-family:var(--mono);font-weight:800;font-size:12.5px}
#s-history .pk .cf{font-size:10px;font-family:var(--mono);opacity:.85}
#s-history .cell.hit .pk{background:color-mix(in srgb,var(--win) 15%,transparent);color:var(--win)}
#s-history .cell.miss .pk{background:color-mix(in srgb,var(--loss) 11%,transparent);color:var(--loss)}
#s-history .cell .mk{position:absolute;top:3px;right:6px;font-size:10px;font-weight:800}
#s-history .cell.hit .mk{color:var(--win)}
#s-history .cell.miss .mk{color:var(--loss)}
#s-history .cell.lock .pk{background:repeating-linear-gradient(135deg,var(--surface-2),var(--surface-2) 5px,transparent 5px,transparent 10px);color:var(--faint)}
#s-history .cell.live .pk{background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--accent-deep)}
#s-history .cell.nopick .pk{color:var(--faint)}
#s-history .cell.void .pk{background:var(--surface-2);color:var(--faint)}
#s-history .tot{position:sticky;right:0;background:var(--surface);border-left:1px solid var(--line);font-family:var(--mono);font-weight:800;text-align:center;padding:8px 12px;color:var(--ink)}
#s-history thead .tot{z-index:4;background:var(--surface-2);color:var(--muted);font-weight:700;font-size:11.5px}

/* mobile game cards (hidden on desktop; shown at the phone breakpoint) */
#s-history .hist-cards{display:none;flex-direction:column;gap:12px}
#s-history .gcard{border:1px solid var(--line);border-radius:14px;background:var(--surface);overflow:hidden;box-shadow:var(--shadow)}
#s-history .gchead{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:11px 14px;background:var(--surface-2);border-bottom:1px solid var(--line)}
#s-history .gchead .mu2{font-family:var(--mono);font-weight:800;font-size:14px}
#s-history .gchead .mu2 .x{color:var(--faint);margin:0 3px}
#s-history .gchead .sport{font-family:var(--mono);font-size:10px;color:var(--faint);text-transform:uppercase;letter-spacing:.08em}
#s-history .gchead .res2{font-family:var(--mono);font-size:12px;color:var(--muted);text-align:right}
#s-history .gchead .res2 b{color:var(--ink)}
#s-history .badge{font-family:var(--mono);font-size:10px;font-weight:800;border-radius:5px;padding:2px 7px}
#s-history .badge.live{color:var(--accent-deep);border:1px solid color-mix(in srgb,var(--accent) 40%,transparent);background:color-mix(in srgb,var(--accent) 12%,transparent)}
#s-history .badge.lock{color:var(--faint);border:1px solid var(--line);background:var(--surface-2)}
#s-history .gcside{padding:10px 14px}
#s-history .gcside + .gcside{border-top:1px solid var(--line-soft)}
#s-history .gcside .sh{display:flex;align-items:center;gap:8px;font-family:var(--mono);font-weight:800;margin-bottom:9px;font-size:13px}
#s-history .gcside .sh .n{margin-left:auto;font-size:11px;color:var(--muted);font-weight:700}
#s-history .gcside.cover .sh{color:var(--win)}
#s-history .gcside.nocov .sh{color:var(--loss)}
#s-history .gcside .tag{font-size:10px;font-weight:800;padding:1px 6px;border-radius:5px}
#s-history .gcside.cover .tag{color:var(--win);background:color-mix(in srgb,var(--win) 16%,transparent)}
#s-history .gcside.nocov .tag{color:var(--loss);background:color-mix(in srgb,var(--loss) 12%,transparent)}
#s-history .plist{display:flex;flex-wrap:wrap;gap:6px}
#s-history .pchip{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line);border-radius:999px;padding:4px 6px 4px 4px;font-size:12.5px;background:var(--surface-2);cursor:pointer}
#s-history .pchip .mini{width:20px;height:20px;border-radius:6px;display:grid;place-items:center;font-size:9px;font-weight:800;color:var(--avatar-ink)}
#s-history .pchip .cf3{font-family:var(--mono);font-size:11px;color:var(--muted);font-weight:700}
#s-history .pchip .nobody,#s-history .plist .nobody{color:var(--faint);font-size:12px}
#s-history .locked{padding:16px;text-align:center;color:var(--faint);font-size:13px}
#s-history .empty{padding:40px;text-align:center;color:var(--muted)}

/* the player drill-down reuses the app's modal; these style its body only */
.modal-body.hist-modal .hist-season{display:flex;gap:8px;padding:2px 0 12px}
.modal-body.hist-modal .wkb{flex:1;border:1px solid var(--line);border-radius:10px;padding:10px;text-align:center}
.modal-body.hist-modal .wkb .l{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.08em}
.modal-body.hist-modal .wkb .v{font-family:var(--mono);font-weight:800;font-size:18px;margin-top:2px}
.modal-body.hist-modal .wkb .r{font-size:11px;color:var(--faint);font-family:var(--mono)}
.modal-body.hist-modal .wkb.on{border-color:color-mix(in srgb,var(--accent) 45%,transparent)}
.modal-body.hist-modal .mrow{display:flex;align-items:center;gap:10px;padding:9px 2px;border-bottom:1px solid var(--line-soft)}
.modal-body.hist-modal .mrow .g{font-family:var(--mono);font-size:12px;color:var(--muted);flex:1}
.modal-body.hist-modal .mrow .ppk{font-family:var(--mono);font-weight:800;font-size:13px}
.modal-body.hist-modal .mrow .cf2{font-family:var(--mono);font-size:11px;color:var(--muted);width:42px;text-align:right}
.modal-body.hist-modal .mrow .res{width:20px;text-align:center;font-weight:800}
.modal-body.hist-modal .mrow.hit .ppk,.modal-body.hist-modal .mrow.hit .res{color:var(--win)}
.modal-body.hist-modal .mrow.miss .ppk,.modal-body.hist-modal .mrow.miss .res{color:var(--loss)}
.modal-body.hist-modal .mrow .ppk.faint{color:var(--faint);font-weight:700}
@media (max-width:720px){
  #s-history .hist-gridwrap{display:none}
  #s-history .hist-cards{display:flex}
}
