  /* Tokens are defined in assets/tokens.css (--vt-*). These short aliases keep
     the existing rules below working while making the design system authoritative. */
  :root{
    --bg:var(--vt-bg); --panel:var(--vt-panel); --panel2:var(--vt-panel-2); --line:var(--vt-line);
    --text:var(--vt-text); --fg:var(--vt-text); --muted:var(--vt-muted);
    --accent:var(--vt-accent); --accent2:var(--vt-blue);
    --track:var(--vt-track); --shadow:var(--vt-shadow); --hdr:var(--vt-header-h);
  }
  *{box-sizing:border-box}
  /* longhands, not `background:… fixed`: the backdrop is a two-layer gradient list
     (see tokens.css) and in the shorthand `fixed` would only attach to the last layer.
     The court watermark is body::before (tokens.css), NOT a background layer here. */
  /* flex column + min-height:100vh so the footer sticks to the true bottom of the viewport on
     short pages (e.g. Power Rankings) instead of leaving dead canvas below it — .wrap gets
     flex:1 and footer gets margin-top:auto below (operator 2026-07-04: "make it the end of the
     page"). */
  body{margin:0;min-height:100vh;display:flex;flex-direction:column;
    background-image:var(--vt-backdrop);background-color:var(--vt-bg);
    background-attachment:fixed;
    color:var(--text);font:var(--vt-fs-body)/var(--vt-lh-body) var(--vt-font-ui);
    -webkit-font-smoothing:antialiased}
  /* Saira (display face) — wordmark, headings, big numerals, controls. Body stays
     on the system UI stack for table density. */
  .brand h1,.slatedate .dval,.slatecounts .scv,.scard .v,.statrow .v,
  .pod .ppts,.pod .pname,.gprow .s1,.prow .pj,td.proj,.rank .elo,
  .pchead .n,.ms .val,.qgrid .q .val,.statstrip .stat .v,.biascell .v,
  .gmrow .gv,.gmpp,.tabs button,.statbar button,.authbtn,.densebtn,
  .seg button,.pager button,.cookie .cx,.gtotals,.medal,.chartbox h3{
    font-family:var(--vt-font-display)}
  /* flex:1 (fills the remaining height below the sticky .top header) + flex-direction:column so
     .main (below) can be given flex:1 too, pinning footer to the true bottom of the viewport on
     short pages (e.g. Power Rankings) instead of leaving dead canvas below it — that was the actual
     bug behind "the gap below Data updated..., even bigger on Power Rankings" (operator 2026-07-04):
     body had no min-height/flex layout, so a short tab's footer just trailed off wherever its
     content ended, nowhere near the viewport's true bottom. Bottom padding still has to clear the
     floating tab bar (94px = .tabs' 26px bottom-offset + ~52px height + a small breathing gap) —
     that part of the space is a real functional reservation, not visual fat; don't shrink it
     further without also verifying .tabs doesn't overlap the footer at full scroll. */
  .wrap{max-width:1140px;margin:0 auto;width:100%;flex:1 1 auto;display:flex;flex-direction:column;
    padding:24px 20px 94px}
  /* wraps every section except <footer> (see template.html) — flex:1 makes THIS grow to fill any
     leftover height on short tabs (the actual sticky-footer push), while footer keeps a normal
     fixed margin-top below instead of margin-top:auto, so there's always a real gap above it even
     on tall pages where .main overflows and .wrap has no free space left (operator 2026-07-04:
     "footer isn't glued to page content" — margin-top:auto alone resolves to 0 in that case). */
  .main{flex:1 1 auto;display:flex;flex-direction:column}
  a{color:inherit}
  /* topbar */
  /* full-bleed sticky topbar: the bar spans the whole viewport from the very top;
     its content (.topin) aligns to the app column. --hdr is synced to its real
     height by syncHdr() so sticky table headers offset correctly. */
  .top{position:sticky;top:0;z-index:30;margin:0;
    background:rgba(11,13,18,.82);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
    border-bottom:1px solid var(--line)}
  /* accent hairline bleeding out of the brand corner — follows the favorite-team reskin */
  .top::after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:1px;pointer-events:none;
    background:linear-gradient(90deg,var(--accent) 0,rgba(0,0,0,0) 34%);opacity:.5}
  /* row 1: brand · slate · auth — full-bleed so the logo hugs the left edge and auth the right */
  .topin{max-width:none;margin:0;padding:10px 24px;display:flex;flex-wrap:wrap;gap:12px 20px;
    align-items:center}
  .top .auth{margin-left:auto}
  .brand{display:flex;align-items:center;gap:15px;text-decoration:none;color:inherit}
  /* monogram as a CSS mask so it can be tinted: --vt-logo is near-white by default and
     becomes the favorite team's colour via applyAccent() (operator: personalise the logo) */
  .brand .mk{width:52px;height:38px;display:block;background:var(--vt-logo);
    -webkit-mask:url(vates-mark.svg) center/contain no-repeat;mask:url(vates-mark.svg) center/contain no-repeat;
    transition:background .2s}
  .brand h1{font-size:calc(18px*var(--vt-scale-wordmark,1));margin:0;line-height:1.05;font-weight:800;
    letter-spacing:var(--vt-tracking-wordmark);text-transform:uppercase}
  /* mantra sits beside the wordmark behind a hairline divider, not below it */
  .brand .tag{color:var(--muted);font-size:12.5px;line-height:1.32;
    padding-left:15px;border-left:1px solid var(--line);align-self:stretch;display:flex;
    flex-direction:column;justify-content:center}
  @media(max-width:560px){.brand .tag{display:none}}
  .pick{display:flex;align-items:center;gap:10px;position:relative}
  /* custom slate picker (replaces the native date input) */
  .datebtn{display:flex;align-items:center;gap:10px;background:var(--panel2);border:1px solid var(--line);
    border-radius:12px;padding:7px 14px;cursor:pointer;color:var(--text);line-height:1.25;transition:border-color .15s}
  .datebtn:hover{border-color:var(--accent2)}
  .datebtn .dlab{color:var(--accent);font-size:10.5px;font-weight:700;letter-spacing:.07em;text-transform:uppercase}
  .datebtn .dval{font-size:14px;font-weight:600}
  .datebtn .dchev{color:var(--muted);font-size:11px;transition:transform .18s}
  .datebtn[aria-expanded="true"] .dchev{transform:rotate(180deg)}
  /* opens to the RIGHT of the date button (anchored to the slate, which is position:relative) */
  .datemenu{position:absolute;left:calc(100% + 10px);top:0;z-index:46;width:268px;max-width:74vw;
    display:flex;flex-direction:column;max-height:min(300px,46vh);background:var(--panel);
    border:1px solid var(--line);border-radius:14px;box-shadow:0 18px 50px rgba(0,0,0,.5);overflow:hidden}
  .datemenu[hidden]{display:none}
  /* month-paged header: ‹ Month Year › (replaces the free-text date filter) */
  .datemenu-head{display:flex;align-items:center;gap:8px;padding:9px 10px;border-bottom:1px solid var(--line)}
  .dmonthlab{flex:1;text-align:center;font-size:14px;font-weight:800;color:var(--text);
    font-family:var(--vt-font-display);font-variant-numeric:tabular-nums}
  .dnav{width:30px;height:30px;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
    background:var(--panel2);border:1px solid var(--line);border-radius:8px;color:var(--muted);
    font-size:17px;line-height:1;cursor:pointer;transition:.14s}
  .dnav:hover:not(:disabled){color:var(--accent);border-color:var(--accent)}
  .dnav:disabled{opacity:.35;cursor:default}
  .datemenu-list{overflow-y:auto;padding:6px}
  .ditem{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 10px;
    border-radius:9px;cursor:pointer;transition:background .12s}
  .ditem:hover{background:var(--panel2)}
  /* selected = the language-picker treatment (soft wash + full ring) in the favorite-team
     tone — no hardcoded orange fade, no left side-bar (operator 2026-07-03, V1) */
  .ditem.on{background:var(--vt-accent-soft);box-shadow:inset 0 0 0 1px var(--vt-accent)}
  .ditem .dd{font-size:14px;font-weight:700;font-family:var(--vt-font-display);display:flex;align-items:center;gap:7px}
  .ditem.on .dd{color:var(--accent)}
  .ditem .udot{width:6px;height:6px;border-radius:50%;background:var(--accent2);flex:0 0 auto}
  .ditem .dn{font-size:11px;color:var(--muted);background:var(--panel2);border:1px solid var(--line);
    border-radius:20px;padding:2px 9px;white-space:nowrap}
  .ditem.on .dn{color:#cdd3df}
  .dmenu-empty{padding:20px;text-align:center;color:var(--muted);font-size:13px}
  select,input[type=search]{background:var(--panel2);color:var(--text);border:1px solid var(--line);
    border-radius:9px;padding:9px 12px;font-size:14px;outline:none}
  select:focus,input:focus{border-color:var(--accent2)}
  /* slate box now lives IN the topbar (date picker + games/players counts), compact + inline */
  .slate{position:relative;display:flex;align-items:center;gap:14px}
  .slate .slatedate{display:flex;align-items:center;gap:9px;background:var(--panel2);border:1px solid var(--line);
    border-radius:11px;padding:6px 12px;cursor:pointer;color:var(--text);transition:border-color .15s}
  .slate .slatedate:hover{border-color:var(--accent)}
  /* label height now matches the date, with a discrete vertical separator between them */
  .slate .slatedate .dlab{color:var(--accent);font-size:13px;font-weight:800;letter-spacing:.02em;text-transform:uppercase;
    font-family:var(--vt-font-display);padding-right:9px;border-right:1px solid var(--line)}
  .slate .slatedate .dval{font-size:15px;font-weight:800;letter-spacing:-.01em}
  .slate .slatedate .dchev{color:var(--accent);font-size:13px;transition:transform .18s;margin-left:1px}
  .slate .slatedate[aria-expanded="true"] .dchev{transform:rotate(180deg)}
  .slate .slatecounts{display:flex;gap:13px}
  .slate .slatecounts .sc{display:flex;align-items:baseline;gap:5px}
  .slate .slatecounts .scv{font-size:15px;font-weight:800;letter-spacing:-.02em}
  .slate .slatecounts .scl{color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.04em}
  @media(max-width:760px){.slate .slatecounts{display:none}}   /* keep just the date picker when tight */
  /* summary cards: a metric per card (Top projections / Highest ceiling / Highest floor /
     Most consistent), each listing the PTS, AST and REB leader — title in a separated header,
     rows = name pill + value with its label; the consistent card also shows a steadiness badge. */
  .summary{display:grid;grid-template-columns:repeat(4,1fr);gap:11px;margin-bottom:20px}
  .scard{position:relative;display:flex;flex-direction:column;overflow:hidden;
    background:linear-gradient(180deg,var(--panel2),var(--panel));border:1px solid var(--line);
    border-radius:13px;box-shadow:var(--shadow),inset 0 1px 0 rgba(255,255,255,.05)}
  /* header casts a shadow onto the body, marking the boundary (like the game-window headers).
     The jersey-style halftone (was "H" here) moved to the PAGE BACKGROUND instead (operator
     2026-07-03: "try it on the website background rather than the summary card headers, to see
     something") — see body::after in tokens.css. Headers are plain again. */
  .scard .sk{position:relative;z-index:1;color:var(--muted);font-size:calc(11px*var(--vt-scale-label,1));font-weight:800;text-transform:uppercase;
    letter-spacing:.05em;text-align:center;padding:9px 10px;border-bottom:1px solid var(--line);
    box-shadow:0 4px 8px -3px rgba(0,0,0,.55)}
  /* R1 rows: the full row is the team colour (rowGrad via --rowc), name moulded in, no pill.
     Rows touch, separated by a 1px dark line + a faint top sheen — same recipe as the tables. */
  .scard .sbody{display:flex;flex-direction:column}
  .scard .srow{position:relative;display:flex;align-items:center;justify-content:space-between;gap:7px;
    padding:8px 13px;cursor:pointer;background:var(--rowc);
    border-bottom:1px solid rgba(0,0,0,.34);box-shadow:inset 0 1px 0 rgba(255,255,255,.12)}
  .scard .srow:last-child{border-bottom:0}
  /* hover: back to the SAME rounded-rect trace used on game-window player rows (operator
     2026-07-03: the orbiting glow "looks nothing like" that effect and "leaves traces" — dropped
     the whole cos()/sin() orbit experiment). Reduced-motion fallback = plain ring. Shared
     .prow/.scard .srow rule lives under the motion media query further down. */
  /* No separate hover ring at the ROW'S OWN edge any more — that duplicated the ::after trace
     drawn further in (operator 2026-07-04: the highlighted border should sit where the moving
     team-secondary trace sits, not at the row border). The trace itself (further below) now
     shows a static ring on hover even without motion, so this rule is redundant — removed. */
  .scard .snm{flex:0 1 auto;min-width:0;font:800 12.5px/1.15 var(--vt-font-display);
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-shadow:0 1px 2px rgba(0,0,0,.45)}
  .scard .sv{flex:0 0 auto;display:flex;align-items:baseline;gap:4px;font-variant-numeric:tabular-nums}
  /* slanted numerals = speed; plain neutral fill (the favorite-team stroke was dropped, operator
     2026-07-03 — the team colour now shows up as a hover reveal instead of an always-on outline) */
  .scard .sv b{font:800 calc(15px*var(--vt-scale-stat,1))/1 var(--vt-font-num);color:var(--vt-proj);font-style:oblique 9deg}
  .scard .sv .u{font-size:8.5px;color:rgba(255,255,255,.72);text-transform:uppercase;letter-spacing:.03em;text-shadow:0 1px 1px rgba(0,0,0,.5)}
  .scard .sv .steady{font-size:9px;font-weight:800;color:#7dfc9a;margin-left:2px;text-shadow:0 1px 1px rgba(0,0,0,.5)}
  @media(max-width:980px){.summary{grid-template-columns:repeat(2,1fr)}}
  /* leaderboard podium — horizontal cards: big portrait left, info right */
  .podium{display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px;margin-bottom:18px}
  .pod{position:relative;display:flex;align-items:center;gap:13px;overflow:hidden;
    /* team tint via --podc (set inline by renderPodium) + Color-Studio material knobs:
       --vt-pod-a1/a2 = gradient tint strengths (stock 40%/12% = the old #hex66/#hex1f
       alphas), --vt-pod-blur/-sat = backdrop glass (0px/100% = stock, no glass) */
    background:linear-gradient(180deg,
      color-mix(in srgb,var(--podc,var(--panel2)) var(--vt-pod-a1,40%),transparent),
      color-mix(in srgb,var(--podc,var(--panel)) var(--vt-pod-a2,12%),transparent));
    -webkit-backdrop-filter:blur(var(--vt-pod-blur,0px)) saturate(var(--vt-pod-sat,100%));
    backdrop-filter:blur(var(--vt-pod-blur,0px)) saturate(var(--vt-pod-sat,100%));
    border:2px solid var(--line);
    border-radius:14px;padding:13px;cursor:pointer;text-align:left;transition:filter .15s}
  /* hover = a calm brightness lift only — the medal outline (gold/silver/bronze) is preserved,
     never overridden by an accent ring (operator request). No outer glow. */
  .pod:hover{filter:brightness(1.07)}
  .pod .medal{position:absolute;top:8px;left:8px;z-index:2;width:22px;height:22px;border-radius:50%;
    display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;
    font-variant-numeric:tabular-nums;background:rgba(13,16,22,.85);border:1px solid var(--line);color:#fff;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 1px 3px rgba(0,0,0,.5)}
  /* gold / silver / bronze coins — layered metallic gradient + bright top sheen */
  .pod.first .medal{background:linear-gradient(160deg,#FFEFB0 6%,#F2C53A 46%,#B7811A 100%);
    border-color:#C99312;color:#4a3500;box-shadow:inset 0 1px 0 rgba(255,252,235,.85),0 1px 4px rgba(0,0,0,.45)}
  .pod.second .medal{background:linear-gradient(160deg,#FBFCFE 8%,#CBD2DC 48%,#9098A4 100%);
    border-color:#9aa2af;color:#2a2f38;box-shadow:inset 0 1px 0 rgba(255,255,255,.9),0 1px 4px rgba(0,0,0,.45)}
  .pod.third .medal{background:linear-gradient(160deg,#F2C49B 8%,#C77F3D 48%,#8A4E1E 100%);
    border-color:#9c5d2a;color:#3a1e00;box-shadow:inset 0 1px 0 rgba(255,232,205,.8),0 1px 4px rgba(0,0,0,.45)}
  .pod .avatar.lg{width:66px;height:88px;font-size:22px;border-radius:11px;margin:0;flex:0 0 auto}
  .pod .pinfo{min-width:0;flex:1}
  .pod .pname{font-weight:700;font-size:15px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .pod .pteam{margin-top:4px;font-size:11px;color:var(--muted);display:flex;align-items:center;gap:6px}
  .pod .ppts{font-size:27px;font-weight:800;color:var(--vt-proj);margin-top:6px;letter-spacing:-.02em;line-height:1;
    font-style:oblique 9deg}
  .pod .ppts small{font-size:12px;color:var(--muted);font-weight:600}
  .pod .prange{font-size:10.5px;color:var(--muted);margin-top:5px;font-variant-numeric:tabular-nums}
  .pod .pbar{margin-top:9px}
  /* metallic frame: rich border tone + a bright inner top-edge sheen (no outer glow) */
  .pod.first{border-color:#E0A91E;box-shadow:inset 0 1px 0 rgba(255,244,214,.5)}
  .pod.second{border-color:#B4BCC8;box-shadow:inset 0 1px 0 rgba(255,255,255,.55)}
  .pod.third{border-color:#B5732F;box-shadow:inset 0 1px 0 rgba(255,226,196,.45)}
  .pod.first .avatar.lg{width:74px;height:98px}
  .pod.first .ppts{font-size:31px}
  @media(max-width:900px){.podium{grid-template-columns:1fr}}
  /* tabs */
  /* floating section switcher: liquid-glass bar pinned bottom-centre. .tabs is now just the
     CHROME — background/border/rounded shape/padding — and never scrolls or clips itself, so it
     always reads as one solid, fully-opaque pill. The scrollable button strip lives in the inner
     #tabscroll (below), inset by .tabs' own padding: that padding is what shows as the curved
     "gap" at each end, and it's now a hard structural inset (buttons physically can't render
     there), not a fade — replaces the mask-image fade from 2026-07-04, which the operator found
     wrong: it faded the WHOLE bar's own background/border near the edges (since the mask applied
     to .tabs itself), not just the scrolling text, instead of keeping a permanent, always-visible
     gap like the bar's resting state (operator 2026-07-05: "always have the curved gap... even
     when you scroll"). */
  /* #tabscroll ALSO gets its own border-radius:999px (operator 2026-07-05, round 2: a plain
     rectangular inset "cuts straight" instead of following the pill's own curve at the rounded
     end-caps — the gap only looked right along the flat top/bottom). Since #tabscroll's box is
     concentrically inset from .tabs' box by exactly .tabs' own padding on every side, giving it
     the SAME "fully round" radius value auto-clamps to half of ITS OWN (smaller) height — that's
     the standard nested-pill trick for a uniform-width ring gap that follows the curve at every
     point of the border, not just the straight sides. */
  /* nudged up from 20px (operator 2026-07-04) — the footer now sits close behind it (.wrap's
     bottom padding trimmed above), so the bar needs a touch more clearance to stay a floating
     glass element in front of the page's true end, not glued to its edge. */
  .tabs{position:fixed;left:50%;bottom:26px;transform:translateX(-50%);z-index:45;
    padding:6px;border-radius:999px;width:max-content;max-width:calc(100vw - 24px);
    background:rgba(22,26,34,.5);backdrop-filter:blur(18px) saturate(1.5);-webkit-backdrop-filter:blur(18px) saturate(1.5);
    border:1px solid rgba(255,255,255,.14);
    box-shadow:0 12px 36px rgba(0,0,0,.52),inset 0 1px 0 rgba(255,255,255,.22),inset 0 -1px 0 rgba(0,0,0,.34)}
  .tabscroll{position:relative;display:flex;gap:6px;overflow-x:auto;max-width:100%;border-radius:999px}
  .tabscroll::-webkit-scrollbar{display:none}
  .tabs .glider{position:absolute;top:0;bottom:0;left:0;width:0;z-index:0;background:var(--accent);
    border-radius:999px;transition:transform var(--vt-dur-slow) var(--vt-ease-slide),width var(--vt-dur-slow) var(--vt-ease-slide);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 1px 2px rgba(0,0,0,.4),0 0 16px var(--vt-accent-soft)}
  .tabs button{position:relative;z-index:1;background:transparent;border:0;color:var(--muted);font-size:14px;
    font-weight:600;padding:8px 16px;border-radius:999px;cursor:pointer;white-space:nowrap;flex:0 0 auto}
  .tabs button.on{color:var(--vt-accent-ink)}
  .statbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:0 0 18px}
  .statbar .lbl{color:var(--muted);font-size:12px;text-transform:uppercase;letter-spacing:.06em;margin-right:2px}
  .statbar .quick{display:flex;gap:6px;flex-wrap:wrap}
  .statbar button{background:var(--panel2);border:1px solid var(--line);color:var(--muted);
    font-size:13px;font-weight:700;padding:7px 15px;border-radius:999px;cursor:pointer;transition:.15s;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 1px 2px rgba(0,0,0,.4)}
  .statbar button:hover{color:var(--accent)}
  /* ink follows the selected-tab rule (--vt-accent-ink), not a hardcoded dark brown, so it stays
     readable on any favorite-team accent (e.g. white on Hawks red) — operator 2026-07-03 */
  .statbar button.on{background:var(--accent);border-color:var(--accent);color:var(--vt-accent-ink)}
  .statbar button:active{transform:var(--vt-press)}
  /* "Other…" stat = a custom dropdown (date/language-picker style); the button shows the
     selected non-quick stat (replacing "Other…"); the menu opens to the RIGHT, overlapping it */
  .statpick{position:relative;display:inline-block}
  .statbtn{display:inline-flex;align-items:center;gap:8px;background:var(--panel2);border:1px solid var(--line);
    color:var(--muted);font-size:13px;font-weight:700;padding:7px 15px;border-radius:999px;cursor:pointer;
    line-height:normal;   /* match the quick-stat pills' height exactly (1.2 rendered ~2px shorter) */
    box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 1px 2px rgba(0,0,0,.4);transition:.15s}
  .statbtn:hover{color:var(--accent)}
  .statbtn.on{background:var(--accent);border-color:var(--accent);color:#1a1208}
  .statbtn .dchev{font-size:11px;transition:transform .18s}
  .statbtn[aria-expanded="true"] .dchev{transform:rotate(180deg)}
  .statmenu{position:absolute;left:calc(100% + 10px);top:0;z-index:46;width:226px;max-width:74vw;
    max-height:min(360px,60vh);overflow-y:auto;background:var(--panel);border:1px solid var(--line);
    border-radius:14px;box-shadow:0 18px 50px rgba(0,0,0,.5);padding:6px}
  .statmenu[hidden]{display:none}
  .statmenu .sgrp{color:var(--muted);font-size:9.5px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;padding:7px 9px 3px}
  .statmenu .sopt{padding:7px 10px;border-radius:8px;cursor:pointer;font-size:13px;font-weight:600;transition:background .12s}
  .statmenu .sopt:hover{background:var(--panel2);color:var(--accent)}
  .statmenu .sopt.on{background:linear-gradient(90deg,rgba(240,136,62,.16),transparent);box-shadow:inset 2px 0 0 var(--accent);color:var(--accent)}
  #view-season .controls{margin-top:24px}   /* breathing room under the scatter */
  #view-season .controls{margin-top:24px}   /* breathing room under the scatter */
  /* projections grid: TWO columns of categories; the Scoring group (with shooting) spans both */
  /* stretch so paired groups in the same row match height (Playmaking=Rebounding,
     Defense/Hustle=Impact); the non-scoring groups flex their box-row to fill */
  .mstats{display:grid;grid-template-columns:1fr 1fr;gap:10px 12px;align-items:stretch}
  .msgroup{min-width:0;display:flex;flex-direction:column}
  .msgroup:not(.scoring) .msrow{flex:1}
  .msgroup.scoring{grid-column:1/-1}
  /* PTS is the only chip in the Scoring group, so the default auto-fit .msrow grid (below)
     stretched it across the full card width — much bigger than the FG/3P/2P/FT boxes right below
     it. Force the same 2-column split as .shoot so it matches their width exactly (operator
     2026-07-04: "decrease the PTS projection box to the size of the shooting projection boxes").
     The 2nd column is simply left empty — same visual rhythm as the shoot grid's own gaps. */
  .msgroup.scoring .msrow{grid-template-columns:1fr 1fr}
  @media(max-width:520px){.mstats{grid-template-columns:1fr}}
  /* category title = a team-secondary pill (colour set inline, same as the card header).
     align-self keeps it a snug pill — without it, .msgroup's flex stretch (added for
     equal box heights) pulls the title's WIDTH to fill the column too. */
  .msglab{display:inline-block;align-self:flex-start;font:800 9.5px/1 var(--vt-font-display);text-transform:uppercase;letter-spacing:.05em;
    padding:4px 11px;border-radius:999px;margin:0 0 7px;box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 1px 2px rgba(0,0,0,.4)}
  .msrow{display:grid;grid-template-columns:repeat(auto-fit,minmax(62px,1fr));gap:7px}
  .ms{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:8px 6px;
    display:flex;flex-direction:column;
    text-align:center;cursor:pointer;transition:.15s}
  .ms:hover{border-color:var(--muted)}
  /* selected stat: solid panel + a ring AND number in the CARD'S OWN team-secondary tone
     (--tsec, set inline on .pbody per player) rather than the generic favorite-team accent —
     the highlight should read as "this player's team", matching the rest of the card body
     (operator 2026-07-03, superseding the earlier all-orange rule). */
  .ms.on{border-color:var(--tsec,var(--accent));box-shadow:0 0 0 1px var(--tsec,var(--accent))}
  .ms .lab{color:var(--muted);font-size:9.5px;text-transform:uppercase;letter-spacing:.03em}
  .ms .val{font-size:18px;font-weight:800;margin-top:1px}
  .ms.on .val{color:var(--tsec,var(--accent))}
  .ms .rng{color:var(--muted);font-size:9.5px;margin-top:2px}
  /* explicit line-height:1 on every child too (not just inherited) — verified pixel-perfect
     centring in Chromium already, this hardens against any engine that resolves the inherited
     unitless line-height differently per child font-size (operator 2026-07-05: still seeing
     misalignment on a real device after the round-4 fix). */
  .ms .act{flex:1;display:flex;align-items:center;justify-content:center;line-height:1;
    margin-top:4px;padding-top:4px;border-top:1px solid var(--line);font-size:11px;font-weight:700;color:#cdd3df}
  .ms .act *{line-height:1}
  .ms .act .al{color:var(--muted);font-weight:600;font-size:8px;text-transform:uppercase;letter-spacing:.04em;margin-right:4px}
  /* display:flex on .ms .act drops the whitespace text node that used to
     separate the number from its delta — restore the gap explicitly */
  .ms .act .d{margin-left:4px}
  .act .d.up{color:#3fb950}
  .act .d.dn{color:#f85149}
  .s2.result{margin-top:4px;color:#dfe4ec;font-size:12.5px}
  .s2.result b{color:#fff}
  .controls{display:flex;gap:12px;align-items:center;margin:0 0 14px}
  input[type=search]{flex:1;min-width:200px}
  .count{color:var(--muted);font-size:13px;white-space:nowrap}
  /* avatars + chips */
  /* rounded-rectangle portrait (BR headshot is ~2:3) — shows the whole face,
     unlike the old circular crop that cut off heads / big hair */
  .avatar{position:relative;display:inline-flex;align-items:center;justify-content:center;
    width:34px;height:46px;border-radius:9px;overflow:hidden;flex:0 0 auto;
    font-size:13px;font-weight:700;color:#fff;border:1px solid rgba(255,255,255,.12);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 1px 2px rgba(0,0,0,.4)}
  .avatar img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center top;background:#222}
  .avatar.lg{width:58px;height:78px;font-size:20px;border-radius:12px}
  .avatar.sm{width:28px;height:38px;font-size:11px;border-radius:7px}
  /* team pill — the signature .gtp style (solid team colour + top inset highlight + drop shadow) */
  .chip{display:inline-block;font:800 11px/1.1 var(--vt-font-display);letter-spacing:.03em;padding:3px 9px;
    border-radius:8px;box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 1px 2px rgba(0,0,0,.4)}
  /* subtle grey Vates monogram standing in for a locked/hidden stat value (anon) */
  .lockmk{display:inline-block;width:1.05em;height:.78em;vertical-align:-.06em;background:var(--muted);opacity:.5;
    -webkit-mask:url(vates-mark.svg) center/contain no-repeat;mask:url(vates-mark.svg) center/contain no-repeat}
  .ms-lock .val .lockmk{width:22px;height:16px;opacity:.4}
  /* game cards (auto-fit: several games tile; a single game is capped and LEFT-aligned) */
  .games{display:grid;grid-template-columns:repeat(auto-fit,minmax(460px,1fr));gap:16px}
  .games.single{display:block;max-width:680px}   /* left-aligned (no margin:auto) */
  /* flex column so the coloured player band stretches to fill a taller sibling card
     (fixes the shorter game's colours stopping short in a 2-up row) */
  .game{display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);
    border-radius:16px;overflow:hidden;box-shadow:var(--shadow),inset 0 1px 0 rgba(255,255,255,.05)}
  .game .gbody{position:relative;flex:1;display:flex;flex-direction:column}
  /* split header: each side solid in that team's SECONDARY colour (no fade); a PRIMARY-colour
     team pill on the outer edge, the big team name next to it, a vs badge sitting on the seam */
  /* header sits above the body and casts a shadow onto the colour band, marking the boundary.
     (selectors are un-scoped from .game so the game-totals modal can reuse the exact header.) */
  .ghead{position:relative;z-index:2;display:flex;align-items:stretch;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.18),0 5px 11px -3px rgba(0,0,0,.6),0 1px 0 rgba(0,0,0,.5)}
  /* taller header + bigger pill/name (operator: fill more space). Horizontal padding for
     the centre seam is handled per-side inline in gameHeadHTML (E overlap), so no
     first-child/.gt.r padding overrides here. */
  .ghead .gt{flex:1;min-width:0;display:flex;align-items:center;gap:13px;padding:17px 18px;font-weight:800}
  /* straight vertical join between the two team halves (replaces the old diagonal seam). No
     rendered separator line at the seam (operator 2026-07-03: remove it) — the two colours abut
     directly, the contrast between them is the boundary. */
  .ghead .gt.r{flex-direction:row-reverse;text-align:right}
  .ghead .gtp{flex:0 0 auto;font:800 15px/1 var(--vt-font-display);letter-spacing:.02em;padding:7px 12px;
    border-radius:9px;box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 1px 2px rgba(0,0,0,.4)}
  /* bigger team name; Saira has no width axis so compress only lightly (faux-condensed) */
  .ghead .gtn{flex:1 1 auto;min-width:0;font:800 26px/1.15 var(--vt-font-display);letter-spacing:0;
    text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
    display:block;transform:scaleX(.9);transform-origin:left center}
  .ghead .gt.r .gtn{transform-origin:right center}
  /* narrow phones: single .game card is ~340px wide, so each .gt half is only ~170px -- the
     desktop 26px name font left only 2-3 letters before ellipsis ("KN…"/"SP…", operator
     2026-07-04). Shrink the pill + name + tighten padding/gap so full short names (Knicks,
     Wolves, Cavaliers…) fit without truncating. */
  @media(max-width:480px){
    .ghead .gt{padding:13px 12px;gap:8px}
    .ghead .gtp{font-size:11px;padding:5px 8px}
    .ghead .gtn{font-size:15px}
  }
  /* plain team-primary colour columns (set inline); flex so they fill the stretched card height */
  .teamcols{display:grid;grid-template-columns:1fr 1fr;flex:1}
  /* top padding covers the .gtotalsbar's 12px dip (above) plus a clear few px of team colour
     below it, so the bar's glass overlap never reaches the first player row. */
  .teamcols .col{padding:20px 8px 9px;min-width:0;display:flex;flex-direction:column;gap:6px}
  .teamcols .col+.col{border-left:1px solid rgba(0,0,0,.25)}
  /* each player row is a protective panel box so it stays readable over the plain colour band.
     position:relative anchors the hover trace ring below. */
  .prow{position:relative;display:flex;align-items:center;gap:9px;padding:7px 9px;border-radius:10px;
    cursor:pointer;background:var(--panel);border:1px solid var(--line);box-shadow:0 1px 3px rgba(0,0,0,.32);
    transition:border-color .16s ease}
  /* base hover (also the reduced-motion fallback): a team-secondary (--tsec) border, no fill, no
     lift — was the generic favorite-team accent, a residual from before the trace itself switched
     to --tsec (operator 2026-07-03). */
  .prow:hover{border-color:color-mix(in srgb,var(--tsec,var(--accent)) 55%,var(--line))}
  /* hover-trace overlay: ONE line turning around the row's rounded-rect border (conic-gradient +
     mask). See the shared @property/keyframe under the motion media query below. Hidden by
     default. .scard .srow gets its OWN version right after — same effect, different box (operator
     2026-07-03: summary rows should contour the name+projection content, INSIDE the row, not the
     row's outer border). Colour is --tsec (the row's own team SECONDARY, set inline per row —
     teamSec() in teams-data.js is specifically "guaranteed to contrast with the primary"), not the
     global accent: the row's own background is always team-PRIMARY-coloured, so a favorite-team
     accent that happens to match that primary would make the trace invisible (operator 2026-07-03:
     "use the secondary color... when the background is the same color"). Falls back to --accent
     if --tsec is somehow unset. */
  .prow::after{content:"";position:absolute;inset:-1px;border-radius:inherit;
    pointer-events:none;z-index:2;padding:var(--vt-trace-bw,1.4px);
    background:conic-gradient(from var(--gtrace),transparent 0 var(--vt-trace-a,65%),var(--tsec,var(--accent)) var(--vt-trace-b,85%),transparent 100%);
    -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
    -webkit-mask-composite:xor;mask-composite:exclude;opacity:0;transition:opacity .2s ease}
  /* inset LESS than the row's own padding (8px/13px) so there's a visible gap between the trace
     and the text (operator 2026-07-03: the exact-padding inset sat flush against the text) —
     still a rounded rect INSIDE the row, just with breathing room, not hugging the row's outer
     edge either. */
  .scard .srow::after{content:"";position:absolute;inset:4px 6px;border-radius:9px;
    pointer-events:none;z-index:2;padding:var(--vt-trace-bw,1.4px);
    background:conic-gradient(from var(--gtrace),transparent 0 var(--vt-trace-a,65%),var(--tsec,var(--accent)) var(--vt-trace-b,85%),transparent 100%);
    -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
    -webkit-mask-composite:xor;mask-composite:exclude;opacity:0;transition:opacity .2s ease}
  /* the ACTUAL highlighted frame on hover — thin + DIMMED (same colour-mix recipe as .prow's own
     hover border: --tsec mixed 55% into the base --line tone, not a full-strength border), at the
     SAME inset/radius as the ::after trace above, so it reads as "a frame you can barely see" —
     the ::after trace then rides on top as the one bright, moving accent, clearly distinct from
     the dim frame instead of blending into it (operator 2026-07-04, round 2: thinner + dimmed +
     the moving line must actually be visible against it). */
  .scard .srow:hover::before{content:"";position:absolute;inset:4px 6px;border-radius:9px;
    pointer-events:none;z-index:1;border:1px solid color-mix(in srgb,var(--tsec,var(--accent)) 55%,var(--line))}
  /* games row as a GRID: [avatar] [name+PTS] [stats]. Grid cells can't overlap, so the PTS — in
     its own min-56px column — always stays fully visible no matter how narrow the card gets. */
  .gprow{display:grid;grid-template-columns:auto minmax(56px,1fr) auto;gap:9px;align-items:center}
  .gprow .gname{min-width:0;overflow:hidden}
  .gprow .gname .n{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .gprow .gname .s1{font-size:calc(16px*var(--vt-scale-stat,1));font-weight:800;color:var(--vt-proj);line-height:1.1;margin-top:1px;
    white-space:nowrap;font-variant-numeric:tabular-nums;font-family:var(--vt-font-display)}
  .gprow .gname .s1 b{font-size:9px;font-weight:600;opacity:.6;margin-left:2px;text-transform:uppercase}
  .gprow .gstats{flex:0 0 auto;display:grid;grid-template-columns:auto auto;gap:3px;
    font-family:var(--vt-font-display)}
  /* each minor stat in its own compact "sticker box" */
  .gprow .gstats span{font-variant-numeric:tabular-nums;white-space:nowrap;font-size:calc(11px*var(--vt-scale-stat,1));font-weight:700;color:var(--vt-proj-2,#cdd3df);
    background:var(--panel2);border:1px solid var(--line);border-radius:6px;padding:2px 6px;text-align:right}
  .gprow .gstats b{font-weight:600;font-size:.76em;color:var(--muted);margin-left:3px;text-transform:uppercase}
  /* dimmed stat: this player isn't a game top-3 here, so the value is masked to a
     "·" placeholder (mirrors the locked-stat treatment in the player modal) */
  /* locked secondary stat: the Vates monogram (LOCKMARK) stands in for the value — bumped from
     near-invisible so the mark actually reads on the 2×2 grid, matching the PTS lock (operator C4) */
  .gprow .gstats span.dim{color:var(--muted);opacity:.62;background:transparent;border-color:transparent}
  /* bugfix (operator 2026-07-03: "still don't see the logo for other stats than PTS"): the generic
     ".gprow .gstats span" rule above (2 classes + 1 type) was beating ".lockmk"'s own background
     (1 class), repainting the monogram's mask-fill as --panel2 (near-invisible on the dark card)
     instead of --muted. Higher-specificity override restores the correct fill. */
  .gprow .gstats span.dim .lockmk{background:var(--muted)}
  .gprow .gname .s1.dim{color:var(--muted);opacity:.5}
  /* "Projected team totals" is its OWN independent sub-header bar between .ghead and .gbody —
     a full-width strip, control centred and flanked by fading rules. Glass recipe matches the
     floating TAB BAR (lower background opacity, stronger blur+saturate, light border). It dips
     DOWN into the team-coloured columns by 12px (negative margin-bottom pulls .gbody up under
     it) so the blur actually has team colour to pick up — sitting flush between two sections
     with nothing colourful behind it made the glass invisible (operator 2026-07-03: "we should
     see some blue on the left and black on the right"). Does NOT overlap the header (no overlap
     there) and .teamcols .col's top padding is bumped so this dip never reaches the first player
     row. */
  .gtotalsbar{display:flex;align-items:center;gap:12px;padding:8px 16px;position:relative;z-index:2;
    margin-bottom:-12px;
    background:rgba(22,26,34,.5);backdrop-filter:blur(18px) saturate(1.5);-webkit-backdrop-filter:blur(18px) saturate(1.5);
    border-bottom:1px solid rgba(255,255,255,.14);
    box-shadow:0 1px 0 rgba(0,0,0,.4),inset 0 1px 0 rgba(255,255,255,.14)}
  .gtotalsbar::before,.gtotalsbar::after{content:"";flex:1;height:1px;min-width:0}
  .gtotalsbar::before{background:linear-gradient(90deg,transparent,var(--line))}
  .gtotalsbar::after{background:linear-gradient(90deg,var(--line),transparent)}
  .gtotals{display:inline-flex;align-items:center;gap:5px;flex:0 0 auto;white-space:nowrap;
    text-decoration:none;color:#cdd3df;font-size:11px;font-weight:700;cursor:pointer;
    background:none;border:0;padding:0;transition:color .16s}
  .gtotals:hover{color:var(--accent)}
  .gtotals .lockmk{opacity:.7}
  /* game-totals modal — close button now lives INSIDE the header (gmghead), matching the
     player-card header, instead of a separate floating modal sibling (that stray element was
     the root cause of an occasional shifted-card/extra-close-box bug — operator 2026-07-04). */
  .gmghead{position:relative}
  /* Room for the close button is made via a MARGIN on the right team's own pill (.gtp), not via
     padding on .gt.r itself. Padding on .gt.r would grow that flex item's own minimum size (it
     can't shrink below its own padding even with min-width:0 on a flex-basis:0% item), which is
     exactly what made the two team-colour backgrounds visibly unequal (right side ~34px wider)
     even though both are "flex:1" — operator 2026-07-04: "make the team colors background the
     same size... aligned to the gap between the two rows of projection boxes." A margin on the
     pill only affects internal layout inside .gt.r, not .gt.r's own outer width, so both team
     halves now split the header exactly 50/50 — verified this lands the seam exactly on the
     .gmtotals column gap's centre. */
  .gmghead .gt.r .gtp{margin-right:27px}
  /* shrunk to match the team pill's own height (29px, was a flat 32px square) — same size class
     as the pills it sits next to (operator 2026-07-04), still vertically centred on the header
     via top:50%/translateY like before. */
  .gmghead .close{position:absolute;top:50%;right:8px;transform:translateY(-50%);z-index:3;
    width:29px;height:29px}
  /* wider than the default 580px sheet — the 2-column totals grid didn't fit at that width
     without the boxes overflowing/wrapping (operator 2026-07-04, round 2). Compound selector
     (not just .gmsheet) so it beats .sheet's own max-width regardless of source order — both are
     single-class-equivalent specificity otherwise, and .sheet is declared later in this file. */
  .sheet.gmsheet{max-width:640px}
  .gmsheet .ghead{border-radius:20px 20px 0 0;overflow:hidden}
  .gmsheet .pbody{border-radius:0 0 20px 20px}
  /* boxed pill, same recipe as the player card's .psect ("PROJECTIONS"/"SEASON AVERAGES")
     instead of plain muted uppercase text — harmonizes it with .gmlead (also a boxed panel)
     and the totals boxes below, instead of sitting between two boxed elements as bare text
     (operator 2026-07-04, round 4: "harmonize the size... PROJECTED TEAM TOTALS text, Projected
     edge box"). Compound selector: the generic .sectlab rule (color/weight/margin) is declared
     LATER in this file and would otherwise win on equal specificity and override these. */
  .sectlab.gmsect{display:flex;align-items:center;justify-content:center;gap:7px;
    font:800 12px/1 var(--vt-font-display);color:#fff;text-transform:uppercase;letter-spacing:.06em;
    padding:8px 13px;border-radius:9px;margin:0 0 12px;
    background:rgba(0,0,0,.34);border:1px solid rgba(255,255,255,.14);box-shadow:inset 0 1px 0 rgba(255,255,255,.08)}
  .gmhead{flex:1;display:flex;align-items:center;justify-content:center;gap:16px;font-weight:800}
  .gmteam{display:flex;align-items:center;gap:8px}
  .gmnick{font-size:16px;color:#fff}
  .gmvs{color:#fff;opacity:.85;font-size:12px;font-weight:700}
  /* now the LAST element in the sheet (moved below .gmtotals, operator 2026-07-04: "at the bottom
     of the window") — margin flipped from top-gap to bottom-gap accordingly. */
  .gmlead{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:9px 13px;
    font-size:13px;color:#cdd3df;margin:12px 0 0}
  .gmlead b{color:var(--accent)}
  /* compact + centred rather than stretched to the sheet's full width — the values are a
     handful of characters, so a full-width 1fr/1fr grid left a lot of empty space either side
     of them (operator 2026-07-04). */
  /* two columns (3 rows instead of 6) — compacts the sheet further (operator 2026-07-04) */
  .gmtotals{display:grid;grid-template-columns:repeat(2,max-content);justify-content:center;gap:6px 10px;margin-bottom:6px}
  /* widened (90/74/90 -> 96/74/96) so the 2-column grid's combined width flushes exactly with the
     lead banner / section-label boxes above it (594px content width: 2 boxes + the 10px gap),
     instead of sitting centred with visible margin on both sides (operator 2026-07-04, round 5). */
  .gmrow{display:grid;grid-template-columns:96px 74px 96px;align-items:center;width:max-content;max-width:100%;
    padding:8px 12px;background:var(--panel2);border:1px solid var(--line);border-radius:10px}
  .gmrow .gv{font-size:17px;font-weight:800;color:#cdd3df;font-variant-numeric:tabular-nums}
  .gmrow .gv:first-child{text-align:right}.gmrow .gv:last-child{text-align:left}
  .gmrow .gl{color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.04em;text-align:center}
  /* game totals row: actual results mode (past games) */
  .gmrow-act{align-items:center}
  .gmrow-act .gvcol{display:flex;flex-direction:column;gap:1px}
  .gmrow-act .gvcol:first-child{text-align:right}
  .gmrow-act .gvcol:last-child{text-align:left}
  .gmrow-act .act-v{font-size:17px;font-weight:800;color:#cdd3df;font-variant-numeric:tabular-nums}
  .gmrow-act .gvproj{font-size:10px;color:var(--muted);font-variant-numeric:tabular-nums}
  /* lock row in game card player columns */
  .lockrow{cursor:pointer;justify-content:center;font-size:12px;color:var(--accent);
    opacity:.8;text-decoration:none;font-weight:600}
  .lockrow:hover{border-color:var(--accent);opacity:1}
  /* lock banner in player modal */
  .lock-banner{background:rgba(255,165,0,.08);border:1px solid rgba(255,165,0,.22);border-radius:10px;
    padding:9px 14px;font-size:13px;color:#cdd3df;margin-bottom:10px}
  .lock-banner a{color:var(--accent);text-decoration:none;font-weight:600}
  /* sitewide account-creation banner (anon visitors only, every tab) — same warm-accent
     treatment as .lock-banner, but full-width with two explicit CTAs instead of one link */
  .acctbanner{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:16px;
    background:rgba(255,165,0,.08);border:1px solid rgba(255,165,0,.22);border-radius:10px;
    padding:16px 38px 16px 20px;font-size:15px;line-height:1.45;color:#cdd3df;margin-bottom:14px;text-align:center}
  /* an explicit display: above beats the UA [hidden]{display:none} on equal specificity,
     so updateAcctBanner() setting el.hidden=true had no visual effect — restore it here */
  .acctbanner[hidden]{display:none}
  .acctbanner b{color:var(--text)}
  .acctbanner .authbtn{font-size:13.5px}
  .acctbanner .abclose{position:absolute;top:6px;right:8px;background:transparent;border:0;
    color:var(--muted);font-size:17px;line-height:1;padding:4px 6px;cursor:pointer;border-radius:6px}
  .acctbanner .abclose:hover{color:var(--text);background:rgba(255,255,255,.07)}
  /* locked stat chip in modal */
  .ms-lock{cursor:default;opacity:.55;pointer-events:none}
  .ms-lock .val{font-size:14px}
  /* locked shooting splits: dim the whole block like the other hidden stats */
  .shoot.locked{opacity:.55;pointer-events:none}
  .shoot.locked .shv-lock{border-color:transparent;cursor:default}
  /* no-projection placeholder (stat has an actual but no projected line) */
  .val-na{color:var(--muted);font-weight:600}
  .gmtops{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:6px}
  .gmp{display:flex;align-items:center;gap:8px;padding:6px;border-radius:9px;cursor:pointer;transition:background .12s}
  .gmp:hover{background:var(--panel2)}
  .gmpn{flex:1;min-width:0;font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .gmpp{font-weight:800;color:var(--accent);font-variant-numeric:tabular-nums}
  @media(max-width:560px){.gmtops{grid-template-columns:1fr}}
  /* leaderboard */
  /* faint top inset light on the big surfaces — the chips' signature depth cue,
     dialed way down for panel scale (operator direction: more 3D depth) */
  .card{background:var(--panel);border:1px solid var(--line);border-radius:14px;overflow:clip;
    box-shadow:var(--shadow),inset 0 1px 0 rgba(255,255,255,.05)}
  table{width:100%;border-collapse:collapse;font-variant-numeric:tabular-nums}
  th,td{padding:10px 14px;text-align:left}
  thead th{position:sticky;top:var(--hdr);z-index:5;background:var(--panel2);color:var(--muted);font-size:11px;font-weight:700;
    text-transform:uppercase;letter-spacing:.05em;cursor:pointer;user-select:none;white-space:nowrap;
    border-bottom:1px solid var(--line)}
  thead th.num{text-align:right}
  /* Board + Season: INTERNAL vertical scroll (operator: scroll AND pages) — the header sticks to the TOP
     of the scroll box so it sits flush on the table. Team Stats also scrolls (its columns can be wide). */
  /* position:relative — the shared #rowTrace hover box (app.js wireRowTrace) is reparented in
     here and absolutely positioned relative to THIS box, so it scrolls/clips naturally with
     the table instead of needing page-coordinate math + a scroll listener. */
  #view-board .tscroll,#view-season .tscroll{max-height:min(620px,70vh);overflow:auto;position:relative}
  #view-board thead th,#view-season thead th{position:sticky;top:0;z-index:5}
  #view-teams .tscroll{max-width:100%;max-height:min(620px,68vh);overflow:auto;position:relative}
  #view-teams thead th{position:sticky;top:0;z-index:5}
  /* align numeric column headers to their (centred) values */
  #view-season thead th.num,#view-teams thead th.num{text-align:center}
  #view-season td.num{text-align:center}
  /* board + season cards hug their content (no empty space); Team Stats stays full-width and scrolls
     horizontally inside (13 columns are naturally wider than the viewport) */
  #view-board .card.board,#view-season .card{width:max-content;max-width:100%}
  #view-teams .card{max-width:100%}
  td.num{text-align:right;font-size:14px;font-variant-numeric:tabular-nums}
  thead th .ar{opacity:.6;font-size:9px;margin-left:3px}
  tbody tr{cursor:pointer}
  td.match{color:var(--muted);font-size:13px;white-space:nowrap}
  td.proj{text-align:right;font-size:18px;font-weight:800;color:var(--accent);width:62px}
  td.range{width:280px}
  /* ---- Leaderboard + Season Leaders + Team Stats: VIVID full team-colour RECTANGULAR rows that fill the
     whole row width. Colour rides on the CELLS (via --rowc on the <tr>); rows touch (no gaps) and are
     separated by ONE crisp 1px line — no blurred inset shadow, so there are no faint vertical seams
     between columns. The hover (further below) is a PILL around the row content, NOT an outline. ---- */
  #view-board table,#view-season table{width:max-content;border-collapse:separate;border-spacing:0}
  #view-teams table{width:100%;border-collapse:separate;border-spacing:0}   /* fill the body width — no empty gap right of the last column */
  #view-board td,#view-season td,#view-teams td{padding:9px 10px;background:var(--rowc);
    vertical-align:middle;border-bottom:1px solid rgba(0,0,0,.34);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.12)}   /* 1px top sheen — the "refined vivid" depth */
  #view-board td.who,#view-season td.who{min-width:0;white-space:nowrap}
  #view-board td.range{width:262px}
  /* rank and star are TWO SEPARATE bubbles in the first cell (max-content table → columns already
     hug their content, so NO width:1% trick — it mis-distributes width in a max-content table) */
  .rkcell{white-space:nowrap}
  .pinpill,.rkpill{display:inline-flex;align-items:center;justify-content:center;background:#0e1219;
    border:1px solid rgba(255,255,255,.16);border-radius:999px;box-shadow:inset 0 1px 0 rgba(255,255,255,.07)}
  .pinpill{padding:2px 6px;margin-right:6px}
  .rkpill{padding:3px 10px;min-width:26px}
  .rkpill .rk{font:800 12px/1 var(--vt-font-display);color:#cdd3df;font-variant-numeric:tabular-nums}
  .pinpill .pin{font-size:13px;padding:0;line-height:1}
  /* the rank column header is not a sort control (no pointer) */
  .rkhdr,#teamsTable thead th.rkhdr{cursor:default}
  #view-teams td.rank,#view-teams th.rkhdr{width:46px;min-width:46px;white-space:nowrap;text-align:center}
  /* team name + player name on one baseline */
  #view-board td.who .npill,#view-season td.who .npill,#view-season td.who .teamtag{vertical-align:middle}
  /* player name pill — team SECONDARY (stands out from the vivid primary row) */
  .npill{display:inline-block;max-width:100%;font:800 12px/1.2 var(--vt-font-display);padding:3px 11px;
    border-radius:999px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 1px 2px rgba(0,0,0,.4)}
  /* solid dark "bubble" — readable on any row colour */
  .tpill{display:inline-flex;align-items:center;justify-content:center;gap:6px;background:#0e1219;
    border:1px solid rgba(255,255,255,.16);border-radius:999px;padding:3px 10px;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.07);white-space:nowrap}
  .teamtag{display:inline-block;padding:3px 9px;font:800 10.5px/1 var(--vt-font-display);letter-spacing:.03em;
    border-radius:999px;box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 1px 2px rgba(0,0,0,.4)}
  td.match .tpill .vsopp{color:#cdd3df;font-size:12.5px}
  td.proj .tpill{font:800 16px/1 var(--vt-font-display);color:var(--vt-proj);font-variant-numeric:tabular-nums;padding:5px 12px}
  td.range .tpill{display:block;width:100%;padding:8px 12px 6px;border-radius:13px}
  /* team-stats numbers centred in their bubbles; team name column centred too */
  #view-teams td.num{text-align:center}
  #view-teams td.num .tpill{padding:3px 9px;font-variant-numeric:tabular-nums;min-width:48px}
  #view-teams td.tm{text-align:center}
  #view-teams td.tm .npill{vertical-align:middle}
  #view-teams th[data-tk="team"]{text-align:center}
  /* rank bubble matches the other value bubbles' height so the first cell is the full row height */
  #view-teams td.rank .tpill{padding:3px 9px;min-width:30px;font:800 14px/21px var(--vt-font-display)}
  /* range bar: a neutral P10–P90 band with P10/P90 end-caps and a ringed P50 dot */
  .bar{position:relative;height:8px;background:var(--track);border-radius:999px}
  /* translucent team-coloured P10–P90 band (fill+border set inline) */
  .bar .iqr{position:absolute;top:0;bottom:0;border-radius:999px;border:1px solid;box-sizing:border-box;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 1px 2px rgba(0,0,0,.4)}
  .bar .zero{position:absolute;top:-3px;bottom:-3px;width:1px;background:#6b7280;opacity:.7;z-index:1}
  /* P50 marker = the brand point mark (downward triangle): white fill, team-colour stroke */
  .bar .p50{position:absolute;top:50%;width:12px;height:9px;margin-left:-6px;z-index:2;
    transform:translateY(-50%);overflow:visible;filter:drop-shadow(0 1px 2px rgba(0,0,0,.55))}
  /* P10/P50/P90 values, each positioned UNDER its marker (not fixed) */
  .rlab{position:relative;height:13px;margin-top:7px;font-variant-numeric:tabular-nums}
  .rlab .q{position:absolute;transform:translateX(-50%);font-size:10.5px;color:#aab2c0;white-space:nowrap}
  .rlab .q.mid{color:#fff;font-weight:700}
  /* leaderboard controls: compact toggle, pin-to-compare, pagination */
  .densebtn{background:var(--panel2);border:1px solid var(--line);color:var(--muted);border-radius:9px;
    padding:9px 14px;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;transition:.15s}
  .densebtn:hover{color:var(--text);border-color:var(--accent2)}
  /* same ink rule as the selected-tab label (operator 2026-07-03: Leaderboard/Season selector) */
  .densebtn.on{background:var(--accent);border-color:var(--accent);color:var(--vt-accent-ink)}
  th.pincol,td.pincol{width:26px;padding-left:10px;padding-right:0}
  .pin{background:none;border:0;color:#48516a;font-size:15px;cursor:pointer;line-height:1;padding:2px;
    transition:color .12s,transform .1s}
  /* the star highlights only when the STAR itself is hovered — not on row hover */
  .pin.on,.pin:hover{color:var(--accent)}
  .pin:active{transform:scale(.85)}
  tr.pinned{background:linear-gradient(90deg,rgba(240,136,62,.07),transparent)}
  /* not clickable (only "clear all" inside it is), so it shouldn't look it either — was
     inheriting tbody tr's cursor:pointer (operator 2026-07-05). */
  .grouphdr{cursor:default}
  .grouphdr td{background:var(--panel2);color:var(--muted);font-size:10px;font-weight:700;
    text-transform:uppercase;letter-spacing:.06em;padding:6px 14px}
  /* fav-team tone, not the static brand-blue utility colour (operator 2026-07-04) */
  .grouphdr td .clearpins{float:right;color:var(--accent);cursor:pointer;text-transform:none;letter-spacing:0;font-size:11px}
  .pager{display:flex;align-items:center;justify-content:center;gap:14px;padding:14px;
    color:var(--muted);font-size:13px;border-top:1px solid var(--line)}
  .pager[hidden]{display:none}
  .pager button{background:var(--panel2);border:1px solid var(--line);color:var(--text);border-radius:9px;
    padding:7px 14px;font-size:13px;font-weight:600;cursor:pointer;transition:.15s}
  .pager button:hover:not(:disabled){border-color:var(--accent2)}
  .pager button:disabled{opacity:.4;cursor:not-allowed}
  /* season-leaders model-bias insight strip */
  .biaswrap{margin:16px 0 2px}
  .biashead{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.05em;font-weight:700;margin:0 0 9px}
  .biashead b{color:var(--text)}
  .biasrow{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}
  .biascell{background:linear-gradient(180deg,var(--panel2),var(--panel));border:1px solid var(--line);
    border-radius:12px;padding:11px 12px;text-align:center}
  .biascell.tot{border-color:var(--accent2)}
  .biascell .l{color:var(--muted);font-size:10.5px;text-transform:uppercase;letter-spacing:.04em}
  .biascell .v{font-size:21px;font-weight:800;margin-top:3px}
  .biascell .r{color:var(--muted);font-size:10px;margin-top:3px}
  .biascap{color:var(--muted);font-size:11.5px;margin:9px 2px 0;line-height:1.5}
  @media(max-width:560px){.biasrow{grid-template-columns:repeat(2,1fr)}}
  /* segmented toggle (e.g. Season Leaders "Compare vs") */
  .seg{display:inline-flex;align-items:center;gap:3px;background:var(--panel2);border:1px solid var(--line);
    border-radius:10px;padding:4px}
  .seg[hidden]{display:none}
  .seg .seglbl{color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.05em;font-weight:700;padding:0 7px}
  .seg button{background:transparent;border:0;color:var(--muted);font-size:12.5px;font-weight:600;
    padding:6px 12px;border-radius:7px;cursor:pointer;transition:background .15s,color .15s}
  .seg button:hover{color:var(--text)}
  .seg button.on{background:var(--accent);color:var(--vt-accent-ink)}
  /* compact density mode (season leaders only — the leaderboard is compact by default now) */
  #view-season.compact th[data-sk="vol"],#view-season.compact td.vol,
  #view-season.compact th[data-sk="mae"],#view-season.compact td.maec,
  #view-season.compact th[data-sk="bias"],#view-season.compact td.biasc{display:none}
  #view-season.compact .avatar{width:24px;height:32px;font-size:10px;border-radius:6px}
  #view-season.compact th,#view-season.compact td{padding:6px 12px}
  /* charts */
  .charts{display:grid;grid-template-columns:1fr 1fr;gap:16px}
  .chartbox{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:16px;
    box-shadow:var(--shadow),inset 0 1px 0 rgba(255,255,255,.05);overflow:hidden}
  .chartbox h3{margin:0 0 12px;font-size:15px}
  /* the middle-dot glyph sits flush against its neighbours in Saira — a plain
     space after it isn't enough breathing room, so give it explicit margin
     instead of relying on glyph kerning */
  .chartbox h3 .dotsep{margin:0 4px}
  .chartbox h3 .infodot{vertical-align:middle;margin-left:5px}
  .chartbox h3 .infodot .tip{font-weight:400;width:280px;text-transform:none;letter-spacing:normal}
  .chartbox p{margin:0 0 12px;color:var(--muted);font-size:12px}
  .chartbox .cwrap{position:relative;height:420px;overflow:hidden}
  .chartbox.tall .cwrap{height:520px}
  /* Team Stats / Season Leaders scatter charts: a 520px-tall canvas inside a ~350px-wide mobile
     card stretches the plot into an oddly tall aspect ratio (operator 2026-07-04). Explicitly
     excludes .powerchart (the ELO chart, which sizes itself via its own flex layout, see
     .powerchart .cwrap below) — on a real device this rule was still bleeding into the ELO chart
     and squeezing it down to almost nothing (operator 2026-07-05 screenshot: chart cut off right
     after the "1900" axis tick), even though flex-basis should in theory take priority over a
     plain height property here. Not worth relying on that theory holding on every engine —
     :not(.powerchart) removes the ambiguity outright. */
  @media(max-width:560px){
    .chartbox:not(.powerchart) .cwrap{height:300px}
    .chartbox.tall:not(.powerchart) .cwrap{height:340px}
  }
  /* power rankings */
  .powerwrap{display:grid;grid-template-columns:minmax(320px,420px) 1fr;gap:16px;height:600px}
  /* min-width:0 stops the canvas's own (Chart.js-managed) pixel size from ever
     dictating this grid track's width — without it, a stale large inline
     canvas width (e.g. right after leaving the "almost full screen" expanded
     view) creates a feedback loop: the track stretches to the canvas's old
     size, Chart.js measures that inflated track as the "new" size, and the
     bloat never resolves itself back down. */
  .powerwrap>.card,.elocol{display:flex;flex-direction:column;min-height:0;min-width:0}
  .elocol{gap:12px}
  .elocol .powerchart{flex:1;min-height:0;display:flex;flex-direction:column}
  .ranks{flex:1;overflow-y:auto;min-height:0}
  .powerchart h3{display:flex;align-items:center}
  /* chart on top, team inspector below. The column (window-preset row +
     title + chart + inspector) must stay within the ranks column's 600px —
     so unlike a plain "tall" chart, the chart here gives up its fixed 520px
     and instead flexes to fill whatever's left after the preset row, title
     and inspector take their fixed share. The pill-stacking in app.js
     (eloEndLabels) compresses to fit whatever height that ends up being, so
     pills stay fully inside the chart regardless of the exact pixel count. */
  .powerchart .elobody{display:flex;flex-direction:column;gap:12px;min-height:0;flex:1}
  .powerchart .cwrap{min-width:0;flex:1;height:auto;min-height:160px}
  /* real column headers above the ranking values (also the sort controls) */
  /* tm shrunk to a minmax track (it's just a 3-letter code now) and rec widened
     — wider windows push records to 3-4 digits each side ("1490–1197") which
     used to wrap inside the old 50px column */
  .rankhdr{display:grid;grid-template-columns:24px 4px minmax(34px,1fr) 78px 50px 56px 60px;align-items:center;gap:10px;
    padding:11px 14px;border-bottom:1px solid var(--line);color:var(--muted);font-size:10.5px;
    font-weight:700;text-transform:uppercase;letter-spacing:.05em}
  .rankhdr .rec,.rankhdr .pct,.rankhdr .elo,.rankhdr .chg{text-align:center}
  .rankhdr .sortable{cursor:pointer;user-select:none;white-space:nowrap;transition:color .15s}
  .rankhdr .sortable:hover{color:var(--text)}
  .rankhdr .sortable.on{color:var(--accent)}
  .rankhdr .ar{font-size:8px;margin-left:3px;opacity:.9}
  /* phones: the full 7-column grid (380px content) didn't fit a ~350px card and had no scroll
     mechanism (.card clips overflow) -- Pct is redundant with Rec (a plain derived win%), drop it
     to fit the rest without needing horizontal scroll at all, which also sidesteps any
     header/body misalignment risk from a scrolling table (operator 2026-07-04). */
  @media(max-width:480px){
    .rankhdr,.rank{grid-template-columns:22px 4px minmax(34px,1fr) 70px 52px 56px}
    .rankhdr .pct,.rank .pct{display:none}
  }
  .rank{display:grid;grid-template-columns:24px 4px minmax(34px,1fr) 78px 50px 56px 60px;align-items:center;gap:10px;
    padding:9px 14px;border-bottom:1px solid var(--line);cursor:pointer}
  .rank:hover{background:var(--panel2)}
  /* fav-team-tone team name on hover instead of a side accent bar (operator 2026-07-03: the bar
     read as "very AI-ish") — see the .rank:hover .tm rule further down, which replaces the old
     box-shadow side bar. */
  /* disabled once the ELO chart hits its 10-team cap (deselect one to add another) */
  .rank.capped{opacity:.45;cursor:not-allowed}
  .rank.capped:hover{background:none}
  .rank.sel{background:linear-gradient(90deg,var(--panel2),transparent)}
  .rank.sel .rk{color:var(--accent)}
  .rank .rk{color:var(--muted);font-size:13px;font-weight:700;text-align:left;font-variant-numeric:tabular-nums}
  .rank .bar{width:4px;height:26px;border-radius:3px}
  .rank .tm{font:800 14px/1 var(--vt-font-display);color:var(--text);letter-spacing:.03em;transition:color .14s ease}
  .rank .rec{color:var(--muted);font-size:12.5px;font-variant-numeric:tabular-nums;text-align:center;white-space:nowrap}
  .rank .pct{color:var(--muted);font-size:12.5px;font-variant-numeric:tabular-nums;text-align:center;white-space:nowrap}
  .rank .elo{font-size:15px;font-weight:700;text-align:center;font-variant-numeric:tabular-nums;white-space:nowrap}
  .rank .chg{font-size:11.5px;font-weight:600;text-align:center;font-variant-numeric:tabular-nums;white-space:nowrap}
  /* .powerchart is flex:1 (flex-basis:0%) so it can share .powerwrap's fixed 600px desktop row
     with .elowin — but .powerwrap switches to height:auto here, and a flex-basis:0% item inside
     an auto-height flex chain gets sized by content/min-height, NOT by an explicit height
     property (flex-basis wins over "height" whenever it isn't itself "auto") — so the height:460px
     below was silently inert, and the whole chain collapsed down to .cwrap's 160px min-height
     floor, cutting the ELO chart down to almost nothing (operator 2026-07-05 screenshot: chart
     stops right after the "1900" tick). flex:none makes .powerchart size by its OWN height
     property again instead of fighting flex-basis. */
  @media(max-width:840px){.powerwrap{grid-template-columns:1fr;height:auto}
    .ranks{max-height:440px}
    /* .elocol .powerchart (2 classes) beats a bare .powerchart (1 class) regardless of source
       order — match that specificity here so flex:none actually wins over the unscoped
       .elocol .powerchart{flex:1} desktop rule. */
    .elocol .powerchart{flex:none;height:460px}}
  /* "almost full screen": hides the ranks table + date controls so the chart
     and team inspector can claim most of the viewport, without a true
     fullscreen takeover (header/nav stay reachable around the edges). Only
     here does the chart itself flex to fill the (much larger) available
     space — the normal/inline state keeps cwrap's plain fixed height. */
  .powerwrap.eloexpanded>.card{display:none}
  .powerwrap.eloexpanded .elowin{display:none}
  /* 100dvh (dynamic viewport height), not 100vh — same mobile-Safari fix already used for the
     player-card sheet below: 100vh is calculated against the LARGEST possible viewport (as if
     the URL bar were hidden), so on a real phone with the toolbar showing this box could extend
     past the actually-visible area, reading as "cropped at the top" (operator 2026-07-04). */
  #eloChartBox.eloexpanded{position:fixed;top:14px;left:14px;right:14px;z-index:200;background:var(--bg);
    display:flex;flex-direction:column;box-shadow:0 24px 64px rgba(0,0,0,.6);height:calc(100dvh - 28px);margin:0}
  /* dims whatever's left peeking around the expanded box's 14px margin —
     ::before instead of a separate DOM node since the box is reparented onto
     <body> for the duration anyway (see eloFsAnchor in app.js) */
  #eloChartBox.eloexpanded::before{content:"";position:fixed;inset:0;z-index:-1;background:rgba(2,4,8,.72)}
  #eloChartBox.eloexpanded .elobody{flex:1;min-height:0}
  #eloChartBox.eloexpanded .cwrap{flex:1;height:auto;min-height:0}
  /* team stats */
  #view-teams .chartbox{margin-bottom:16px}
  #teamsTable thead th{cursor:pointer;user-select:none;white-space:nowrap}
  #teamsTable thead th.on{color:var(--accent)}
  #teamsTable tbody tr{cursor:default}
  #teamsTable td.tm{font-weight:600;white-space:nowrap}
  #teamsTable td.tm em{color:var(--muted);font-style:normal;font-weight:500;font-size:11px;margin-left:5px}
  #teamsTable .bar{display:inline-block;width:4px;height:18px;border-radius:3px;margin-right:9px;vertical-align:-4px}
  /* 3-colour heat: neutral (default) · green good · red bad — with the EXTREMES (|z|≥1.1) bold */
  .tg{color:#3fb950}.tg.s{font-weight:800}
  .tb{color:#f85149}.tb.s{font-weight:800}
  @media(max-width:980px){#teamsTable th.opt,#teamsTable td.opt{display:none}
    #teamsTable td.tm em{display:none}}
  /* modal */
  /* card has a FIXED viewport-capped height: header pinned, body scrolls inside it
     (operator: no outer page scroll — keep the card a self-contained panel) */
  .modal{position:fixed;inset:0;background:rgba(4,6,10,.74);backdrop-filter:blur(3px);display:none;
    align-items:center;justify-content:center;z-index:50;padding:16px;overflow:hidden}
  .modal.on{display:flex}
  /* dvh (dynamic viewport height) so the card fits Safari, whose 100vh excludes the URL bar
     and let the card overflow the visible window */
  .sheet{background:var(--panel);border:1px solid var(--line);border-radius:20px;max-width:580px;width:100%;
    max-height:calc(100dvh - 32px);display:flex;flex-direction:column;
    box-shadow:0 24px 60px rgba(0,0,0,.5);overflow:hidden;animation:pop .22s cubic-bezier(.2,.9,.3,1.2)}
  @keyframes pop{from{opacity:0;transform:translateY(12px) scale(.97)}to{opacity:1;transform:none}}
  .pchead{position:relative;flex:0 0 auto;display:flex;align-items:center;gap:16px;padding:20px 22px 18px;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.14)}
  .pchead::after{content:"";position:absolute;inset:0;opacity:.5;z-index:0;
    -webkit-mask:linear-gradient(180deg,#000,transparent);mask:linear-gradient(180deg,#000,transparent)}
  .pchead>*{position:relative;z-index:1}
  .pchead .meta{flex:1;min-width:0}
  .pchead .n{font-size:23px;font-weight:800;letter-spacing:-.02em}
  .pchead .s{color:#dfe4ec;font-size:13px;margin-top:3px;display:flex;align-items:center;gap:7px;flex-wrap:wrap}
  .pchead .s2{color:var(--muted);font-size:12px;margin-top:2px}
  /* fav-team tone, same recipe as the ELO fullscreen toggle's active state (.elofs.on) —
     operator 2026-07-04: every modal close box should follow the reskin, not sit static grey. */
  .close{background:var(--accent);border:1px solid transparent;color:var(--vt-accent-ink);width:32px;height:32px;
    border-radius:9px;cursor:pointer;font-size:17px;line-height:1;align-self:flex-start;transition:filter .15s}
  .close:hover{filter:brightness(1.08)}
  /* top padding matches .psect's own bottom margin (10px) so the header→"Projections" gap equals
     the "Projections"→"Scoring & shooting" gap (operator 2026-07-03) */
  .pbody{padding:10px 22px 22px;flex:1 1 auto;min-height:0;overflow-y:auto}
  /* the body now carries the plain team primary colour — keep loose labels legible on it */
  .pbody>.sectlab{color:#fff;opacity:.92;text-shadow:0 1px 2px rgba(0,0,0,.45)}
  /* player-card section header — its own boxed bar (Projections / Season averages) */
  .pbody .psect{display:flex;align-items:center;justify-content:center;font:800 12px/1 var(--vt-font-display);color:#fff;
    text-transform:uppercase;letter-spacing:.06em;margin:0 0 10px;padding:8px 13px;border-radius:9px;
    background:rgba(0,0,0,.34);border:1px solid rgba(255,255,255,.14);box-shadow:inset 0 1px 0 rgba(255,255,255,.08)}
  .pbody .psect.gap{margin-top:16px}   /* clear gap between projections and season averages */
  /* season averages — compact, tabular (deliberately different from the projection boxes) */
  .savg{display:flex;flex-wrap:wrap;gap:6px}
  .savg span{display:inline-flex;align-items:baseline;gap:5px;background:var(--panel2);border:1px solid var(--line);
    border-radius:8px;padding:4px 10px;font:700 13px/1 var(--vt-font-display);font-variant-numeric:tabular-nums}
  .savg span i{font-style:normal;color:var(--muted);font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}
  .savg .vsmeet{border-color:var(--accent2)}
  .savg .vsmeet i{color:var(--accent2)}
  /* self-hosted flag icon for birthplace — team-pill 3D treatment (gloss + drop shadow, no outline);
     country name on hover via title. Wrapped span so the inset gloss paints OVER the image. */
  .flagic{position:relative;display:inline-block;width:22px;height:16px;border-radius:3px;overflow:hidden;
    vertical-align:-3px;box-shadow:0 1px 2px rgba(0,0,0,.45)}
  .flagic img{display:block;width:100%;height:100%;object-fit:cover}
  .flagic::after{content:"";position:absolute;inset:0;border-radius:3px;pointer-events:none;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.6),inset 0 -2px 3px rgba(0,0,0,.28)}
  /* shooting splits — Made / Att / % per shot type, now a 2-col grid (compact, fits the column
     layout). margin-top keeps a clear gap between the scoring/points boxes and the FG boxes. */
  .shoot{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-top:12px}
  @media(max-width:520px){.shoot{grid-template-columns:1fr}}
  .shrow{display:grid;grid-template-columns:40px 1fr auto;align-items:center;align-content:center;gap:0 10px;
    background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:6px 12px}
  .shrow .shtype{font:800 13px/1 var(--vt-font-display);color:#cfd5e0}
  .shrow .shma{display:flex;align-items:center;gap:5px;font-variant-numeric:tabular-nums}
  .shrow .shv{font-size:16px;font-weight:800;cursor:pointer;padding:1px 7px;border-radius:6px;
    border:1px solid transparent;transition:.12s}
  .shrow .shv:hover{border-color:var(--muted)}
  /* selected shooting stat uses the CARD'S OWN team secondary (--tsec), matching the other
     (non-shooting) stat boxes' .ms.on rule — was still the generic favorite-team accent here
     (operator 2026-07-05), the one spot that hadn't been switched over with the rest. */
  .shrow .shv.on{color:var(--tsec,var(--accent));border-color:var(--tsec,var(--accent));
    background:linear-gradient(180deg,var(--tsec,var(--accent))1f,transparent)}
  .shrow .shma i{color:var(--muted);font-style:normal;font-size:13px}
  .shrow .shma .lbl{color:var(--muted);font-size:9.5px;text-transform:uppercase;letter-spacing:.04em;margin-left:2px}
  .shrow .shpct{font-size:15px;font-weight:800;color:var(--accent2);font-variant-numeric:tabular-nums;
    min-width:52px;text-align:right}
  .shrow .shpct .pc{font-size:10px;color:var(--muted);font-weight:600;margin-left:1px}
  /* P10–P90 range for made/attempted — the other (non-shooting) stat boxes already show this via
     .rng; shooting boxes were missing it entirely (operator 2026-07-05). Sits between the
     main M/A/% row and the (played-game) actual line, same full-width centred treatment. */
  .shrow .shrng{grid-column:1/-1;text-align:center;font-size:10px;color:var(--muted);
    font-variant-numeric:tabular-nums;margin-top:1px}
  .shrow .shrng i{font-style:normal;margin:0 3px}
  .shrow .shrng .lbl{font-size:8px;text-transform:uppercase;letter-spacing:.04em;margin:0 6px 0 1px}
  /* actual shooting line on played games (spans the full row width under the projection).
     display:flex+align-items:center (was plain text-align:center, i.e. default baseline
     alignment) so the smaller "actual" label sits vertically centred against the bigger number
     next to it, not baseline-aligned (operator 2026-07-04). justify-content:center keeps the
     same overall horizontal centring text-align:center gave it. */
  .shrow .shact{grid-column:1/-1;border-top:1px solid var(--line);margin-top:4px;padding-top:4px;
    display:flex;align-items:center;justify-content:center;line-height:1;
    font-size:11px;font-weight:700;color:#cdd3df;font-variant-numeric:tabular-nums}
  .shrow .shact *{line-height:1}
  .shrow .shact .al{font-size:8px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);font-weight:600;margin-right:6px}
  .shrow .shact i{color:var(--muted);font-style:normal}
  /* %-point delta of actual vs projected shooting % (same green/red as the other stats) */
  .shrow .shact .d{font-weight:800}
  .shrow .shact .d.up{color:#3fb950}
  .shrow .shact .d.dn{color:#f85149}
  .pbio{display:flex;flex-wrap:wrap;gap:7px;margin:2px 0 16px}
  .pbio .b{background:var(--panel2);border:1px solid var(--line);border-radius:7px;padding:4px 9px;
    font-size:12px;color:#cfd5e0;font-weight:600}
  /* bio chips now live INSIDE the header banner — translucent so they sit on the team gradient */
  .pchead .pbio{margin:8px 0 4px}
  .pchead .pbio .b{background:rgba(0,0,0,.30);border-color:rgba(255,255,255,.18);color:#eef1f6;
    font-size:11.5px;padding:3px 8px}
  /* the matchup ("Tonight · Home vs …") gets its own readable box, not washed-out header text */
  .pchead .pbio .b.match{background:rgba(0,0,0,.5);border-color:rgba(255,255,255,.3);color:#fff;font-weight:700}
  .qgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:0 0 14px}
  .qgrid .q{background:var(--panel2);border:1px solid var(--line);border-radius:11px;padding:10px;text-align:center}
  .qgrid .q .lab{color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.05em}
  .qgrid .q .val{font-size:20px;font-weight:800;margin-top:2px}
  .qgrid .q.hi{border-color:var(--accent)}
  .qgrid .q.hi .val{color:var(--accent)}
  .statstrip{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:0 0 16px}
  .statstrip .stat{background:linear-gradient(180deg,var(--panel2),var(--panel));border:1px solid var(--line);
    border-radius:11px;padding:11px 8px;text-align:center}
  .statstrip .stat .v{font-size:19px;font-weight:800}
  .statstrip .stat .l{color:var(--muted);font-size:10px;margin-top:2px;text-transform:uppercase;letter-spacing:.04em}
  .sectlab{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;font-weight:700;
    margin:14px 0 9px}
  /* last-10 chart sits in a panel box, matching the projection stat boxes */
  .l10box{background:var(--panel2);border:1px solid var(--line);border-radius:11px;padding:11px 13px 12px;margin-top:14px;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}
  .l10box .l10head{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;margin:0 0 9px}
  .l10box .l10lab{margin:0}
  /* custom legend pills (the Chart.js legend was hard to read on some team colours) */
  .l10leg{display:flex;gap:6px;flex-wrap:wrap}
  .l10pill{display:inline-flex;align-items:center;gap:6px;background:var(--panel);border:1px solid var(--line);
    border-radius:999px;padding:3px 9px;font-size:10.5px;font-weight:700;color:#cdd3df;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.06)}
  .l10pill .sw{width:13px;height:9px;border-radius:2px;flex:0 0 auto}
  .l10pill .sw.line{height:3px;border-radius:2px}
  .l10pill .sw.dash{height:0;width:14px;border-top:2px dashed #9aa3b2}
  .last10{position:relative;height:200px}
  .nohist{color:var(--muted);font-size:13px;padding:8px 0}
  .cap{color:var(--muted);font-size:11px;margin-top:6px}
  .empty{padding:56px 20px;text-align:center;color:var(--muted)}
  /* Fixed margin-top (not auto) so there's always a real gap above the footer, even on tall pages
     where .main overflows and .wrap has no free space left for an auto-margin to absorb (operator
     2026-07-04: "isn't glued to page content"). .main{flex:1} (above) is what pins the footer near
     the bottom on SHORT pages — this margin is on top of that, not instead of it. Glassy panel
     (same blur/tint recipe as .top, the topbar) replaces the old plain border-top line — a solid
     readable backdrop now that the page has the court watermark behind it.
     16px matches the gap BELOW the footer at full scroll (.wrap's 94px bottom padding minus the
     tab bar's own 26px+52px footprint = 16px) — operator 2026-07-04: "match the gap [above] to the
     one it has with the game tab selector", i.e. symmetric top/bottom spacing around the footer. */
  footer{color:var(--muted);font-size:12px;margin-top:16px;line-height:1.7;
    background:rgba(11,13,18,.62);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
    border:1px solid rgba(255,255,255,.10);border-radius:14px;padding:16px 18px 14px}
  footer code{background:var(--panel2);padding:1px 5px;border-radius:4px}
  .legend{display:inline-flex;align-items:center;gap:6px;margin-right:14px}
  .sw{width:22px;height:6px;border-radius:4px;display:inline-block;
    background:linear-gradient(90deg,#48516580,#8893a6)}
  .tick{display:inline-block;width:12px;height:9px;background:#fff;
    clip-path:polygon(0 0,100% 0,50% 100%);vertical-align:middle}
  .fmeta{margin-top:10px;color:#6b7384;font-size:11px}
  .capleg{display:inline-block;width:2px;height:12px;background:var(--muted);border-radius:1px;vertical-align:middle}
  @media(max-width:840px){.games,.charts{grid-template-columns:1fr}}
  /* phones: stack a game's two teams, and drop the wide range column from the table */
  @media(max-width:560px){
    .wrap{padding:18px 12px 90px}
    .summary{grid-template-columns:1fr}
    .games{grid-template-columns:1fr}
    /* Season Leaders' controls row (search + count + Compare-vs segment) doesn't fit one line on
       a phone -- was overflowing off the right edge instead of adapting (operator 2026-07-04).
       Let it wrap; the search box takes its own full-width row, count+segment share the next. */
    .controls{flex-wrap:wrap}
    .controls input[type=search]{flex:1 1 100%}
    .teamcols{grid-template-columns:1fr}
    .teamcols .col+.col{border-left:0;border-top:1px solid var(--line)}
    td.range,thead th[data-k="p50"]{display:none}
    td.who{min-width:0}
    td.match .vsopp{display:none}
    #teamsTable th[data-tk="pace"],#teamsTable td.c-pace{display:none}
    th,td{padding:9px 10px}
    .qgrid,.statstrip{grid-template-columns:repeat(2,1fr)}
    /* topbar: less padding, logo-mark only (wordmark hidden to fit kofi+lang+user in one row) */
    .topin{padding:8px 12px;gap:8px 14px}
    .brand .mk{width:38px;height:28px}
    .brand h1{display:none}
    /* hide date label — redundant next to the date value on small screens */
    .slatedate .dlab{display:none}
    /* dropdowns: open below instead of to the right */
    .datemenu,.statmenu{left:0;right:auto;top:calc(100% + 8px);max-width:calc(100vw - 24px)}
    /* stat bar: horizontal scroll so pills never wrap onto two lines. .quick (the pill group)
       has its OWN flex-wrap:wrap for desktop, which was fighting this: it kept wrapping its
       buttons onto a 2nd line inside the still-nowrap .statbar, leaving "Other…" stranded far to
       the right of a short 2nd row with a big gap (operator 2026-07-04 screenshot). Force .quick
       nowrap too so every pill lines up in the one scrollable row. */
    .statbar{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:2px}
    .statbar .quick{flex-wrap:nowrap;flex:0 0 auto}
    .statbar .lbl,.statbar button,.statpick{flex:0 0 auto}
    .statbar::-webkit-scrollbar{height:0}
    /* player card modal: tighter header on small screens */
    .pchead{padding:14px 16px 12px;gap:11px}
    .pchead .n{font-size:19px}
    /* hide full username in topbar — show avatar initial only */
    .username{display:none}
    /* bottom tab bar: wider side padding so first/last tab feel inset */
    .tabs{padding:5px 10px}
    .tabs button{padding:7px 13px;font-size:13px}
    /* (was forced back to a 2-col grid here — removed 2026-07-04: at phone widths (~340px per
       card) 2-up left no room for full player names, e.g. "Victor Wem…" — single column (set
       earlier in this same block) reads far better. See also the 480px game-header fix below. */
  }
  /* auth entry (topbar) */
  .auth{display:flex;align-items:center;gap:8px}
  /* language selector — custom dropdown (date-picker style): flag + code trigger, a 4×2 grid below */
  .langpick{position:relative}
  .langbtn{display:flex;align-items:center;gap:8px;background:var(--panel2);border:1px solid var(--line);
    border-radius:11px;padding:10px 11px;cursor:pointer;color:var(--text);transition:border-color .15s}
  .langbtn:hover{border-color:var(--accent)}
  /* flag carries the team-pill 3D treatment (inset top sheen + drop shadow), no outline */
  .langbtn .langflag>img{width:22px;height:16px;border-radius:3px;object-fit:cover;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.55),0 1px 2px rgba(0,0,0,.45)}
  .langbtn .langflag .flagstack{width:25px;height:18px}
  .langbtn .langcode{font:800 13px/1 var(--vt-font-display);letter-spacing:.02em}
  .langbtn .dchev{color:var(--accent);font-size:13px;transition:transform .18s;margin-left:-1px}
  .langbtn[aria-expanded="true"] .dchev{transform:rotate(180deg)}
  .langmenu{position:absolute;right:0;top:calc(100% + 8px);z-index:46;
    display:grid;grid-template-columns:repeat(4,1fr);gap:5px;padding:8px;
    background:var(--panel);border:1px solid var(--line);border-radius:13px;box-shadow:0 18px 50px rgba(0,0,0,.5)}
  .langmenu[hidden]{display:none}
  .langopt{display:flex;flex-direction:column;align-items:center;gap:5px;padding:7px 9px;border-radius:9px;
    cursor:pointer;transition:background .12s;min-width:54px}
  .langopt:hover{background:var(--panel2)}
  /* one coherent selected treatment (soft wash + ring from the SAME colour) — the old
     hardcoded orange gradient clashed with the team-tone accent ring under the reskin */
  .langopt.on{background:var(--vt-accent-soft);box-shadow:inset 0 0 0 1px var(--accent)}
  .langopt .flagbox>img{width:27px;height:20px;border-radius:3px;object-fit:cover;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.55),0 1px 2px rgba(0,0,0,.45)}
  .langopt .flagbox .flagstack{width:31px;height:23px}
  /* two-flag combo (English = UK+US): GB back top-left, US front bottom-right, same diagonal stagger as
     before. The gap is a real cutout (a notch clipped out of the back flag) instead of a box-shadow ring,
     so it reads as negative space and doesn't pick up a stray colour against .langopt.on — and the notch's
     inner corner is a rounded arc that echoes the front flag's own corner radius, not a hard 90° cut.
     Coordinates are hand-fit to each breakpoint's exact pixel size (clip-path:path() can't use %). */
  .flagstack{position:relative;display:inline-block}
  .flagstack .f1,.flagstack .f2{position:absolute;object-fit:cover}
  .flagstack .f1{top:0;left:0;z-index:1;filter:drop-shadow(0 1px 2px rgba(0,0,0,.4))}
  .flagstack .f2{bottom:0;right:0;z-index:2;border-radius:3px;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.55),0 1px 2px rgba(0,0,0,.45)}
  .langbtn .langflag .flagstack .f1,.langbtn .langflag .flagstack .f2{width:18px;height:13px}
  .langbtn .langflag .flagstack .f1{clip-path:path("M 3,0 L 15,0 A 3,3 0 0,1 18,3 L 10,3 A 5,5 0 0,0 5,8 L 5,13 L 3,13 A 3,3 0 0,1 0,10 L 0,3 A 3,3 0 0,1 3,0 Z")}
  .langopt .flagbox .flagstack .f1,.langopt .flagbox .flagstack .f2{width:22px;height:17px}
  .langopt .flagbox .flagstack .f1{clip-path:path("M 3,0 L 19,0 A 3,3 0 0,1 22,3 L 22,4 L 12,4 A 5,5 0 0,0 7,9 L 7,17 L 3,17 A 3,3 0 0,1 0,14 L 0,3 A 3,3 0 0,1 3,0 Z")}
  .langopt .lc{font:800 10px/1 var(--vt-font-display);color:var(--muted);letter-spacing:.04em}
  /* one-time "this site is available in your language" offer (initLang). Anchored to #langPick
     (same pattern as .langmenu below) instead of floating fixed+centred under the header — that
     used to land it directly on top of the login/create-account banner (operator 2026-07-04).
     Squarer shape (text on top, buttons stacked below) instead of one wide row. Overlapping the
     login banner below is fine (operator 2026-07-05) — the gap that matters is above, from the
     topbar itself (18px, up from 8px), so the popover doesn't crowd the header it hangs off of. */
  .langsuggest{position:absolute;top:calc(100% + 18px);right:0;z-index:47;width:230px;
    display:flex;flex-direction:column;align-items:stretch;gap:10px;text-align:center;
    background:var(--panel2);border:1px solid var(--line);border-radius:12px;padding:14px;
    font-size:13px;color:var(--text);box-shadow:0 14px 40px rgba(0,0,0,.5)}
  .langsuggest button{border-radius:8px;padding:8px 12px;font-size:12.5px;font-weight:700;cursor:pointer}
  .langsuggest .lsyes{background:var(--accent);color:var(--vt-accent-ink);border:0}
  .langsuggest .lsyes:hover{filter:brightness(1.06)}
  .langsuggest .lsno{background:transparent;color:var(--muted);border:1px solid var(--line)}
  .langsuggest .lsno:hover{color:var(--text)}
  /* narrow viewports: anchoring to #langPick's own edge would push a full-width box off-screen
     (langPick sits near the topbar's own right edge) — switch to a fixed, centred popover instead,
     same trick used elsewhere on mobile (e.g. the cookie dialog). */
  @media(max-width:560px){.langsuggest{position:fixed;top:calc(var(--hdr) + 18px);left:50%;right:auto;
    transform:translateX(-50%);width:calc(100vw - 28px)}}
  .langopt.on .lc{color:var(--accent)}
  @media(max-width:560px){.langbtn .langcode,.langbtn .dchev{display:none}.langbtn{padding:6px 7px}}
  @media(max-width:380px){.kofi-btn{width:28px;height:28px}.kofi-btn svg{width:14px;height:14px}}
  /* auth buttons use the .gtp pill recipe (top inset highlight + drop shadow) */
  .authbtn{display:inline-flex;align-items:center;gap:6px;text-decoration:none;border-radius:999px;
    padding:8px 16px;font-size:13px;font-weight:700;cursor:pointer;transition:.15s;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 1px 2px rgba(0,0,0,.4)}
  .authbtn.ghost{background:var(--panel2);color:var(--muted);border:1px solid var(--line)}
  /* same pill shape as the topbar's sign-in/account button, so it gets the same hover
     (operator 2026-07-03): text takes the accent colour, no border change. */
  .authbtn.ghost:hover{color:var(--accent)}
  .authbtn.solid{background:var(--accent);color:#1a1208;border:1px solid var(--accent)}
  .authbtn.solid:hover{filter:brightness(1.06)}
  .authbtn:active{transform:var(--vt-press)}
  /* selected/accent controls (Compact, P50/Projection segment, stat select, .cx) share the .gtp pill look */
  .densebtn.on,#statSel.on,.seg button.on,.cookie .cx,.cx{
    box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 1px 2px rgba(0,0,0,.4)}
  /* prediction disclaimer (legal — Q-001 two-tier: Tier-1 floor always visible,
     Tier-2 expansion collapsible after a one-time "I understand") */
  /* solid panel (no see-through), no accent side-stripe — plain quiet notice */
  .disclaimer{background:var(--panel);border:1px solid var(--line);
    border-radius:10px;padding:7px 12px;margin:0 0 18px;color:var(--muted);font-size:12px;line-height:1.4}
  .disclaimer .dfloor{display:flex;align-items:center;gap:8px}
  .disclaimer .ic{flex:0 0 auto;width:16px;height:16px;border-radius:50%;border:1px solid var(--line);
    color:var(--muted);display:inline-flex;align-items:center;justify-content:center;
    font:italic 700 10px/1 Georgia,serif}
  .disclaimer b{color:var(--text)}
  .disclaimer a{color:var(--accent2);text-decoration:none;white-space:nowrap}
  .disclaimer a:hover{text-decoration:underline}
  .disclaimer .dmore{display:flex;align-items:center;gap:14px;flex-wrap:wrap;
    margin-top:8px;padding-top:8px;border-top:1px solid var(--line)}
  .disclaimer .dmore.hide{display:none}
  .disclaimer .dmore p{margin:0;flex:1;min-width:240px}
  /* text ink follows the same rule as the selected-tab label (--vt-accent-ink) so it stays
     readable on any favorite-team accent, e.g. white on Hawks red (operator 2026-07-03) */
  .disclaimer .dmore .cx{flex:0 0 auto;background:var(--accent);color:var(--vt-accent-ink);border:0;border-radius:9px;
    padding:7px 14px;font-size:12px;font-weight:700;cursor:pointer}
  .disclaimer .dmore .cx:hover{filter:brightness(1.06)}
  .disclaimer .dmore .cx:active{transform:var(--vt-press)}
  /* reusable (i) info popover — replaces always-on explanatory micro-copy. Pure CSS
     (hover + focus-within), keyboard-reachable, no third-party scripts. */
  .infodot{position:relative;display:inline-flex;flex:0 0 auto}
  .infodot>.i{width:18px;height:18px;border-radius:50%;border:1px solid var(--line);background:var(--panel2);
    color:var(--muted);font:italic 700 10px/1 Georgia,serif;display:inline-flex;align-items:center;
    justify-content:center;cursor:help;padding:0;transition:.15s}
  .infodot>.i:hover,.infodot>.i:focus-visible{color:var(--accent2);border-color:var(--accent2);outline:none}
  .infodot .tip{position:absolute;left:0;top:calc(100% + 7px);z-index:30;width:240px;
    background:var(--panel2);border:1px solid var(--line);border-radius:9px;padding:8px 11px;
    font:400 11.5px/1.45 var(--vt-font-ui);color:#cdd3df;box-shadow:0 10px 28px rgba(0,0,0,.5);
    opacity:0;visibility:hidden;transform:translateY(-3px);transition:.14s;pointer-events:none}
  .infodot .tip b{color:var(--text)}
  .infodot:hover .tip,.infodot:focus-within .tip{opacity:1;visibility:visible;transform:none}
  @media(max-width:560px){.infodot .tip{left:auto;right:0}}
  /* the search-box info tip sits near the right edge — open it inward so it never overflows the page */
  .controls .infodot .tip{left:auto;right:0}
  /* footer legal nav */
  .flinks{display:flex;flex-wrap:wrap;gap:6px 18px;margin:0 0 12px}
  .flinks a{color:var(--accent2);text-decoration:none;font-size:12px;font-weight:600}
  .flinks a:hover{text-decoration:underline}
  /* cookie notice banner (informational, one-time) — a scrim forces a real choice
     instead of letting visitors ignore it and browse with consent left undecided */
  .cookiescrim{position:fixed;inset:0;z-index:59;background:rgba(0,0,0,.72);
    backdrop-filter:blur(3px);opacity:0;transition:opacity .25s ease}
  .cookiescrim.on{opacity:1}
  .cookiescrim[hidden]{display:none}
  /* dead-centre, dialog-weight (not a bottom strip): the visitor must answer before
     browsing, so it should read as the page's current focal point */
  .cookie{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:60;width:min(560px,calc(100% - 28px));
    display:none;align-items:center;gap:14px;background:var(--panel2);border:1px solid var(--line);
    border-radius:16px;padding:18px 20px;box-shadow:0 24px 70px rgba(0,0,0,.6);font-size:13px;color:var(--muted)}
  .cookie.on{display:flex;animation:cookieIn .3s ease}
  @keyframes cookieIn{from{opacity:0;transform:translate(-50%,calc(-50% + 12px))}to{opacity:1;transform:translate(-50%,-50%)}}
  .cookie b{color:var(--text)}
  .cookie a{color:var(--accent2);text-decoration:none}
  .cookie a:hover{text-decoration:underline}
  .cookie .cx{flex:0 0 auto;background:var(--accent);color:#1a1208;border:1px solid transparent;border-radius:9px;
    padding:8px 14px;font-size:12.5px;font-weight:700;cursor:pointer}
  .cookie .cx:hover{filter:brightness(1.06)}
  /* gentle nudge toward Accept (Reject stays equally functional/visible — just unstyled) */
  .cookie .cx:not(.cxrej){animation:cookieNudge 2.2s ease-in-out infinite}
  @keyframes cookieNudge{0%,100%{box-shadow:0 0 0 0 rgba(240,136,62,0)}50%{box-shadow:0 0 0 5px rgba(240,136,62,.22)}}
  .cookie .cxrej{background:transparent;color:var(--muted);border-color:var(--line)}
  .cookie .cxrej:hover{color:var(--text);filter:none}
  @media(max-width:560px){.cookie{flex-direction:column;align-items:stretch;text-align:center}}
  /* ---------- motion & polish ---------- */
  /* interaction = calm HIGHLIGHT, no movement (no translate/scale on hover). "Motion" on hover
     (where used, e.g. .prow below) means the COLOUR itself appearing/travelling — never the
     element's position — per operator clarification 2026-07-03. */
  @property --gtrace{syntax:'<angle>';inherits:false;initial-value:0deg}
  .scard[data-code]{cursor:pointer;
    transition:border-color .16s ease,box-shadow .16s ease,background-color .16s ease}
  .scard[data-code]:hover{
    border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}
  tbody tr,.rank{transition:background-color .14s ease,box-shadow .14s ease}
  /* Table row hover — the SAME rounded-frame + moving-line effect as the summary cards / game
     windows (operator 2026-07-04, round 2: explicitly wants parity, not a different box-shadow
     treatment). Round 1 tried a per-cell box-shadow (no containing-block risk, but no rounded
     corners and no moving line — the operator rejected that as not matching). A pure-CSS
     `<tr>::after` can't be trusted (see JS below for why), and a real single rotating line has to
     be ONE element spanning the whole row — so this is driven by `wireRowTrace()` in app.js: a
     single shared `#rowTrace` div, positioned via `getBoundingClientRect()` over whichever row is
     hovered, reusing this exact CSS. */
  /* z-index kept BELOW the floating tab bar (.tabs, z-index:45), the modal (z-index:50) AND the
     table's own sticky <thead> (z-index:5) — that last one is deliberate: it's what makes the
     header visually CUT the box as a row scrolls up behind it (opaque header paints over
     whatever's behind it) instead of the box needing to be resized/repositioned to the visible
     area (operator 2026-07-05: box should be "cut", not resized). Still above the table's own
     plain cells (z-index:auto). Border bumped 1px->1.6px (still dim, just a touch more visible). */
  .rowtrace{position:absolute;pointer-events:none;z-index:4;border-radius:9px;
    border:var(--vt-trace-bw2,1.6px) solid color-mix(in srgb,var(--tsec,var(--accent)) 55%,var(--line));
    opacity:0;transition:opacity .15s ease}
  .rowtrace.on{opacity:1}
  .rowtrace::after{content:"";position:absolute;inset:-1px;border-radius:inherit;
    padding:var(--vt-trace-bw,1.4px);
    background:conic-gradient(from var(--gtrace),transparent 0 var(--vt-trace-a,65%),var(--tsec,var(--accent)) var(--vt-trace-b,85%),transparent 100%);
    -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
    -webkit-mask-composite:xor;mask-composite:exclude;opacity:0;transition:opacity .2s ease}
  /* replaces the old inset-side-bar hover (operator 2026-07-03: "very AI-ish") — the team
     abbreviation itself takes the favorite-team tone instead. */
  .rank:hover .tm{color:var(--accent)}
  .tabs button{transition:background .2s ease,color .2s ease}
  .statbar button,#statSel{transition:background .18s,border-color .18s,color .18s}
  .authbtn,.histbtn,.cx,.cookie .cx{transition:background .15s,border-color .15s,color .15s,filter .15s}
  @media (prefers-reduced-motion: no-preference){
    /* per-tab directional slide (direction set by switchTab via --sx) */
    @keyframes tabSlide{from{opacity:0;transform:translateX(var(--sx,24px))}to{opacity:1;transform:none}}
    /* one-time boot reveal of the page shell (body.boot removed by JS after it plays) */
    body.boot .top{animation:boot .5s ease both}
    body.boot .summary{animation:boot .5s ease .1s both}
    body.boot .disclaimer{animation:boot .5s ease .15s both}
    body.boot .statbar{animation:boot .5s ease .2s both}
    body.boot #view-games{animation:boot .55s ease .26s both}
    @keyframes boot{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
    /* card-level entrance cascade (movement lives at ENTRANCES only — hover stays still
       per the house rule above). Cards re-render on date/tab change, so the cascade
       replays exactly when the content actually changes, never on hover or keystrokes. */
    .summary .scard,.games .game,.podium .pod{animation:boot .45s var(--vt-ease-slide) both}
    .summary .scard:nth-child(2){animation-delay:.06s}
    .summary .scard:nth-child(3){animation-delay:.12s}
    .summary .scard:nth-child(4){animation-delay:.18s}
    .games .game:nth-child(2){animation-delay:.07s}
    .games .game:nth-child(3){animation-delay:.14s}
    .games .game:nth-child(4){animation-delay:.21s}
    .podium .pod:nth-child(2){animation-delay:.07s}
    .podium .pod:nth-child(3){animation-delay:.14s}
    /* modal backdrop fade (the sheet keeps its own pop animation) */
    .modal.on{animation:backdropIn .2s ease}
    @keyframes backdropIn{from{opacity:0}to{opacity:1}}
    /* slate picker dropdown */
    .datemenu:not([hidden]){animation:menuIn .16s ease}
    @keyframes menuIn{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
    /* summary cards re-pop when the slate/stat changes (renderSummary replaces them) */
    .summary .scard{animation:cardPop .3s ease both}
    .summary .scard:nth-child(2){animation-delay:.04s}
    .summary .scard:nth-child(3){animation-delay:.08s}
    @keyframes cardPop{from{opacity:0;transform:translateY(7px) scale(.985)}to{opacity:1;transform:none}}
    /* player-row hover, game windows AND summary cards alike (operator 2026-07-03: summary rows
       should use this SAME rounded-rect trace, not a separate orbit effect) — ONE line turning
       around the row's border. ASYNC per-row duration/start-offset (--gdur/--gdelay, gtraceVars()
       in app.js) so it never reads as one synchronised, mechanical loop across the grid. Reduced-
       motion users get the plain border/ring hover above instead (the trace stays opacity:0). */
    .prow:hover::after,.scard .srow:hover::after,.rowtrace.on::after,.elosel.on::after{
      opacity:1;animation-name:gtraceSpin;
      animation-timing-function:linear;animation-iteration-count:infinite;
      animation-duration:var(--gdur,1.6s);animation-delay:var(--gdelay,0s)}
    @keyframes gtraceSpin{to{--gtrace:360deg}}
  }
  /* ---- logged-in user menu ---- */
  #authGuest,#authUser{display:flex;align-items:center;gap:8px}
  #authGuest[hidden],#authUser[hidden]{display:none}
  #authUser{position:relative}
  .userbtn{display:flex;align-items:center;gap:8px;background:var(--panel2);border:1px solid var(--line);
    border-radius:999px;padding:5px 10px 5px 5px;cursor:pointer;color:var(--text);text-decoration:none;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 1px 2px rgba(0,0,0,.4)}
  .userbtn .dchev{color:var(--accent);font-size:13px;line-height:1;transition:transform .18s}
  .userbtn[aria-expanded="true"] .dchev{transform:rotate(180deg)}
  /* the initial(s) use the same ink as the selected-tab label (--vt-accent-ink over the accent
     fill) so they read correctly under any favorite-team reskin — e.g. white on Hawks red */
  .userinit{width:26px;height:26px;border-radius:50%;background:var(--accent);color:var(--vt-accent-ink);
    display:flex;align-items:center;justify-content:center;
    font:800 12px/1 var(--vt-font-display);flex:0 0 auto}
  .guestinit{background:var(--panel2);color:var(--muted);border:1px solid var(--line)}
  /* this pill doesn't share the date/language selectors' shape, so its hover skips the border
     outline in favor of a text-colour reveal (operator 2026-07-03) — "Sign in" or the username. */
  .username{font:700 13px/1 var(--vt-font-display);max-width:120px;transition:color .16s ease;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .userbtn:hover .username{color:var(--accent)}
  .userdrop{position:absolute;right:0;top:calc(100% + 8px);z-index:46;min-width:156px;
    background:var(--panel);border:1px solid var(--line);border-radius:12px;
    box-shadow:0 14px 40px rgba(0,0,0,.5);padding:4px}
  .userdrop[hidden]{display:none}
  .uditem{display:block;width:100%;text-align:left;padding:8px 13px;border-radius:8px;
    font-size:13px;font-weight:600;color:var(--text);text-decoration:none;
    background:none;border:0;cursor:pointer;transition:background .12s;font-family:inherit}
  .uditem:hover{background:var(--panel2)}
  .udout{color:#f85149}
  .udmember{display:flex;align-items:center;gap:6px;padding:6px 13px 4px;font-size:11px;font-weight:700;
    color:var(--accent);letter-spacing:.04em;border-bottom:1px solid var(--line);margin-bottom:3px}
  .udmember .udmark{width:15px;height:11px;flex:0 0 auto;background:var(--vt-logo);
    -webkit-mask:url(vates-mark.svg) center/contain no-repeat;mask:url(vates-mark.svg) center/contain no-repeat}
  .udfav{display:block;padding:4px 13px 8px;font-size:11.5px;
    color:var(--muted);border-bottom:1px solid var(--line);margin-bottom:3px}

/* ---- Ko-fi topbar button ---- */
/* Ko-fi disc follows the favorite-team accent (operator 2026-07-02); defaults to orange */
.kofi-btn{display:flex;align-items:center;justify-content:center;width:38px;height:38px;border-radius:50%;
  overflow:hidden;background:var(--vt-accent);border:none;cursor:pointer;flex-shrink:0;
  box-shadow:0 1px 3px rgba(0,0,0,.35);
  transition:background .2s,filter .15s;padding:0}
.kofi-btn:hover,.kofi-btn[aria-expanded="true"]{filter:brightness(.9)}
/* ---- Ko-fi donation panel (slide-up from bottom-right) ---- */
#kofiPanel{position:fixed;inset:0;z-index:200;background:rgba(0,0,0,.4);backdrop-filter:blur(2px)}
#kofiPanel[hidden]{display:none}
.kofi-panel-inner{position:absolute;bottom:24px;right:24px;width:340px;height:480px;
  border-radius:16px;overflow:hidden;background:var(--panel);
  box-shadow:0 24px 64px rgba(0,0,0,.6);border:1px solid var(--line);
  display:flex;flex-direction:column}
.kofi-panel-close{position:absolute;top:10px;right:10px;z-index:1;
  width:28px;height:28px;border-radius:50%;border:none;background:rgba(0,0,0,.4);
  color:#fff;font-size:13px;cursor:pointer;display:flex;align-items:center;justify-content:center;
  transition:background .15s}
.kofi-panel-close:hover{background:rgba(0,0,0,.65)}
@media(max-width:400px){.kofi-panel-inner{right:8px;left:8px;width:auto;bottom:16px}}

/* ELO trajectory · time-window controls (presets + dual-thumb date range) */
.elowin{display:flex;flex-direction:column;gap:10px;margin:2px 0 12px;padding:10px;
  background:var(--panel);border:1px solid var(--line);border-radius:13px}
.elowinhdr{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}
.elocursel{font:700 12px/1 var(--vt-font-display);color:var(--text);
  font-variant-numeric:tabular-nums;letter-spacing:.02em}
.elopresets{display:inline-flex;flex-wrap:wrap;gap:3px;background:var(--bg);
  border:1px solid var(--line);border-radius:10px;padding:3px;align-self:flex-start}
.elopresets button{background:transparent;border:0;border-radius:8px;
  color:var(--muted);font-family:var(--vt-font-display);font-size:12px;font-weight:700;
  padding:7px 14px;cursor:pointer;transition:background .15s,color .15s}
/* a soft white-wash on hover, not a contrasting border colour — avoids the old
   blue outline that clashed with the orange .on (selected) preset right next to it */
.elopresets button:hover:not(:disabled):not(.on){background:rgba(255,255,255,.08);color:var(--text)}
.elopresets button.on{background:var(--accent);color:var(--vt-accent-ink)}
.elopresets button:disabled{opacity:.45;cursor:default}
.elorange{display:flex;flex-direction:column;gap:2px;padding:2px 4px 0}
.elorangetrack{position:relative;height:24px}
.elorangetrack::before{content:"";position:absolute;left:0;right:0;top:50%;height:5px;
  transform:translateY(-50%);background:var(--track);border-radius:4px}
.elorangefill{position:absolute;top:50%;height:5px;transform:translateY(-50%);
  background:var(--accent);border-radius:4px;pointer-events:none}
.elorangetrack input[type=range]{position:absolute;left:0;top:0;width:100%;height:24px;margin:0;
  -webkit-appearance:none;appearance:none;background:transparent;pointer-events:none}
/* a vertical handle (not a circular dot) — reads more like a slider grip */
.elorangetrack input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
  pointer-events:auto;width:9px;height:22px;border-radius:4px;background:var(--accent);
  box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.45);cursor:pointer;transition:transform .12s,box-shadow .12s}
.elorangetrack input[type=range]::-webkit-slider-thumb:hover{transform:scaleX(1.3)}
.elorangetrack input[type=range]::-webkit-slider-thumb:active{box-shadow:inset 0 0 0 2px rgba(255,255,255,.6)}
.elorangetrack input[type=range]::-moz-range-thumb{pointer-events:auto;width:9px;height:22px;
  border:0;border-radius:4px;background:var(--accent);box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.45);cursor:pointer}
.elorangetrack input[type=range]::-moz-range-track{background:transparent;border:0}
/* sparse year markers spanning the full track, for spatial orientation before dragging */
.elorangeticks{position:relative;height:14px;margin-top:2px}
.elorangeticks .ertick{position:absolute;top:0;transform:translateX(-50%);
  color:var(--muted);font-family:var(--vt-font-display);font-size:10px;font-weight:600;
  font-variant-numeric:tabular-nums;white-space:nowrap}
/* presets drive the window on mobile; the drag-slider is a desktop refinement (dragging a thin
   handle with a finger is impractical, and there's nothing left to fine-tune once only whole
   presets are selectable). The "Oct 25 – Jun 26" custom-range label only makes sense alongside
   that slider, so hide it here too; the presets fill the full width instead of sitting small and
   left-aligned next to a now-empty header (operator 2026-07-04). */
@media(max-width:560px){
  .elorange{display:none}
  .elocursel{display:none}
  .elowinhdr{justify-content:center}
  /* align-self:stretch only affects the CROSS axis in a row-direction flex parent (.elowinhdr) --
     it stretched .elopresets vertically, not to the header's full WIDTH, so the pill group still
     sat at its own natural size instead of filling the box (operator 2026-07-04 and again
     2026-07-05: "still don't fit the entire box"). flex:1 + width:100% actually fills the main
     axis; .elocursel is hidden here so .elopresets is the only item left to grow into that space. */
  .elopresets{flex:1;width:100%;justify-content:center}
  .elopresets button{flex:1;padding:7px 4px;text-align:center;font-size:11px;white-space:nowrap}
}

.elofs{margin-left:auto;background:transparent;border:1px solid var(--line);border-radius:8px;
  color:var(--muted);cursor:pointer;font-size:13px;line-height:1;padding:5px 8px;
  transition:background .15s,color .15s}
.elofs:hover{background:rgba(255,255,255,.08);color:var(--text)}
.elofs.on{background:var(--accent);color:var(--vt-accent-ink);border-color:transparent}

/* ELO team inspector: team / date / value / avg·min·max for the hovered or
   locked line — a compact strip BELOW the chart (a side column ate too much
   width) so this detail doesn't have to live in a floating tooltip */
.elosel{position:relative;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:14px;
  min-height:44px;background:var(--panel2);border:1px solid var(--line);border-radius:11px;
  padding:10px 14px;transition:border-color .15s}
/* .on's border-color is set inline (eloUpdateSelBox() in app.js) to the INSPECTED TEAM'S own
   colour — was a static favorite-team-accent rule here, a residual from before that per-team
   colouring existed (operator 2026-07-03). Fallback if JS somehow doesn't set it. */
.elosel.on{border-color:var(--vt-accent-line)}
/* Same rotating-conic-gradient trace as the table/card/game-row hover effect (.rowtrace,
   .prow::after) — reused here as a PERPETUAL animation (not hover-only) for as long as a team
   is being inspected (.on, already toggled by eloUpdateSelBox whenever a team is hovered or
   locked). Frame = the team's own primary (set inline via borderColor above); moving accent =
   --tsec, set inline to teamAccentSafe(team) — the SAME dark-tone floor (near-black secondary
   → white) used for every other hover trace in the app (operator 2026-07-05). */
.elosel::after{content:"";position:absolute;inset:-1.6px;border-radius:inherit;
  pointer-events:none;padding:var(--vt-trace-bw,1.4px);
  background:conic-gradient(from var(--gtrace),transparent 0 var(--vt-trace-a,65%),var(--tsec,var(--accent)) var(--vt-trace-b,85%),transparent 100%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;opacity:0;transition:opacity .2s ease}
.elosel .esbhint{color:var(--muted);font-size:12.5px}
.elosel .esbpill{font:800 12px/1 var(--vt-font-display);padding:4px 10px;border-radius:999px;letter-spacing:.02em}
.elosel .esblock{font:700 10px/1 var(--vt-font-display);color:var(--accent);text-transform:uppercase;
  letter-spacing:.06em;border:1px solid var(--vt-accent-line);border-radius:999px;padding:3px 7px}
.elosel .esbrow{display:inline-flex;align-items:baseline;gap:5px;
  font-size:11.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;white-space:nowrap}
.elosel .esbrow b{color:var(--text);font:800 14px/1 var(--vt-font-display);letter-spacing:normal;
  text-transform:none;font-variant-numeric:tabular-nums}

/* ==== Color Studio FX hover modes — PER SURFACE (VT_THEME.fx.hover.{tables|game|summary|elo}
   → html.vt-hov-<surface>-{tint|off} classes, applyTheme() in teams-data.js). Shipped in site
   CSS (not the studio's own stylesheet) because an EXPORTED theme runs on production without
   the studio loaded. "tint" = the row's key text takes the team accent instead of the rotating
   outline (!important where the text carries an inline colour, e.g. the summary rows' textOn
   ink). The tables' JS overlay (#rowTrace) is suppressed in app.js via vtHover("tables"). ==== */
html.vt-hov-tables-off .rowtrace,html.vt-hov-tables-tint .rowtrace{display:none}
html.vt-hov-tables-tint tbody tr:hover td{color:var(--tsec,var(--accent))}
html.vt-hov-game-off .prow:hover::after,html.vt-hov-game-tint .prow:hover::after{
  animation:none!important;opacity:0!important}
html.vt-hov-game-tint .prow:hover .gname .n{color:var(--tsec,var(--accent))}
html.vt-hov-summary-off .scard .srow:hover::after,html.vt-hov-summary-tint .scard .srow:hover::after{
  animation:none!important;opacity:0!important}
html.vt-hov-summary-tint .scard .srow:hover .snm{color:var(--tsec,var(--accent))!important}
html.vt-hov-elo-off .elosel.on::after{animation:none!important;opacity:0!important}
/* entrance animations off (VT_THEME.fx.entrances === false) */
html.vt-fx-noenter .summary .scard,html.vt-fx-noenter .games .game,html.vt-fx-noenter .podium .pod,
html.vt-fx-noenter body.boot .top,html.vt-fx-noenter body.boot .summary,
html.vt-fx-noenter body.boot .disclaimer,html.vt-fx-noenter body.boot .statbar,
html.vt-fx-noenter body.boot #view-games{animation:none!important}
