:root { --bg:#0f1216; --panel:#1a1e25; --line:#2a3038; --txt:#dde2e8;
        --dim:#828b98; --acc:#f0a830; --ax1:#6fb3e0; --ax2:#e0706f; }
* { box-sizing:border-box; }
/* Scrollbars, themed site-wide rather than per widget. Several areas scroll --
   .scroll tables, .sb-teams, .ddcol, the sticky rail -- and every one of them
   drew the browser's default white-and-grey bar on a dark page.
   scrollbar-color is the standard property (Firefox, Chrome 121+); the
   ::-webkit- rules cover Safari and older Chrome, which ignore it. */
html { background:var(--bg);
  scrollbar-color:var(--line) transparent; scrollbar-width:thin; }
::-webkit-scrollbar { width:10px; height:10px; }
::-webkit-scrollbar-track { background:transparent; }
::-webkit-scrollbar-thumb { background:var(--line); border-radius:6px;
  /* inset border keeps the thumb clear of the track edge without a second
     element, so it reads as a slim bar rather than filling the gutter */
  border:2px solid transparent; background-clip:content-box; }
::-webkit-scrollbar-thumb:hover { background:var(--dim);
  background-clip:content-box; }
::-webkit-scrollbar-corner { background:transparent; }
body { background:radial-gradient(1200px 500px at 50% -200px,#1c2530,var(--bg));
       color:var(--txt); margin:0; min-height:100vh; min-height:100dvh;
       font:15px/1.45 system-ui,Segoe UI,sans-serif; }
a { color:var(--ax1); text-decoration:none; } a:hover { text-decoration:underline; }
.wrap { max-width:1120px; margin:0 auto; padding:26px 22px 40px; }
.hdr { border-bottom:1px solid var(--line); padding-bottom:14px; margin-bottom:18px; }
h1 { font-size:30px; margin:0; letter-spacing:1px; font-weight:800; }
/* logo sits on the far left of the topbar and fills the vertical space with a
   FIXED height — bounded so it can never stretch the bar or push the separator
   down. The wordmark/tagline/nav column sits to its right. */
.brandlogo-link { display:flex; align-items:center; flex:0 0 auto;
  text-decoration:none; }
.brandlogo { height:190px; width:auto; display:block; object-fit:contain; }
.brand-col { display:flex; flex-direction:column; justify-content:center;
  align-items:flex-start; gap:8px; flex:1 1 auto; min-width:0; max-width:100%; }
.brand-top { display:flex; align-items:center; justify-content:flex-start;
  gap:18px; flex-wrap:wrap; width:100%; max-width:100%; }
.brandlock { display:inline-flex; flex-direction:column; justify-content:center;
  align-items:flex-start; text-align:left; text-decoration:none; max-width:100%; }
.brandlock h1 { line-height:1; }
/* .nav and .brand-top are flex items of a flex-start-aligned column, so
   without an explicit width they size to their own (unwrapped) content and
   can overflow the header on narrow screens instead of wrapping; max-width
   alone still let inline content overflow the box, so force a definite
   width (100% of the column) so line-wrapping actually applies to it. */
.brand-col .nav { text-align:left; width:100%; max-width:100%; }
@media (max-width:520px){ h1{ font-size:24px } }
h1 a { color:var(--txt); } h1 .acc { color:var(--acc); }
.tagline { color:var(--dim); font-size:13.5px; margin:5px 0 0; }
.tagline b { color:var(--txt); font-weight:600; }
h2 { font-size:13px; color:var(--dim); margin:26px 0 9px;
     text-transform:uppercase; letter-spacing:1px; font-weight:700; }
.cards { display:flex; gap:11px; flex-wrap:wrap; margin-top:4px; }
/* A card's value never breaks: overflow-wrap:anywhere split "Germany" into
   "Germa/ny" and "5,086,361" into "5,086,/361" once seven cards shared a row.
   The 140px floor lives in flex-basis, not min-width, so the default
   min-width:auto keeps each card at least as wide as its unbroken value and
   flex-wrap moves a whole card to the next row instead. */
.card { background:linear-gradient(180deg,#222934,#1a1e25);
        border:1px solid var(--line); border-radius:10px;
        padding:14px 20px; flex:1 1 140px; }
.card b { display:block; font-size:28px; color:var(--acc); font-weight:800;
          font-variant-numeric:tabular-nums; line-height:1.1;
          white-space:nowrap; }
@media (max-width:520px){ .card b { font-size:24px; } }
/* the name card holds text, not a number: smaller, allowed to wrap+grow */
.card.name { flex:2 1 200px; }
.card.name b { font-size:19px; white-space:normal; overflow-wrap:anywhere; }
.card span { color:var(--dim); font-size:11.5px; text-transform:uppercase;
             letter-spacing:.8px; }
table { width:100%; border-collapse:collapse; background:var(--panel);
        border:1px solid var(--line); border-radius:10px; overflow:hidden; }
th, td { padding:7px 11px; text-align:right; white-space:nowrap; }
th:first-child, td:first-child { text-align:left; }
/* Ranked tables lead with the "#" column, so the name is the SECOND cell and
   the rule above missed it: names sat flush right against the numbers. */
td.rank + td, table:has(td.rank) th:nth-child(2) { text-align:left; }
th { background:#222934; color:var(--dim); font-size:11.5px;
     text-transform:uppercase; letter-spacing:.5px; user-select:none; }
/* sortable headers are server-side links; keep them looking like headers */
th a { color:inherit; text-decoration:none; display:block; cursor:pointer; }
th a:hover { color:var(--txt); text-decoration:none; }
th a.sort-on { color:var(--acc); }
.sort-arr { font-size:9px; vertical-align:middle; }
tr:nth-child(even) td { background:#161a20; }
td { border-top:1px solid var(--line); font-variant-numeric:tabular-nums; }
tbody tr:hover td { background:#202732; }
.t1 { color:var(--ax1); } .t2 { color:var(--ax2); }
.dim { color:var(--dim); } .ai { color:var(--dim); font-style:italic; }
.rank { color:var(--dim); font-variant-numeric:tabular-nums; width:1px; }
tbody tr:nth-child(1) .rank { color:#ffd34d; font-weight:800; }
tbody tr:nth-child(2) .rank { color:#cfd4da; font-weight:700; }
tbody tr:nth-child(3) .rank { color:#d6924a; font-weight:700; }
.muted { font-size:12px; color:var(--dim); margin-top:22px; }
.muted b { color:var(--acc); }
/* class fingerprint: a lives-weighted stacked bar of soldier classes */
.fp { margin-top:4px; }
.fp-arch { font-size:14px; font-weight:700; color:var(--txt);
           margin:0 0 8px; }
.fp-arch .dim { font-weight:400; }
.fp-bar { display:flex; height:26px; border-radius:7px; overflow:hidden;
          border:1px solid var(--line); background:var(--panel); }
.fp-seg { min-width:2px; }
.fp-legend { display:flex; flex-wrap:wrap; gap:6px 14px; margin-top:9px;
             font-size:11.5px; color:var(--dim); }
.fp-legend span { display:inline-flex; align-items:center; gap:5px;
                  white-space:nowrap; }
.fp-dot { width:10px; height:10px; border-radius:3px; display:inline-block; }
.fp-legend b { color:var(--txt); font-variant-numeric:tabular-nums;
               font-weight:700; }
/* flex+gap instead of margin-right on each link: a trailing inline margin
   on the last link of a wrapped line still extends past the line box, which
   let the nav overflow its container (and the viewport) on narrow screens
   even though the links themselves wrapped correctly. */
.nav { margin:0; font-size:13.5px; display:flex; flex-wrap:wrap;
        column-gap:18px; row-gap:6px; }
.nav a { color:var(--dim); font-weight:600; }
.nav a:hover { color:var(--acc); }
.nav a.active { color:var(--acc); border-bottom:2px solid var(--acc);
                padding-bottom:2px; }
/* touch targets are cramped at this line-height on phones; add breathing
   room so each link is comfortably tappable without enlarging desktop nav */
@media (max-width:640px){ .nav a{ display:inline-block; padding:6px 0; } }
/* Nav groups are native <details>/<summary>: click a label to expand its
   links, no JS required for the disclosure itself (NAV_JS only auto-opens
   the current section's group and adds the active-highlight class). Reset
   the browser's default marker/indent so it reads as a nav pill, not a
   generic collapsible box. */
.nav-group { position:relative; }
.nav-group summary { list-style:none; cursor:pointer; color:var(--txt);
                      font-weight:700; padding:2px 0; }
.nav-group summary::-webkit-details-marker { display:none; }
.nav-group summary::after { content:'\25BE'; display:inline-block;
                             margin-left:5px; font-size:10px; color:var(--dim); }
.nav-group[open] summary::after { content:'\25B4'; }
/* A pinned group stays open after the pointer leaves, so it needs to look
   different from one that's merely open under the cursor -- otherwise the
   menu that follows you and the menu that doesn't are indistinguishable.
   The accent marker is the affordance: it says "this one is held". */
.nav-group.pinned summary::after { color:var(--acc); }
/* The group holding the current page. This used to take the accent color,
   which made three different things on one screen the same orange: the
   brand wordmark, this label, and the actual current-page link inside the
   menu (which is ALSO underlined). Readers took the label to mean "the
   page you're on" when it only opens a menu, and it competed with the real
   current-page marker one line below.
   The accent now stays reserved for the page you're actually on. The
   section keeps a quieter cue: normal text color, plus a soft underline
   that reads as "you're somewhere in here" without impersonating a link. */
.nav-group.active summary { color:var(--txt);
                            box-shadow:inset 0 -2px 0 var(--line); }
/* Hover bridge. The popover is offset `margin-top:4px` from the summary, so
   the pointer crosses a dead strip on its way down and mouseleave fires --
   the menu would shut just as you reached for it. This invisible pad spans
   the gap so the group stays hovered the whole way. It exists only while
   the group is open, or it would swallow clicks on the row beneath.
   Paired with the 180ms close delay in NAV_JS; belt and braces, since the
   pad can't cover a diagonal exit past the popover's edge. */
.nav-group[open]::after { content:''; position:absolute; top:100%; left:0;
                          width:100%; height:6px; }
.nav-links { display:flex; flex-direction:column; gap:4px;
             position:absolute; top:100%; left:0; z-index:5;
             background:var(--panel); border:1px solid var(--line);
             border-radius:6px; padding:8px 12px; margin-top:4px;
             min-width:140px; }
/* Collapsed groups stack in normal flow at mobile width instead of
   floating a popover, matching the existing .nav wrap behavior there. */
@media (max-width:640px){
  .nav-links { position:static; border:none; background:none;
               padding:4px 0 4px 14px; margin-top:0; }
}
.footer-links { display:flex; flex-wrap:wrap; column-gap:18px; row-gap:6px;
                margin-top:10px; font-size:12px; }
.footer-links a { color:var(--dim); font-weight:600; }
.footer-links a:hover { color:var(--acc); }
/* display:flex is load-bearing for the scroll hint, not cosmetic. ::after was
   position:absolute with right:0, but .scroll is BOTH the containing block and
   the scroller, so `right:0` resolved against the full scrollWidth -- the hint
   sat at the end of the CONTENT and slid inward as you swiped. Measured in a
   360px viewport: it shifted -272px (exactly the overflow), landing over the
   middle columns. Reported on Android Chrome + Firefox 2026-08-18.
   A flex container lets ::after be position:sticky instead, which tracks the
   visible edge. The alternative -- a non-scrolling wrapper div -- is the
   textbook fix but would touch all 24 `class=scroll` call sites. */
/* Per-player Elo derivation blocks (/match). One collapsible block each --
   the explanation has four stages and a table row cannot hold them. */
.xp { border:1px solid var(--line); border-radius:8px; background:var(--panel);
  margin:0 0 6px; }
.xp summary { cursor:pointer; list-style:none; padding:8px 12px;
  display:flex; align-items:baseline; gap:10px; }
.xp summary::-webkit-details-marker { display:none; }
.xp summary::after { content:'BE'; margin-left:auto; color:var(--dim);
  font-size:10px; }
.xp[open] summary::after { content:'B4'; }
.xp summary:hover { background:#202732; }
/* Layout only, like td.xp-n. This is always emitted alongside a sign class,
   and the amber it used to set here beat that class on source order alone --
   which meant a rating DROP rendered the same colour as a gain, and would have
   silently come back if the rules were ever reordered. */
.xp-d { font-variant-numeric:tabular-nums; font-weight:700; }
/* Signed figures in the rating explainer follow the site convention used by
   .t1/.t2 and .sb-delta: --ax1 up, --ax2 down. Colour is redundant with the
   +/- glyph and the "helped/hurt your rating" wording, never the only signal.
   Listed after .xp-d so it wins over that rule's amber on the headline delta. */
.xp-up { color:var(--ax1); }
.xp-dn { color:var(--ax2); }
.xp-flat { color:var(--dim); }
/* before -> after in a block's summary. The arrow and the starting rating stay
   dim; only the landing rating takes the direction colour, so the eye reads
   "where it ended up" rather than the pair fighting for attention. */
.xp-arrow { font-variant-numeric:tabular-nums; }
.xp-body { padding:2px 12px 12px; border-top:1px solid var(--line); }
.xp-h { font-size:11px; text-transform:uppercase; letter-spacing:.8px;
  color:var(--dim); font-weight:700; margin:12px 0 4px; }
.xp-body p { margin:4px 0; font-size:12.5px; max-width:70ch; }
/* NO overflow here. The symbols inside carry their definition in an absolutely
   positioned ::after, and overflow on an ancestor establishes a clipping
   context, so the tooltip was cut off at the edge of this box. It also drew the
   browser's default white scrollbar on a dark panel.
   A formula line that is too wide now wraps instead of scrolling: it is
   arithmetic, so a wrapped line is still readable, and nothing is hidden. */
.xp-calc { font-family:ui-monospace,Consolas,monospace; font-size:12px;
  background:#12161c; border:1px solid var(--line); border-radius:6px;
  padding:8px 10px; margin:6px 0;
  white-space:normal; overflow-wrap:anywhere; line-height:1.7;
  /* positioning parent for the symbol tooltips below, so they can be pinned to
     this box's edges instead of tracking a one-character symbol */
  position:relative; }
.xp-metrics { width:auto; border:0; background:none; margin:4px 0; }
.xp-metrics td { border:0; padding:1px 14px 1px 0; font-size:12.5px; }
/* NO `color` here. This selector is 0-2-1 and .xp-up/.xp-dn are 0-1-0, so a
   colour set here silently beat the sign colours no matter how late they were
   declared -- the contribution numbers rendered plain white. Layout only;
   the colour is the sign class's job. */
.xp-metrics td.xp-n { font-variant-numeric:tabular-nums; text-align:right; }
.xp-metrics tr:nth-child(even) td { background:none; }
/* Each block is a label/value grid rather than headed paragraphs, so the same
   row sits in the same place for every player and two blocks can be compared
   by eye. Labels are a fixed column; the note after a value is the plain-words
   reading that used to be a sentence of its own. */
.xp-grid { display:grid; grid-template-columns:max-content 1fr; gap:3px 14px;
  margin:6px 0 2px; font-size:12.5px; align-items:baseline; }
.xp-grid dt { color:var(--dim); font-size:11px; text-transform:uppercase;
  letter-spacing:.7px; white-space:nowrap; padding-top:2px; }
.xp-grid dd { margin:0; font-variant-numeric:tabular-nums; }
.xp-note { color:var(--dim); font-variant-numeric:normal; }
/* the metric breakdown rides in the value column under an empty <dt>, so it
   lines up beneath the Stat line figure it belongs to */
.xp-grid dd > .xp-metrics { margin:1px 0 3px; }
/* the key in the section intro: prose values, so no tabular numerals and the
   label column may wrap on a phone */
/* Row help. Each row label explains itself instead of a key sitting seven rows
   above the numbers it describes.

   NOT title=: browsers delay that by about a second and never show it on
   touch. This is a CSS tooltip, so it appears on the first frame of hover.

   The target is a real <button>, which is the part that makes touch work: iOS
   Safari does not focus a non-interactive element on tap, so the same rules
   hung off a <dt tabindex=0> drew the "i" icon and then did nothing when
   pressed. A button also brings Enter/Space and Esc with it, still no JS.

   These rules are scoped to .xp-help itself, NOT to .xp-grid. They were
   grid-scoped once, and a symbol control in a <p> then matched none of them, so
   the browser drew its own chrome: a white box with a border round every
   variable. A reset must reach everywhere the class it resets can appear. */
.xp-grid dt { position:relative; }
.xp-help {
  /* strip the browser's button chrome -- this has to read as text */
  appearance:none; -webkit-appearance:none; background:none; border:0;
  border-radius:0; box-shadow:none; margin:0; padding:0; font:inherit;
  color:inherit; letter-spacing:inherit; text-transform:inherit;
  text-align:left; cursor:help; position:relative; }
/* The "i" cue is unconditional. It used to live only inside
   @media (hover: none), which is all-or-nothing and wrong on both edges: a
   touchscreen laptop and an iPad with a trackpad both report hover:hover and
   so got no icon at all, while on a desktop a label with help looked exactly
   like one without, so there was nothing to tell you the help existed. */
.xp-help::before {
  content:'i'; display:inline-flex; align-items:center;
  justify-content:center; width:13px; height:13px; margin-right:5px;
  border:1px solid var(--dim); border-radius:50%; font-size:9px;
  font-style:italic; font-weight:700; line-height:1; vertical-align:1px;
  color:var(--dim); flex:none; }
.xp-help:hover::before,
.xp-help:focus::before { border-color:var(--acc); color:var(--acc); }
.xp-help::after {
  content:attr(data-help); position:absolute; left:0; top:calc(100% + 4px);
  z-index:40; width:max-content; max-width:min(42ch, 78vw);
  background:var(--panel); color:var(--txt); border:1px solid var(--line);
  border-radius:6px; padding:7px 9px; font-size:12px; line-height:1.4;
  font-weight:400; text-transform:none; letter-spacing:0; white-space:normal;
  box-shadow:0 6px 18px rgba(0,0,0,.45);
  /* hidden without display:none; the accessible copy is the aria-label, since
     a ::after tooltip is decorative and never announced */
  opacity:0; visibility:hidden; pointer-events:none; }
.xp-help:hover::after,
.xp-help:focus::after,
.xp-help:focus-visible::after { opacity:1; visibility:visible; }
.xp-help:hover, .xp-help:focus { color:var(--acc); }
.xp-help:focus { outline:none; }
/* the last rows would push their tooltip off the bottom of a short block */
.xp-grid dt:nth-last-of-type(-n+2) .xp-help::after {
  top:auto; bottom:calc(100% + 4px); }

/* A symbol (Q, sigma, g, d2, e-hat) in the symbol row. Same control as a row
   label, so it gets the same instant tooltip and the same "i" cue -- it used to
   be <abbr title=>, which is delayed on a mouse and silent on touch, so the
   symbols had no definition at all on a phone.

   A smaller icon so the cue does not outweigh a one-character symbol, and the
   dotted underline is the affordance <abbr> used to provide. */
.xp-sym { white-space:nowrap; border-bottom:1px dotted var(--dim); }
.xp-sym::before { width:11px; height:11px; font-size:8px; margin-right:3px; }
/* NOT centred on the symbol. That is what pushed the tooltip off the right edge
   of the page: half its width extended past a symbol sitting near the edge, and
   the viewport clips the overflow. The .xp-calc rules below pin it to the box
   instead. */
/* Inside a formula line the "i" circle reads as clutter, so drop it there and
   let the dotted underline carry the affordance on its own. The tooltip and the
   tap target are unaffected. */
.xp-sym-bare::before { content:none; }
.xp-sym-bare { border-bottom-style:dashed; }
.xp-sym-bare:hover, .xp-sym-bare:focus { border-bottom-color:var(--acc); }
/* A symbol is one character wide, so a tooltip anchored to it hangs off the
   right edge of the page whenever the symbol sits near it -- and the viewport
   clips what hangs off. Anchor to the .xp-calc box instead: position:static on
   the button makes the ::after resolve against that box, and pinning left AND
   right to 0 makes the tooltip exactly as wide as the box, which is on-screen
   by definition. No JS, and no dependence on where in the line the symbol is. */
.xp-calc .xp-help { position:static; }
.xp-calc .xp-help::after {
  left:0; right:0; width:auto; max-width:none;
  transform:none; text-align:left; }

/* Touch refinements only. The icon itself is drawn unconditionally above --
   this block just makes it and its target finger-sized, which is wasted space
   on a mouse. If the query never matches, nothing here is required. */
@media (hover: none) {
  .xp-help::before { width:15px; height:15px; font-size:10px; }
  /* the whole label is the tap target, not just the word */
  .xp-grid .xp-help { padding:6px 2px 6px 0; min-height:30px; }
  /* a phone-width tooltip anchors to the row, not the label */
  .xp-help::after { max-width:86vw; }
  /* a symbol is one character wide -- give the tap something to land on */
  .xp-sym { padding:4px 2px; }
  .xp-sym::before { width:13px; height:13px; font-size:9px; }
  /* no max-width override here: inside .xp-calc the tooltip is pinned to the
     box, which is already narrower than the viewport */
}
.xp-key dd { font-variant-numeric:normal; max-width:62ch; }
@media (max-width:520px) {
  .xp-grid { grid-template-columns:1fr; gap:1px 0; }
  .xp-grid dt { padding-top:6px; }
}
.xp-more { margin:8px 0 0; }
.xp-more summary { cursor:pointer; list-style:none; font-size:11.5px;
  color:var(--dim); padding:2px 0; }
.xp-more summary::-webkit-details-marker { display:none; }
.xp-more summary::after { content:'BE'; margin-left:5px; font-size:9px; }
.xp-more[open] summary::after { content:'B4'; }
.xp-more summary:hover { color:var(--acc); }
/* The formula panel was a grey line of body text nobody noticed. Give it a
   border, a box and the accent colour so it reads as a control, and keep it
   distinct from the plain .xp-more disclosures elsewhere in the section. */
.xp-formula { margin-top:10px; border:1px solid var(--line); border-radius:4px;
  background:rgba(255,255,255,.02); }
.xp-formula > summary { font-size:12px; font-weight:600; color:var(--acc);
  padding:6px 10px; letter-spacing:.2px; }
.xp-formula > summary:hover { background:rgba(240,168,48,.07); }
.xp-formula[open] > summary { border-bottom:1px solid var(--line); }
.xp-formula > p, .xp-formula > .xp-calc { margin:8px 10px; }
/* No `.xp-calc abbr` rules: the symbols are .xp-help buttons now, styled above.
   The abbr rules that used to live here were dead once the markup changed. */

.scroll { position:relative; overflow-x:auto; border:1px solid var(--line);
  border-radius:10px; -webkit-overflow-scrolling:touch; display:flex; }
/* the table must not shrink inside the flex container, or flex-shrink would
   compress it to fit and there would be nothing left to scroll. */
.scroll > table { flex:0 0 auto; }
/* The generic `table` rule sets overflow:hidden to clip its own border-radius.
   Inside .scroll that makes the TABLE a scroll container, so position:sticky on
   its cells anchors to the table (which never scrolls) instead of to .scroll --
   the sticky first column below silently did nothing. .scroll already clips and
   rounds, so the table does not need its own. */
.scroll table { border:0; overflow:visible; }
/* the pinned column must be fully opaque and sit above both the scrolling
   cells and the ::after swipe hint (z-index:2), or scrolled content shows
   through it. background must track --panel: a hardcoded colour drifts from
   the table behind it and leaves a mismatched stripe down the left edge. */
.scroll th:first-child, .scroll td:first-child {
  position:-webkit-sticky; position:sticky; left:0; background:var(--panel);
  z-index:3; }
/* horizontal-scroll hint: a fade + chevron pinned to the right edge, shown only
   while the table has more to swipe. JS (scrollHintJs) toggles .at-end when the
   user reaches the right edge, and .no-scroll when nothing overflows. */
.scroll::after {
  /* U+203A single right-pointing angle quote. ONE backslash: a doubled one
     makes CSS emit the literal text "\203A" (reported 2026-07-30). */
  content:"\203A"; position:sticky; right:0; flex:0 0 44px; margin-left:-44px;
  align-self:stretch;
  display:flex; align-items:center; justify-content:flex-end; padding-right:9px;
  font:800 26px/1 system-ui,sans-serif; color:var(--acc); pointer-events:none;
  background:linear-gradient(90deg, rgba(26,30,37,0), var(--panel) 72%);
  opacity:1; transition:opacity .2s; z-index:2; }
.scroll.at-end::after, .scroll.no-scroll::after { opacity:0; }
@media (min-width:1000px){ /* desktop rarely needs the swipe hint */
  .scroll::after { font-size:20px; width:34px; } }
/* On a phone the 44px hint is a big slice of a ~346px table, and reaching full
   --panel at 72% left the last column washed out under it -- the tier label on
   /elo sat behind the opaque end of the gradient at rest (reported 2026-08-15).
   Narrow it and hold the ramp translucent until the final few px, so the fade
   still says "more to swipe" without veiling a column you are trying to read. */
@media (max-width:640px){
  .scroll::after { width:26px; padding-right:4px; font-size:19px;
    background:linear-gradient(90deg, rgba(26,30,37,0) 0%,
                               rgba(26,30,37,.72) 62%, var(--panel) 100%); } }
/* .wrap's 22px side padding costs 44px of a 390px phone -- a wide table was
   boxed in a gutter and lost ~13% of its width to it. Let scroll containers
   break out to the full viewport while every other child keeps the padding
   (headings, filters and pagers still need it). Side border and radius go too,
   since a full-bleed panel has no visible edge to round. Desktop is unaffected:
   there the gutter is wanted and the table is nowhere near the viewport edge. */
@media (max-width:640px){
  .wrap > .scroll { margin-left:-22px; margin-right:-22px;
    width:calc(100% + 44px); border-left:0; border-right:0; border-radius:0; } }
.wide th, .wide td { padding:5px 9px; font-size:12px; }
.lead td:first-child { font-weight:600; }
/* give the player/name column room so wide (e.g. CJK) names don't cramp */
.lead th:nth-child(2), .lead td:nth-child(2) { min-width:200px; }
/* mobile: 200px on the name column hid the actual data (e.g. the weapons
   Kills column). Trim it + tighten padding so narrow tables fit; wide tables
   still scroll (with the swipe hint). Seeing the numbers beats never wrapping. */
@media (max-width:640px) {
  .lead th:nth-child(2), .lead td:nth-child(2) { min-width:118px; }
  .lead th, .lead td { padding:6px 7px; }
  /* The career leaderboard (.lead.compact) is 19 columns and always scrolls on
     a phone, so the name column can afford a wider floor than the 118px above
     -- 150px fits a 17-char handle like "CaptCatrenko@live" at 11.5px instead
     of wrapping or crushing it. Ranked tables lead with a numeric "#", so the
     .scroll ellipsis cap below never applies to the name cell here; opt it out
     of nowrap explicitly anyway so an over-long name wraps to a second line
     rather than being silently truncated. */
  .lead.compact th:nth-child(2), .lead.compact td:nth-child(2) { min-width:150px; }
  .lead.compact td:nth-child(2) { white-space:normal; overflow-wrap:anywhere; }
  /* Pin the NAME as well as the rank. .scroll pins :first-child, but on a ranked
     leaderboard that is the ~31px numeric "#" -- swiping right kept a bare digit
     and scrolled the player name away, so the visible stats belonged to nobody
     identifiable (reported 2026-08-15). Pin both columns and offset the name by
     the rank width so they sit flush. Scoped to tables that actually have a rank
     cell: on /player the .lead first column is already the name, and pinning two
     columns there would eat ~300px of a 390px viewport.
     The pinned cells must repeat the tr:nth-child(even) stripe from line 60 --
     they paint their own background, so without it every other row shows the
     unstriped --panel and the frozen pair looks detached from its row. */
  .scroll table:has(td.rank) th:first-child,
  .scroll table:has(td.rank) td:first-child {
    width:31px; min-width:31px; z-index:4; }
  .scroll table:has(td.rank) th:nth-child(2),
  .scroll table:has(td.rank) td:nth-child(2) {
    position:-webkit-sticky; position:sticky; left:31px; z-index:4;
    background:var(--panel); min-width:118px; max-width:118px;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
    border-right:1px solid var(--line);
    box-shadow:6px 0 6px -4px rgba(0,0,0,.55); }
  .scroll table:has(td.rank) tr:nth-child(even) td:first-child,
  .scroll table:has(td.rank) tr:nth-child(even) td:nth-child(2) {
    background:#161a20; }
  /* .longname tables (maps, vehicles) have very long names that otherwise fill
     the whole viewport and hide the ranked metric. Cap + ellipsis the name so
     the primary stat column is visible; full name is on the detail page. */
  .longname td:nth-child(2) { max-width:150px; overflow:hidden;
    text-overflow:ellipsis; white-space:nowrap; }
  .longname th:nth-child(2), .longname td:nth-child(2) { min-width:0; }
  /* some tables (player match-history / weapons-tab / vehicles) lead with a
     long NAME in the sticky first column, which fills the viewport and hides
     the stats. Cap that first cell too; a numeric "#" first cell is short and
     never hits the cap, so ranked tables are unaffected. */
  .scroll td:first-child { max-width:150px; overflow:hidden;
    text-overflow:ellipsis; white-space:nowrap; }
  /* Ladder name cell carrying an incognito owner tag `Alias (RealName)`: two
     names in one cell would otherwise double the column width and push the Elo
     badge off a phone screen (th/td default to nowrap). Let it wrap to a second
     line instead, and shrink the tag so the alias stays primary. min-width:0 is
     required -- the .lead floor above would otherwise keep reserving the
     single-name width and the wrap would never help. */
  td.lb-name { white-space:normal; overflow-wrap:anywhere; min-width:0;
               max-width:170px; }
  td.lb-name .name-now { font-size:.78em; margin-left:.25em;
                         display:inline-block; }
}
/* Very narrow phones: the owner tag is supplementary, so let the alias itself
   keep the space. Kept as its own breakpoint rather than hiding it at 640px --
   at typical phone widths both still fit on two short lines, and the whole
   point of the tag is that people can see whose run it is. */
@media (max-width:380px) {
  td.lb-name { max-width:132px; }
  td.lb-name .name-now { font-size:.74em; }
}
/* compact: squeeze a wide many-column leaderboard so it fits w/o scroll */
.compact th, .compact td { padding:4px 6px; font-size:11.5px; }
.compact th { letter-spacing:0; }
/* Desktop only: drop .lead's 200px name floor so 19 columns fit unscrolled.
   Scoped to a width query because on a phone this rule used to win over
   .lead's mobile floor (same specificity, declared later) and collapsed the
   name column to its narrowest possible box -- names like "CaptCatrenko@live"
   and CJK handles ended up cramped against the Elo badge. The table already
   lives in .scroll with a sticky first column and a swipe hint, so below this
   breakpoint the name keeps a real floor and the row scrolls instead. */
@media (min-width:641px) {
  .compact th:nth-child(2), .compact td:nth-child(2) { min-width:0; }
}
/* match scoreboard: two per-team lists side by side, wrap on narrow screens */
/* break out of the 1120 wrap cap so the wide gap fits without wrapping */
.teamboards { display:flex; gap:75px; flex-wrap:nowrap; align-items:flex-start;
              width:calc(100vw - 44px); max-width:1600px; }
.teamcol { flex:1; min-width:0; }
.teamcol h3 { margin:0 0 6px; font-size:15px; }
.teamcol table { width:100%; }
/* vertical dividers between columns in the per-team scoreboards */
.teamcol th + th, .teamcol td + td { border-left:1px solid var(--line); }
/* let the players leaderboard use the full window width, not the 1120 cap */
.fullbleed { max-width:none; margin-right:22px; }
/* .filt REMOVED. It was the pre-rework inline-filter-row class. Every one of
   its six call sites (home.py x2, elo.py x2, player.py x2) migrated to .fbar,
   so the rule styled nothing. Left in place it would invite a new page to
   reach for the old row class instead of the labelled filter bar -- the same
   hazard that let .pill/.subtabs/.pager drift into byte-identical rules.
   Pinned by test_no_dead_control_css_survives. */

/* Filter bar: one labelled container per page for every filter axis. */
.fbar { border:1px solid var(--line); border-radius:7px; padding:9px 12px;
        background:#141a20; margin:14px 0 10px; }
.fbar-row { display:flex; align-items:center; gap:9px; flex-wrap:wrap;
            margin-bottom:6px; }
.fbar-row:last-child { margin-bottom:0; }
.fbar-l { flex:0 0 auto; min-width:56px; font-size:10px; text-transform:uppercase;
          letter-spacing:.07em; color:var(--dim); font-weight:700; }
.fbar-note { margin:7px 0 0; font-size:12px; color:var(--dim); }
@media (max-width:640px){
  .fbar-row { align-items:flex-start; }
  .fbar-l { flex-basis:100%; }
}

/* Applied-filter summary: names each active value, not just a count. */
.fapplied { display:flex; align-items:center; gap:7px; flex-wrap:wrap;
            margin:8px 0 0; font-size:12px; }
.fchip { display:inline-flex; align-items:center; gap:5px; padding:2px 4px 2px 9px;
         border:1px solid var(--line); border-radius:11px; color:var(--dim); }
.fchip b { color:var(--txt); font-weight:600; }
.fchip-x { padding:0 5px; color:var(--dim); font-weight:700; }
.fchip-x:hover { color:var(--ax2); text-decoration:none; }
.fclear { color:var(--ax1); }
.patreon-btn { display:inline-block; background:#FF424D; color:#fff !important;
               font-weight:700; font-size:12.5px; padding:5px 13px;
               border-radius:6px; text-decoration:none !important;
               white-space:nowrap; letter-spacing:.3px; }
.patreon-btn:hover { background:#e03040; color:#fff !important; }
.patreon-btn .heart { display:inline-block; margin-right:6px;
                      color:#fff; font-size:.95em; transform:translateY(.5px); }
.patreon-btn:hover .heart { animation:heartbeat .9s ease-in-out infinite; }
@keyframes heartbeat { 0%,100%{transform:translateY(.5px) scale(1);}
  25%{transform:translateY(.5px) scale(1.22);}
  40%{transform:translateY(.5px) scale(1.05);}
  60%{transform:translateY(.5px) scale(1.18);} }
@media (prefers-reduced-motion:reduce){ .patreon-btn:hover .heart{animation:none;} }
.search-wrap { position:relative; display:inline-block; margin-left:auto; }
#psearch { background:#222934; border:1px solid var(--line); border-radius:7px;
           color:var(--txt); padding:6px 13px; font-size:14px; width:240px;
           outline:none; }
#psearch:focus { border-color:var(--acc); }
#psearch::placeholder { color:var(--dim); }
/* iOS Safari auto-zooms the page on focus if an input's font-size is under
   16px; bump it back up on narrow screens without touching desktop sizing */
@media (max-width:640px){ #psearch{ font-size:16px } }
#psearch-results { position:absolute; top:calc(100% + 4px); left:0; right:0;
                   background:#222934; border:1px solid var(--line);
                   border-radius:7px; z-index:100; display:none;
                   max-height:280px; overflow-y:auto; }
#psearch-results a { display:block; padding:7px 13px; color:var(--txt);
                     font-size:14px; text-decoration:none; }
#psearch-results a:hover, #psearch-results a.on { background:#2a3038; color:var(--acc); }
/* .pill was removed 2026-08-17: every filter call site now uses .seg below.
   It was byte-identical to .subtabs and .pager span.pg.on, which is what made
   filters, view-swapping and paging look like the same control. */
/* Segmented control: the FILTER token. One enclosed track with divided
   segments -- deliberately distinct from .nav/.rail anchors (bare text +
   underline), because a box means "pick a value" and bare text means "go
   somewhere". Replaces the old .pill-row idiom, which was visually identical
   to .subtabs and .pager and so said nothing about what a click would do. */
.seg { display:inline-flex; border:1px solid var(--line); border-radius:6px;
       overflow:hidden; background:#151a20; vertical-align:middle;
       flex-wrap:wrap; }
.seg a { padding:4px 12px; font-size:13px; color:var(--txt); font-weight:500;
         border-right:1px solid var(--line); }
.seg a:last-child { border-right:none; }
.seg a:hover { background:#1e242c; text-decoration:none; }
.seg a.on { background:var(--acc); color:#15181d; font-weight:700; }
.seg b { font-weight:700; opacity:.65; margin-left:4px; }
.ddgrid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:11px;
          align-items:start; }
.ddcol { min-width:0; }
.ddcol table { font-size:11.5px; }
.ddcol th, .ddcol td { padding:3px 8px; }
/* stack to one column on narrow screens; each column's table can scroll
   horizontally inside itself so a long stat key never overflows the page.
   (grid track uses minmax(0,1fr) so it can shrink below the table's width.) */
@media (max-width:820px){
  .ddgrid { grid-template-columns:minmax(0,1fr); }
  .ddcol { overflow-x:auto; -webkit-overflow-scrolling:touch; }
}
/* Elo tiers — one hue per rank band, readable on the dark panel */
.elo { font-weight:700; font-variant-numeric:tabular-nums; }
.elo-bronze   { color:#cd7f32; }
.elo-silver   { color:#c8d2dc; }
.elo-gold     { color:#ffd34d; }
.elo-platinum { color:#5fd7c9; }
.elo-diamond  { color:#8ab8ff; }
.elo-none     { color:var(--dim); font-weight:400; }
.elo-delta { font-size:11px; margin-left:4px; }
.tier-tag { font-size:10.5px; text-transform:uppercase; letter-spacing:1px;
            font-weight:800; display:block; }
/* ladder placement on the player-page elo card; tabular so the digits
   don't jitter between the main and console cards */
.place { color:var(--txt); font-weight:700; font-variant-numeric:tabular-nums; }
.tq { background:#222934; border:1px solid var(--line); border-radius:7px;
      color:var(--txt); padding:6px 13px; font-size:14px; width:230px;
      outline:none; }
.tq:focus { border-color:var(--acc); }
.tq::placeholder { color:var(--dim); }
@media (max-width:640px){ .tq{ font-size:16px } }
/* full-bleed top bar: spans the whole viewport, content stays centered */
.topbar { border-bottom:1px solid var(--line); background:#13171c;
          padding:0; margin:0; }
.topbar-inner { max-width:1120px; margin:0; padding:12px 18px;
  display:flex; align-items:center; gap:20px; }
@media (max-width:520px){ .brandlogo{ height:56px } }
/* pagination control — page-size selector + prev/next + numbered pages */
.pager { display:flex; align-items:center; gap:10px; flex-wrap:wrap;
         margin:12px 0; font-size:13px; color:var(--dim); }
.pager .pages { display:flex; gap:4px; flex-wrap:wrap; }
/* DEMOTED deliberately: paging is table-local state and the least important of
   the four control kinds, but it used to share .pill's exact border+accent
   treatment, so two pagers per table competed with the filters and the rail.
   Muted border + no accent on .on. The pager still renders TWICE per table
   ({pg}{tbl}{pg}) -- the top copy carries the page-size selector, so removing
   it would be a functional regression. */
.pager a, .pager span.pg { display:inline-block; min-width:30px; text-align:center;
         padding:5px 9px; border:1px solid #232a32; border-radius:6px;
         color:var(--dim); font-weight:600; font-variant-numeric:tabular-nums; }
.pager a:hover { border-color:#3a424c; text-decoration:none; }
.pager span.pg.on { background:#2a3038; color:var(--txt); border-color:#3a424c; }
.pager span.pg.gap { border:0; color:var(--dim); min-width:0; padding:5px 2px; }
.pager .szsel { margin-left:auto; }
.pager select { background:#222934; border:1px solid var(--line); border-radius:6px;
         color:var(--txt); padding:4px 8px; font-size:13px; }
/* .subtabs was removed 2026-08-17 with the player profile's tab strip -- the
   site's only one. Sections now render together with a .rail to jump between
   them, so there is no view-swapping control left to style. */
/* lobby-type badges */
.ltype { display:inline-block; font-size:10px; font-weight:700; letter-spacing:.5px;
         text-transform:uppercase; padding:2px 6px; border-radius:4px; vertical-align:middle;
         margin-left:5px; }
.ltype-mixed   { background:#2a3a2a; color:#6fcf6f; border:1px solid #3a5a3a; }
.ltype-console { background:#2a2a3a; color:#7faeff; border:1px solid #3a3a5a; }
.ltype-pc      { background:#3a2a3a; color:#cf8eff; border:1px solid #5a3a5a; }
.ltype-bots    { background:#3a3a2a; color:#cfbf6f; border:1px solid #5a5a2a; }
/* lobby distribution bar */
.ltype-bar { display:flex; height:8px; border-radius:4px; overflow:hidden;
             margin:6px 0 2px; gap:1px; }
.ltype-bar span { display:block; height:100%; }
/* ---- Enlisted-style visual scoreboard (recreated from the game's Dagor UI:
   scoreTableColumns.nut / mkScoresStatistics.nut / style.nut; emblems from
   uiskin.vromfs.bin). Fluid: each team fills 100% of the container width and
   reflows with the window / zoom — the name column absorbs all slack. ------- */
.sb {
  --sb-ally-hdr:#3a586b; --sb-enemy-hdr:#7a483e;   /* muted, from in-game shot */
  --sb-t0:rgb(150,160,255); --sb-t1:rgb(255,160,160); --sb-my:rgb(150,255,160);
  --sb-line-h:40px; --sb-idx:34px; --sb-elo:96px;
  --sb-num:48px; --sb-score:66px; --sb-hicon:30px; --sb-emblem:26px;
  width:100%;
}
.sb-strip { display:flex; flex-wrap:wrap; padding:2px 4px 8px; color:var(--txt); gap:2px 0; }
.sb-strip .side { flex:1 1 240px; font-size:18px; color:#d6dde3; }
.sb-strip .side.r { text-align:right; }
.sb-strip .side b { font-weight:700; }
.sb-strip .mid { flex:0 0 8px; text-align:center; white-space:nowrap;
                 font-size:16px; color:var(--dim); }
.sb-teams { display:flex; flex-direction:column; gap:20px; width:100%; }
.sb-team { width:100%; min-width:0; }
.sb-hdr { display:flex; align-items:center; height:var(--sb-line-h); font-size:18px; }
.sb-hdr.ally  { background:var(--sb-ally-hdr); }
.sb-hdr.enemy { background:var(--sb-enemy-hdr); }
/* title block spans idx+name: grows to fill everything the fixed cells leave */
.sb-title { flex:1 1 auto; min-width:0;
            display:flex; align-items:center; gap:9px; padding-left:8px; overflow:hidden; }
.sb-title img { width:var(--sb-emblem); height:var(--sb-emblem); flex:0 0 var(--sb-emblem); }
.sb-title .nm { color:#eef2f5; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.sb-hcell { width:var(--sb-num); flex:0 0 var(--sb-num); display:flex;
            align-items:center; justify-content:center; }
.sb-hcell.elo { width:var(--sb-elo); flex:0 0 var(--sb-elo); font-size:14px; color:#dfe6ea; }
.sb-hcell.score { width:var(--sb-score); flex:0 0 var(--sb-score); }
.sb-hcell img { width:var(--sb-hicon); height:var(--sb-hicon); }
.sb-rows { border:1px solid; border-top:0; }
.sb-team.ally  .sb-rows { border-color:var(--sb-ally-hdr); }
.sb-team.enemy .sb-rows { border-color:var(--sb-enemy-hdr); }
.sb-row { display:flex; align-items:stretch; height:var(--sb-line-h); }
.sb-row:nth-child(odd)  { background:rgba(255,255,255,.045); }
.sb-row:nth-child(even) { background:rgba(0,0,0,.28); }
.sb-row:hover { background:rgba(120,140,160,.14); }
.sb-cell { display:flex; align-items:center; justify-content:center;
           font-size:16px; font-variant-numeric:tabular-nums;
           width:var(--sb-num); flex:0 0 var(--sb-num); border-left:1px solid; }
.sb-team.ally  .sb-cell { color:var(--sb-t0); border-left-color:var(--sb-ally-hdr); }
.sb-team.enemy .sb-cell { color:var(--sb-t1); border-left-color:var(--sb-enemy-hdr); }
.sb-cell.idx  { width:var(--sb-idx); flex:0 0 var(--sb-idx); border-left:0;
                font-size:13px; color:var(--dim); }
/* name column absorbs all slack so each team fills the container at any width */
.sb-cell.name { flex:1 1 auto; min-width:0; justify-content:flex-start;
                gap:7px; padding:0 6px 0 8px; overflow:hidden; }
.sb-cell.name .nick { flex:0 1 auto; overflow:hidden; text-overflow:ellipsis;
                      white-space:nowrap; }
.sb-cell.name a { color:inherit; }
/* current-identity annotation: `Played (now Current)` / `(incognito · Current)`.
   Muted + smaller so the played name stays primary; incognito tinted. */
.name-now { color:var(--dim); font-size:.85em; margin-left:.4em; }
.name-now::before { content:"("; } .name-now::after { content:")"; }
.name-now a { color:var(--dim) !important; text-decoration:none; }
.name-now a:hover { text-decoration:underline; }
.name-now.incog { color:var(--acc); }
.name-now.incog a { color:var(--acc) !important; }
.sb-cell.score { width:var(--sb-score); flex:0 0 var(--sb-score); color:var(--txt); }
.sb-cell.elo  { width:var(--sb-elo); flex:0 0 var(--sb-elo); gap:5px; }
.sb-row.my .name .nick, .sb-row.my .idx { color:var(--sb-my) !important; }
.sb-row.dc .sb-cell { opacity:.55; }
.sb-squad { margin-left:auto; display:inline-flex; align-items:center;
            justify-content:center; width:19px; height:19px; border-radius:50%;
            font-size:12px; color:#12181f; }
.sb-team.ally  .sb-squad { background:var(--sb-t0); }
.sb-team.enemy .sb-squad { background:var(--sb-t1); }
.sb .elo-val { font-variant-numeric:tabular-nums; }
.sb .sb-delta { font-size:11px; }
.sb .sb-delta.up { color:var(--ax1); } .sb .sb-delta.dn { color:var(--ax2); }
.sb .sb-bot { font-size:12px; font-weight:700; letter-spacing:.06em; color:var(--dim); }
.sb .sb-none { color:var(--dim); }
/* very narrow (phones): shed a little padding so the fixed columns still fit */
@media (max-width:820px) {
  .sb { --sb-num:38px; --sb-elo:74px; --sb-score:54px; --sb-idx:26px; font-size:15px; }
  .sb-cell, .sb-hcell.elo { font-size:14px; }
  /* idx+elo+6 stat cols alone need ~328px at these sizes, before the name
     column gets anything — instead of crushing every column illegibly, give
     the name column a sane floor and let each team's table scroll
     horizontally (header + rows together) rather than overflow the page.
     Breakpoint is 820px, not 520px: at 590 CSS px (a DPR-2 phone) the fixed
     columns already starve the name to a single character, and the old query
     never fired there.
     `flex:none` + `width:max-content` are load-bearing. .sb-teams is a COLUMN
     flex container, so its default align-items:stretch sized each .sb-team to
     the container's 393px cross size and .sb-team's own `width:100%` resolved
     against that same box — min-width:480px never won, the row never grew past
     the viewport, overflow-x had nothing to scroll, and .sb-cell.name (flex:1
     1 auto; min-width:0) absorbed the entire shortfall down to ~21px: one
     truncated character per row. Opting out of the stretch and sizing to
     content makes the row genuinely wider than the viewport, which is what
     turns overflow-x:auto into an actual scroll.

     min-width is 100%, not the old 480px floor. max-content sizes to CONTENT,
     which also means it stops at the content when the content is NARROWER than
     the container: at an 820px viewport the box is 776px and the table drew at
     610px, stranding 166px of dead space down the right (the gap opened from
     ~656px up to the breakpoint, worst exactly at 820px). min-width:100% settles
     both directions from one declaration -- max-content wins while the content
     overflows, 100% wins once it fits. The 480px floor it replaces is subsumed:
     100% of the container is >=480px wherever 480px would have mattered, and
     below that the fixed columns overflow anyway, so the scroll still engages. */
  .sb-teams { overflow-x:auto; -webkit-overflow-scrolling:touch; align-items:flex-start; }
  .sb-team { flex:none; width:max-content; min-width:100%; }
  /* the name column is the point of the scroll — give it a real floor so it
     grows with the row instead of staying collapsed */
  .sb-cell.name { flex:1 0 150px; min-width:150px; }
  /* ...but a floor is not enough on its own: the rank+name pair must also stay
     PINNED while the rest of the row scrolls past. Without this the identity
     columns swipe away with the stats and every row becomes an anonymous strip
     of numbers -- reported 2026-09-01 ("except i cant see shit"), measured in a
     390px viewport as .sb-cell.name at x=-215, fully outside the visible box.
     The <table> pages have had this since the sticky-column work; the
     scoreboard missed it because it is a flexbox grid, so neither
     `.scroll td:first-child` nor the `:has(td.rank)` pair ever matched it.

     Backgrounds are load-bearing, not decoration: a pinned cell paints over the
     cells sliding beneath it, so a transparent one would show the numbers
     ghosting through. .sb-row's own stripes are semi-transparent overlays, so
     the pinned pair repeats them as SOLID colours composited against --bg
     (#0f1216): .045 white -> #1a1d20, .28 black -> #0b0d10. Recompute both if
     --bg ever moves, or a seam appears down the frozen edge.

     `left:var(--sb-idx)` rather than a literal 26px: this same block rewrites
     --sb-idx (34px desktop -> 26px here), and a hardcoded offset would leave
     the name overlapping the rank or floating a gap away from it. */
  .sb-row .sb-cell.idx {
    position:-webkit-sticky; position:sticky; left:0; z-index:3;
    background:#1a1d20; }
  .sb-row .sb-cell.name {
    position:-webkit-sticky; position:sticky; left:var(--sb-idx); z-index:3;
    background:#1a1d20;
    box-shadow:6px 0 6px -4px rgba(0,0,0,.55); }
  .sb-row:nth-child(even) .sb-cell.idx,
  .sb-row:nth-child(even) .sb-cell.name { background:#0b0d10; }
  /* the header's title block spans exactly those two columns; pin it as well so
     the team name and emblem stay aligned over their frozen rows instead of
     sliding off while the rows below hold still. It is opaque via the .sb-hdr
     team colour, which it must repeat for the same reason as above. */
  .sb-hdr .sb-title {
    position:-webkit-sticky; position:sticky; left:0; z-index:3; }
  .sb-hdr.ally  .sb-title { background:var(--sb-ally-hdr); }
  .sb-hdr.enemy .sb-title { background:var(--sb-enemy-hdr); }
}
/* ---- In-page anchor rail -------------------------------------------------
   Sticky table of contents for long pages. Deliberately styled like .nav
   (bare text, accent underline/bar, NO box) because it does the same KIND of
   job at a smaller scale: .nav moves between pages, .rail moves within one.
   Boxes are reserved for .seg, which picks a value.

   No scrollspy -- native fragment jumps. Sports-Reference ships the same
   pattern with zero IntersectionObserver on a 1.94MB page.

   .topbar is NOT sticky (see its rule below), so `top` only needs a small
   gutter rather than clearing a fixed header.

   CAUTION: `overflow:hidden` on an ancestor breaks sticky containment -- see
   the .scroll comment above. Do not add it to .railwrap or .railmain. */
.railwrap { display:grid; grid-template-columns:200px minmax(0,1fr); gap:26px;
            align-items:start; }
.railmain { min-width:0; }
.rail { position:-webkit-sticky; position:sticky; top:18px; align-self:start;
        max-height:calc(100vh - 36px); overflow-y:auto; }
.rail-h { margin:0 0 6px; font-size:10px; text-transform:uppercase;
          letter-spacing:.09em; color:var(--dim); font-weight:700; }
.rail a { display:block; padding:3px 8px; color:var(--ax1); font-size:13px;
          border-left:2px solid transparent; }
.rail a:hover { color:var(--acc); text-decoration:none; }
.rail a.on { color:var(--acc); font-weight:700; border-left-color:var(--acc); }
.rail a.rail-out { color:var(--dim); }
.rail hr { border:none; border-top:1px solid var(--line); margin:9px 0 7px; }
/* Railed pages need more width than the 1120px default. */
.wrap.railed { max-width:1400px; }
/* Collapse to a horizontal anchor row above the content. Same breakpoint the
   .ddgrid data-dictionary grid uses, so the two agree. */
@media (max-width:820px){
  .railwrap { grid-template-columns:minmax(0,1fr); gap:12px; }
  .rail { position:static; max-height:none; overflow:visible;
          display:flex; flex-wrap:wrap; gap:4px 14px; align-items:baseline;
          border-bottom:1px solid var(--line); padding-bottom:9px; }
  .rail a { border-left:none; padding:2px 0; }
  .rail a.on { border-bottom:2px solid var(--acc); }
  .rail hr { display:none; }
  .rail-h { flex-basis:100%; margin:0; }
}

.wsb{max-width:378px;padding:8px;background:rgba(19,24,31,.85);border:1px solid rgba(179,189,193,.35);
 color:#C6C7DC;font-size:13px;border-radius:4px}
.wsb-head{display:flex;justify-content:space-between;align-items:baseline;gap:8px;margin-bottom:6px}
.wsb-type{color:#B3BDC1;font-size:12px;text-transform:uppercase;letter-spacing:.04em}
.wsb-name{color:#FAFAFA;font-weight:600;text-align:right;font-size:16px;line-height:1.2}
.wsb-row{display:flex;justify-content:space-between;gap:8px;padding:2px 0}
.wsb-row .v{color:#FAFAFA;white-space:nowrap}
.wsb-bar{height:8px;background:#3E413F;margin:1px 0 4px}
.wsb-fill{height:100%;background:#FCB11D}
.wsb-tbl{width:100%;border:1px solid #B3BDC1;border-collapse:collapse;margin:2px 0 6px;font-size:12px}
.wsb-tbl th,.wsb-tbl td{border-left:1px solid #B3BDC1;text-align:center;padding:2px 3px}
.wsb-tbl th:first-child,.wsb-tbl td:first-child{border-left:none}
.wsb-tbl th{color:#B3BDC1;font-weight:400}
.wsb-tbl td{color:#FAFAFA}
.wsb-chg{color:#FCB11D;font-size:11px;margin-left:4px}
.wsb-chg s{color:#B3BDC1}
.wsb-raw{margin-top:8px;border-top:1px solid rgba(179,189,193,.25);padding-top:6px}
.wsb-raw summary{cursor:pointer;color:#B3BDC1;font-size:12px}
.wsb-raw .wsb-row{font-size:12px}
.wsb-derived{margin-top:8px;border-top:1px solid rgba(179,189,193,.25);padding-top:6px}
.wsb-derived summary{cursor:pointer;color:#FCB11D;font-size:12px}
.wsb-derived .wsb-row{font-size:12px}
.wsb-upgrades{margin-top:6px;font-size:12px;color:#B3BDC1}
.wsb-upgrades .wsb-row span:first-child{color:#FCB11D;flex:none}
.wsb-tier{color:#FCB11D;letter-spacing:.05em;margin-left:6px}
.wsb-base{color:#B3BDC1;font-size:11px;margin-left:4px}
.wsb-base s{color:#7C8592}
.wsb-ident{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px;margin:-2px 0 6px;
 font-size:12px;color:#B3BDC1}
.wsb-ident .br{color:#FCB11D;font-weight:600;letter-spacing:.03em}
.wsb-ident .fac{color:#C6C7DC}
.wsb-ident .iid{color:#7C8592;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;
 word-break:break-all}
.wsb-usage{margin-top:8px;border-top:1px solid rgba(179,189,193,.25);padding-top:6px}
.wsb-usage .lbl{color:#B3BDC1;font-size:11px;text-transform:uppercase;letter-spacing:.04em;
 display:block;margin-bottom:2px}
.wsb-usage .wsb-row{font-size:12px}
.wsb-sub{color:#7C8592;font-size:11px}
.wsb-strong{color:#FCB11D;font-weight:600}
.wsb-warn{color:#FCB11D;font-size:11px;font-style:italic;padding:1px 0 3px}
.wsb-slash{position:relative;color:#7C8592;font-size:11px;padding:1px 7px;cursor:help;
 border:1px solid rgba(214,86,86,.55);border-radius:2px;background:rgba(214,86,86,.07);overflow:hidden}
.wsb-slash::after{content:'';position:absolute;inset:0;background:linear-gradient(to top right,
 transparent calc(50% - 1px),rgba(214,86,86,.85) 50%,transparent calc(50% + 1px))}
.wsb-slash:hover,.wsb-slash:focus{color:#C6C7DC;border-color:rgba(214,86,86,.9);outline:none}
.wsb-chips{display:flex;flex-wrap:wrap;gap:3px;margin:6px 0}
.wsb-chip-r{position:absolute;opacity:0;pointer-events:none}
.wsb-chip{font-size:10px;padding:2px 6px;border:1px solid rgba(179,189,193,.4);border-radius:2px;
 color:#B3BDC1;cursor:pointer;text-transform:uppercase;letter-spacing:.03em}
.wsb-pane{display:none}
.wsb-pane.solo{display:block}
.wsbtr0,.wsbtr1{position:absolute;opacity:0;pointer-events:none}
.wsb-star-toggle{display:flex;gap:2px;margin:-2px 0 6px}
.wsb-star-toggle label{font-size:10px;padding:2px 7px;border:1px solid rgba(179,189,193,.4);
 border-radius:2px;color:#B3BDC1;cursor:pointer;text-transform:uppercase;letter-spacing:.03em}
.wsb .wsbtr1:checked ~ .wsb-star-toggle .wsbtl1,.wsb .wsbtr0:checked ~ .wsb-star-toggle .wsbtl0
 {background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
/* Default (3-star checked): show the starred value, hide base. */
.wsbv0{display:none}
.wsbv1{display:inline}
/* Base checked: swap which span shows, everywhere a toggle pair appears --
 a plain row's .v span or a table cell, both are siblings-of-siblings of the
 radios (every row/table is a direct child of .wsb, same as the radios), so
 one selector pair reaches both shapes. */
.wsb .wsbtr0:checked ~ .wsb-row .wsbv0,.wsb .wsbtr0:checked ~ .wsb-tbl .wsbv0{display:inline}
.wsb .wsbtr0:checked ~ .wsb-row .wsbv1,.wsb .wsbtr0:checked ~ .wsb-tbl .wsbv1{display:none}
/* The Computed section is a <details>, so its rows are not siblings of the
 radios -- reach them through the details element itself. */
.wsb .wsbtr0:checked ~ .wsb-derived .wsbv0{display:inline}
.wsb .wsbtr0:checked ~ .wsb-derived .wsbv1{display:none}
/* Spread: class comparison strip (see _spread_block). */
.wsb-sp{margin-top:8px;padding-top:6px;border-top:1px solid rgba(179,189,193,.18);font-size:12px}
.wsb-sp .lbl{font-size:10px;color:#7C8592;text-transform:uppercase;letter-spacing:.03em}
.wsb-sp-txt{margin:3px 0 4px}
.wsb-sp-strip{display:block;width:100%;height:auto;margin:2px 0 6px}
/* The kill cluster: filter buttons and the numbers they change, boxed
 together so the grouping is visible and not just implied by adjacency. */
.wsb-kill{margin:4px 0 2px;padding:6px;border:1px solid rgba(179,189,193,.22);
 border-radius:3px;background:rgba(255,255,255,.025)}
.wsb-kill .wsb-row:last-child{padding-bottom:0}
/* Kill values are lists ("2 hits to 14.5m · 3 hits to 39.6m · ..."): a fast-
 falloff pistol runs to seven bands, which nowrap pushed out of the panel on
 every screen size. Wrap them, right-aligned, and give the label a fixed share
 so neither side is squeezed to one word per line. */
.wsb-kill .wsb-row .v{white-space:normal;text-align:right;min-width:0}
.wsb-kill .wsb-row>span:first-child{flex:0 0 36%}
.wsb-kill-f{margin-bottom:4px}
.wsb-fr{display:flex;align-items:flex-start;gap:4px;margin-bottom:4px}
.wsb-fr:last-child{margin-bottom:2px}
.wsb-fr .lbl{font-size:10px;color:#7C8592;align-self:center;min-width:34px;
 text-transform:uppercase;letter-spacing:.03em}
.wsb-f{display:flex;flex-wrap:wrap;gap:3px}
.wsb-f button{font:inherit;font-size:10px;line-height:1;padding:3px 6px;cursor:pointer;
 background:transparent;color:#B3BDC1;border:1px solid rgba(179,189,193,.4);border-radius:2px;
 text-transform:uppercase;letter-spacing:.03em}
.wsb-f button:hover,.wsb-f button:focus{color:#FAFAFA;border-color:rgba(179,189,193,.8);outline:none}
.wsb-f button[aria-pressed=true]{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr0:checked ~ .wsb-pane.wsbp0{display:block}
.wsb .wsbr0:checked ~ .wsb-chips .wsbc0{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr1:checked ~ .wsb-pane.wsbp1{display:block}
.wsb .wsbr1:checked ~ .wsb-chips .wsbc1{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr2:checked ~ .wsb-pane.wsbp2{display:block}
.wsb .wsbr2:checked ~ .wsb-chips .wsbc2{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr3:checked ~ .wsb-pane.wsbp3{display:block}
.wsb .wsbr3:checked ~ .wsb-chips .wsbc3{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr4:checked ~ .wsb-pane.wsbp4{display:block}
.wsb .wsbr4:checked ~ .wsb-chips .wsbc4{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr5:checked ~ .wsb-pane.wsbp5{display:block}
.wsb .wsbr5:checked ~ .wsb-chips .wsbc5{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr6:checked ~ .wsb-pane.wsbp6{display:block}
.wsb .wsbr6:checked ~ .wsb-chips .wsbc6{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr7:checked ~ .wsb-pane.wsbp7{display:block}
.wsb .wsbr7:checked ~ .wsb-chips .wsbc7{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr8:checked ~ .wsb-pane.wsbp8{display:block}
.wsb .wsbr8:checked ~ .wsb-chips .wsbc8{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr9:checked ~ .wsb-pane.wsbp9{display:block}
.wsb .wsbr9:checked ~ .wsb-chips .wsbc9{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr10:checked ~ .wsb-pane.wsbp10{display:block}
.wsb .wsbr10:checked ~ .wsb-chips .wsbc10{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr11:checked ~ .wsb-pane.wsbp11{display:block}
.wsb .wsbr11:checked ~ .wsb-chips .wsbc11{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr12:checked ~ .wsb-pane.wsbp12{display:block}
.wsb .wsbr12:checked ~ .wsb-chips .wsbc12{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr13:checked ~ .wsb-pane.wsbp13{display:block}
.wsb .wsbr13:checked ~ .wsb-chips .wsbc13{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr14:checked ~ .wsb-pane.wsbp14{display:block}
.wsb .wsbr14:checked ~ .wsb-chips .wsbc14{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}
.wsb .wsbr15:checked ~ .wsb-pane.wsbp15{display:block}
.wsb .wsbr15:checked ~ .wsb-chips .wsbc15{background:#FCB11D;color:#13181F;border-color:#FCB11D;font-weight:600}

.dm-item{margin:14px 0 4px;font-size:15px}
.dm-changes{border-collapse:collapse;font-size:13px}
.dm-changes td{padding:2px 10px 2px 0;border-bottom:1px solid var(--line);white-space:normal;overflow-wrap:anywhere}
.dm-delta.up{color:#3DB613}.dm-delta.down{color:#FF5522}.dm-delta.same{color:var(--dim)}
.dm-src,.dm-list{color:var(--dim);font-size:12.5px}
.dm-scene{font-size:13px;margin:4px 0 10px;padding-left:18px;overflow-wrap:anywhere}
