/* polbielytics — a stats terminal. Square edges, hairlines, tabular figures. */

:root {
  /* A blue-black ground rather than the green-black it started on. The shift
     is a few degrees of hue and no lightness, and it sits better under the
     cyan accent — which is unchanged, as are the win-rate colours. Colour
     still encodes a number or it is not used. */
  --bg:    #0B0D12;
  --panel: #141821;
  --row:   #10141B;
  --rule:  #232A36;
  --ink:   #E4E8EE;
  /* Both of these came up half a step. #8B95A5 cleared AA on the ground but
     #5C6575 did not — it measured 3.3:1 against the page and 3.0:1 against a
     panel, and it is what every secondary label, column header and sample
     count on the site is written in. The hierarchy is unchanged; it just
     starts from a readable floor now. */
  --mute:  #9AA4B4;
  --dim:   #79839A;
  --gold:  #35C6E0;   /* accent — cyan */
  --good:  #3E8E7E;
  --bad:   #B4544A;

  /* Outfit replaces Barlow for text. At the 13px this site runs its tables
     at, Barlow's narrow apertures close up and Outfit stays open; the gain is
     purely legibility at small sizes. Figures stay monospaced — tabular
     numerals are the whole reason a stats table is readable. */
  --cond: "Barlow Condensed", "Arial Narrow", sans-serif;
  --body: "Outfit", system-ui, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, Menlo, monospace;

  /* Square corners were a rule; now they are a default with exceptions. 4px
     is small enough that the page still reads as ruled rather than carded,
     and it takes the hard edge off chips, thumbnails and controls. */
  /* Softened from the original 0, then from 4px. A 6px corner still reads as
     ruled rather than carded at table scale, and takes the hard edge off the
     larger blocks. */
  /* 2026-09-24: a scale instead of nine literal radii. Chips take the small
     one, controls the middle, cards and panels the large. */
  --r-sm: 4px;
  --r: 8px;
  --r-lg: 12px;

  /* Five type sizes (docs/ux-proposals-2026-09-24.md, P4). There were 23,
     twelve of them between 9px and 13px: density without hierarchy. Nothing
     is set below --fs-1 except the single-glyph info marker. The home hero's
     clamp() display sizes sit outside the scale on purpose. */
  --fs-1: 11px;    /* labels, counts, footnotes */
  --fs-2: 12.5px;  /* table figures, secondary text */
  --fs-3: 14px;    /* body, controls, names */
  --fs-4: 18px;    /* sub-headings, stat values */
  --fs-5: 28px;    /* page headings */

  /* Category accents. These mark which family a figure belongs to, never its
     value — win rate is still the only thing whose colour encodes a number. */
  --acc-amber:  #C8933C;
  --acc-violet: #9B7BD4;

  /* Depth and hairlines, as tokens rather than as literals.
     A shadow that works on #0B0D12 is invisible on #F7F8FA, and a hairline
     drawn in white-at-3.5% disappears entirely — these are the two things a
     theme has to flip that are not in the palette. */
  --shadow-1: 0 10px 26px rgba(0,0,0,.5);
  --shadow-2: 0 14px 34px rgba(0,0,0,.55);
  --shadow-3: 0 24px 80px rgba(0,0,0,.65);
  --hairline: rgba(255,255,255,0.035);
  --hairline-2: rgba(255,255,255,0.05);
  --faint: rgba(255,255,255,0.016);
  --scrim: rgba(4,6,10,.78);
  --lift: rgba(255,255,255,0.07);

  /* A type chip is the game's own colour. On a dark ground it is that colour
     mixed toward black with white text; on a light one the same mix would
     turn every one of the seventeen into the same near-black pill, so it
     inverts — a pale wash of the colour with the colour itself as the ink. */
  --chip-base: #000;
  --chip-fill: 26%;
  --chip-edge: 55%;
  --chip-ink: #fff;

  /* The six tier bands. S is hottest, F is grey, and the ramp between them
     is the only other place on the site where colour carries an ordering. */
  --band-s: #E0574B;
  --band-a: #E08B3E;
  --band-b: #D5B63F;
  --band-c: #6FA86B;
  --band-d: #4C89A8;
  --band-f: #9299A5;

  /* First, second, third — used by every placing badge on the site. */
  --medal-gold:   #E7B95B;
  --medal-silver: #B8C2CC;
  --medal-bronze: #C98C5C;

  color-scheme: dark;
}

/**
 * Light mode.
 *
 * Two selectors for the same block: the media query is what someone who has
 * never touched the switch gets, and the attribute is what the switch sets.
 * `:root:not([data-theme="dark"])` is the part that matters — without it,
 * choosing dark on a machine set to light would be overridden by the system.
 *
 * The accent had to move. #35C6E0 on white is about 1.9:1, which is not a
 * colour you can read a word in; the same hue at #0B7489 clears 4.5:1 and
 * still reads as the site's cyan beside the dark one. Good and bad moved for
 * the same reason, and the win-rate scale is the one thing on the site whose
 * colour carries a number, so it could not be left to chance.
 */
:root[data-theme="light"] {
  --bg:    #F6F8FB;
  --panel: #FFFFFF;
  --row:   #EDF1F6;
  --rule:  #D5DCE6;
  --ink:   #11161F;
  --mute:  #556072;
  --dim:   #7C8798;
  --gold:  #0B7489;
  --good:  #1F6B5B;
  --bad:   #A23A2E;

  --acc-amber:  #8A6113;
  --acc-violet: #5E42A0;

  --shadow-1: 0 8px 20px rgba(17,22,31,.10);
  --shadow-2: 0 12px 28px rgba(17,22,31,.13);
  --shadow-3: 0 20px 60px rgba(17,22,31,.18);
  --hairline: rgba(17,22,31,0.06);
  --hairline-2: rgba(17,22,31,0.08);
  --faint: rgba(17,22,31,0.022);
  --scrim: rgba(28,34,45,.42);
  --lift: rgba(17,22,31,0.05);

  --chip-base: #fff;
  --chip-fill: 15%;
  --chip-edge: 42%;
  --chip-ink: color-mix(in srgb, var(--c, #555) 74%, #000);

  /* Every band darkened to clear 4.5:1 on the light ground while keeping its
     hue — the ramp is an ordering, so it has to survive the move intact. */
  --band-s: #B83C31;
  --band-a: #9A5C0C;
  --band-b: #7E6A0A;
  --band-c: #3D7A39;
  --band-d: #2C6285;
  --band-f: #5D6472;

  --medal-gold:   #8A6113;
  --medal-silver: #5E6873;
  --medal-bronze: #8A5326;

  --dim:   #616B7A;

  color-scheme: light;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
  --bg:    #F6F8FB;
  --panel: #FFFFFF;
  --row:   #EDF1F6;
  --rule:  #D5DCE6;
  --ink:   #11161F;
  --mute:  #556072;
  --dim:   #7C8798;
  --gold:  #0B7489;
  --good:  #1F6B5B;
  --bad:   #A23A2E;

  --acc-amber:  #8A6113;
  --acc-violet: #5E42A0;

  --shadow-1: 0 8px 20px rgba(17,22,31,.10);
  --shadow-2: 0 12px 28px rgba(17,22,31,.13);
  --shadow-3: 0 20px 60px rgba(17,22,31,.18);
  --hairline: rgba(17,22,31,0.06);
  --hairline-2: rgba(17,22,31,0.08);
  --faint: rgba(17,22,31,0.022);
  --scrim: rgba(28,34,45,.42);
  --lift: rgba(17,22,31,0.05);

  --chip-base: #fff;
  --chip-fill: 15%;
  --chip-edge: 42%;
  --chip-ink: color-mix(in srgb, var(--c, #555) 74%, #000);

  /* Every band darkened to clear 4.5:1 on the light ground while keeping its
     hue — the ramp is an ordering, so it has to survive the move intact. */
  --band-s: #B83C31;
  --band-a: #9A5C0C;
  --band-b: #7E6A0A;
  --band-c: #3D7A39;
  --band-d: #2C6285;
  --band-f: #5D6472;

  --medal-gold:   #8A6113;
  --medal-silver: #5E6873;
  --medal-bronze: #8A5326;

  --dim:   #616B7A;

  color-scheme: light;
}
}

* { box-sizing: border-box; margin: 0; padding: 0; }
[hidden] { display: none !important; }

/* iOS inflates text in landscape unless told not to, which breaks a layout
   built on fixed column widths and tabular figures. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--body);
  font-size: var(--fs-3);
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }


/* ---------- corners ----------
   Applied by role, not globally. Anything that is an object in its own hand —
   a chip, a thumbnail, a control, a floating panel — gets the 4px; the table
   rules and page structure stay square, which is what keeps the page reading
   as ruled rather than carded. */

.chip, .thumb, .orb, .res, .form i, .lineup-ball, .owner-badge,
.legend-swatch, .tl-item, .tl-card, .omni-results, .omni input,
.gate input, .gate button, .pill, .depth-warn, .banner,
.ball-hero > img, .player-hero > img, .strip, .kpi,
.conf-track, .conf-fill, .meter-track, .meter-fill,
.tl-pos, .tl-pos::after { border-radius: var(--r); }

/* Pill-ish things that read better fully round. */
.conf-track, .conf-fill, .tl-pos, .tl-pos::after { border-radius: var(--r-sm); }
.orb { border-radius: var(--r-sm); }

/* Controls keep their square seating in the chrome strip, since they butt
   against each other and a radius would leave gaps between them. */
.subnav button, .leagues button, .nav a { border-radius: 0; }

/* ---------- masthead ---------- */

.top {
  display: flex; align-items: stretch; gap: 0;
  border-bottom: 1px solid var(--rule); background: var(--panel);
  position: sticky; top: 0; z-index: 40;
}
.brand {
  font-family: var(--cond); font-weight: 700; font-size: var(--fs-4);
  letter-spacing: 0.08em; text-transform: uppercase;
  padding: 12px var(--gutter); border-right: 1px solid var(--rule);
  display: flex; align-items: center; white-space: nowrap;
}
.brand b { color: var(--gold); font-weight: 700; }

/* Every sideways scroller contains its overscroll. Without it, running out of
   room inside one hands the gesture to the page, and on iOS a leftward flick
   that reaches the end becomes a back-swipe — the reader loses the page rather
   than the row. */
.nav { display: flex; overflow-x: auto; overscroll-behavior-x: contain; }
.nav a {
  font-family: var(--cond); font-weight: 600; font-size: var(--fs-3);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--mute);
  padding: 12px 18px; border-right: 1px solid var(--rule);
  display: flex; align-items: center; white-space: nowrap;
}
.nav a:hover { color: var(--ink); background: var(--row); }
.nav a[aria-current="page"] { color: var(--gold); box-shadow: inset 0 -2px 0 var(--gold); }

.stamp {
  margin-left: auto; font-family: var(--mono); font-size: var(--fs-1); color: var(--dim);
  padding: 12px 18px; border-left: 1px solid var(--rule);
  display: flex; align-items: center; white-space: nowrap;
}

/* ---------- chrome: sub-tabs and league picker ---------- */

/* Drawn only where a route asks for it, so pages that filter nothing are bare. */
.chrome {
  display: flex; align-items: stretch; flex-wrap: wrap;
  border-bottom: 1px solid var(--rule); background: var(--row);
}
.chrome.is-tierlist { display: block; }
.tl-view-axes, .tl-scope {
  display: flex; align-items: stretch; flex-wrap: wrap;
}
.tl-view-axes { background: var(--row); }
.tl-scope {
  border-top: 1px solid var(--rule); background: var(--panel);
}
.tl-view-axes .subnav { border-right: 1px solid var(--rule); }
.tl-view-axes .subnav:last-child { border-right: 0; }
.tl-tier-choose { display: flex; align-items: center; gap: 4px; }
.tl-tier-choose select {
  margin: 5px 12px 5px 0; padding: 6px 28px 6px 9px;
  min-width: 138px; border: 1px solid var(--rule); border-radius: var(--r);
  background: var(--row); color: var(--ink); font: var(--fs-2) var(--body);
  cursor: pointer;
}
.tl-tier-choose select:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
@media (pointer: coarse) {
  .tl-tier-choose select { font-size: 16px; min-height: 40px; }
}

.subnav { display: flex; border-right: 1px solid var(--rule); }
.subnav button {
  font-family: var(--cond); font-weight: 600; font-size: var(--fs-2); letter-spacing: 0.07em;
  text-transform: uppercase; background: none; border: 0; color: var(--mute);
  padding: 10px 15px; cursor: pointer; border-right: 1px solid var(--rule);
}
.subnav button:last-child { border-right: 0; }
.subnav button:hover { color: var(--ink); }
.subnav button[aria-pressed="true"] { color: var(--gold); box-shadow: inset 0 -2px 0 var(--gold); }

.leagues { display: flex; align-items: center; }
.flabel {
  font-family: var(--cond); font-size: var(--fs-2); font-weight: 700; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink); padding: 0 10px 0 15px;
  border-left: 1px solid var(--rule); align-self: stretch; display: flex; align-items: center;
}
.leagues button {
  font-family: var(--cond); font-weight: 600; font-size: var(--fs-2); letter-spacing: 0.05em;
  text-transform: uppercase; background: none; border: 0; color: var(--mute);
  padding: 9px 11px; cursor: pointer; border-left: 1px solid var(--rule);
}
.leagues button:hover { color: var(--ink); }
.leagues button[aria-pressed="true"] { color: var(--bg); background: var(--gold); }

/* ---------- omnibox ---------- */

.omni { position: relative; margin-left: auto; border-left: 1px solid var(--rule); }
.omni input {
  background: none; border: 0; color: var(--ink); font-family: var(--body);
  font-size: var(--fs-2); padding: 13px 16px; width: 250px;
}
.omni input::placeholder { color: var(--dim); }
.omni input:focus { outline: none; background: var(--row); }

.omni-results {
  /* 330px is what the results want; on a 320px phone it is more than there is.
     Clamped to the viewport less a gutter, so the panel narrows instead of
     hanging off the edge and dragging the page sideways with it. */
  position: absolute; top: 100%; right: 0; z-index: 60;
  min-width: min(330px, calc(100vw - 16px));
  max-width: calc(100vw - 16px);
  background: var(--panel); border: 1px solid var(--rule); border-top: 0;
  max-height: 380px; overflow-y: auto; overscroll-behavior: contain;
  box-shadow: var(--shadow-2);
}
.omni-row {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  background: none; border: 0; border-bottom: 1px solid var(--rule);
  padding: 8px 12px; cursor: pointer; color: var(--ink); font-family: var(--body);
}
.omni-row:last-child { border-bottom: 0; }
.omni-row:hover, .omni-row.is-active { background: var(--row); }
.omni-row /* Not yet requested: keep the box, hide the alt text. */
img[data-src]:not([src]) { visibility: hidden; }

.thumb { width: 26px; height: 26px; }
.omni-name { flex: 1; font-size: var(--fs-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.omni-kind {
  font-family: var(--cond); font-size: var(--fs-1); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--dim); border: 1px solid var(--rule); padding: 2px 5px;
}
.omni-empty { padding: 14px 12px; color: var(--dim); font-size: var(--fs-2); margin: 0; }

/* ---------- section titles ---------- */

/* An h2 now rather than a div, so the page has an outline a screen reader can
   move through. The reset keeps it looking exactly as it did: the size and
   weight live on .st-label, not here. */
.section-title {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin: 0; font: inherit; font-weight: 400;
  padding: 16px var(--gutter) 8px;
}
.st-label {
  font-family: var(--cond); font-size: var(--fs-2); font-weight: 700; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink);
}
.st-hint { font-family: var(--mono); font-size: var(--fs-1); color: var(--dim); }

thead th.nosort { cursor: default; }

/* ---------- tables ---------- */

.wrap { overflow-x: auto; overscroll-behavior-x: contain; }
/* Space consecutive tables so one never reads as a continuation of the last. */
.wrap + .note, table + .note { margin-top: 4px; }

table { border-collapse: collapse; width: 100%; min-width: 720px; }

/* Not sticky. `.wrap` scrolls horizontally, which makes it the scroll
   container for BOTH axes, so a sticky header pins against the page instead
   and paints over the rows above it. A header that scrolls away beats one
   that covers data. */
thead th {
  background: var(--panel); border-bottom: 1px solid var(--rule);
  border-top: 1px solid var(--rule);
  font-family: var(--body); font-weight: 700; font-size: var(--fs-1);
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--mute);
  padding: 9px 12px; text-align: right; white-space: nowrap; cursor: pointer;
  user-select: none;
}
thead th:first-child, thead th.left { text-align: left; }
thead th:hover { color: var(--ink); }
thead th[data-dir]::after { content: " ▾"; color: var(--gold); }
thead th[data-dir="asc"]::after { content: " ▴"; color: var(--gold); }

/* Outfit sets larger on the body than Barlow did at the same px, so the sizes
   come down a notch to hold the row height. Density is the aesthetic; a
   friendlier typeface is not worth losing four rows a screen for. */
tbody td {
  border-bottom: 1px solid var(--hairline); padding: 6px 12px;
  line-height: 1.35; text-align: right; font-family: var(--mono); font-size: var(--fs-2);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
tbody td:first-child, tbody td.left { text-align: left; font-family: var(--body); font-size: var(--fs-3); }
tbody tr:hover td { background: var(--panel); }
tbody tr.thin td { color: var(--dim); }
tbody tr.thin td:first-child { color: var(--dim); }

.rank { color: var(--dim); font-family: var(--mono); font-size: var(--fs-1); width: 1%; }

/* Type glyphs sit inline with the name, never on their own line. */
.types { display: inline-flex; gap: 3px; vertical-align: -4px; margin-right: 7px; }
.types img { width: 17px; height: 17px; display: block; }

.name { font-weight: 500; }
.sub { color: var(--dim); font-size: var(--fs-1); font-family: var(--mono); margin-left: 7px; }

/* Win rate is the only coloured figure. */
.wr { font-weight: 600; }
.wr.hi { color: var(--good); }
.wr.lo { color: var(--bad); }

/* ---- hierarchy ----
   Seven columns of equal weight is seven columns nobody reads. One column is
   the answer, two or three support it, and the rest are context. */

/* The spanning header row that turns three adjacent win rates into one
   measurement taken three ways. */
thead tr.group-row th {
  border-bottom: 0; padding: 9px 12px 2px; font-size: var(--fs-1); font-weight: 700;
  letter-spacing: 0.16em; color: var(--dim); text-align: center;
  cursor: default; background: var(--panel);
}
thead tr.group-row th.group-blank { border-top-color: transparent; }
thead tr.group-row th:not(.group-blank) {
  border-left: 1px solid var(--rule); border-right: 1px solid var(--rule);
}
thead tr.group-row + tr th { border-top: 0; padding-top: 4px; }

thead th.support { color: var(--dim); font-size: var(--fs-1); }
thead th.primary { color: var(--ink); }

/* Supporting figures step back so the ranked one can step forward. */
tbody td.support { color: var(--dim); font-size: var(--fs-1); }

/* The ranked figure: the raw rate, with the bound the sort actually uses
   underneath it. That relationship was correct and completely invisible. */
.wr-primary { padding-top: 4px; padding-bottom: 4px; }
.wr-primary b {
  display: block; font-size: var(--fs-3); font-weight: 600; line-height: 1.15;
  font-variant-numeric: tabular-nums; color: var(--mute);
}
.wr-primary b.hi { color: var(--good); }
.wr-primary b.lo { color: var(--bad); }
.wr-bound {
  display: block; font-size: var(--fs-1); color: var(--dim); line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

/* ---- hover that surfaces detail ----
   Row hover used to do nothing but tint. These lines are rendered into the
   cell and reserved for at all times, so revealing them costs no reflow and
   the row never jumps under the cursor. */

.cell-detail {
  display: block; font-size: var(--fs-1); line-height: 1.1; color: var(--dim);
  font-variant-numeric: tabular-nums;
  opacity: 0; transition: opacity 110ms linear;
}
tbody tr:hover .cell-detail, tbody tr:focus-within .cell-detail { opacity: 1; }

.wr-side { padding-top: 4px; padding-bottom: 4px; }
.wr-side b {
  display: block; font-weight: 600; line-height: 1.15;
  font-variant-numeric: tabular-nums; color: var(--mute);
}
.wr-side b.hi { color: var(--good); }
.wr-side b.lo { color: var(--bad); }

/* Hovering a row lifts the ranked figure out of the page slightly, so the eye
   lands on the column the table is actually sorted by. */
tbody tr:hover .wr-primary b { color: var(--ink); }
tbody tr:hover .wr-primary b.hi { color: var(--good); }
tbody tr:hover .wr-primary b.lo { color: var(--bad); }
tbody tr:hover .conf-fill { opacity: 1; }

/* Sample size drawn as confidence. A count makes the reader do the statistics;
   the bar is the interval width, inverted, so long means pinned down. */
.conf { padding-top: 4px; padding-bottom: 4px; }
.conf-box { display: inline-grid; justify-items: end; gap: 1px; min-width: 62px; }
.conf-box b {
  font-size: var(--fs-2); font-weight: 500; font-variant-numeric: tabular-nums;
  line-height: 1.15; color: var(--ink);
}
.conf-pm {
  font-size: var(--fs-1); color: var(--dim); line-height: 1.1;
  transition: opacity 110ms linear;
}
/* The ± and the raw split share one slot, so revealing the split costs no
   height and the confidence bar below it never moves. */
.conf-swap { display: grid; }
.conf-swap > * { grid-area: 1 / 1; }
tbody tr:hover .conf-pm { opacity: 0; }
.conf-track {
  width: 100%; height: 2px; background: var(--rule); display: block; margin-top: 2px;
}
.conf-fill { display: block; height: 100%; background: var(--mute); opacity: 0.85; }
tbody tr.thin .conf-box b { color: var(--dim); }

/* ---- info marker ----
   One glyph where a rule changes how a figure should be read. The account
   itself is on #/method; this is the reminder, not the lecture. */

.info {
  display: inline-grid; place-items: center; width: 14px; height: 14px;
  margin-left: 6px; flex-shrink: 0; vertical-align: 1px;
  border: 1px solid var(--rule); border-radius: 50%;
  font-family: var(--mono); font-size: 9px; font-style: italic; /* the one exception to the type scale: a single glyph in a 14px ring */
  color: var(--dim); text-decoration: none; cursor: help; line-height: 1;
  background: transparent; padding: 0;
}
.info:hover, .info:focus-visible { color: var(--gold); border-color: var(--gold); }
thead th .info { vertical-align: 0; }

.method-dialog {
  /* A modal <dialog> is centred by the UA stylesheet's `margin: auto`, which
     the `* { margin: 0 }` reset at the top of this file silently removes —
     leaving it pinned to the top-left. Restoring it here is the whole fix. */
  margin: auto;
  width: min(600px, calc(100vw - 28px)); max-height: min(78vh, 720px);
  padding: 0; border: 1px solid var(--gold); border-radius: var(--r);
  background: var(--panel); color: var(--ink);
  box-shadow: var(--shadow-3);
}
.method-dialog::backdrop { background: var(--scrim); }
.method-dialog-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 14px var(--gutter); border-bottom: 1px solid var(--rule);
}
.method-dialog-head h2 {
  font: 700 var(--fs-4)/1.2 var(--cond); text-transform: uppercase; letter-spacing: .05em;
}
.method-dialog-close {
  background: transparent; border: 1px solid var(--rule); color: var(--mute);
  padding: 6px 10px; cursor: pointer; font: var(--fs-1) var(--mono);
}
.method-dialog-close:hover, .method-dialog-close:focus-visible {
  border-color: var(--gold); color: var(--gold);
}
.method-dialog-body { overflow: auto; max-height: calc(min(78vh, 720px) - 58px); }
.method-dialog-context {
  padding: 18px var(--gutter); font: var(--fs-2)/1.65 var(--body); border-bottom: 1px solid var(--rule);
  /* An explanation may carry a paragraph break; keep it. */
  white-space: pre-line;
}
.method-dialog-full summary {
  padding: 13px var(--gutter); cursor: pointer; color: var(--gold);
  font: 700 var(--fs-2) var(--cond); letter-spacing: .08em; text-transform: uppercase;
}
.method-dialog-sections { border-top: 1px solid var(--rule); }
.method-dialog-section { padding: 16px var(--gutter); border-bottom: 1px solid var(--rule); }
.method-dialog-section h3 {
  color: var(--gold); font: 700 var(--fs-2) var(--cond); text-transform: uppercase;
  letter-spacing: .1em; margin-bottom: 14px;
}
.method-dialog-section h4 { font: 600 var(--fs-2) var(--body); margin: 12px 0 3px; }
.method-dialog-section p { color: var(--mute); font: var(--fs-1)/1.65 var(--mono); }

/* A hairline bar behind pick rate, so scale reads without a chart. */
.bar { position: relative; }
.bar::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: var(--w, 0%); background: var(--rule);
}
.bar span { position: relative; }

/* ---------- matchup grid ---------- */

.grid { border-collapse: collapse; font-family: var(--mono); font-size: var(--fs-1); }
.grid th {
  position: static; background: var(--panel); color: var(--mute);
  font-family: var(--cond); font-size: var(--fs-1); letter-spacing: 0.08em;
  padding: 4px 5px; border: 1px solid var(--rule); text-transform: uppercase;
}
.grid th.rowhead { text-align: left; white-space: nowrap; position: sticky; left: 0; z-index: 10; }
.grid td {
  border: 1px solid var(--rule); padding: 4px 5px; text-align: center;
  font-variant-numeric: tabular-nums; min-width: 42px;
}
.stat-primer {
  display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
  padding: 8px var(--gutter); border-bottom: 1px solid var(--rule);
  color: var(--mute); font: var(--fs-1)/1.5 var(--body);
}
.stat-primer b {
  color: var(--gold); font: 700 var(--fs-1) var(--cond);
  letter-spacing: 0.12em; text-transform: uppercase;
}
.matchup-explorer { border-bottom: 1px solid var(--rule); }
.matchup-head {
  display: flex; align-items: end; justify-content: space-between; gap: 18px;
  padding: 18px var(--gutter); flex-wrap: wrap; border-bottom: 1px solid var(--rule);
}
.matchup-head h2 { font: 700 var(--fs-5) var(--cond); text-transform: uppercase; letter-spacing: 0.04em; }
.matchup-head p { color: var(--mute); font-size: var(--fs-2); margin-top: 4px; }
.matchup-choose { display: grid; gap: 5px; min-width: min(300px, 100%); }
.matchup-choose span { color: var(--dim); font: 700 var(--fs-1) var(--cond); text-transform: uppercase; letter-spacing: 0.12em; }
.matchup-choose select {
  width: 100%; min-height: 38px; padding: 7px 10px; background: var(--panel);
  color: var(--ink); border: 1px solid var(--rule); border-radius: var(--r);
  font: var(--fs-2) var(--body);
}
.matchup-summary { display: flex; gap: 12px; align-items: baseline; flex-wrap: wrap;
  padding: 10px var(--gutter); border-bottom: 1px solid var(--rule); }
.matchup-summary b { color: var(--ink); font-weight: 600; }
.matchup-summary span { color: var(--dim); font: var(--fs-1) var(--mono); }
.matchup-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; background: var(--rule); }
.matchup-list section { background: var(--bg); min-width: 0; }
.matchup-list h3, .matchup-balls h4 { padding: 9px var(--gutter); color: var(--dim);
  font: 700 var(--fs-1) var(--cond); letter-spacing: 0.12em; text-transform: uppercase; }
.matchup-result {
  width: 100%; display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 12px; align-items: center; text-align: left; padding: 8px var(--gutter);
  color: var(--ink); background: var(--bg); border: 0; border-top: 1px solid var(--rule);
  font: var(--fs-2) var(--body); cursor: pointer;
}
.matchup-result:hover, .matchup-result:focus-visible { background: var(--panel); outline: none; }
.matchup-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.matchup-result b { font: 600 var(--fs-2) var(--mono); }
.matchup-games { color: var(--dim); font: var(--fs-1) var(--mono); min-width: 86px; text-align: right; }
.matchup-pair { border-top: 1px solid var(--rule); }
.matchup-pair-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; padding: 13px var(--gutter) 4px; }
.matchup-pair-head h3 { font: 700 var(--fs-4) var(--cond); text-transform: uppercase; }
.matchup-pair-head span, .matchup-pair-note, .matchup-none { color: var(--dim); font: var(--fs-1)/1.5 var(--mono); }
.matchup-pair-note { padding: 0 var(--gutter) 10px; }
.matchup-none { padding: 12px var(--gutter); }
.matchup-balls { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; background: var(--rule); }
.matchup-balls section { background: var(--bg); min-width: 0; }
.matchup-balls a { display: flex; gap: 9px; align-items: center; padding: 5px var(--gutter);
  border-top: 1px solid var(--rule); color: var(--ink); font-size: var(--fs-2); }
.matchup-balls a:hover { background: var(--panel); }
.matchup-balls .thumb { width: 26px; height: 26px; }
.matchup-balls a span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.matchup-balls a i { color: var(--dim); font: normal var(--fs-1) var(--mono); }
.matchup-full summary { padding: 12px var(--gutter); color: var(--mute); cursor: pointer;
  font: 700 var(--fs-1) var(--cond); letter-spacing: 0.12em; text-transform: uppercase; }
.matchup-full summary:hover { color: var(--ink); }
@media (max-width: 640px) {
  .matchup-list, .matchup-balls { grid-template-columns: minmax(0, 1fr); }
  .matchup-result { gap: 7px; }
  .matchup-games { min-width: 64px; }
}

/* ---------- pvp calculator ---------- */

.pvp-gate { padding: 30px var(--gutter); display: grid; gap: 13px; justify-items: start; max-width: 66ch; }
.pvp-gate h2 { font: 700 var(--fs-5) var(--cond); text-transform: uppercase; letter-spacing: 0.04em; }
.pvp-gate-lede { color: var(--mute); font: var(--fs-2)/1.65 var(--body); }
.pvp-gate-why { color: var(--ink); font: var(--fs-2) var(--body); }
.pvp-demo { width: 100%; display: grid; gap: 1px; background: var(--rule);
  border: 1px solid var(--rule); border-radius: var(--r); overflow: hidden; }
.pvp-demo-label { background: var(--panel); padding: 7px 12px; color: var(--dim);
  font: 700 var(--fs-1) var(--cond); letter-spacing: 0.12em; text-transform: uppercase; }
.pvp-demo-row { display: grid; grid-template-columns: 26px minmax(0, 1fr) 150px;
  gap: 12px; align-items: center; background: var(--bg); padding: 9px 12px; }
.pvp-demo-slot { font: 700 var(--fs-3) var(--mono); color: var(--dim); }
.pvp-demo-bar { height: 8px; border-radius: 999px; background: var(--rule); }
.pvp-demo-row.r2 .pvp-demo-bar { background: linear-gradient(90deg, var(--good), transparent 78%); }
.pvp-demo-row.r0 .pvp-demo-bar { background: linear-gradient(90deg, var(--mute), transparent 52%); }
.pvp-demo-row.rm1 .pvp-demo-bar { background: linear-gradient(90deg, var(--bad), transparent 40%); }
.pvp-demo-row.r2 .pvp-verdict b { color: var(--good); }
.pvp-demo-row.r0 .pvp-verdict b { color: var(--mute); }
.pvp-demo-row.rm1 .pvp-verdict b { color: var(--bad); }

/* ---- the steps ---- */
.pvp-steps { display: grid; }
.pvp-step { border-bottom: 1px solid var(--rule); }
/* A step that cannot be answered yet is shown and dimmed, not removed.
   Hiding it took the box off the page, so a step going back to unanswered
   left the numbering reading 1, 2, 4. */
.pvp-step.is-waiting { opacity: .42; }
.pvp-step.is-waiting .pvp-step-n { border-style: dashed; }
.pvp-step-sum.is-waiting { color: var(--dim); font-style: italic; }
.pvp-step-head { display: flex; gap: 10px; align-items: center; padding: 13px var(--gutter); }
.pvp-step-n {
  width: 22px; height: 22px; flex: none; display: grid; place-items: center;
  border-radius: 50%; background: var(--panel); border: 1px solid var(--rule);
  color: var(--dim); font: 700 var(--fs-1) var(--mono);
}
.pvp-step.is-current .pvp-step-n { background: var(--gold); border-color: var(--gold); color: var(--bg); }
.pvp-step.is-done .pvp-step-n { color: var(--good); border-color: var(--good); }
.pvp-step-head h3 { font: 700 var(--fs-2) var(--cond); letter-spacing: 0.12em; text-transform: uppercase; }
.pvp-step.is-done .pvp-step-head h3 { color: var(--mute); }
.pvp-step-sum { flex: 1; min-width: 0; color: var(--ink); font: var(--fs-2) var(--body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pvp-step-edit { margin-left: auto; padding: 3px 9px; background: transparent; color: var(--mute);
  border: 1px solid var(--rule); border-radius: var(--r); font: var(--fs-1) var(--body); cursor: pointer; }
.pvp-step-edit:hover { color: var(--ink); border-color: var(--mute); }
.pvp-step-body { display: grid; gap: 11px; justify-items: start; padding: 0 var(--gutter) 16px; }
.pvp-hint { color: var(--dim); font: var(--fs-1)/1.6 var(--body); max-width: 70ch; }

.pvp-inline-form { display: flex; gap: 7px; width: min(420px, 100%); }
.pvp-inline-form input {
  flex: 1; min-width: 0; min-height: 36px; padding: 7px 10px; background: var(--panel);
  color: var(--ink); border: 1px solid var(--rule); border-radius: var(--r); font: var(--fs-2) var(--body);
}
.pvp-choices { display: flex; gap: 9px; flex-wrap: wrap; }
.pvp-choice {
  display: grid; gap: 2px; text-align: left; padding: 10px 13px; min-width: 150px;
  background: var(--panel); color: var(--ink); border: 1px solid var(--rule);
  border-radius: var(--r); cursor: pointer;
}
.pvp-choice:hover { border-color: var(--mute); }
.pvp-choice.is-on { border-color: var(--gold); background: color-mix(in srgb, var(--gold) 10%, transparent); }
.pvp-choice b { font: 700 var(--fs-2) var(--cond); letter-spacing: 0.08em; text-transform: uppercase; }
.pvp-choice small { color: var(--dim); font: var(--fs-1) var(--body); }
.pvp-choices.is-tight .pvp-choice { min-width: 116px; }

.pvp-who { display: flex; gap: 8px; align-items: center; min-width: 0; }
.pvp-who .thumb { width: 28px; height: 28px; flex: none; border-radius: 50%; }
.pvp-who.is-big .thumb { width: 40px; height: 40px; }
.pvp-who-text { display: grid; gap: 1px; min-width: 0; }
.pvp-who-text b { font: 600 var(--fs-2) var(--body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pvp-who-text small { color: var(--dim); font: var(--fs-1) var(--mono); }

.pvp-ladder { width: 100%; display: grid; gap: 1px; background: var(--rule);
  border: 1px solid var(--rule); border-radius: var(--r); max-height: 340px; overflow-y: auto; }
.pvp-ladder-row { display: flex; gap: 10px; align-items: center; text-align: left;
  padding: 7px 11px; background: var(--bg); border: 0; cursor: pointer; }
.pvp-ladder-row:hover { background: var(--panel); }
.pvp-ladder-row .div-crest { width: 26px; height: 18px; flex: none; }
.pvp-ladder-row .pvp-who { flex: 1; }
.pvp-ladder-sort { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.pvp-choice.is-small { min-width: 0; padding: 6px 10px; font: 600 var(--fs-1) var(--body); }
.pvp-choice:disabled { opacity: 0.5; cursor: default; }

/* Your chance against one defender, on the ladder. */
.pvp-odds { margin-left: auto; flex: none; min-width: 44px; padding: 3px 7px; text-align: center;
  border-radius: 999px; font: 700 var(--fs-2) var(--mono); border: 1px solid currentColor; }
.pvp-odds.is-good { color: var(--good); background: color-mix(in srgb, var(--good) 12%, transparent); }
.pvp-odds.is-even { color: var(--mute); }
.pvp-odds.is-bad { color: var(--bad); background: color-mix(in srgb, var(--bad) 9%, transparent); }
.pvp-odds.is-rough { border-style: dashed; }
.pvp-odds.is-none { color: var(--dim); border-color: transparent; }

.pvp-reveal { width: 100%; display: grid; gap: 6px; }
.pvp-reveal-n { color: var(--dim); font: 700 var(--fs-1) var(--cond); letter-spacing: 0.12em; text-transform: uppercase; }
.pvp-reveal-picks { display: flex; gap: 7px; flex-wrap: wrap; }
.pvp-pick-ball {
  display: grid; gap: 2px; justify-items: center; width: 92px; padding: 7px 5px;
  background: var(--panel); color: var(--ink); border: 1px solid var(--rule);
  border-radius: var(--r); font: var(--fs-1) var(--body); cursor: pointer; text-align: center;
}
.pvp-pick-ball:hover { border-color: var(--mute); }
.pvp-pick-ball.is-on { border-color: var(--gold); background: color-mix(in srgb, var(--gold) 12%, transparent); }
.pvp-pick-ball .thumb { width: 38px; height: 38px; }
.pvp-pick-ball span { width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pvp-pick-ball i { color: var(--dim); font: normal var(--fs-1) var(--mono); }

/* ---- rows ---- */
.pvp-pair { width: 100%; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px; background: var(--rule); border: 1px solid var(--rule); border-radius: var(--r); }
.pvp-side { background: var(--bg); padding: 11px 13px 14px; min-width: 0; }
.pvp-side-head { display: flex; gap: 10px; align-items: center; justify-content: space-between;
  flex-wrap: wrap; margin-bottom: 9px; }
.pvp-side-head h4 { font: 700 var(--fs-1) var(--cond); letter-spacing: 0.12em; text-transform: uppercase; color: var(--dim); }
.pvp-slots { display: grid; gap: 6px; }
/* One column. It was 30px and then the rest, from when the slot number sat
   inside the cell; the number moved to its own column on the board, and every
   ball tile was left squeezed into the 30px, its name cut to "Europ…". */
.pvp-slot { position: relative; display: grid; grid-template-columns: minmax(0, 1fr);
  gap: 9px; align-items: center; min-height: 50px; padding: 5px 28px 5px 7px;
  background: var(--panel); border: 1px solid var(--rule); border-radius: var(--r); }
.pvp-slot.is-over { border-color: var(--gold); background: color-mix(in srgb, var(--gold) 9%, transparent); }
.pvp-slot.is-hidden { border-style: dashed; }
.pvp-slot-head { display: grid; gap: 1px; justify-items: center; }
.pvp-slot-head b { font: 700 var(--fs-3) var(--mono); }
.pvp-slot-head i { color: var(--dim); font: normal var(--fs-1) var(--cond); letter-spacing: 0.08em; text-transform: uppercase; }
.pvp-slot.is-hidden .pvp-slot-head i { color: var(--acc-amber, var(--mute)); }
.pvp-slot-empty { color: var(--dim); font: var(--fs-1) var(--mono); }
/* Taking a ball out. It used to float at the far right of a wide cell,
   vertically centred in empty space, which read as a stray character rather
   than as a control belonging to the tile. It sits on the tile corner now and
   only comes forward when the slot is pointed at or something in it is
   focused — it is a correction, not the main action. */
.pvp-tile-off { position: absolute; top: 5px; right: 5px;
  width: 20px; height: 20px; padding: 0; background: var(--panel); color: var(--dim);
  border: 1px solid var(--rule); border-radius: 50%; font: var(--fs-1) var(--mono); cursor: pointer;
  opacity: 0; transition: opacity .14s var(--ease), color .14s var(--ease), border-color .14s var(--ease); }
.pvp-slot:hover .pvp-tile-off, .pvp-slot:focus-within .pvp-tile-off { opacity: 1; }
.pvp-tile-off:hover, .pvp-tile-off:focus-visible { opacity: 1; color: var(--bad); border-color: var(--bad); }
@media (hover: none) { .pvp-tile-off { opacity: 1; } }
/* touch-action keeps a tap from being read as the start of a scroll or a
   long-press selection, which is what a finger on a draggable tile got. */
.pvp-tile { display: flex; gap: 8px; align-items: center; min-width: 0; cursor: grab;
  user-select: none; -webkit-user-select: none; touch-action: manipulation;
  -webkit-touch-callout: none; }
.pvp-slot, .pvp-bench, .pvp-counters-btn { touch-action: manipulation; }
.pvp-tile.is-plain { cursor: default; }
.pvp-tile:active { cursor: grabbing; }
.pvp-tile.is-dragging { opacity: 0.4; }
.pvp-tile.is-held { outline: 2px solid var(--gold); outline-offset: 3px; border-radius: var(--r-sm); }
.pvp-tile .thumb { width: 32px; height: 32px; flex: none; }
.pvp-tile-text { display: grid; gap: 1px; min-width: 0; }
.pvp-tile-text b { font: 600 var(--fs-2) var(--body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pvp-tile-text small { color: var(--dim); font: var(--fs-1) var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pvp-tile.is-bench { padding: 5px 7px; background: var(--panel); border: 1px solid var(--rule); border-radius: var(--r); }
.pvp-tile.is-bench:hover { border-color: var(--mute); }
.pvp-bench-head { margin: 13px 0 6px; color: var(--dim); font: 700 var(--fs-1) var(--cond);
  letter-spacing: 0.12em; text-transform: uppercase; }
.pvp-bench { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 6px;
  min-height: 52px; padding: 6px; border: 1px dashed var(--rule); border-radius: var(--r); }
.pvp-bench.is-over { border-color: var(--gold); background: color-mix(in srgb, var(--gold) 6%, transparent); }
.pvp-bench-empty { grid-column: 1 / -1; color: var(--dim); font: var(--fs-1) var(--mono); padding: 4px; }
.pvp-actions { display: flex; gap: 9px; flex-wrap: wrap; }

/* ---- verdicts ---- */
.pvp-result { border-bottom: 1px solid var(--rule); }
.pvp-outlook { display: flex; gap: 12px; align-items: center; margin: 14px var(--gutter) 10px;
  padding: 12px 14px; border-radius: var(--r); border: 1px solid var(--rule); background: var(--panel); }
.pvp-outlook b { display: block; font: 700 var(--fs-4) var(--cond); letter-spacing: 0.06em; text-transform: uppercase; }
.pvp-outlook-chance { display: grid; justify-items: center; flex: none; padding-right: 12px;
  border-right: 1px solid var(--rule); }
.pvp-outlook .pvp-outlook-chance b { font: 700 var(--fs-5)/1 var(--mono); letter-spacing: 0; }
.pvp-outlook .pvp-outlook-chance small { font: 700 var(--fs-1) var(--cond); letter-spacing: 0.12em; text-transform: uppercase; color: var(--dim); }
.pvp-outlook small { color: var(--mute); font: var(--fs-1)/1.5 var(--body); }
.pvp-outlook.is-good { border-color: var(--good); }
.pvp-outlook.is-good b { color: var(--good); }
.pvp-outlook.is-bad { border-color: var(--bad); }
.pvp-outlook.is-bad b { color: var(--bad); }
.pvp-outlook.is-even b { color: var(--mute); }
.pvp-tip { display: flex; gap: 14px; align-items: center; justify-content: space-between;
  flex-wrap: wrap; margin: 0 var(--gutter) 10px; padding: 11px 13px;
  background: var(--panel); border: 1px solid var(--gold); border-radius: var(--r); }
.pvp-tip b { display: block; font: 600 var(--fs-2) var(--body); }
.pvp-tip small { color: var(--dim); font: var(--fs-1)/1.5 var(--mono); }
.pvp-nohope { margin: 0 var(--gutter) 10px; color: var(--mute); font: var(--fs-2)/1.6 var(--body); max-width: 70ch; }
.pvp-hidden-note { padding: 0 var(--gutter) 10px; color: var(--dim); font: var(--fs-1)/1.6 var(--body); max-width: 78ch; }
.pvp-hidden-note b { color: var(--acc-amber, var(--mute)); }
.pvp-out { display: grid; grid-template-columns: 30px minmax(0, 1fr) 132px minmax(0, 1fr);
  gap: 12px; align-items: center; padding: 9px var(--gutter); border-top: 1px solid var(--rule); }
.pvp-out.is-guess { background: var(--faint); }
.pvp-out-ball { min-width: 0; padding: 5px 7px; border: 1px solid transparent; border-radius: var(--r); }
.pvp-out-ball.is-winner { border-color: var(--good); background: rgba(62,142,126,0.12); }
.pvp-out-ball.is-loser { border-color: var(--bad); background: rgba(180,84,75,0.1); opacity: 0.78; }
.pvp-verdict { display: grid; gap: 1px; text-align: center; }
.pvp-verdict b { font: 700 var(--fs-2) var(--cond); letter-spacing: 0.08em; text-transform: uppercase; }
.pvp-verdict small { color: var(--dim); font: var(--fs-1) var(--mono); }
.pvp-out.r2 .pvp-verdict b, .pvp-out.r1 .pvp-verdict b { color: var(--good); }
.pvp-out.r0 .pvp-verdict b { color: var(--mute); }
.pvp-out.rm1 .pvp-verdict b, .pvp-out.rm2 .pvp-verdict b { color: var(--bad); }
.pvp-out.runk .pvp-verdict b { color: var(--dim); }
.pvp-assumptions summary { padding: 12px var(--gutter); color: var(--mute); cursor: pointer;
  font: 700 var(--fs-1) var(--cond); letter-spacing: 0.12em; text-transform: uppercase; }
.pvp-assumptions summary:hover { color: var(--ink); }
.pvp-assumptions ul { padding: 0 var(--gutter) 14px 34px; display: grid; gap: 5px; }
.pvp-assumptions li { color: var(--dim); font: var(--fs-1)/1.5 var(--body); }

@media (max-width: 860px) {
  .pvp-pair { grid-template-columns: minmax(0, 1fr); }
  .pvp-out { grid-template-columns: 30px minmax(0, 1fr); row-gap: 7px; }
  .pvp-out .pvp-verdict { grid-column: 2; text-align: left; }
  .pvp-demo-row { grid-template-columns: 22px minmax(0, 1fr); }
  .pvp-demo-row .pvp-verdict { grid-column: 2; text-align: left; }
  .pvp-bench { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }
  .pvp-choice { min-width: 0; flex: 1 1 140px; }
  .pvp-step-head { flex-wrap: wrap; }
  .pvp-step-sum { flex-basis: 100%; white-space: normal; }
}

/* ---------- player ---------- */

.player-head {
  display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap;
  padding: 18px var(--gutter); border-bottom: 1px solid var(--rule); background: var(--panel);
}
.player-head h1 { font-family: var(--cond); font-size: var(--fs-5); letter-spacing: 0.04em;
  text-transform: uppercase; font-weight: 700; }
.player-head .meta { font-family: var(--mono); font-size: var(--fs-2); color: var(--mute); }

.form { display: flex; gap: 3px; padding: 14px var(--gutter); flex-wrap: wrap; }
.form i {
  width: 20px; height: 20px; display: grid; place-items: center;
  font-family: var(--mono); font-size: var(--fs-1); font-style: normal;
  border: 1px solid var(--rule); color: var(--dim);
}
.form i.w { color: var(--good); border-color: var(--good); }
.form i.l { color: var(--bad); border-color: var(--bad); }

/* ---------- chrome ---------- */

.note {
  padding: 10px var(--gutter); font-size: var(--fs-2); color: var(--dim);
  border-bottom: 1px solid var(--rule); font-family: var(--mono);
}
.empty { padding: 40px var(--gutter); color: var(--mute); font-family: var(--mono); font-size: var(--fs-2); }

/* ---------- footer ---------- */

footer {
  border-top: 1px solid var(--rule); margin-top: 48px; padding: 16px var(--gutter) 22px;
  color: var(--dim); font-size: var(--fs-1); font-family: var(--mono); line-height: 1.7;
}
.foot-by { color: var(--mute); }
.foot-by strong { color: var(--ink); font-weight: 600; }
.foot-legal { margin-top: 2px; }
.foot-costs { margin-top: 6px; color: var(--mute); }
.foot-meta { margin-top: 8px; }
.foot-stats:empty { display: none; }
footer a { color: var(--mute); text-decoration: underline; text-underline-offset: 2px; }
footer a:hover { color: var(--gold); }
/* Screen readers only: read aloud, never drawn. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* Off-screen until a keyboard reaches it, then it sits over the masthead. */
.skip-link {
  position: absolute; left: 12px; top: -48px; z-index: 200;
  padding: 8px 14px; border-radius: var(--r);
  background: var(--gold); color: var(--bg); font: 600 var(--fs-2) var(--body); text-decoration: none;
}
.skip-link:focus { top: 10px; }
/* The page takes focus after a navigation so a screen reader starts there;
   it is a container, not a control, so it draws no ring. */
#view:focus { outline: none; }

/* Download badges, home page only. */
.foot-get { padding-bottom: 16px; margin-bottom: 14px; border-bottom: 1px solid var(--rule); }
.foot-get-title { font: 700 var(--fs-2) var(--cond); letter-spacing: .12em; text-transform: uppercase; color: var(--ink); margin: 0 0 10px; }
.foot-badges { display: flex; flex-wrap: wrap; gap: 10px; }
.foot-badges a { display: block; padding: 0; border-radius: var(--r); }
/* The badge art carries transparent margins (about 10px on App Store, 3px on
   Google Play at this height); pull each back so the drawn edge lines up
   with the text. */
.foot-badges a:first-child { margin-left: -10px; }
.foot-badges a:last-child { margin-left: -3px; }
.foot-badges a:hover { filter: brightness(1.2); }
.foot-badges img { display: block; width: auto; height: 40px; }
.foot-official { margin-top: 10px; color: var(--mute); }


/* Preview notice. Removed when ingestion moves to the official API. */
.preview {
  background: #06191E; border-bottom: 1px solid var(--rule);
  color: var(--gold); font-family: var(--mono); font-size: var(--fs-1);
  padding: 7px var(--gutter); letter-spacing: 0.01em;
}
.preview a { color: var(--gold); text-decoration: underline; text-underline-offset: 2px; }

/* ---------- attribute chips ---------- */

.chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 6px 2px 3px;
  background: color-mix(in srgb, var(--c, #555) var(--chip-fill), var(--chip-base));
  border: 1px solid color-mix(in srgb, var(--c, #555) var(--chip-edge), var(--chip-base));
  font-family: var(--body); font-size: var(--fs-1); font-weight: 600;
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--chip-ink);
  white-space: nowrap; line-height: 1.2;
}
.chip img { width: 15px; height: 15px; display: block; }
.chips { display: inline-flex; gap: 4px; vertical-align: -3px; }

/* ---------- art in tables ---------- */

.thumb {
  width: 34px; height: 34px; object-fit: contain; display: block; flex-shrink: 0;
  background: #0A0E0F; border: 1px solid var(--rule);
}
.cellflex { display: flex; align-items: center; gap: 10px; min-width: 0; }
/* Anything wordy in a table cell is cut with an ellipsis rather than pushed
   through the side of the cell. `.name` alone missed ball links, which carry
   player-set nicknames — "Trench Fighter Austro-Hungarian Empire" ran 114px
   past its cell on a phone, over whatever was next to it. */
.cellflex .name, .cellflex .ball-link, .cellflex > a.plain {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
tbody tr { transition: background 90ms linear; }
tbody td:first-child { padding-left: 12px; }

/* ---------- landing ---------- */

.hero { position: relative; overflow: hidden; border-bottom: 1px solid var(--rule); }
.hero-art {
  position: absolute; inset: 0; display: flex; align-items: center; gap: 34px;
  opacity: 0.17; pointer-events: none; will-change: transform;
  animation: drift 64s linear infinite;
}
.hero-art img { width: 88px; height: 88px; object-fit: contain; flex-shrink: 0; }
@keyframes drift { from { transform: translateX(0); } to { transform: translateX(-50%); } }

.hero-inner { position: relative; padding: 52px 24px 44px; max-width: 1080px; }
.hero h1 {
  font-family: var(--cond); font-weight: 700; text-transform: uppercase;
  font-size: clamp(38px, 8vw, 76px); line-height: 0.92; letter-spacing: 0.01em;
  color: var(--ink);
}
.hero h1 b { color: var(--gold); }
.hero-sub {
  font-family: var(--cond); font-size: clamp(15px, 2.4vw, 20px); font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--mute);
  margin-top: 14px; max-width: 44ch; line-height: 1.4;
}

/* top-of-meta strips */
.strips { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(270px, 100%), 1fr)); }
.strip { border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }
.strip h2 {
  font-family: var(--cond); font-size: var(--fs-1); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--dim); padding: 12px 16px;
  border-bottom: 1px solid var(--rule); background: var(--panel);
}
.home-context { padding: 9px var(--gutter); border-bottom: 1px solid var(--rule);
  color: var(--mute); font: var(--fs-1)/1.45 var(--mono); }
/* The rest of the site, for a reader who has not found the nav yet. */
.home-more { border-top: 1px solid var(--rule); padding: 18px var(--gutter) 24px; }
.home-more h2 {
  font: 700 var(--fs-1) var(--cond); letter-spacing: .12em; text-transform: uppercase;
  color: var(--mute); margin-bottom: 12px;
}
/* Borders on the cards, not a sheet behind them: auto-fit leaves empty cells
   on the last row, and a background showing through them reads as a stray
   panel — which is exactly how it looked with nine cards over seven columns. */
.home-more-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  border-top: 1px solid var(--rule); border-left: 1px solid var(--rule);
}
.home-more-card {
  display: flex; flex-direction: column; gap: 3px; padding: 12px 14px;
  background: var(--bg); color: inherit; text-decoration: none;
  border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
}
.home-more-card:hover { background: var(--panel); }
.home-more-card b { font: 600 var(--fs-3) var(--body); color: var(--ink); }
.home-more-card span { font: var(--fs-2)/1.45 var(--body); color: var(--mute); }

.strip ol { list-style: none; }
.strip li {
  display: flex; align-items: center; gap: 10px; padding: 8px 16px;
  border-bottom: 1px solid var(--rule); font-size: var(--fs-2);
}
.strip li:last-child { border-bottom: 0; }
.strip li:hover { background: var(--panel); }
.strip .pos { font-family: var(--mono); font-size: var(--fs-1); color: var(--dim); width: 14px; }
.strip .who { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.strip .val { font-family: var(--mono); font-size: var(--fs-2); font-weight: 600; }

/* entrances, once, from a visible resting state */
.rise { animation: rise 420ms cubic-bezier(.22,.9,.3,1) both; }
@keyframes rise { from { opacity: 0; transform: translateY(9px); } to { opacity: 1; transform: none; } }

/* Switching sub-tab, league or route used to blink straight to new content,
   which reads as a page reload rather than as the same table re-filtered.
   Short and shallow on purpose: long enough to say "this changed", not long
   enough to be in the way of someone clicking through four leagues. */
#view.is-swapping { animation: swap 170ms ease-out both; }
@keyframes swap {
  from { opacity: 0.25; transform: translateY(3px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-art { animation: none; }
  .rise { animation: none; }
  tbody tr { transition: none; }
  .tl-item, .tl-dist-bar, .tl-pos::after { transition: none; }
  .tl-item:hover, .tl-item:focus-visible { transform: none; }
  .cell-detail, .conf-pm { transition: none; }
  #view.is-swapping { animation: none; }
}

/* ---------- tier list ---------- */

.tl-note {
  padding: 12px var(--gutter); font-family: var(--mono); font-size: var(--fs-1); color: var(--dim);
  border-bottom: 1px solid var(--rule); line-height: 1.7; max-width: 96ch;
}

/* The distribution the bands are cut from. Band zones sit behind the bars so
   the letters read as slices of one curve rather than as a verdict. */
.tl-dist { padding: 16px var(--gutter) 10px; border-bottom: 1px solid var(--rule); }
.tl-dist-plot {
  position: relative; height: 84px; background: var(--row);
  border: 1px solid var(--rule); overflow: hidden;
}
/* The wash is in the background colour, not in `opacity` on the element.
   Opacity applies to children too, so the band letter sitting on top of the
   zone was being dimmed to 16% along with it — no colour could have made it
   readable, and on the light theme it came out at 3:1. */
.tl-zone { position: absolute; top: 0; bottom: 0;
  background: color-mix(in srgb, var(--band) 16%, transparent); }
.tl-zone[data-band="S"] { --band: var(--band-s); }
.tl-zone[data-band="A"] { --band: var(--band-a); }
.tl-zone[data-band="B"] { --band: var(--band-b); }
.tl-zone[data-band="C"] { --band: var(--band-c); }
.tl-zone[data-band="D"] { --band: var(--band-d); }
.tl-zone[data-band="F"] { --band: var(--band-f); }
.tl-zone-tag {
  position: absolute; top: 3px; left: 4px; font-family: var(--cond);
  font-size: var(--fs-1); font-weight: 700; letter-spacing: 0.1em;
  color: color-mix(in srgb, var(--band, var(--mute)) 55%, var(--ink));
}

.tl-dist-bars {
  position: absolute; inset: 0; display: flex; align-items: flex-end; gap: 1px;
  padding: 0 1px;
}
.tl-dist-bar {
  flex: 1; background: var(--ink); opacity: 0.55; min-height: 1px;
  transition: opacity 110ms linear;
}
.tl-dist-bar:hover { opacity: 1; }
/* The mean: every z-score on the page is measured from this line. */
.tl-mean {
  position: absolute; top: 0; bottom: 0; width: 1px; background: var(--ink);
  opacity: 0.8;
}
.tl-mean::after {
  content: ""; position: absolute; top: 0; left: -3px;
  border-left: 3px solid transparent; border-right: 3px solid transparent;
  border-top: 4px solid var(--ink);
}
.tl-dist-axis {
  display: flex; justify-content: space-between; gap: 10px; padding-top: 5px;
  font-family: var(--mono); font-size: var(--fs-1); color: var(--dim);
}
.tl-axis-mid { color: var(--mute); }

/* ---- bands ---- */

.tier { display: grid; grid-template-columns: 86px 1fr; border-bottom: 1px solid var(--rule); }
.tier-label {
  display: grid; align-content: center; justify-items: center; gap: 1px;
  padding: 8px 4px; color: #0B0F10; border-right: 1px solid var(--rule);
}
.tier-label b {
  font-family: var(--cond); font-weight: 700; font-size: var(--fs-5);
  letter-spacing: 0.04em; line-height: 1;
}
/* The interval, in the units a reader thinks in. This is the difference
   between "S beats A" and knowing by how much. */
.tier-label .tier-range {
  font-family: var(--mono); font-size: var(--fs-1); font-weight: 600;
  opacity: 0.72; white-space: nowrap;
}
.tier-label .tier-count {
  font-family: var(--cond); font-size: var(--fs-1); letter-spacing: 0.1em;
  opacity: 0.55;
}
.tier[data-band="S"] .tier-label { background: var(--band-s); }
.tier[data-band="A"] .tier-label { background: var(--band-a); }
.tier[data-band="B"] .tier-label { background: var(--band-b); }
.tier[data-band="C"] .tier-label { background: var(--band-c); }
.tier[data-band="D"] .tier-label { background: var(--band-d); }
.tier[data-band="F"] .tier-label { background: var(--band-f); }
/* Carry the tier colour in the letter and a narrow edge, leaving the entries
   and their rates as the page's visual centre. */
.tier[data-band] .tier-label { background: var(--panel); border-left: 4px solid var(--band-color); }
.tier-label { color: var(--band-color); }
.tier[data-band="S"] { --band-color: var(--band-s); }
.tier[data-band="A"] { --band-color: var(--band-a); }
.tier[data-band="B"] { --band-color: var(--band-b); }
.tier[data-band="C"] { --band-color: var(--band-c); }
.tier[data-band="D"] { --band-color: var(--band-d); }
.tier[data-band="F"] { --band-color: var(--band-f); }
.tl-guide { display: flex; gap: 14px; flex-wrap: wrap; padding: 8px var(--gutter);
  border-bottom: 1px solid var(--rule); color: var(--mute); font: var(--fs-1)/1.5 var(--body); }
.tl-guide b { color: var(--ink); font-weight: 600; }

.tier-row { display: flex; flex-wrap: wrap; gap: 8px; padding: 10px; align-items: flex-start; }

.tl-item {
  position: relative; display: flex; align-items: center; gap: 8px;
  padding: 5px 10px 7px 5px; background: var(--panel);
  border: 1px solid var(--rule); cursor: default;
  transition: border-color 110ms, background 110ms, transform 110ms;
}
.tl-item.is-link { cursor: pointer; }
.tl-item:hover, .tl-item:focus-visible {
  border-color: var(--gold); background: var(--row); transform: translateY(-1px);
  outline: none;
}
.tl-item img { width: 30px; height: 30px; object-fit: contain; display: block; }
.tl-text { display: grid; gap: 1px; min-width: 0; }
.tl-item .nm { font-size: var(--fs-2); font-weight: 500; white-space: nowrap; line-height: 1.2; }
.tl-figs {
  display: flex; align-items: baseline; gap: 6px;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
}
.tl-figs b { font-size: var(--fs-1); font-weight: 600; color: var(--mute); }
.tl-figs i { font-size: var(--fs-1); font-style: normal; color: var(--dim); }
.tl-item .chips { margin-left: 1px; }
.tl-item .chips { opacity: 0.68; }
.tl-item:hover .chips, .tl-item:focus-visible .chips { opacity: 1; }
.tl-item .nm { font-weight: 600; }
.tl-figs i { opacity: 0.8; }

/* Where this entry sits between its band's floor and ceiling. Two chips in the
   same row can be nearly a band apart, and nothing else on the page says so. */
.tl-pos {
  position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--rule);
}
.tl-pos::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 0;
  width: var(--p, 0%); background: var(--gold); opacity: 0.55;
}
.tl-item:hover .tl-pos::after, .tl-item:focus-visible .tl-pos::after { opacity: 1; }

/* ---- hover detail ---- */

.tl-card {
  position: absolute; z-index: 80; width: 260px; padding: 10px 12px 11px;
  background: var(--panel); border: 1px solid var(--gold);
  box-shadow: var(--shadow-2); pointer-events: none;
}
.tl-card-head { display: flex; align-items: baseline; gap: 8px; }
.tl-card-band {
  font-family: var(--cond); font-weight: 700; font-size: var(--fs-2);
  letter-spacing: 0.1em; color: var(--gold);
}
.tl-card-name { font-size: var(--fs-3); font-weight: 500; }
.tl-card .chips { margin-top: 6px; }
.tl-card-grid { display: grid; gap: 3px; margin-top: 9px; }
.tl-card-row {
  display: grid; grid-template-columns: 1fr auto auto; gap: 8px; align-items: baseline;
  padding: 3px 0; border-top: 1px solid var(--rule);
}
/* The side the placement came from, marked so the card does not read as two
   numbers of equal standing. */
.tl-card-row.is-rated .k { color: var(--ink); }
.tl-card-row.is-rated .k::after {
  content: " ← rated"; font-family: var(--cond); font-size: var(--fs-1);
  letter-spacing: 0.1em; color: var(--gold);
}
.tl-card-row .k {
  font-family: var(--cond); font-size: var(--fs-1); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--dim);
}
.tl-card-row .v {
  font-family: var(--mono); font-size: var(--fs-2); font-weight: 600;
  font-variant-numeric: tabular-nums; color: var(--mute);
}
.tl-card-row .v.hi { color: var(--good); }
.tl-card-row .v.lo { color: var(--bad); }
.tl-card-row .n { font-family: var(--mono); font-size: var(--fs-1); color: var(--dim); }
.tl-card-foot {
  display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 9px; padding-top: 7px;
  border-top: 1px solid var(--rule);
  font-family: var(--mono); font-size: var(--fs-1); color: var(--dim);
}

@media (max-width: 640px) {
  .tier { grid-template-columns: 64px 1fr; }
  .tier-label b { font-size: var(--fs-5); }
  .tier-label .tier-range { font-size: var(--fs-1); }
  .tl-item .nm { font-size: var(--fs-1); }
  .tl-dist-plot { height: 64px; }
}

/* ---------- ball detail ---------- */

.ball-hero { display: flex; gap: 20px; align-items: center; padding: 22px var(--gutter);
  border-bottom: 1px solid var(--rule); background: var(--panel); flex-wrap: wrap; }
.ball-hero > img { width: 96px; height: 96px; object-fit: contain; }
.ball-hero h1 { font-family: var(--cond); font-size: var(--fs-5); text-transform: uppercase;
  letter-spacing: 0.03em; font-weight: 700; line-height: 1; }
.ball-hero .rarity { font-family: var(--cond); font-size: var(--fs-1); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--mute); }
.ball-stats { display: flex; gap: 26px; margin-top: 12px; flex-wrap: wrap; }
.ball-stats div b { display: block; font-family: var(--mono); font-size: var(--fs-4); }
.ball-stats div span { font-family: var(--cond); font-size: var(--fs-1); letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--dim); }

.orbs { display: inline-flex; gap: 2px; }
.orb { width: 19px; height: 16px; display: grid; place-items: center;
  font-family: var(--mono); font-size: var(--fs-1); border: 1px solid var(--rule); color: var(--dim); }
.orb.on { color: var(--bg); background: var(--gold); border-color: var(--gold); }

tbody tr.clickable { cursor: pointer; }
tbody tr.clickable:hover td:first-child { color: var(--gold); }
.backlink { color: var(--gold); font-family: var(--mono); font-size: var(--fs-2); }

/* Weekly ladder movement: division I promotes, division IV demotes. */
.move { font-size: var(--fs-1); }
.move.up { color: var(--good); }
.move.down { color: var(--bad); }

/* ---------- player pages ---------- */

/* Both ladders side by side, the same size. Open and rarity are two standings,
   not a standing and a footnote — and each is now the game's own division
   emblem rather than three stacked lines of text. The strip is the same one
   the badges use, without its padding: the hero already has its own. */
.hero-badges { padding: 0; margin-top: 13px; gap: 9px; }
.hero-badges .badge { width: 180px; min-width: 0; box-sizing: border-box; }
.hero-badges .badge-text { width: 100%; overflow: hidden; }
.hero-badges .badge.is-peak { border-color: var(--good); }
.hero-badges .badge.is-peak .badge-kind { color: var(--good); }
.hero-badges .badge.is-peak:hover,
.hero-badges .badge.is-peak:focus-visible { border-color: var(--good); }

/* ---- alliance crest ----
   Shape, pattern and colours are drawn from stored primitives; the symbol
   itself is not published anywhere readable, so initials stand in. See
   lib/emblem.js. */

.alliance { display: inline-flex; align-items: center; gap: 9px; }
.crest { display: block; flex-shrink: 0; }
.alliance-name {
  font-family: var(--cond); font-size: var(--fs-2); font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--mute);
}
.alliance.is-approximate .alliance-name {
  border-bottom: 1px dotted var(--dim); cursor: help;
}

/* The stat strip is shared with ball pages; only standalone uses need padding. */
.ball-stats.strip-pad {
  padding: 14px var(--gutter); border-bottom: 1px solid var(--rule); margin-top: 0;
}

.note .dim { color: var(--mute); }
p.note.dim { color: var(--mute); }
/* A cell holding a weaker fact than the rest of its column — on a ball page,
   a CP read off a sampled battle where every other row's came from a roster. */
td.dim { color: var(--mute); }

a.plain { color: inherit; text-decoration: none; }
a.plain:hover { color: var(--gold); }

td .sub {
  font-family: var(--mono); font-size: var(--fs-1); color: var(--dim); margin-left: 6px;
}

/* Win or loss, then which side of the fight it was. */
.res {
  display: inline-grid; place-items: center; width: 18px; height: 18px;
  font-family: var(--mono); font-size: var(--fs-1); border: 1px solid var(--rule);
  color: var(--dim); margin-right: 7px;
}
.res.w { color: var(--good); border-color: var(--good); }
.res.l { color: var(--bad); border-color: var(--bad); }
.side { font-family: var(--cond); font-size: var(--fs-1); letter-spacing: 0.1em; color: var(--mute); }

.lineup { display: flex; gap: 2px; }
.lineup-ball {
  width: 26px; height: 26px; display: grid; place-items: center;
  border: 1px solid var(--rule);
}
.lineup-ball img { width: 22px; height: 22px; object-fit: contain; }

@media (max-width: 700px) {
  /* `.leagues { gap: 12px }` sat here unscoped, meant for the hero's league
     blocks. Those are gone, and it was never reaching them anyway — it was
     hitting the chrome strip's tier buttons, which butt together on purpose
     and grew gaps between their seams under 700px. */
  .lineup-ball { width: 22px; height: 22px; }
  .lineup-ball img { width: 18px; height: 18px; }
}

/* ---------- player profile ---------- */

.player-hero { position: relative; overflow: hidden; }
.player-banner {
  position: absolute; inset: 0; pointer-events: none; z-index: 0;
}
.player-banner img {
  width: 100%; height: 100%; object-fit: cover; object-position: center 32%;
  opacity: 0.22; filter: saturate(0.85);
}
/* Keep the banner from washing out the text sitting on it. */
.player-banner::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, var(--panel) 0%, rgba(17,23,26,0.72) 45%, rgba(17,23,26,0.35) 100%);
}
.player-hero > *:not(.player-banner) { position: relative; z-index: 1; }
.player-hero > img {
  border: 1px solid var(--rule); background: #0A0E0F;
}

.owner-badge {
  display: inline-block; font-family: var(--mono); font-size: var(--fs-1); font-weight: 600;
  padding: 2px 6px; border: 1px solid var(--rule); color: var(--mute);
}
.owner-badge.silver { color: var(--ink); border-color: var(--mute); }
.owner-badge.gold {
  color: var(--bg); background: var(--gold); border-color: var(--gold); font-weight: 700;
}

@media (max-width: 900px) {
  .omni { margin-left: 0; border-left: 0; width: 100%; border-top: 1px solid var(--rule); }
  .omni input { width: 100%; }
  .omni-results { left: 0; right: 0; min-width: 0; }
  .top { flex-wrap: wrap; }
}

/* ---------- thin-tier warning ----------
   A tier can be published off a few hundred battles and look exactly as
   authoritative as one built on twelve thousand. This is the site saying so
   before the reader acts on it, rather than in a footnote afterwards. */

.depth-warn {
  display: flex; gap: 9px; flex-wrap: wrap; align-items: center;
  padding: 9px var(--gutter); border-bottom: 1px solid var(--rule);
  background: #1A1408; border-left: 2px solid #C8933C;
  font-family: var(--mono); font-size: var(--fs-1); line-height: 1.6; color: var(--mute);
}
.depth-warn b {
  font-family: var(--cond); font-size: var(--fs-1); letter-spacing: 0.12em;
  text-transform: uppercase; color: #C8933C; font-weight: 700;
}
.depth-warn.is-thin { background: #1C0F0D; border-left-color: var(--bad); }
.depth-warn.is-thin b { color: var(--bad); }

/* An account the ladder has never listed. Shown, because it was in the battle,
   but visibly not somewhere you can go. */
.unknown-player { color: var(--dim); font-style: italic; cursor: help; }

/* ---------- tier list: distribution disclosure ---------- */

.tl-dist-wrap { border-bottom: 1px solid var(--rule); }
.tl-dist-summary {
  font-family: var(--cond); font-size: var(--fs-1); letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--dim); cursor: pointer;
  padding: 10px var(--gutter); list-style: none; user-select: none;
}
.tl-dist-summary::-webkit-details-marker { display: none; }
.tl-dist-summary::before { content: "+ "; color: var(--gold); }
.tl-dist-wrap[open] .tl-dist-summary::before { content: "− "; }
.tl-dist-summary:hover { color: var(--mute); }
.tl-dist { padding: 2px var(--gutter) 14px; border-bottom: 0; }
.tl-dist-how {
  font-family: var(--mono); font-size: var(--fs-1); color: var(--dim);
  line-height: 1.7; max-width: 82ch; margin-bottom: 12px;
}

/* ---------- tier list: the drill-down drawer ----------
   A tier list says what is strong and immediately raises "on what, and for
   whom". The drawer answers in place rather than sending the reader away. */

.tl-drawer {
  grid-column: 1 / -1; border-top: 1px solid var(--rule);
  background: var(--bg);
}
.tl-drawer-head {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  padding: 11px 14px; border-bottom: 1px solid var(--rule);
}
.tl-drawer-head b {
  font-family: var(--cond); font-size: var(--fs-3); font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--gold);
}
.tl-drawer-head span {
  font-family: var(--mono); font-size: var(--fs-1); color: var(--dim); flex: 1;
}
.tl-drawer-close {
  font-family: var(--cond); font-size: var(--fs-1); letter-spacing: 0.1em;
  text-transform: uppercase; background: none; color: var(--mute);
  border: 1px solid var(--rule); border-radius: var(--r);
  padding: 4px 10px; cursor: pointer;
}
.tl-drawer-close:hover { color: var(--ink); border-color: var(--mute); }
.tl-drawer-load {
  padding: 16px 14px; font-family: var(--mono); font-size: var(--fs-1); color: var(--dim);
}
.tl-drawer-table { min-width: 560px; }
.tl-drawer .wrap { padding-bottom: 2px; }

/* The open entry stays marked while its drawer is below. */
.tl-item.is-open {
  border-color: var(--gold); background: var(--row);
  box-shadow: inset 0 -2px 0 var(--gold);
}

/* "High elo" is a scope across three divisions, not one of them. A hairline
   on its right edge groups it with All rather than with the divisions. */
.leagues button.is-scope { border-right: 1px solid var(--rule); }
.leagues button.is-scope[aria-pressed="true"] { border-right-color: var(--gold); }

/* ---------- profile panes ----------
   A profile answers nine questions, and it used to stack nine full-width
   tables to do it — a two-row "by league" carrying the same weight as a
   five-row head-to-head, and paired opposites sitting one above the other
   instead of side by side.

   The grid separates them with the same 1px rule the tables use, showing the
   page ground through the gaps, so panes read as divided rather than as
   cards. */

/* Exactly two columns, not auto-fit.
   The panes come in pairs — beats against loses to, wins into against loses
   into — and at three columns the flow puts each pair on a diagonal, which is
   the one arrangement that destroys the comparison they exist to make. */
.pgrid {
  display: grid; gap: 1px; background: var(--rule);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  margin-top: 16px;
}
@media (max-width: 720px) { .pgrid { grid-template-columns: minmax(0, 1fr); } }
.pane { background: var(--bg); min-width: 0; padding-bottom: 10px; }
.pane.is-wide { grid-column: 1 / -1; }

.pane-head {
  display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
  padding: 12px 16px 9px;
}
.pane-head h3 {
  font-family: var(--cond); font-size: var(--fs-2); font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink);
}
.pane-head span {
  font-family: var(--mono); font-size: var(--fs-1); color: var(--dim);
}
.pane-sub {
  font-family: var(--cond); font-size: var(--fs-1); font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--dim);
  padding: 8px 16px 4px;
}
.pane-body > .pane-sub:first-child { padding-top: 0; }

/* The stat strip sits flush inside a pane rather than carrying its own rule. */
.pane .ball-stats.strip-pad { border-bottom: 0; padding: 4px 16px 8px; gap: 20px; }

/* ---- compact rows ----
   A six-column table is unreadable at half width, so the two supporting rates
   move to the row's title. Same data, legible column count. */

.mini { display: grid; }
.mini-row {
  display: grid; grid-template-columns: 16px 1fr auto auto;
  align-items: baseline; gap: 10px;
  padding: 5px 16px; width: 100%; text-align: left;
  background: none; border: 0; border-top: 1px solid var(--hairline);
  color: var(--ink); font-family: var(--body); font-size: var(--fs-2);
}
.mini-row:first-child { border-top: 0; }
button.mini-row { cursor: pointer; }
.mini-row:hover { background: var(--panel); }
.mini-pos { font-family: var(--mono); font-size: var(--fs-1); color: var(--dim); }
.mini-who { min-width: 0; display: flex; align-items: baseline; gap: 7px; }
.mini-who b {
  font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mini-who i {
  font-style: normal; font-family: var(--mono); font-size: var(--fs-1); color: var(--dim);
  white-space: nowrap;
}
.mini-n {
  font-family: var(--mono); font-size: var(--fs-1); color: var(--dim);
  font-variant-numeric: tabular-nums;
}
.mini-wr {
  font-family: var(--mono); font-size: var(--fs-2); font-weight: 600; color: var(--mute);
  font-variant-numeric: tabular-nums; min-width: 48px; text-align: right;
}
.mini-wr.hi { color: var(--good); }
.mini-wr.lo { color: var(--bad); }

/* ---- account facts ---- */

.facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr));
  gap: 10px 22px; padding: 4px 16px 6px;
}
.facts dt {
  font-family: var(--cond); font-size: var(--fs-1); letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--dim);
}
.facts dd {
  font-family: var(--mono); font-size: var(--fs-3); color: var(--ink);
  font-variant-numeric: tabular-nums; margin-top: 1px;
}


/* The sample line carries the back link rather than the link owning a row. */
.note-sample { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }

/* The billed-vs-counted block on the admin usage panel: Cloudflare own totals
   next to the part collection can attribute, and the remainder. Spaced away
   from the counters above it so the two sources do not read as one row. */
.usage-billed { margin-top: 14px; display: grid; gap: 6px; }
.note-sample .backlink { flex-shrink: 0; }
.note-sample .note-body { color: var(--mute); }

/* Tables inside a pane are half-width, so they lose the 720px floor that the
   full-width stat tables need. */
.pane table { min-width: 0; }
.pane .wrap { overflow-x: auto; overscroll-behavior-x: contain; }

/* ---------- ladder switch ----------
   Open and Rarity are separate competitions, so the control that moves
   between them is filled rather than underlined — a different kind of choice
   from picking a tier within one. */

.ladders { display: flex; align-items: center; border-right: 1px solid var(--rule); }
.ladders button {
  font-family: var(--cond); font-weight: 700; font-size: var(--fs-2);
  letter-spacing: 0.08em; text-transform: uppercase;
  background: none; border: 0; color: var(--mute);
  padding: 9px 14px; cursor: pointer; border-left: 1px solid var(--rule);
}
.ladders button:hover { color: var(--ink); }
.ladders button[aria-pressed="true"] { color: var(--bg); background: var(--gold); }

/* ---------- badges ----------
   Both kinds existed and neither was findable: top-owner placings were a chip
   in the last column of a 150-row table on another tab, and peak rank was not
   shown at all. */

.badges {
  display: flex; flex-wrap: wrap; gap: 8px; padding: 4px 16px 10px;
}
.badge {
  display: flex; align-items: center; gap: 9px;
  padding: 7px 12px 7px 8px; background: var(--panel);
  border: 1px solid var(--rule); border-radius: var(--r);
  text-align: left; font-family: var(--body); color: var(--ink);
  transition: border-color 110ms, background 110ms;
}
button.badge { cursor: pointer; }
button.badge:hover { border-color: var(--gold); background: var(--row); }
.badge .thumb { width: 30px; height: 30px; }
.badge-text { display: grid; gap: 1px; min-width: 0; }
.badge-kind {
  font-family: var(--cond); font-size: var(--fs-1); font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--dim);
}
.badge b {
  font-size: var(--fs-2); font-weight: 600; line-height: 1.2;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.badge-note {
  font-family: var(--mono); font-size: var(--fs-1); color: var(--dim); line-height: 1.3;
}

/* ---- division badges ----
   The peak badge used to be a bordered box with a coloured rail down its left
   edge and three stacked grey lines: a shape that would look at home in any
   product, which is the test `DESIGN.md` sets for getting it wrong. The game
   draws its own emblem for each tier and publishes it, so the emblem carries
   the tier's identity and its colour, and the rail is gone. */

.badge.is-div {
  align-items: center; gap: 11px; padding: 8px 14px 8px 9px;
  cursor: default; text-align: left;
}
.badge.is-div b {
  font-family: var(--cond); font-size: var(--fs-4); letter-spacing: 0.06em;
  text-transform: uppercase; line-height: 1.1;
}

/* The art is ~400px square with wide transparent margins and an emblem that
   sits broader than it is tall, so the box is landscape and the image is
   contained in it rather than cropped to a square thumbnail. */
.div-crest {
  width: 46px; height: 38px; object-fit: contain; display: block;
  flex-shrink: 0;
}
.div-crest.is-blank { opacity: 0; }

/* One slot, two lines. The resting figure and the detail are stacked in the
   same grid area — the `.conf-swap` idiom from the stat tables — so revealing
   one costs no reflow and the strip never jumps under the cursor. */
.div-swap { display: grid; min-width: 0; }
.div-swap > * {
  grid-area: 1 / 1;
  font-family: var(--mono); font-size: var(--fs-1); color: var(--dim);
  line-height: 1.3; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
  transition: opacity 110ms linear;
}
.div-detail { opacity: 0; }
.badge.is-div:hover .div-rest,
.badge.is-div:focus-visible .div-rest,
.badge.is-div[aria-expanded="true"] .div-rest { opacity: 0; }
.badge.is-div:hover .div-detail,
.badge.is-div:focus-visible .div-detail,
.badge.is-div[aria-expanded="true"] .div-detail { opacity: 1; color: var(--mute); }

/* The peak date range may outgrow a uniform badge. Reveal its full length in
   place instead of enlarging the header or cutting the date off. */
.badge.is-peak .div-detail { text-overflow: clip; }
.badge.is-peak .div-detail-text { display: inline-block; white-space: nowrap; }
.badge.is-peak:hover .div-detail.is-overflowing .div-detail-text,
.badge.is-peak:focus-visible .div-detail.is-overflowing .div-detail-text,
.badge.is-peak[aria-expanded="true"] .div-detail.is-overflowing .div-detail-text {
  animation: peak-date-scroll var(--scroll-duration, 5s) ease-in-out infinite;
}
@keyframes peak-date-scroll {
  0%, 12% { transform: translateX(0); }
  70%, 88% { transform: translateX(calc(-1 * var(--scroll-distance, 0px))); }
  100% { transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  .badge.is-peak .div-detail.is-overflowing { overflow-x: auto; }
  .badge.is-peak .div-detail.is-overflowing .div-detail-text { animation: none !important; }
}

.badge.is-div:hover, .badge.is-div:focus-visible {
  border-color: var(--rule); background: var(--row); outline: none;
}
/* A ladder that exists but has not been read. Shown rather than omitted, so
   an absent standing does not read as "plays only one league". */
.badge.is-div.is-muted b { color: var(--dim); }

@media (prefers-reduced-motion: reduce) { .div-swap > * { transition: none; } }

.badge.is-top { border-color: var(--mute); }
.badge.is-first { border-color: var(--gold); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gold) 25%, transparent); }
.badge.is-first .badge-kind { color: var(--gold); }
/* Keep every placing visible while giving the profile room to breathe. */
.pane > .badges {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 6px; padding: 6px 12px 10px;
}
.pane > .badges .badge {
  width: 100%; min-width: 0; height: 54px; box-sizing: border-box;
  gap: 7px; padding: 5px 8px 5px 6px;
}
.pane > .badges .badge-text { width: 100%; overflow: hidden; }
.pane > .badges .badge .thumb { width: 24px; height: 24px; }
.pane > .badges .badge .thumb,
.pane > .badges .badge .div-crest { flex-shrink: 0; }
.pane > .badges .badge b { font-size: var(--fs-1); }
.pane > .badges .badge-kind, .pane > .badges .badge-note { font-size: var(--fs-1); }
.pane > .badges .badge.is-div { gap: 7px; padding: 5px 8px 5px 6px; }
.pane > .badges .badge.is-div .div-crest { width: 36px; height: 30px; }
.pane > .badges .badge.is-div b { font-size: var(--fs-2); }
.pane > .badges .badge-name { display: block; overflow: hidden; }
.pane > .badges .badge-name > span { display: inline-block; white-space: nowrap; }
.pane > .badges .badge-name.is-overflow { mask-image: linear-gradient(90deg, #000 88%, transparent); }
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .pane > .badges .badge:hover .badge-name.is-overflow > span {
    animation: badge-name-scroll 4s ease-in-out 250ms infinite alternate;
  }
}
@keyframes badge-name-scroll {
  0%, 14% { transform: translateX(0); }
  86%, 100% { transform: translateX(calc(-1 * var(--name-travel))); }
}
@media (max-width: 600px) {
  .pane > .badges { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.pane-note {
  font-family: var(--mono); font-size: var(--fs-1); color: var(--dim);
  padding: 0 16px 10px; line-height: 1.6; max-width: 70ch;
}

@media (prefers-reduced-motion: reduce) { .badge { transition: none; } }

/* Where one CP band ends and the next begins on a ball page. Everything inside
   a band is a CP tie, so its order comes from the tie-breakers; the rule makes
   that visible instead of leaving the sequence looking arbitrary. */
tbody tr.band-start td { border-top: 1px solid var(--rule); }


/* ---------- method page ----------
   Everything the pages used to say in place. One page, linked from the footer,
   so a reader who wants the reasoning can have all of it and a reader who
   wants the ranking is not made to read it first. */

.method-head { padding: 34px var(--gutter) 18px; border-bottom: 1px solid var(--rule); }
.method-head h1 {
  font-family: var(--cond); font-size: clamp(26px, 5vw, 38px); font-weight: 700;
  letter-spacing: 0.03em; text-transform: uppercase; color: var(--ink);
}
.method-head p {
  font-family: var(--mono); font-size: var(--fs-1); color: var(--dim); margin-top: 8px;
}

.method {
  display: grid; gap: 1px; background: var(--rule);
  grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
  border-bottom: 1px solid var(--rule);
}
.method-sec { background: var(--bg); padding: 18px var(--gutter); }
.method-sec h2 {
  font-family: var(--cond); font-size: var(--fs-2); font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--gold);
  margin-bottom: 14px;
}
.method-sec dl { display: grid; gap: 14px; }
.method-sec dt {
  font-size: var(--fs-3); font-weight: 600; color: var(--ink); margin-bottom: 3px;
}
.method-sec dd {
  font-family: var(--mono); font-size: var(--fs-1); line-height: 1.75; color: var(--mute);
  max-width: 62ch;
}

/* ---------- typing → balls hover ----------
   At a small tier a typing is often one or two balls, so its win rate is
   really a statement about those balls. The card says which. */

.tcard { width: 300px; padding: 0; }
.tcard-head {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 9px 11px; border-bottom: 1px solid var(--rule);
}
.tcard-head b { font-size: var(--fs-2); font-weight: 600; }
.tcard-head span { font-family: var(--mono); font-size: var(--fs-1); color: var(--dim); }
.tcard-list { display: grid; max-height: 260px; overflow-y: auto; }
.tcard-row {
  display: grid; grid-template-columns: auto 1fr auto auto;
  align-items: center; gap: 8px; padding: 4px 11px;
  border-top: 1px solid var(--hairline);
}
.tcard-row:first-child { border-top: 0; }
.tcard-row .thumb { width: 22px; height: 22px; }
.tcard-name {
  font-size: var(--fs-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tcard-n {
  font-family: var(--mono); font-size: var(--fs-1); color: var(--dim);
  font-variant-numeric: tabular-nums;
}
.tcard-wr {
  font-family: var(--mono); font-size: var(--fs-1); font-weight: 600; color: var(--mute);
  font-variant-numeric: tabular-nums; min-width: 44px; text-align: right;
}
.tcard-wr.hi { color: var(--good); }
.tcard-wr.lo { color: var(--bad); }
.tcard-load, .tcard-more {
  font-family: var(--mono); font-size: var(--fs-1); color: var(--dim); padding: 8px 11px;
}
/* A row that has something to show says so, faintly. */
tbody tr.has-detail td:nth-child(2) { position: relative; }
tbody tr.has-detail:hover td:nth-child(2)::after {
  content: "›"; position: absolute; right: 4px; top: 50%;
  transform: translateY(-50%); color: var(--gold); font-size: var(--fs-2);
}

/* ---------- profile groups ----------
   Account, PVP and Raids are three different questions about one person, so
   the page says so instead of running them together. */

.group-title {
  display: flex; align-items: center; gap: 4px;
  font-family: var(--cond); font-size: var(--fs-2); font-weight: 700;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--gold);
  padding: 22px var(--gutter) 2px;
  border-top: 1px solid var(--rule); margin-top: 8px;
}
.group-title:first-of-type { border-top: 0; margin-top: 0; }
/* The figure strips sit directly under their heading, not in a panel. */
.group-title + .ball-stats.strip-pad { border-bottom: 0; padding-top: 10px; }
.ball-stats.strip-pad + .ball-stats.strip-pad { padding-top: 0; }
.ball-stats.strip-pad .info { align-self: center; }


/* ---------- account compartments ----------
   The account block was two rows of bare figures with tiny labels: a level, a
   CP and a date rendered identically, so nothing said which mattered or how to
   read it. Compartments group them, tiles carry their own unit and context,
   and colour marks the family rather than the value. */

.comparts {
  display: grid; gap: 14px; padding: 12px var(--gutter) 18px;
  grid-template-columns: repeat(auto-fit, minmax(min(330px, 100%), 1fr));
}
.compart {
  background: var(--panel); border: 1px solid var(--rule);
  border-radius: var(--r); overflow: hidden;
}
.compart-head {
  display: flex; align-items: center; gap: 4px;
  padding: 11px 14px 9px; border-bottom: 1px solid var(--rule);
}
.compart-head h3 {
  font-family: var(--cond); font-size: var(--fs-1); font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--mute);
}
.compart-grid {
  display: grid; gap: 1px; background: var(--rule);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
/* An odd tile count would otherwise leave a hole in the second column, and
   which tiles exist depends on what is known about the account. */
.compart-grid > .stile:last-child:nth-child(odd) { grid-column: 1 / -1; }
@media (max-width: 420px) {
  .compart-grid { grid-template-columns: minmax(0, 1fr); }
}

.stile {
  display: grid; align-content: start; gap: 2px;
  background: var(--panel); padding: 11px 13px 12px;
  border-top: 2px solid transparent;
}
.stile-label {
  font-family: var(--cond); font-size: var(--fs-1); font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--dim);
}
.stile-value {
  font-family: var(--mono); font-size: var(--fs-4); font-weight: 600; line-height: 1.15;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.stile-sub {
  font-family: var(--mono); font-size: var(--fs-1); color: var(--dim); line-height: 1.4;
}
.stile-bar {
  display: block; height: 3px; border-radius: var(--r-sm); margin-top: 5px;
  background: var(--lift);
}
.stile-bar > span { display: block; height: 100%; border-radius: var(--r-sm); background: currentColor; }

/* The family stripe. Colour names the group, never the figure. */
.stile.is-cyan   { border-top-color: color-mix(in srgb, var(--gold) 55%, transparent); }
.stile.is-cyan   .stile-label, .stile.is-cyan   .stile-bar { color: var(--gold); }
.stile.is-amber  { border-top-color: color-mix(in srgb, var(--acc-amber) 55%, transparent); }
.stile.is-amber  .stile-label, .stile.is-amber  .stile-bar { color: var(--acc-amber); }
.stile.is-violet { border-top-color: color-mix(in srgb, var(--acc-violet) 55%, transparent); }
.stile.is-violet .stile-label, .stile.is-violet .stile-bar { color: var(--acc-violet); }
.stile.is-good   { border-top-color: color-mix(in srgb, var(--good) 65%, transparent); }
.stile.is-good   .stile-label, .stile.is-good   .stile-bar { color: var(--good); }
.stile.is-good   .stile-value { color: var(--good); }
.stile.is-bad    { border-top-color: color-mix(in srgb, var(--bad) 65%, transparent); }
.stile.is-bad    .stile-label, .stile.is-bad    .stile-bar { color: var(--bad); }
.stile.is-bad    .stile-value { color: var(--bad); }

/* Softer edges on the larger blocks, now that the corner token is 6px. */
.pgrid, .compart { border-radius: var(--r); }
.pgrid { overflow: hidden; margin-left: var(--gutter); margin-right: var(--gutter); width: auto; }
.badge { border-radius: var(--r); }
.tl-item, .tl-card, .depth-warn, .omni-results { border-radius: var(--r); }


/* ---------- narrow screens ----------
   A phone is not a small desktop and it is not a separate design either. The
   page keeps its density and its columns; what changes is that anything wider
   than the screen scrolls inside its own box rather than dragging the whole
   document sideways, and anything driven by a cursor gets a second way in.

   The one thing allowed to be wider than the viewport is a table, inside
   `.wrap`. Everything else fits. */

/* The filter strip was the only thing forcing a page-level sideways scroll:
   nine tier buttons on one line is 676px, and `.chrome`'s wrap only breaks
   between groups, never inside one. Each group now scrolls on its own, so the
   strip keeps its shape and the document stays 375 wide. */
.subnav, .ladders, .leagues, .nav {
  max-width: 100%;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.subnav::-webkit-scrollbar, .ladders::-webkit-scrollbar,
.leagues::-webkit-scrollbar, .nav::-webkit-scrollbar { display: none; }

/* A strip that scrolls has to say so, or five of the nine tiers are simply
   missing on a phone. The classic two-layer background trick: the opaque
   cover scrolls with the content (`local`) and the darkening does not
   (`scroll`), so the edge shades exactly while there is more beyond it and
   clears itself at both ends. It is not a shadow separating two objects —
   it is the row continuing under its own edge. */
.subnav, .ladders, .leagues { --scroll-ground: var(--row); }
.nav { --scroll-ground: var(--panel); }
.subnav, .ladders, .leagues, .nav {
  background-image:
    linear-gradient(to right, var(--scroll-ground) 14px, transparent),
    linear-gradient(to left,  var(--scroll-ground) 14px, transparent),
    linear-gradient(to right, color-mix(in srgb, var(--bg) 88%, transparent), transparent),
    linear-gradient(to left,  color-mix(in srgb, var(--bg) 88%, transparent), transparent);
  background-position: left center, right center, left center, right center;
  background-repeat: no-repeat;
  background-size: 24px 100%, 24px 100%, 14px 100%, 14px 100%;
  background-attachment: local, local, scroll, scroll;
}

/* The group's label stays put while its buttons scroll past, so a strip that
   has been pushed along still says what it is filtering. */
.flabel {
  position: sticky; left: 0; z-index: 2; background: var(--row);
  align-self: stretch; display: flex; align-items: center; flex: 0 0 auto;
}
/* A scrolling flex row lets its children shrink by default, which would crush
   the labels rather than move them off-screen. */
.subnav button, .ladders button, .leagues button, .nav a { flex: 0 0 auto; }
/* The strip's own groups must not stretch each other past the screen. */
.chrome > * { max-width: 100%; min-width: 0; }

@media (max-width: 900px) {
  /* The masthead wraps to two rows here, and at two rows a sticky header is
     taking a tenth of the screen from the data. Pulled in rather than
     unpinned: the nav is still one flick away at the top of any page. */
  .brand { font-size: var(--fs-4); padding: 10px 13px; }
  .nav a { padding: 10px 13px; font-size: var(--fs-2); }
  .omni input { padding: 10px 13px; }
  .stamp { display: none; }
}

/* Under about 400px the brand and three nav items no longer fit on one line,
   and the masthead wrapped to three rows — a brand on a row of its own, then
   the nav, then the search. The nav takes what is left of the row and scrolls
   for the rest, which is what it already does inside its own box. */
@media (max-width: 400px) {
  .top .nav { flex: 1 1 0; min-width: 0; }
  .brand { font-size: var(--fs-3); padding: 10px 11px; }
  .nav a { padding: 10px 11px; }
}

@media (max-width: 640px) {
  /* The hero's 64px of air is a desktop proportion; on a phone it is most of
     the first screen. */
  .hero-inner { padding: 34px 16px 30px; }
  .hero-sub { margin-top: 10px; }

  /* Body padding comes in with the screen, so the gutter stays a gutter
     instead of eating a third of the line. */
  .section-title, .note, .tl-note, .tl-dist-summary, .tl-dist,
  .depth-warn, .preview, .empty, .method-sec, .method-head,
  .comparts, .group-title, .ball-hero, .player-head, .form, footer {
    padding-left: var(--gutter); padding-right: var(--gutter);
  }
  thead th, tbody td, tbody td:first-child { padding-left: 10px; padding-right: 10px; }
  .pgrid { margin-left: var(--gutter); margin-right: var(--gutter); }
  .pane-head, .pane-sub, .pane-note, .mini-row, .badges, .facts,
  .pane .ball-stats.strip-pad { padding-left: var(--gutter); padding-right: var(--gutter); }

  /* ---- the row keeps its name ----
     A stat table is 720px wide and a phone is 375, so the ranked column — the
     one the table is sorted by and the whole reason for the page — is a full
     screen to the right of the name. Scrolling to it used to leave seven
     anonymous numbers. Pinning the identity is the one thing that makes a
     wide table usable on a narrow screen, and it is the same device the
     matchup grid already uses for its row heads.

     `.grid` is excluded because it pins its own. */
  .wrap > table:not(.grid) th.rank,
  .wrap > table:not(.grid) td.rank {
    position: sticky; left: 0; z-index: 3;
    width: 34px; min-width: 34px; max-width: 34px;
    padding-left: 8px; padding-right: 2px;
  }
  .wrap > table:not(.grid) th.rank + th,
  .wrap > table:not(.grid) td.rank + td,
  /* Tables that are not ranked put the name first and have nothing to pair. */
  .wrap > table:not(.grid) thead th:first-child:not(.rank),
  .wrap > table:not(.grid) tbody td:first-child:not(.rank) {
    position: sticky; z-index: 3;
    max-width: 52vw; overflow: hidden; text-overflow: ellipsis;
  }
  .wrap > table:not(.grid) th.rank + th,
  .wrap > table:not(.grid) td.rank + td { left: 34px; }
  .wrap > table:not(.grid) thead th:first-child:not(.rank),
  .wrap > table:not(.grid) tbody td:first-child:not(.rank) { left: 0; }

  /* A sticky cell is transparent by default and would let the columns it is
     pinned over show through it. */
  .wrap > table:not(.grid) tbody td.rank,
  .wrap > table:not(.grid) tbody td.rank + td,
  .wrap > table:not(.grid) tbody td:first-child:not(.rank) { background: var(--bg); }
  .wrap > table:not(.grid) tbody tr:hover td.rank,
  .wrap > table:not(.grid) tbody tr:hover td.rank + td,
  .wrap > table:not(.grid) tbody tr:hover td:first-child:not(.rank) { background: var(--panel); }

  /* The seam where the pinned columns end, so the scrolled figures do not
     look like they belong to the name they slide under. */
  .wrap > table:not(.grid) th.rank + th,
  .wrap > table:not(.grid) td.rank + td,
  .wrap > table:not(.grid) thead th:first-child:not(.rank),
  .wrap > table:not(.grid) tbody td:first-child:not(.rank) {
    border-right: 1px solid var(--rule);
  }

  /* Two stat groups per row rather than four across a 375px screen. */
  .ball-stats { gap: 14px 20px; }
  .ball-hero > img, .player-hero > img { width: 72px; height: 72px; }
  .ball-hero h1 { font-size: var(--fs-5); }
}

/* ---------- touch ----------
   Two assumptions the desktop page makes that a phone breaks: that a pointer
   can rest on a row, and that a 33px control is easy to hit. */

@media (hover: none) {
  /* The tier chip's card is opened on focus so a keyboard can reach it. On
     touch a tap both focuses the chip and opens its drawer, and the card then
     lands on top of the drawer that already says the same thing. */
  .tl-card:not(.tcard) { display: none; }

  /* The typing card is the other way round: a tap is now the way in (see
     `attachTypingHover`), so the marker that says a row has something behind
     it has to be permanent rather than appearing on hover. */
  tbody tr.has-detail td:nth-child(2)::after {
    content: "›"; position: absolute; right: 4px; top: 50%;
    transform: translateY(-50%); color: var(--gold); font-size: var(--fs-2);
  }

  /* The per-side sample split stays where it is. It is rendered into cells
     sized for one figure, and forcing it on makes every rate column overflow
     into its neighbour; the pooled sample is a column of its own either way. */
}

@media (pointer: coarse) {
  /* 33px is a comfortable click and an uncomfortable tap. These are the
     controls a reader works through fastest — tier, ladder, sub-tab — so they
     are the ones worth the extra six pixels. */
  .subnav button { padding: 13px 16px; }
  .leagues button { padding: 12px 13px; }
  .ladders button { padding: 12px 15px; }
  .nav a { padding: 13px 14px; }
  .tl-drawer-close { padding: 8px 12px; }

  /* Under 16px, iOS Safari zooms the page when the field takes focus and does
     not zoom back out. */
  .omni input { font-size: 16px; }
}


/* ---------- item page ----------
   Two things the ball page has no equivalent of: a resonance attribute, which
   is a chip because that is what an attribute is everywhere else here, and an
   effect, which is the one piece of real prose the data carries and so is
   allowed to read as a sentence. */

.item-facts { display: grid; gap: 5px; margin-top: 9px; }
.item-fact { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* A fixed label column so resonance and requirement line up as a pair rather
   than as two unrelated lines. */
.item-fact-k {
  font-family: var(--cond); font-size: var(--fs-1); font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--dim);
  min-width: 74px;
}
.item-fact-v {
  font-family: var(--mono); font-size: var(--fs-2); color: var(--ink);
}
.item-effect {
  padding: 0 var(--gutter) 14px; max-width: 76ch;
  font-family: var(--mono); font-size: var(--fs-2); line-height: 1.7; color: var(--ink);
}
@media (max-width: 640px) { .item-effect { padding-left: var(--gutter); padding-right: var(--gutter); } }


/* ---------- the UNC easter egg ----------
   One account's profile never loads. See views/unc.js for why this exists and
   what keeps it from being a nuisance; the short version is that it matches
   one exact username and it holds still for `prefers-reduced-motion`. */

.unc { position: relative; overflow: hidden; min-height: 70vh; }

.unc-sky { position: absolute; inset: 0; pointer-events: none; z-index: 0; }
.unc-bit {
  position: absolute; line-height: 1; user-select: none;
  animation-name: unc-fly; animation-timing-function: ease-in-out;
  animation-iteration-count: infinite; animation-direction: alternate;
  will-change: transform;
}
@keyframes unc-fly {
  from { transform: translate3d(0, 0, 0) rotate(0deg); }
  to   { transform: translate3d(var(--drift, 80px), calc(var(--drift, 80px) * -0.6), 0)
                    rotate(var(--spin, 360deg)); }
}

.unc-box {
  position: relative; z-index: 1; padding: 54px var(--gutter) 60px;
  max-width: 78ch; margin: 0 auto; text-align: center;
}

/* Two lines, because the joke is that it says it twice. */
.unc-shout {
  display: grid; gap: 2px;
  font-family: var(--cond); font-weight: 700; text-transform: uppercase;
  font-size: clamp(30px, 8vw, 68px); line-height: 0.95; letter-spacing: 0.02em;
  color: var(--bad);
}
.unc-shout span:last-child { color: var(--ink); opacity: 0.55; }
.unc:not(.is-calm) .unc-shout { animation: unc-shake 620ms steps(2, end) infinite; }
@keyframes unc-shake {
  0%, 100% { transform: translateX(0); }
  50%      { transform: translateX(3px); }
}

.unc-sub {
  font-family: var(--mono); font-size: var(--fs-2); color: var(--mute);
  margin-top: 14px; line-height: 1.7;
}

.unc-log {
  margin: 18px auto 0; padding: 12px 14px; max-width: 74ch;
  background: var(--panel); border: 1px solid var(--rule); border-radius: var(--r);
  font-family: var(--mono); font-size: var(--fs-1); line-height: 1.75; color: var(--dim);
  text-align: left; white-space: pre; overflow-x: auto; overscroll-behavior-x: contain;
}
.unc-status {
  font-family: var(--mono); font-size: var(--fs-1); color: var(--bad); margin-top: 12px;
}
.unc-fine {
  font-family: var(--mono); font-size: var(--fs-1); color: var(--dim); margin-top: 22px;
}
/* The way out of the joke. Quiet until you look for it, then obviously a
   button — it is the only real control on the page. */
.unc-jk {
  margin-top: 10px; padding: 5px 12px;
  background: none; border: 1px solid var(--rule); border-radius: 999px;
  font-family: var(--mono); font-size: var(--fs-1); color: var(--mute);
  cursor: pointer; transition: color .15s, border-color .15s;
}
.unc-jk:hover { color: var(--ink); border-color: var(--mute); }
.unc-jk:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* Held still, but still told. The scatter stays; only the motion goes. */
@media (prefers-reduced-motion: reduce) {
  .unc-bit { animation: none; }
  .unc-shout { animation: none; }
}

/* "No bonus effect" is an answer, not an absence, so it is set apart from a
   real effect without being hidden. */
.item-effect.is-none { color: var(--dim); }

/* A peak that has no settled week behind it yet. Dimmed rather than hidden:
   it is the honest reading available today and it will be replaced. */
.badge.is-provisional-peak .badge-kind::after {
  content: " · provisional"; color: var(--dim); letter-spacing: 0.08em;
}

/* ---- meta page --------------------------------------------------------- */
.cell-sub { display: block; font: var(--fs-1) var(--mono); color: var(--dim); margin-top: 1px; }
.meta-tag { margin-left: 8px; font: var(--fs-1) var(--mono); color: var(--dim); border: 1px solid var(--rule); border-radius: var(--r-sm); padding: 1px 5px; white-space: nowrap; }
.meta-pick { gap: 10px; }
.meta-pick label { display: flex; align-items: center; gap: 8px; }
.meta-pick .flabel { border-left: 0; padding: 0; }
.meta-pick select { background: var(--bg); color: var(--ink); border: 1px solid var(--rule); border-radius: var(--r);
  font: var(--fs-2) var(--body); padding: 6px 8px; max-width: 70vw; }

/* Top-ball placings: the rank is the point, so it is the biggest thing. */
.badge.is-owner { position: relative; }
.badge-rank {
  margin-left: auto; padding-left: 8px; flex-shrink: 0;
  font: 700 var(--fs-5)/1 var(--cond); color: var(--mute); font-variant-numeric: tabular-nums;
}
.badge.is-top .badge-rank { color: var(--ink); }
.badge.is-first .badge-rank { color: var(--gold); }

.thumb.is-blank { display: inline-block; width: 26px; height: 26px; border-radius: 50%; background: var(--panel); }

@media (min-width: 700px) { .pane .wrap table { min-width: 0; } }
.pane td.stars { color: var(--acc-amber); letter-spacing: 1px; }

/* Profiles read centred on wide screens. */
#view.is-centered { max-width: 1240px; margin-left: auto; margin-right: auto; }
@media (min-width: 1300px) { #view.is-centered { border-left: 1px solid var(--rule); border-right: 1px solid var(--rule); } }
a.alliance-link { color: inherit; text-decoration: none; display: inline-block; }
a.alliance-link:hover .alliance-name { color: var(--gold); }
td b.up { color: var(--good); } td b.down { color: var(--bad); }
.hero-patch { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 10px; margin: 10px 0 0; font-size: var(--fs-3); }
/* `.flabel` is sticky with a filled ground for the scrolling filter strips;
   here it is a caption, and the fill drew a box around it on the hero. */
.hero-patch .flabel { border-left: 0; padding: 0; color: var(--mute); position: static; background: none; }
/* A date never breaks at its hyphens: at 375px "since 2026-09-20" was
   splitting into "since 2026-09-" and "20". The group wraps instead. */
.hero-patch span:last-child { color: var(--mute); font-size: var(--fs-2); white-space: nowrap; }
.alliance-dot { display: inline-block; width: 14px; height: 14px; border-radius: 50%; background: var(--dim); flex-shrink: 0; }
.alliance-desc { color: var(--mute); max-width: 640px; margin: 4px 0 10px; }
tr.clickable { cursor: pointer; }

/* Strongest balls strip on the profile overview. */
.strong-row { display: grid; grid-auto-flow: column; grid-auto-columns: 150px; gap: 10px;
  overflow-x: auto; overscroll-behavior-x: contain; padding: 2px 16px 12px; scroll-snap-type: x proximity; }
.strong-card { display: flex; flex-direction: column; align-items: center; gap: 4px; text-align: center;
  padding: 12px 10px; border: 1px solid var(--rule); border-radius: var(--r); background: var(--panel);
  color: var(--ink); text-decoration: none; scroll-snap-align: start; }
.strong-card:hover { border-color: var(--gold); }
.strong-card .thumb { width: 64px; height: 64px; }
.strong-name { font-size: var(--fs-2); line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.strong-cp { font: 600 var(--fs-3) var(--mono); }
.strong-types { justify-content: center; }
.strong-item { display: flex; align-items: center; gap: 5px; font: var(--fs-1) var(--mono); color: var(--mute); }
.strong-item .thumb { width: 20px; height: 20px; }
.strong-since { font: var(--fs-1) var(--mono); color: var(--dim); }
.effect-term { background: none; border: 0; padding: 0; font: inherit; color: var(--gold);
  text-decoration: underline dotted; text-underline-offset: 3px; cursor: pointer; }
.alliance-dot.is-small { width: 10px; height: 10px; }
tr.has-ally-color td:first-child { box-shadow: inset 3px 0 0 var(--ally); }
tr.has-ally-color .name { color: color-mix(in srgb, var(--ally) 55%, var(--ink)); font-weight: 600; }
.territory-chart { width: 100%; height: auto; max-height: 220px; display: block; padding: 0 16px 12px; box-sizing: border-box; }
.territory-chart polyline { fill: none; stroke: var(--gold); stroke-width: 2; vector-effect: non-scaling-stroke; }
.territory-chart text { fill: var(--dim); font: var(--fs-1) var(--mono); }
.ball-stats b.up { color: var(--good); } .ball-stats b.down { color: var(--bad); }

/* Raids: effect cards and Mymic pages. */
/* Raids → Effects: a ruled list, one effect per row. */
.fx-tools { display: flex; align-items: center; gap: 16px; padding: 12px var(--gutter); border-bottom: 1px solid var(--rule); }
.fx-search { flex: 0 1 340px; min-width: 0; box-sizing: border-box; background: var(--panel); color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--r); font: var(--fs-3) var(--body); padding: 8px 10px; }
.fx-search:focus { outline: none; border-color: var(--gold); }
.fx-count-wrap { display: flex; align-items: center; gap: 6px; margin-left: auto; }

/* The sentence above the pairing table. */
.item-pairing-lead {
  padding: 0 16px 10px; font: var(--fs-2)/1.6 var(--body); color: var(--mute); max-width: 78ch;
}
.item-pairing-lead b { color: var(--ink); }

/* ---------- what to bring to a boss ---------- */
.bm { margin: 14px 0 4px; }
.bm .boss-col-title { margin-bottom: 8px; }
.bm-lead { font: var(--fs-2)/1.5 var(--body); color: var(--ink); margin-bottom: 8px; }
.bm-lead.is-bad { color: var(--bad); }
.bm-line { display: flex; flex-direction: column; gap: 5px; margin-bottom: 10px; }
.bm-label {
  font: 700 var(--fs-1) var(--cond); letter-spacing: .12em; text-transform: uppercase;
  color: var(--mute);
}
.bm-run { display: flex; flex-wrap: wrap; gap: 5px; }
.bm-none { font: var(--fs-2) var(--mono); color: var(--dim); }
.bm-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 8px 3px 5px; border: 1px solid var(--rule); border-radius: 999px;
  background: var(--panel);
}
.bm-chip b { font: 600 var(--fs-1) var(--mono); }
.bm-chip.is-good b { color: var(--good); }
.bm-chip.is-bad b { color: var(--bad); }

/* ---------- every ball ---------- */
.dex-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  border-top: 1px solid var(--rule);
}
.dex-card {
  display: flex; align-items: center; gap: 10px; padding: 9px 14px;
  background: var(--bg); color: inherit; text-decoration: none; min-width: 0;
  border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
}
.dex-card:hover { background: var(--panel); }
.dex-card .thumb { width: 34px; height: 34px; flex: none; border-radius: 50%; }
.dex-body { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.dex-name { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.dex-name span {
  font: 500 var(--fs-2) var(--body); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dex-skin { font: var(--fs-1) var(--mono); color: var(--dim); font-style: normal; }
.dex-marks { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.dex-rarity {
  font: 700 var(--fs-1) var(--cond); letter-spacing: .1em; text-transform: uppercase; color: var(--mute);
}
.dex-rarity.is-legendary { color: var(--gold); }
.dex-rarity.is-mythical { color: var(--bad); }

@media (max-width: 640px) {
  .dex-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
}

/* ---------- the equipment dex ---------- */
.eq-filter {
  font: var(--fs-2) var(--body); color: var(--ink); background: var(--panel);
  border: 1px solid var(--rule); border-radius: var(--r-sm); padding: 5px 8px;
}
.eq-filter:focus { outline: none; border-color: var(--gold); }
/* Borders on the cards, not a grey sheet behind them: auto-fill leaves empty
   cells, and a background showing through them reads as a broken panel when a
   filter cuts the list to one. */
.eq-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.eq-card {
  display: flex; flex-direction: column; gap: 8px;
  padding: 13px 15px 15px; background: var(--bg);
  border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
}
.eq-head { display: flex; align-items: center; gap: 10px; }
.eq-head .thumb { width: 38px; height: 38px; flex: none; border-radius: var(--r-sm); }
.eq-titles { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.eq-name {
  font: 600 var(--fs-3) var(--body); color: var(--ink); text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.eq-name:hover { color: var(--gold); }
.eq-marks { display: flex; align-items: center; gap: 6px; }
.eq-tier { font: 600 var(--fs-1) var(--mono); color: var(--gold); }
.eq-stats { display: flex; gap: 12px; font: var(--fs-1) var(--mono); color: var(--mute); }
.eq-effect { font: var(--fs-2)/1.5 var(--body); color: var(--ink); }
.eq-effect.is-unknown { color: var(--dim); font-style: italic; }
.eq-req { font: var(--fs-1) var(--mono); color: var(--mute); }
.eq-from { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: auto; padding-top: 4px; }
.eq-from-label {
  font: 700 var(--fs-1) var(--cond); letter-spacing: .12em; text-transform: uppercase;
  color: var(--mute); width: 100%;
}
.eq-chance { font: var(--fs-1) var(--mono); color: var(--dim); font-style: normal; }

@media (max-width: 640px) {
  .fx-tools { flex-wrap: wrap; gap: 8px; }
  .fx-search { flex: 1 1 100%; }
  .eq-grid { grid-template-columns: minmax(0, 1fr); }
}
.fx-count { font: var(--fs-2) var(--mono); color: var(--mute); white-space: nowrap; }
.fx-row { display: grid; grid-template-columns: minmax(200px, 250px) minmax(0, 1fr) minmax(150px, 240px);
  column-gap: 24px; row-gap: 6px; align-items: start; padding: 10px 16px; border-bottom: 1px solid var(--hairline-2); }
.fx-row[hidden] { display: none; }
.fx-row:not(.fx-head):hover { background: var(--row); }
.fx-head { background: var(--panel); border-bottom: 1px solid var(--rule); padding-top: 9px; padding-bottom: 9px;
  font: 700 var(--fs-1) var(--body); letter-spacing: 0.06em; text-transform: uppercase; color: var(--mute); }
.fx-name { display: flex; align-items: center; gap: 10px; font-weight: 600; font-size: var(--fs-3); color: var(--ink); min-width: 0; }
.fx-name .fx-icon { width: 28px; height: 28px; border-radius: var(--r); }
.fx-icon.is-empty { display: block; visibility: hidden; }
.fx-desc { margin: 0; padding-top: 5px; color: var(--mute); font-size: var(--fs-3); line-height: 1.5; }
.fx-mymics { display: flex; flex-wrap: wrap; gap: 6px 12px; padding-top: 3px; }
.fx-mymic { display: inline-flex; align-items: center; gap: 6px; color: var(--ink); text-decoration: none; font-size: var(--fs-2); }
.fx-mymic .thumb { width: 22px; height: 22px; border-radius: var(--r-sm); }
.fx-mymic:hover span { color: var(--gold); }
.fx-none { color: var(--dim); font: var(--fs-2) var(--mono); padding-top: 3px; }
/* Boss pages: no Mymics column; inside a boss column each row stacks. */
.fx.is-boss .fx-row { grid-template-columns: minmax(200px, 250px) minmax(0, 1fr); }
.fx.is-boss .fx-row:last-child { border-bottom: 0; }
.fx.is-stacked .fx-row { grid-template-columns: minmax(0, 1fr); padding: 10px 0; }
.fx.is-stacked .fx-row:hover { background: none; }
.fx.is-stacked .fx-desc { padding-left: 38px; padding-top: 0; }
.fx.is-boss .meta-tag { margin-left: 0; }
@media (max-width: 760px) {
  .fx.is-boss .fx-row { grid-template-columns: minmax(0, 1fr); }
  .fx-tools { flex-wrap: wrap; gap: 8px; }
  .fx-search { flex: 1 1 100%; }
  .fx-count-wrap { margin-left: 0; }
  .fx-head { display: none; }
  .fx-row { grid-template-columns: minmax(0, 1fr); padding: 12px 16px; }
  .fx-desc, .fx-mymics { padding-left: 38px; padding-top: 0; }
  .fx-mymics.is-empty { display: none; }
}

.mymic-lore { color: var(--mute); max-width: 760px; padding: 0 16px; font-style: italic; line-height: 1.5; }

/* Phones: five nav items no longer fit beside the brand, and the fifth
   (Raids) was hidden off the edge. Brand and search share the first row; the
   nav gets a full row of its own with every item visible. */
@media (max-width: 640px) {
  .top { flex-wrap: wrap; }
  .top .brand { order: 1; }
  .top .omni { order: 2; flex: 1 1 0; min-width: 0; margin-left: 0; }
  .top .omni input { width: 100%; box-sizing: border-box; }
  .top .nav { order: 3; flex: 1 1 100%; min-width: 0; border-top: 1px solid var(--rule); }
  .top .nav a { flex: 1 1 auto; display: flex; justify-content: center; align-items: center; padding: 10px 4px; font-size: var(--fs-2); letter-spacing: 0.04em; }

  /* The results panel escapes the search box.
     Brand and search share a row, which leaves the box 160px wide on a 320px
     phone — fine for typing into, too narrow to read a result in: "Firecracker
     China" had nowhere to go. Making .omni static hands the panel to .top as
     its containing block, so it spans the masthead with a gutter instead, and
     drops below the whole header rather than overlapping the nav row. */
  .top .omni { position: static; }
  .omni-results { left: 8px; right: 8px; top: 100%; max-width: none; }
}
/* Phones: the "effect not counted" tag crowds the item name out of a narrow
   table; a small marker keeps the signal, the full text stays in the tooltip. */
@media (max-width: 640px) {
  .meta-tag { font-size: 0; padding: 0 4px; margin-left: 6px; }
  .meta-tag::before { content: "*"; font-size: var(--fs-1); }
}

/* Multi-boss encounters: one column per boss. */
.boss-cols { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0; margin: 4px 16px 18px;
  border: 1px solid var(--rule); border-radius: var(--r); background: var(--panel); }
.boss-col { padding: 14px 14px 16px; min-width: 0; }
.boss-col + .boss-col { border-left: 1px solid var(--rule); }
.boss-col-head { display: flex; align-items: center; gap: 12px; margin-bottom: 6px; }
.boss-col-head .thumb { width: 64px; height: 64px; }
.boss-col-head h2 { margin: 0 0 6px; font: 700 var(--fs-5) var(--cond); letter-spacing: .04em; text-transform: uppercase; }
.boss-col .ball-stats { padding: 6px 0 4px; }
.boss-col-title { font: 600 var(--fs-2) var(--cond); letter-spacing: .1em; text-transform: uppercase; color: var(--mute); margin: 14px 0 8px; display: flex; align-items: center; gap: 6px; }
.boss-drops { display: grid; gap: 6px; padding: 0 16px 12px; }
.boss-col .boss-drops { padding: 0; }
.boss-drop { display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; column-gap: 8px; align-items: center;
  color: var(--ink); text-decoration: none; }
.boss-drop .thumb { grid-row: span 2; }
.boss-drop:hover span:first-of-type { color: var(--gold); }
@media (max-width: 900px) {
  .boss-cols { grid-template-columns: minmax(0, 1fr); }
  .boss-col + .boss-col { border-left: 0; border-top: 1px solid var(--rule); }
}

/* First recorded clear: gold, on boss pages and player badges. */
.badge.is-first-clear { border-color: var(--acc-amber); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--acc-amber) 35%, transparent); }
.badge.is-first-clear .badge-kind { color: var(--acc-amber); }
.first-clear { margin: 6px 16px 14px; padding: 12px 14px; border: 1px solid var(--acc-amber); border-radius: var(--r);
  background: color-mix(in srgb, var(--acc-amber) 8%, var(--panel)); }
.first-clear-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }
.first-clear-badge { font: 700 var(--fs-2) var(--cond); letter-spacing: .12em; text-transform: uppercase; color: var(--bg);
  background: var(--acc-amber); border-radius: var(--r-sm); padding: 3px 8px; }
.first-clear-crew { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px; font-size: var(--fs-3); }
.first-clear-crew a { color: var(--ink); text-decoration: none; font-weight: 600; }
.first-clear-crew a:hover { color: var(--acc-amber); }
/* The site's own earliest, when the game's announced first sits above it: the
   same block, stepped back so the amber one reads as the record that counts. */
.first-clear.is-quiet { border-color: var(--rule); background: var(--panel); }
.first-clear.is-quiet .first-clear-badge { background: var(--rule); color: var(--ink); }
.first-clear.is-quiet .first-clear-crew a:hover { color: var(--gold); }
/* A record that starts after the boss did. The amber says "this is the first
   clear"; nothing here should, so the block loses the amber entirely and the
   caveat sits above the names rather than beside them. */
.first-clear.is-provisional { border-color: var(--rule); background: var(--panel); }
.first-clear.is-provisional .first-clear-badge { background: var(--rule); color: var(--mute); }
.first-clear.is-provisional .first-clear-crew a:hover { color: var(--ink); }
.first-clear-warn { margin: 10px 0 0; font-size: var(--fs-2); line-height: 1.5; color: var(--mute); }
.first-clear-warn b { color: var(--ink); }

/* Touch screens: small controls keep their look but get a finger-sized hit
   area. The (i) marker is 14px drawn; the invisible ring makes it ~34px. */
@media (pointer: coarse) {
  /* Every (i), not only the ones that happen to be buttons: the same marker
     is rendered as a link in places, and a 14px tap target is 14px either way.
     -13 rather than -10 so the tappable circle clears 40px. */
  .info { position: relative; }
  .info::after { content: ""; position: absolute; inset: -13px; border-radius: 50%; }
  a.backlink { display: inline-block; padding: 8px 0; }
  /* Padded sideways as well: "LIS" and "Help" are 21 and 28 pixels wide, which
     is the word, not the styling — and the separators put the next link right
     beside them. */
  footer a { display: inline-block; padding: 6px 4px; }

  /* Measured with elementFromPoint rather than by eye: from each control's
     centre, how far can a finger land and still hit it. Everything below came
     back under 30px on a phone. */
  .mini-row { padding-top: 9px; padding-bottom: 9px; }
  .copy-link { width: 36px; height: 36px; }
  .dv-chip, .fx-modes button, .pvp-pick-ball { min-height: 34px; }

  /* A chip in a row of chips: pad it and take the space back, so the tile
     around it keeps its height. */
  .fx-mymic { padding-top: 8px; padding-bottom: 8px; margin-top: -8px; margin-bottom: -8px; }

  /* A term inside running prose is the one case left where a control is
     under 30px, and it stays that way on purpose. Padding it makes the boxes
     of consecutive lines overlap, and the topmost wins the tap — so a term
     would start swallowing the one above it, which is worse than small. It is
     also the case the sizing guidance itself exempts, for this exact reason.
     What does help is room: the effect prose is given more line spacing, so
     neighbouring terms are further apart even though each is the size of its
     own word. */
  .fx-desc, .fx-tile-desc { line-height: 1.95; }
}

/* Ball hover card (meta page and anywhere ballLink is used). */
.ball-link { text-decoration: none; }
.ball-link:hover, .ball-link:focus-visible { color: var(--gold); text-decoration: underline; text-underline-offset: 3px; }
.ball-pop { position: fixed; z-index: 90; width: 250px; padding: 10px 12px; background: var(--panel);
  border: 1px solid var(--gold); border-radius: var(--r); box-shadow: var(--shadow-2); pointer-events: none; }
.ball-pop-top { display: flex; gap: 10px; align-items: center; }
.ball-pop-top > img { width: 64px; height: 64px; object-fit: contain; flex-shrink: 0; }
.ball-pop-name { display: block; font-size: var(--fs-3); line-height: 1.25; margin-bottom: 3px; }
.ball-pop-rarity { display: block; font: 600 var(--fs-1) var(--cond); letter-spacing: .1em; text-transform: uppercase; color: var(--mute); margin-bottom: 5px; }
.ball-pop-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--rule); }
.ball-pop-stats span { display: flex; flex-direction: column; }
.ball-pop-stats b { font: 600 var(--fs-2) var(--mono); }
.ball-pop-stats i { font: normal var(--fs-1) var(--mono); color: var(--dim); }
.ball-pop-none { grid-column: 1 / -1; color: var(--mute); font-size: var(--fs-2); }
.strip li.is-link { cursor: pointer; }
.strip li.is-link:hover .who { color: var(--gold); }
.strip .who-link { color: inherit; text-decoration: none; }
.strip .who-link:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; border-radius: var(--r-sm); }

/* Podium places: 1st gold, 2nd silver, 3rd bronze — clear and owner badges alike. */
.badge.is-first-clear.is-place-2 { border-color: #B8C2CC; box-shadow: inset 0 0 0 1px rgba(184,194,204,.3); }
.badge.is-first-clear.is-place-2 .badge-kind { color: var(--medal-silver); }
.badge.is-first-clear.is-place-3 { border-color: #B87A4B; box-shadow: inset 0 0 0 1px rgba(184,122,75,.3); }
.badge.is-first-clear.is-place-3 .badge-kind { color: var(--medal-bronze); }
/* No medal where the record cannot show a place. The badge stays — the clear
   happened and they were in it — but gold on a claim we cannot stand behind is
   the part that was wrong. */
.badge.is-first-clear.is-provisional { border-color: var(--rule); box-shadow: none; }
.badge.is-first-clear.is-provisional .badge-kind { color: var(--mute); }
.first-clear-row { display: grid; grid-template-columns: auto 1fr; gap: 4px 10px; align-items: center; padding: 10px 0 0; margin-top: 10px; border-top: 1px solid color-mix(in srgb, var(--acc-amber) 25%, transparent); }
.first-clear-row .first-clear-crew { grid-column: 1 / -1; margin-top: 2px; }
.place-tag { font: 700 var(--fs-2) var(--cond); letter-spacing: .08em; text-transform: uppercase; border-radius: var(--r-sm); padding: 2px 7px; color: var(--bg); background: var(--acc-amber); }
.place-tag.is-2 { background: #B8C2CC; } .place-tag.is-3 { background: #B87A4B; }
.place-cell { font: 700 var(--fs-2) var(--mono); }
.place-cell.is-1 { color: var(--acc-amber); } .place-cell.is-2 { color: #B8C2CC; } .place-cell.is-3 { color: #C98C5C; }
.item-link { text-decoration: none; }
.item-link:hover, .item-link:focus-visible { color: var(--gold); text-decoration: underline; text-underline-offset: 3px; }
.strong-card { cursor: pointer; }
a.strong-name { color: var(--ink); text-decoration: none; }
a.strong-name:hover { color: var(--gold); }
.strong-item i { font-style: normal; color: var(--dim); }
.strong-item .item-link { color: var(--mute); }
/* Item early-owner badges past the podium: plain, still marked as an achievement. */
.badge.is-first-clear.is-place-4 { border-color: var(--rule); box-shadow: none; }
.badge.is-first-clear.is-place-4 .badge-kind { color: var(--mute); }

/* Privacy policy and other plain-text pages. */
.legal { max-width: 760px; padding: 28px var(--gutter) 40px; }
.legal h1 { font: 700 var(--fs-5) var(--cond); letter-spacing: .03em; text-transform: uppercase; margin: 0 0 4px; }
.legal-date { font: var(--fs-2) var(--mono); color: var(--dim); margin: 0 0 18px; }
.legal h2 { font: 700 var(--fs-3) var(--cond); letter-spacing: .1em; text-transform: uppercase; color: var(--ink); margin: 26px 0 8px; }
.legal p { color: var(--mute); line-height: 1.65; margin: 0 0 10px; font-size: var(--fs-3); }
.legal a:not(.btn) { color: var(--gold); }
.legal-choices { margin-top: 18px; background: var(--panel); color: var(--ink); border: 1px solid var(--rule);
  border-radius: var(--r); padding: 8px 14px; font: 600 var(--fs-2) var(--body); cursor: pointer; }
.legal-choices:hover { border-color: var(--gold); }

/* Room for the one ad unit, only while it is on (see lib/ads.js). */
body.has-ad-anchor footer { padding-bottom: 120px; }
@media (max-width: 760px) { body.has-ad-anchor footer { padding-bottom: 80px; } }
@media (min-width: 1500px) {
  body.has-ad-rail-right { padding-right: 320px; }
  body.has-ad-rail-left { padding-left: 320px; }
}

/* ==========================================================================
   Motion and polish
   Short, eased and small: enough to say "this responds", never enough to be
   in the way of someone clicking through tables. Every transition and
   animation below switches off under prefers-reduced-motion.
   ========================================================================== */

:root { --ease: cubic-bezier(.22, .9, .3, 1); }

/* Keyboard focus is always visible; mouse clicks do not leave a ring. */
:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
button:focus:not(:focus-visible), a:focus:not(:focus-visible) { outline: none; }

/* Images fade in as they arrive instead of popping. */
.art-img { opacity: 0; }
.art-img.is-loaded { opacity: 1; }

/* A thin progress line while a page loads (only if it takes a moment). */
body.is-loading::before {
  content: ""; position: fixed; top: 0; left: 0; height: 2px; width: 36%; z-index: 100;
  background: var(--gold); pointer-events: none; opacity: 0;
  animation: loadbar-in 1ms linear 220ms forwards, loadbar 1.1s var(--ease) 220ms infinite;
}
@keyframes loadbar-in { to { opacity: 1; } }
@keyframes loadbar { from { transform: translateX(-100%); } to { transform: translateX(290%); } }

/* Copy-link button beside a page title. */
.title-row { display: flex; align-items: center; gap: 10px; min-width: 0; }
.title-row h1 { min-width: 0; }
.copy-link {
  flex: none; display: inline-grid; place-items: center; width: 30px; height: 30px;
  background: none; border: 1px solid transparent; border-radius: var(--r); color: var(--dim); cursor: pointer;
}
.copy-link:hover { color: var(--gold); border-color: var(--rule); background: var(--row); }

/* Toast. */
.toast {
  position: fixed; left: 50%; bottom: 24px; z-index: 120; pointer-events: none;
  transform: translate(-50%, 12px); opacity: 0;
  background: var(--panel); color: var(--ink); border: 1px solid var(--rule); border-radius: var(--r);
  padding: 8px 14px; font: 600 var(--fs-2) var(--body);
}
.toast.is-visible { opacity: 1; transform: translate(-50%, 0); }

/* Back to top. */
.to-top {
  position: fixed; right: 16px; bottom: 16px; z-index: 50; width: 40px; height: 40px;
  display: grid; place-items: center; cursor: pointer;
  background: var(--panel); color: var(--mute); border: 1px solid var(--rule); border-radius: var(--r);
  opacity: 0; transform: translateY(8px); pointer-events: none;
}
.to-top.is-visible { opacity: 1; transform: none; pointer-events: auto; }
.to-top:hover { color: var(--gold); border-color: var(--gold); }
body.has-ad-anchor .to-top { bottom: 112px; }
@media (max-width: 760px) { body.has-ad-anchor .to-top { bottom: 72px; } }

/* Search: "/" hint, a heading for recent pages, and a panel that drops in. */
.omni { display: flex; align-items: center; }
.omni-kbd {
  font: 600 var(--fs-1) var(--mono); color: var(--dim); border: 1px solid var(--rule); border-radius: var(--r-sm);
  padding: 1px 6px; margin-right: 12px; pointer-events: none;
}
.omni input:focus + .omni-kbd { opacity: 0; }
@media (pointer: coarse), (max-width: 640px) { .omni-kbd { display: none; } }
.omni-head {
  margin: 0; padding: 8px 12px 6px; font: 700 var(--fs-1) var(--cond); letter-spacing: .14em;
  text-transform: uppercase; color: var(--dim); border-bottom: 1px solid var(--rule);
}

@media (prefers-reduced-motion: no-preference) {
  .art-img { transition: opacity 260ms ease-out; }

  .nav a, .subnav button, .leagues button, .brand {
    transition: color 160ms ease, background-color 160ms ease, box-shadow 220ms var(--ease);
  }
  .brand b { transition: letter-spacing 260ms var(--ease); display: inline-block; }
  .brand:hover b { letter-spacing: 0.12em; }

  tbody td { transition: background-color 120ms ease, color 120ms ease; }
  tbody tr.clickable .thumb, .strip li .thumb, .fx-mymic .thumb, .omni-row .thumb,
  .boss-drop .thumb, .cellflex .thumb { transition: transform 200ms var(--ease); }
  tbody tr.clickable:hover .thumb, .strip li.is-link:hover .thumb, .fx-mymic:hover .thumb,
  .omni-row:hover .thumb, .omni-row.is-active .thumb, .boss-drop:hover .thumb { transform: scale(1.12); }

  .strip li { transition: background-color 140ms ease; }
  .strip li .who { transition: transform 200ms var(--ease), color 140ms ease; }
  .strip li.is-link:hover .who { transform: translateX(3px); color: var(--ink); }

  a.plain, footer a, .backlink, .fx-mymic span, .effect-term, .item-link, .alliance-link {
    transition: color 140ms ease;
  }
  .backlink { display: inline-block; transition: color 140ms ease, transform 200ms var(--ease); }
  .backlink:hover { transform: translateX(-3px); }

  .fx-row, .omni-row { transition: background-color 120ms ease; }
  .omni-results:not([hidden]) { animation: drop-in 160ms var(--ease) both; }
  @keyframes drop-in { from { opacity: 0; transform: translateY(-4px); } }

  .copy-link, .to-top { transition: color 140ms ease, border-color 140ms ease, background-color 140ms ease,
                                   opacity 200ms ease, transform 220ms var(--ease); }
  .toast { transition: opacity 180ms ease, transform 220ms var(--ease); }

  .info { transition: color 140ms ease, border-color 140ms ease, transform 160ms var(--ease); }
  .info:hover { transform: scale(1.12); }

  button:active:not(:disabled) { transform: translateY(1px); }

  /* The big art on a ball, item or boss page leans in a little on hover. */
  .ball-hero > img { transition: transform 400ms var(--ease); }
  .ball-hero > img:hover { transform: rotate(-4deg) scale(1.04); }

  .badge { transition: border-color 140ms ease, background-color 140ms ease, transform 200ms var(--ease); }
  button.badge:hover { transform: translateY(-2px); }

  /* ---- tiles ----

     Half the site is tiles now — raid bosses, raid effects, the calculator's
     balls, the account's stat blocks — and none of them moved. Same three
     moves everywhere so it reads as one behaviour rather than four ideas:
     lift a little, let the art lean in, warm the border.

     Small numbers on purpose. A 2px lift over 180ms is the difference
     between a page that feels alive and one that feels like it is showing
     off; 6px over 400ms is the second thing. */

  .mt, .fx-tile, .pvp-tile.is-bench, .pvp-choice, .stile,
  .pair-ball, .early-badge, .isel-button, .fx-modes button {
    transition: transform 180ms var(--ease), border-color 160ms ease,
                background-color 160ms ease, box-shadow 220ms var(--ease);
  }
  .mt:hover, .fx-tile:hover, .pvp-choice:hover, .pvp-tile.is-bench:hover {
    transform: translateY(-2px);
  }
  .mt:hover { box-shadow: var(--shadow-1); }

  .mt img, .fx-tile .fx-icon, .pvp-tile .thumb {
    transition: transform 220ms var(--ease);
  }
  .mt:hover img { transform: scale(1.07) rotate(-2deg); }
  .fx-tile:hover .fx-icon { transform: scale(1.1) rotate(-3deg); }
  .pvp-tile.is-bench:hover .thumb { transform: scale(1.09); }

  /* A tile grid arriving. Each tile comes in a beat after the one before it,
     which is the whole trick — the wave is what makes a grid feel handled
     rather than dumped. Capped at twelve because past that the last tile is
     waiting half a second for its turn, and only ever on first paint. */
  .mt-grid > *, .fx-grid > *, .comparts > * {
    animation: tile-in 320ms var(--ease) both;
  }
  .mt-grid > :nth-child(1),  .fx-grid > :nth-child(1)  { animation-delay: 0ms; }
  .mt-grid > :nth-child(2),  .fx-grid > :nth-child(2)  { animation-delay: 26ms; }
  .mt-grid > :nth-child(3),  .fx-grid > :nth-child(3)  { animation-delay: 52ms; }
  .mt-grid > :nth-child(4),  .fx-grid > :nth-child(4)  { animation-delay: 78ms; }
  .mt-grid > :nth-child(5),  .fx-grid > :nth-child(5)  { animation-delay: 104ms; }
  .mt-grid > :nth-child(6),  .fx-grid > :nth-child(6)  { animation-delay: 130ms; }
  .mt-grid > :nth-child(7),  .fx-grid > :nth-child(7)  { animation-delay: 156ms; }
  .mt-grid > :nth-child(8),  .fx-grid > :nth-child(8)  { animation-delay: 182ms; }
  .mt-grid > :nth-child(9),  .fx-grid > :nth-child(9)  { animation-delay: 208ms; }
  .mt-grid > :nth-child(10), .fx-grid > :nth-child(10) { animation-delay: 234ms; }
  .mt-grid > :nth-child(11), .fx-grid > :nth-child(11) { animation-delay: 260ms; }
  .mt-grid > :nth-child(n+12), .fx-grid > :nth-child(n+12) { animation-delay: 286ms; }
  @keyframes tile-in { from { opacity: 0; transform: translateY(8px) scale(.985); } }

  /* ---- type chips ----
     They are the site's smallest recurring object and they appear in their
     dozens, so they get the smallest possible acknowledgement. */
  .reach-chips .chip, .chips .chip { transition: transform 150ms var(--ease); }
  .reach-chips:hover .chip:hover, .tsel-face .chip { transform: translateY(-1px); }

  /* ---- the answer column ----
     A verdict that changes while you drag balls around should say so. */
  .pvp-verdict b, .pvp-outlook b { transition: color 200ms ease; }
  .pvp-slot { transition: border-color 180ms ease, background-color 180ms ease; }

  /* ---- the rarity pip, and anything else that is a small round token ---- */
  .rar-pip, .orb { transition: transform 160ms var(--ease); }
  .isel-row:hover .rar-pip { transform: scale(1.18); }

  /* ---- section headings ----
     The rule above a section draws itself in when the section arrives. */
  #view > .section-title { animation: rule-in 420ms var(--ease) both; }
  @keyframes rule-in { from { opacity: 0; transform: translateY(4px); } }

  /* ---- tables ----

     The tiles got all of this and the tables got none, which is backwards:
     the tables are what most of the site is. But a table is not a tile grid
     and must not behave like one — a row that lifts breaks the rules it sits
     between, and forty rows arriving in sequence is a page that will not
     settle. So: rows warm rather than lift, and only the first eight are
     staggered, over a quarter of a second, because by row nine the eye has
     stopped watching the entrance and started reading. */
  #view tbody tr { transition: background-color 130ms ease; }
  #view tbody tr.clickable:hover td:first-child { transform: translateX(2px); }
  #view tbody tr.clickable td:first-child { transition: transform 160ms var(--ease); }
  #view tbody tr:nth-child(-n+8) { animation: row-in 260ms var(--ease) both; }
  #view tbody tr:nth-child(2) { animation-delay: 30ms; }
  #view tbody tr:nth-child(3) { animation-delay: 60ms; }
  #view tbody tr:nth-child(4) { animation-delay: 90ms; }
  #view tbody tr:nth-child(5) { animation-delay: 120ms; }
  #view tbody tr:nth-child(6) { animation-delay: 150ms; }
  #view tbody tr:nth-child(7) { animation-delay: 180ms; }
  #view tbody tr:nth-child(8) { animation-delay: 210ms; }
  @keyframes row-in { from { opacity: 0; transform: translateY(3px); } }

  /* ---- the tier list ----
     Bands, top to bottom, so the ranking reads as being dealt out in order.
     The label block is the part that carries the band colour, so it is the
     part worth moving. */
  .tier { animation: band-in 300ms var(--ease) both; }
  .tier:nth-of-type(2) { animation-delay: 45ms; }
  .tier:nth-of-type(3) { animation-delay: 90ms; }
  .tier:nth-of-type(4) { animation-delay: 135ms; }
  .tier:nth-of-type(n+5) { animation-delay: 180ms; }
  @keyframes band-in { from { opacity: 0; transform: translateY(6px); } }
  .tl-item { transition: transform 160ms var(--ease), border-color 160ms ease, background-color 160ms ease; }
  .tl-item.is-link:hover { transform: translateY(-2px); }

  /* ---- the calculator's board ----
     Slots settle in a row, and the row greys while a newer answer is on its
     way — the one piece of motion here that is information rather than
     manners. */
  .pvp-board-row { animation: row-in 280ms var(--ease) both; }
  .pvp-board-row:nth-child(2) { animation-delay: 40ms; }
  .pvp-board-row:nth-child(3) { animation-delay: 80ms; }
  .pvp-board-row:nth-child(4) { animation-delay: 120ms; }
  .pvp-board-row:nth-child(5) { animation-delay: 160ms; }
  .pvp-outlook { transition: background-color 200ms ease, border-color 200ms ease, opacity 200ms ease; }
  .pvp-outlook.is-stale { opacity: .55; }
}

@media (prefers-reduced-motion: reduce) {
  .mt-grid > *, .fx-grid > *, .comparts > *, #view > .section-title,
  #view tbody tr, .tier, .pvp-board-row { animation: none; }
  .cmp-fill { transition: none; }
  body.is-loading::before { display: none; }
  .art-img { opacity: 1; }
}

/* ---------- accounts ---------- */

.acct {
  display: grid; place-items: center; width: 48px; flex: none; border-left: 1px solid var(--rule); color: var(--mute);
}
.acct:hover { color: var(--gold); background: var(--row); }
.acct img { width: 26px; height: 26px; border-radius: 50%; display: block; }
@media (max-width: 640px) { .top .acct { order: 2; } }

.account { max-width: 760px; padding: 26px var(--gutter) 48px; }
.account h1 { font: 700 var(--fs-5) var(--cond); letter-spacing: .03em; text-transform: uppercase; margin: 0 0 10px; }
.account .section-title { padding-left: 0; padding-right: 0; margin-top: 26px; }
.account-lede { color: var(--mute); font-size: var(--fs-3); line-height: 1.6; margin: 6px 0 14px; }
.account-note { color: var(--dim); font: var(--fs-2) var(--mono); line-height: 1.6; margin: 10px 0; }
.account-error { color: var(--bad); font-size: var(--fs-3); margin: 10px 0; }
.account-perks { margin: 0 0 18px; padding-left: 20px; color: var(--ink); line-height: 1.9; }

.btn {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer; text-decoration: none;
  background: var(--panel); color: var(--ink); border: 1px solid var(--rule); border-radius: var(--r);
  padding: 9px 16px; font: 600 var(--fs-3) var(--body);
}
.btn:hover { border-color: var(--gold); }
.btn:disabled { opacity: .5; cursor: default; }
.btn-primary { background: var(--gold); color: var(--bg); border-color: var(--gold); }
.btn-primary:hover { filter: brightness(1.08); }
.btn-discord { background: #5865F2; border-color: #5865F2; color: #fff; }
.btn-discord:hover { filter: brightness(1.1); border-color: #5865F2; }
.btn-quiet { background: none; }
.btn-small { padding: 3px 9px; font-size: var(--fs-2); }

.account-who { display: flex; align-items: center; gap: 10px; margin: 4px 0 6px; font-weight: 600; }
.account-who img { width: 32px; height: 32px; border-radius: 50%; }
.account-who .btn { margin-left: auto; }
.account-form { display: flex; gap: 8px; flex-wrap: wrap; }
.account-form input {
  flex: 1 1 240px; min-width: 0; background: var(--panel); color: var(--ink); border: 1px solid var(--rule);
  border-radius: var(--r); padding: 9px 12px; font: var(--fs-3) var(--body);
}
.account-form input:focus { border-color: var(--gold); outline: none; }
.account-steps { color: var(--ink); line-height: 2.1; padding-left: 20px; margin: 6px 0 14px; }
.account-code {
  font: 600 var(--fs-4) var(--mono); color: var(--gold); background: var(--panel); border: 1px solid var(--rule);
  border-radius: var(--r-sm); padding: 2px 8px; margin: 0 6px; letter-spacing: .06em;
}
.account-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 8px 0; }
.account-badges { display: grid; gap: 4px; }
.account-badge {
  display: flex; align-items: center; gap: 10px; padding: 6px 8px; border: 1px solid var(--rule);
  border-radius: var(--r); background: var(--panel);
}
.account-badge.is-chosen { border-color: color-mix(in srgb, var(--gold) 55%, var(--rule)); }
.account-badge .thumb { width: 26px; height: 26px; }
.account-badge-label { flex: 1; min-width: 0; font-size: var(--fs-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.account-bg-preview { height: 150px; overflow: hidden; border: 1px solid var(--rule); border-radius: var(--r); margin: 6px 0 10px; background: var(--row); }
.account-bg-preview img { width: 100%; height: 100%; object-fit: cover; opacity: .55; }
.account-bg-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(52px, 1fr)); gap: 6px; max-height: 280px; overflow: auto; padding: 2px; }
.account-bg-option {
  display: grid; place-items: center; aspect-ratio: 1; background: var(--panel); border: 1px solid var(--rule);
  border-radius: var(--r); cursor: pointer; padding: 4px; color: var(--mute); font: 600 var(--fs-1) var(--body);
}
.account-bg-option .thumb { width: 36px; height: 36px; border: 0; background: none; }
.account-bg-option.is-default { grid-column: span 2; aspect-ratio: auto; }
.account-bg-option:hover { border-color: var(--mute); }
.account-bg-option.is-chosen { border-color: var(--gold); box-shadow: inset 0 0 0 1px var(--gold); color: var(--gold); }
.account-save { position: sticky; bottom: 0; background: var(--bg); padding: 12px 0; border-top: 1px solid var(--rule); margin-top: 18px; }

.claimed-mark { color: var(--gold); display: inline-grid; place-items: center; flex: none; }
.customize-link { font: 600 var(--fs-2) var(--body); color: var(--mute); border: 1px solid var(--rule); border-radius: var(--r); padding: 4px 10px; }
.customize-link:hover { color: var(--gold); border-color: var(--gold); }
@media (prefers-reduced-motion: no-preference) {
  .acct, .btn, .account-bg-option, .account-badge, .customize-link { transition: color 140ms ease, border-color 140ms ease, background-color 140ms ease, filter 140ms ease; }
}
.account-or { display: flex; align-items: center; gap: 12px; color: var(--dim); font: var(--fs-2) var(--mono); margin: 16px 0; max-width: 420px; }
.account-or::before, .account-or::after { content: ""; flex: 1; border-top: 1px solid var(--rule); }
.account-h2 { font: 700 var(--fs-4) var(--cond); letter-spacing: .06em; text-transform: uppercase; margin: 14px 0 4px; }
.account-connected { color: var(--ink); font-size: var(--fs-3); }

.account-target { display: flex; align-items: center; gap: 14px; padding: 12px 14px; margin: 10px 0 6px;
  background: var(--panel); border: 1px solid var(--gold); border-radius: var(--r); max-width: 460px; }
.account-target .thumb { width: 56px; height: 56px; border: 0; background: none; }
.account-target b { display: block; font: 700 var(--fs-5) var(--cond); letter-spacing: .03em; text-transform: uppercase; }
.account-target-kind { display: block; font: var(--fs-2) var(--mono); color: var(--mute); }

.account-agree { display: flex; gap: 10px; align-items: flex-start; margin: 14px 0 4px; padding: 12px 14px;
  background: var(--panel); border: 1px solid var(--rule); border-radius: var(--r); font-size: var(--fs-3); line-height: 1.5; max-width: 560px; }
.account-agree input { margin-top: 3px; width: 16px; height: 16px; accent-color: var(--gold); flex: none; }
.account-agree a { color: var(--gold); }
.account-sent { color: var(--good); font-size: var(--fs-3); margin: 8px 0; }
.account-methods { display: grid; gap: 8px; }
.account-method { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; padding: 12px 14px;
  background: var(--panel); border: 1px solid var(--rule); border-radius: var(--r); }
.account-method > b { width: 80px; font-weight: 600; }
.account-method > div { flex: 1 1 280px; }
.account-method-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--mute); }
.account-keys { display: grid; gap: 6px; flex: 1 1 100%; }
.account-keys:empty { display: none; }
.account-key { display: flex; align-items: center; gap: 10px; font-size: var(--fs-3); }
.account-key > span:first-child { flex: 1; }
.account-key .account-note { margin: 0; }

.legal-intro { color: var(--ink) !important; font-size: var(--fs-3) !important; }
.legal-toc { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 14px 0 6px; padding: 12px 14px; background: var(--panel);
  border: 1px solid var(--rule); border-radius: var(--r); }
.legal-toc a { font-size: var(--fs-2); color: var(--mute) !important; }
.legal-toc a:hover { color: var(--gold) !important; }
.legal ul { color: var(--mute); line-height: 1.65; margin: 0 0 12px; padding-left: 20px; font-size: var(--fs-3); }
.legal li { margin: 4px 0; }
.legal h2 { scroll-margin-top: 60px; }
.legal-contact { margin-top: 28px !important; }
.btn-danger { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 45%, var(--rule)); background: none; }
.btn-danger:hover { border-color: var(--bad); }

.account-premium { padding: 16px; background: var(--panel); border: 1px solid var(--rule); border-radius: var(--r); max-width: 560px; }
.account-premium.is-active { border-color: var(--gold); }
.account-premium > b { font: 700 var(--fs-4) var(--cond); letter-spacing: .05em; text-transform: uppercase; }
.account-premium .account-perks { margin: 10px 0; }
.account-premium .account-note a { color: var(--gold); }
/* Premium members: a warm mark, set apart from the cyan that encodes state and numbers. */
.premium-badge { --premium: var(--medal-gold); display: inline-flex; align-items: center; gap: 5px; flex: none;
  font: 700 var(--fs-1) var(--cond); letter-spacing: .12em; text-transform: uppercase; color: var(--premium);
  border: 1px solid color-mix(in srgb, var(--premium) 60%, transparent); background: color-mix(in srgb, var(--premium) 12%, transparent);
  border-radius: var(--r-sm); padding: 2px 8px 2px 6px; line-height: 1.4; }
.premium-badge svg { flex: none; }
.account-who .premium-badge { margin-left: 2px; }

/* The one ad unit (lib/ads.js). */
.ad-anchor {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 45; display: flex; justify-content: center; align-items: center;
  padding: 6px 36px 6px; min-height: 62px; background: rgb(11 13 18 / 94%); border-top: 1px solid var(--rule);
}
@media (min-width: 760px) { .ad-anchor { min-height: 102px; } }
.ad-rail { position: fixed; top: 120px; z-index: 45; width: 300px; min-height: 600px; display: none; }
.ad-rail.is-right { right: 10px; } .ad-rail.is-left { left: 10px; }
@media (min-width: 1500px) { .ad-rail { display: block; } }
.ad-label { position: absolute; top: 3px; left: 8px; font: 600 var(--fs-1) var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--dim); }
.ad-rail .ad-label { position: static; display: block; margin-bottom: 4px; }
.ad-close { position: absolute; top: 4px; right: 6px; width: 26px; height: 26px; border: 1px solid var(--rule); border-radius: var(--r);
  background: var(--panel); color: var(--mute); cursor: pointer; font-size: var(--fs-4); line-height: 1; }
.ad-close:hover { color: var(--ink); border-color: var(--mute); }

/* Sign in (account page, signed out): one short column, the perks beside it on wide screens. */
.account.is-auth { display: grid; grid-template-columns: minmax(0, 400px); gap: 28px; max-width: 860px; }
@media (min-width: 900px) {
  .account.is-auth { grid-template-columns: minmax(0, 400px) minmax(0, 300px); gap: 56px; padding-top: 44px; }
  .auth-side { border-left: 1px solid var(--rule); padding-left: 28px; margin-top: 64px; align-self: start; }
}
@media (max-width: 899px) { .auth-side { border-top: 1px solid var(--rule); padding-top: 18px; } }
.auth-main h1 { margin-bottom: 4px; }
.auth-lede { color: var(--mute); font-size: var(--fs-3); line-height: 1.55; margin: 0 0 20px; }
.auth-h2 { font: 700 var(--fs-4) var(--cond); letter-spacing: .04em; text-transform: uppercase; margin: 0 0 6px; }
.auth-form { display: grid; gap: 8px; }
.auth-form input {
  width: 100%; min-width: 0; background: var(--panel); color: var(--ink); border: 1px solid var(--rule);
  border-radius: var(--r); padding: 10px 12px; font: var(--fs-3) var(--body);
}
.auth-form input:focus { border-color: var(--gold); outline: none; }
.auth-wide, .auth-method { width: 100%; justify-content: center; min-height: 42px; }
.auth-main .account-or { max-width: none; margin: 18px 0; }
.auth-methods { display: grid; gap: 8px; }
.auth-methods .account-error { margin: 0; }
.auth-method[aria-expanded="true"] { border-color: var(--gold); }
.auth-panel { padding: 12px; margin: -2px 0 4px; background: var(--row); border: 1px solid var(--rule); border-radius: var(--r); }
.auth-panel[hidden] { display: none; }
.auth-hint { color: var(--mute); font-size: var(--fs-2); line-height: 1.5; margin: 0 0 10px; }
.auth-fine { color: var(--dim); font-size: var(--fs-2); line-height: 1.55; margin: 18px 0 0; }
.auth-fine a { color: var(--mute); text-decoration: underline; text-underline-offset: 2px; }
.auth-fine a:hover { color: var(--gold); }
.auth-side-title { font: 600 var(--fs-1) var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--mute); margin: 0 0 12px; }
.auth-perks { margin: 0; }
.auth-perks dt { font-weight: 600; font-size: var(--fs-3); color: var(--ink); }
.auth-perks dd { margin: 2px 0 14px; color: var(--mute); font-size: var(--fs-2); line-height: 1.5; }
.account-who .btn + .btn { margin-left: 0; }

/* Help and Contact (views/support.js). */
.support-cta { margin: 4px 0 8px; }
.faq { border-top: 1px solid var(--rule); }
.faq-item { border-bottom: 1px solid var(--rule); }
.faq-item summary { cursor: pointer; list-style: none; padding: 12px 28px 12px 0; position: relative; color: var(--ink); font-weight: 600; font-size: var(--fs-3); }
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after { content: "+"; position: absolute; right: 4px; top: 10px; color: var(--mute); font: var(--fs-4) var(--mono); transition: transform 160ms ease; }
.faq-item[open] summary::after { transform: rotate(45deg); color: var(--gold); }
.faq-item summary:hover { color: var(--gold); }
.faq-item p { margin: 0 0 14px; }
.contact-form { display: grid; gap: 14px; max-width: 560px; margin-top: 18px; }
.contact-field { display: grid; gap: 6px; }
.contact-field > span { font: 600 var(--fs-1) var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--mute); }
.contact-field small, .contact-count { color: var(--dim); font-size: var(--fs-2); }
.contact-count { justify-self: end; margin-top: -8px; }
.contact-form input, .contact-form select, .contact-form textarea {
  width: 100%; box-sizing: border-box; background: var(--panel); color: var(--ink); border: 1px solid var(--rule);
  border-radius: var(--r); padding: 10px 12px; font: var(--fs-3) var(--body);
}
.contact-form textarea { resize: vertical; min-height: 140px; line-height: 1.5; }
.contact-form input:focus, .contact-form select:focus, .contact-form textarea:focus { border-color: var(--gold); outline: none; }
.contact-form .btn-primary { justify-self: start; min-width: 160px; justify-content: center; }
.contact-trap { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
.contact-done { margin-top: 18px; padding: 18px; border: 1px solid var(--good); border-radius: var(--r); background: var(--panel); max-width: 560px; }
.contact-done h2 { margin-top: 0 !important; }
@media (pointer: coarse) { .contact-form input, .contact-form select, .contact-form textarea { font-size: 16px; } }

.btn-google { background: #fff; color: #1f1f1f; border-color: #dadce0; }
.btn-google:hover { background: #f8f9fa; border-color: #c6c9cc; }

.ad-sponsor { display: block; line-height: 0; }
.ad-anchor .ad-sponsor img { max-width: min(728px, calc(100vw - 72px)); max-height: 90px; width: auto; height: auto; }
.ad-rail .ad-sponsor img { width: 300px; max-height: 600px; height: auto; object-fit: contain; }

/* Profile hero on phones: rank cards in two even columns across the width,
   lined up with the name, instead of a narrow indented stack. */
@media (max-width: 640px) {
  .hero-badges { padding-left: 0 !important; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .hero-badges .badge { width: auto; }
  .hero-badges .badge.is-peak { grid-column: 1 / -1; }
  .player-hero > img { width: 64px; height: 64px; }
}

/* Player-name suggestions (lib/player-suggest.js). */
.psuggest { position: relative; flex: 1 1 240px; min-width: 0; }
.psuggest > input { width: 100%; box-sizing: border-box; }
.account-form .psuggest > input { flex: none; }
.psuggest-list { position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 40; max-height: 300px; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--rule); border-radius: var(--r); box-shadow: var(--shadow-2); }
.psuggest-list[hidden] { display: none; }
.psuggest-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; cursor: pointer; }
.psuggest-row .thumb { width: 30px; height: 30px; flex: none; }
.psuggest-row[aria-selected="true"], .psuggest-row:hover { background: var(--row); }
.psuggest-row[aria-selected="true"] b { color: var(--gold); }
.psuggest-text { display: grid; min-width: 0; }
.psuggest-text b { font-weight: 600; font-size: var(--fs-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.psuggest-text small { color: var(--mute); font-size: var(--fs-2); }
.psuggest-empty { padding: 10px 12px; color: var(--mute); font-size: var(--fs-2); }

/* ---------- mymic tiles (bosses and elites) ---------- */

.mt-group {
  display: flex; gap: 9px; align-items: baseline; padding: 18px var(--gutter) 9px;
  font: 700 var(--fs-3) var(--cond); letter-spacing: 0.12em; text-transform: uppercase;
}
.mt-group i { color: var(--dim); font: normal var(--fs-1) var(--mono); }
.mt-stars { color: var(--acc-amber, var(--gold)); letter-spacing: 0.06em; }
.mt-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(178px, 1fr));
  gap: 9px; padding: 0 var(--gutter) 8px;
}
.mt {
  display: grid; gap: 6px; justify-items: center; text-align: center; padding: 12px 10px;
  min-width: 0;
  background: var(--panel); color: var(--ink); border: 1px solid var(--rule);
  border-radius: var(--r); cursor: pointer;
}
.mt:hover { border-color: var(--mute); background: var(--row, var(--panel)); }
.mt:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
/* Grouped mymics carry two or three sprites, and a dual type carries two
   chips, so both rows have to be allowed to wrap — at 375px a 144px column
   cannot hold three 44px sprites side by side, and without this the whole
   page scrolled sideways by 97px. */
.mt-art { display: flex; gap: 3px; justify-content: center; flex-wrap: wrap; }
.mt-art .thumb { width: 52px; height: 52px; }
.mt-name { font: 600 var(--fs-2) var(--body); line-height: 1.25; overflow-wrap: anywhere; }
.mt .chips { justify-content: center; flex-wrap: wrap; }
.mt-stats { flex-wrap: wrap; justify-content: center; }
.mt-stats { display: flex; gap: 12px; margin-top: 2px; }
.mt-stat { display: grid; gap: 0; }
.mt-stat b { font: 600 var(--fs-2) var(--mono); }
.mt-stat i { color: var(--dim); font: normal var(--fs-1) var(--cond);
  letter-spacing: 0.1em; text-transform: uppercase; }
.mt-fx { color: var(--dim); font: var(--fs-1) var(--mono); }
/* What a boss drops, as icons on its tile: a player recognises the item art
   long before the name, and the names stay in the tooltip. */
.mt-drops { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; margin-top: 2px; }
.mt-drop { width: 22px; height: 22px; }
.mt-drop-more { color: var(--dim); font: var(--fs-1) var(--mono); }
.mt-drop-names { color: var(--dim); font: var(--fs-1) var(--body); line-height: 1.3; }
@media (max-width: 560px) {
  .mt-grid { grid-template-columns: repeat(auto-fill, minmax(144px, 1fr)); gap: 7px; padding: 0 var(--gutter) 8px; }
  .mt-art .thumb { width: 44px; height: 44px; }
  .mt-group { padding: 14px var(--gutter) 8px; }
}

/* ---------- pvp board: the row and its verdict as one table ---------- */

/* Not overflow: hidden. It clipped the "What beats this?" list to the row it
   opened in, so hovering showed a button and nothing else. The corners are
   rounded on the first and last rows instead. */
.pvp-board { width: 100%; display: grid; gap: 1px; background: var(--rule);
  border: 1px solid var(--rule); border-radius: var(--r); }
.pvp-board > :first-child { border-radius: var(--r) var(--r) 0 0; }
.pvp-board > :last-child { border-radius: 0 0 var(--r) var(--r); }
/* Each row animates in, which gives it its own stacking context, so a later
   row painted over a list opened in an earlier one. The row with an open list
   is lifted over the rest. */
.pvp-board-row { position: relative; }
.pvp-board-row:has(.pvp-counters-pop:not([hidden])) { z-index: 30; }
.pvp-chance { font: 700 var(--fs-3) var(--mono); }
.pvp-chance.is-good { color: var(--good); }
.pvp-chance.is-even { color: var(--mute); }
.pvp-chance.is-bad { color: var(--bad); }
.pvp-board-head, .pvp-board-row {
  display: grid; grid-template-columns: 40px minmax(0, 1fr) 128px minmax(0, 1fr);
  gap: 10px; align-items: center; background: var(--bg); padding: 8px 11px;
}
.pvp-board-head { background: var(--panel); padding: 7px 11px; }
.pvp-board-head span { color: var(--dim); font: 700 var(--fs-1) var(--cond);
  letter-spacing: 0.12em; text-transform: uppercase;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pvp-board-row.is-guess { background: var(--faint); }
.pvp-board-theirs { display: grid; gap: 5px; min-width: 0; }
.pvp-board .pvp-slot { min-height: 56px; }
.pvp-slot.is-winner { border-color: var(--good); background: rgba(62,142,126,0.12); }
.pvp-slot.is-loser { border-color: var(--bad); background: rgba(180,84,75,0.09); }

/* The item line wraps on a narrow slot rather than pushing the board wide.
   "+8,000 atk · +3,000 def · Nature" is 182px of nowrap text, which on a
   375px phone hung 12px off the right of the page. */
.pvp-item { display: flex; gap: 6px 8px; align-items: baseline; min-width: 0;
  margin-top: 1px; flex-wrap: wrap; }
.pvp-item b { color: var(--acc-violet, var(--gold)); font: 600 var(--fs-1) var(--body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.pvp-item i { color: var(--dim); font: normal var(--fs-1) var(--mono); }
@media (max-width: 560px) { .pvp-item i { white-space: normal; } }
.pvp-item.is-none { color: var(--dim); font: italic var(--fs-1) var(--body); }

.pvp-counters { position: relative; }
.pvp-counters-btn { width: 100%; padding: 4px 8px; background: transparent; color: var(--mute);
  border: 1px dashed var(--rule); border-radius: var(--r); font: var(--fs-1) var(--body); cursor: pointer; }
.pvp-counters-btn:hover { color: var(--ink); border-color: var(--gold); border-style: solid; }
.pvp-counters-pop {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 40; margin-top: 3px;
  display: grid; gap: 1px; background: var(--rule); border: 1px solid var(--rule);
  border-radius: var(--r); box-shadow: var(--shadow-1); overflow: hidden;
}
.pvp-counter { display: flex; gap: 8px; align-items: center; text-align: left;
  padding: 6px 9px; background: var(--panel); border: 0; color: var(--ink); cursor: pointer; }
.pvp-counter:hover { background: var(--bg); }
.pvp-counter .thumb { width: 26px; height: 26px; flex: none; }
.pvp-counter-text { display: grid; gap: 0; min-width: 0; flex: 1; }
.pvp-counter-text b { font: 600 var(--fs-1) var(--body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pvp-counter-text small { color: var(--dim); font: var(--fs-1) var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pvp-counter-odds { font: 600 var(--fs-2) var(--mono); }
.pvp-counter.is-good .pvp-counter-odds { color: var(--good); }
.pvp-counter.is-even .pvp-counter-odds { color: var(--mute); }
.pvp-counter.is-bad .pvp-counter-odds { color: var(--bad); }

.pvp-outlook-actions { margin-left: auto; display: flex; gap: 9px; align-items: center; flex-wrap: wrap; }
.pvp-nudge-note { color: var(--dim); font: var(--fs-1) var(--mono); }
.btn.is-nudge { box-shadow: 0 0 0 2px color-mix(in srgb, var(--gold) 28%, transparent); }
.pvp-outlook.is-stale { opacity: 0.6; }
.pvp-benches { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; width: 100%; }
.pvp-benchbox { min-width: 0; }
.pvp-bench-head { display: flex; gap: 8px; align-items: baseline; margin: 0 0 6px; }
.pvp-bench-head span { color: var(--dim); font: 700 var(--fs-1) var(--cond); letter-spacing: 0.12em; text-transform: uppercase; }
.pvp-bench-head i { color: var(--dim); font: normal var(--fs-1) var(--mono); }

@media (max-width: 860px) {
  .pvp-board-head { display: none; }
  .pvp-board-row { grid-template-columns: 40px minmax(0, 1fr); row-gap: 7px; }
  .pvp-board-row .pvp-verdict { grid-column: 2; text-align: left; }
  .pvp-board-row .pvp-board-theirs { grid-column: 2; }
  .pvp-benches { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- icon dropdowns: legibility and the clear button ---------- */

/* These lists sit inside panes whose own colour is muted, and inherited that
   grey. An option you have to squint at is worse than no icon. */
/* ---------- the picker with art in it ----------

   `iconSelect` is the control under the division badge picker, the type
   pickers on the meta page and the rarity bracket filter, and it shipped with
   no layout CSS at all — only a handful of colour overrides. Everything
   followed from that: the listbox was in normal flow, so it pushed the page
   around instead of opening over it; the button had no box; and a badge with
   no width of its own rendered at its natural size, which is how the division
   dropdown ended up 138px tall.

   One component, one block. Every picker on the site is this. */

.isel { position: relative; display: inline-flex; flex-direction: column; gap: 4px; }
.isel-label {
  font: 700 var(--fs-1) var(--cond); letter-spacing: .12em; text-transform: uppercase;
  color: var(--mute);
}

.isel-button {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  min-height: 38px; padding: 4px 9px 4px 6px; width: 100%;
  background: var(--panel); color: var(--ink); font: inherit; text-align: left;
  border: 1px solid var(--rule); border-radius: var(--r); cursor: pointer;
  transition: border-color .14s var(--ease), background .14s var(--ease);
}
.isel-button:hover { border-color: color-mix(in srgb, var(--gold) 55%, var(--rule)); }
.isel-button[aria-expanded="true"] { border-color: var(--gold); }
.isel-caret { color: var(--dim); font-size: var(--fs-1); line-height: 1; flex: none; }
.isel-button:hover .isel-caret, .isel-button[aria-expanded="true"] .isel-caret { color: var(--gold); }

.isel-face { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.isel-name, .tsel-name {
  color: var(--ink); font: 500 var(--fs-2) var(--body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.isel-button .isel-name, .isel-button .tsel-name { font-weight: 600; }

.isel-list {
  position: absolute; top: calc(100% + 5px); left: 0; z-index: 70;
  min-width: 100%; width: max-content; max-width: min(320px, 86vw);
  max-height: min(340px, 60vh); overflow-y: auto; overscroll-behavior: contain;
  padding: 4px; background: var(--panel); color: var(--ink);
  border: 1px solid var(--gold); border-radius: var(--r);
  box-shadow: var(--shadow-2);
}
/* Opening downward would run off the bottom in the filter strip on short
   screens, so a picker can ask to open upward instead. */
.isel.is-up .isel-list { top: auto; bottom: calc(100% + 5px); }

.isel-row {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 8px; border-radius: var(--r-sm); cursor: pointer;
}
.isel-row:hover { background: color-mix(in srgb, var(--gold) 10%, transparent); }
.isel-row[aria-selected="true"] { background: color-mix(in srgb, var(--gold) 16%, transparent); }
.isel-row[aria-selected="true"] .isel-name,
.isel-row[aria-selected="true"] .tsel-name { color: var(--gold); }

/* ---- the type picker on top of it ---- */

.tsel-face { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.tsel-face .chips { display: inline-flex; flex: none; }
.tsel-none-dot {
  width: 9px; height: 9px; border-radius: 50%; flex: none;
  box-shadow: 0 0 0 1px var(--dim) inset;
}
.tsel { min-width: 0; }
.tsel .isel-button { min-width: 168px; }

.tsel-pair { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; }
.tsel-pair > * { flex: 1 1 200px; min-width: 0; }
.tsel-none-dot + .tsel-name { color: var(--mute); }

.tsel-with-clear { display: flex; gap: 6px; align-items: end; flex: 1 1 210px; min-width: min(210px, 100%); }
.tsel-with-clear .isel { flex: 1; min-width: 0; }
.tsel-clear {
  flex: none; width: 34px; min-height: 38px; padding: 0; background: var(--panel);
  color: var(--mute); border: 1px solid var(--rule); border-radius: var(--r);
  font: var(--fs-3) var(--mono); cursor: pointer;
}
.tsel-clear:hover { color: var(--bad); border-color: var(--bad); }
.tl-tier-choose .tsel-clear { min-height: 32px; }

/* The Rarity bracket row sits under the ladder buttons, same shape, one step
   narrower — it is a filter within a ladder, not a third ladder. */
/* ---------- rarity brackets ----------

   The bracket used to be a second row of filter buttons under the ladder
   switch, which made it look like another set of tiers instead of the thing
   that decides what the page is about. It is a labelled dropdown now.

   `.info` inside a filter strip also needed rescuing: it is a <button>, so
   `.leagues button` and `.ladders button` were overriding its circle with
   square button padding and no border — the (i) came out as a bare letter.
   These rules put the circle back wherever it lands. */

.bracket-choose { gap: 6px; }
.bracket-choose .isel-button { min-width: 132px; }

.leagues .info, .ladders .info, .bracket-choose .info, .tl-tier-choose .info {
  width: 14px; height: 14px; min-height: 0; padding: 0;
  border: 1px solid var(--rule); border-radius: 50%;
  background: transparent; font: italic var(--fs-1)/1 var(--mono);
  letter-spacing: 0; text-transform: none; color: var(--dim);
}
.leagues .info:hover, .ladders .info:hover,
.bracket-choose .info:hover, .tl-tier-choose .info:hover,
.leagues .info:focus-visible, .ladders .info:focus-visible,
.bracket-choose .info:focus-visible, .tl-tier-choose .info:focus-visible {
  color: var(--gold); border-color: var(--gold); background: transparent;
}

/* One dot per bracket, so the dropdown reads as a ladder of four and not as
   four unrelated words. Overall is hollow: it is not a rarity. */
.rar-pip {
  width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0;
  background: var(--rar); box-shadow: 0 0 0 1px color-mix(in srgb, var(--ink) 28%, transparent) inset;
}
.rar-pip.is-common    { --rar: #7C8798; }
.rar-pip.is-uncommon  { --rar: #4E9E6A; }
.rar-pip.is-rare      { --rar: #3E7FD0; }
.rar-pip.is-epic      { --rar: #9B5FD0; }
.rar-pip.is-legendary { --rar: #D9A23B; }
.rar-pip.is-mythical  { --rar: #D4585E; }
.rar-pip.is-all       { background: transparent; box-shadow: 0 0 0 1px var(--dim) inset; }

/* The same dot in a table cell, with the word beside it. */
.rar-cell { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.rar-cell span:last-child { font: 600 var(--fs-1) var(--cond); letter-spacing: .08em;
  text-transform: uppercase; color: var(--mute); }

/* ---------- matchup builder (meta page) ---------- */

.mb { border-bottom: 1px solid var(--rule); padding-bottom: 14px; }
/* Three questions of equal weight — primary, secondary, item — so they get
   three equal columns rather than whatever flex-wrap happened to produce,
   which was one full-width picker on top of two half-width ones. */
.mb-controls {
  display: grid; gap: 12px; align-items: end; padding: 4px var(--gutter) 16px;
  grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
}
.mb-controls .tsel-pair { display: contents; }
.mb-controls .tsel .isel-button { min-width: 0; }
.mb-answer { display: grid; gap: 1px; background: var(--rule); border-top: 1px solid var(--rule); }
.mb-band { background: var(--bg); padding: 10px var(--gutter); }
.mb-band-head { display: flex; gap: 8px; align-items: baseline; margin-bottom: 7px; }
.mb-band-head b { font: 700 var(--fs-2) var(--cond); letter-spacing: 0.1em; text-transform: uppercase; }
.mb-band-head i { color: var(--dim); font: normal var(--fs-1) var(--mono); }
.mb-band.g2 b, .mb-band.g1 b { color: var(--good); }
.mb-band.g0 b { color: var(--dim); }
.mb-band.gm1 b, .mb-band.gm2 b { color: var(--bad); }
.mb-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.mb-chip {
  display: inline-flex; gap: 6px; align-items: center; padding: 4px 9px;
  background: var(--panel); color: var(--ink); border: 1px solid var(--rule);
  border-radius: 999px; font: var(--fs-1) var(--body);
}
.mb-chip .chips { margin: 0; }
/* An item-covered matchup is a different claim from a typing one, so it is
   marked rather than silently mixed in with the ball's own reach. */
.mb-chip.is-item { border-color: var(--acc-violet, var(--gold)); }
.mb-chip.is-item i { color: var(--acc-violet, var(--gold)); font: normal var(--fs-1) var(--mono); }
.mb-gain { background: var(--bg); padding: 10px var(--gutter); color: var(--mute); font: var(--fs-1)/1.6 var(--body); }
.mb-full summary { padding: 12px var(--gutter); color: var(--mute); cursor: pointer;
  font: 700 var(--fs-1) var(--cond); letter-spacing: 0.12em; text-transform: uppercase; }
.mb-full summary:hover { color: var(--ink); }
.mb-note { padding: 0 var(--gutter) 10px; color: var(--dim); font: var(--fs-1)/1.5 var(--mono); }
.mb-grid td { font: 700 var(--fs-1) var(--mono); min-width: 26px; padding: 3px; }
.mb-grid th { font-size: var(--fs-1); padding: 3px 4px; }
.mb-grid th.rowhead { font-size: var(--fs-1); }
.mb-cell.g1 { color: var(--good); background: rgba(62,142,126,0.16); }
.mb-cell.g0 { color: var(--dim); }
.mb-cell.gm1 { color: var(--bad); background: rgba(180,84,75,0.16); }
/* A grid item defaults to min-width:auto and will happily push past its
   track, which let the 17-wide chart scroll the whole page instead of its
   own wrapper. */
.mb, .mb-full { min-width: 0; max-width: 100%; }
.mb-full .wrap { max-width: 100%; }
.mb-grid { min-width: 620px; }

/* ---------- measured matchups, shuffled slots only ---------- */

.mm { border-bottom: 1px solid var(--rule); padding-bottom: 12px; }
.mm-cols { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px;
  background: var(--rule); border-top: 1px solid var(--rule); }
.mm-col { background: var(--bg); min-width: 0; padding-bottom: 6px; }
.mm-col h4 { padding: 9px var(--gutter) 6px; color: var(--dim);
  font: 700 var(--fs-1) var(--cond); letter-spacing: 0.12em; text-transform: uppercase; }
.mm-row { display: grid; grid-template-columns: minmax(0, 1fr) auto 46px;
  gap: 10px; align-items: center; padding: 5px var(--gutter); border-top: 1px solid var(--rule); }
.mm-name { display: flex; gap: 7px; align-items: center; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-2); }
.mm-row b { font: 600 var(--fs-2) var(--mono); }
.mm-row.is-good b { color: var(--good); }
.mm-row.is-bad b { color: var(--bad); }
.mm-row i { color: var(--dim); font: normal var(--fs-1) var(--mono); text-align: right; }
.mm-none, .mm-foot { padding: 10px var(--gutter); color: var(--dim); font: var(--fs-1)/1.6 var(--body); }
@media (max-width: 640px) { .mm-cols { grid-template-columns: minmax(0, 1fr); } }

/* ---------- ball page: who it meets ---------- */

.bmu-mirror { padding: 10px var(--gutter); color: var(--mute); font: var(--fs-1)/1.6 var(--body); max-width: 78ch; }
.bmu-mirror b { color: var(--ink); }

/* ---------- type reach, gear pairing ----------

   Four lists and a pair of tables that all say the same kind of thing: this
   against that. They share the panel treatment the rest of the detail pages
   use rather than inventing a card. */

.reach {
  display: grid; gap: 1px; background: var(--rule);
  border-block: 1px solid var(--rule); margin-bottom: 16px;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
}
.reach-col { background: var(--bg); padding: 11px var(--gutter) 13px; }
.reach-col h4 {
  font: 700 var(--fs-1) var(--cond); letter-spacing: .1em; text-transform: uppercase;
  color: var(--mute); margin-bottom: 8px;
}
.reach-col.is-good h4 { color: var(--good); }
.reach-col.is-bad h4 { color: var(--bad); }
.reach-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.reach-chips .chips { display: inline-flex; }

/* The balls a pairing applies to: a chip you can click through on, not a
   comma-separated list of names. */
.pair-balls { display: flex; flex-wrap: wrap; gap: 5px; }
.pair-ball {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 8px 3px 4px; border: 1px solid var(--rule); border-radius: 999px;
  background: var(--row); color: var(--ink); font: 500 var(--fs-2) var(--body);
  cursor: pointer; transition: border-color .16s var(--ease), transform .16s var(--ease);
}
.pair-ball:hover { border-color: var(--gold); transform: translateY(-1px); }
.pair-ball img { border-radius: 50%; }

/* ---------- roster filters ----------
   A 150-ball roster is the one table where the reader already knows what they
   are after, so it gets narrowing controls on top of the sort. */

.roster-filters {
  display: flex; flex-wrap: wrap; gap: 12px 18px; align-items: center;
  padding: 10px var(--gutter) 14px;
}
.roster-filters .isel-button { min-width: 126px; }
.isel-type-icon { width: 15px; height: 15px; object-fit: contain; flex-shrink: 0; }

@media (max-width: 640px) {
  .roster-filters { gap: 8px 12px; padding: 10px 14px 12px; }
  .roster-filters .tl-tier-choose { flex: 1 1 140px; }
  .roster-filters .isel { flex: 1; min-width: 0; }
  .roster-filters .isel-button { min-width: 0; width: 100%; }
}

/* ---------- early-owner badges ----------
   Was a three-column table above the roster for what is really a row of
   trophies. The place leads, the date is a hover. */

.early-strip { display: flex; flex-wrap: wrap; gap: 8px; padding: 4px var(--gutter) 16px; }
.early-badge {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 12px 5px 5px; border: 1px solid var(--rule); border-radius: 999px;
  background: var(--row); color: var(--ink); cursor: pointer;
  transition: border-color .16s var(--ease), transform .16s var(--ease);
}
.early-badge:hover { border-color: var(--gold); transform: translateY(-1px); }
.early-badge img { width: 28px; height: 28px; border-radius: 50%; }
.early-text { display: flex; align-items: baseline; gap: 7px; }
.early-badge b { font: 600 var(--fs-2) var(--body); }
.early-place {
  font: 700 var(--fs-1) var(--mono); letter-spacing: .04em; color: var(--dim);
}
.early-badge.is-1 { border-color: var(--medal-gold); }
.early-badge.is-1 .early-place { color: var(--medal-gold); }
.early-badge.is-2 { border-color: var(--medal-silver); }
.early-badge.is-2 .early-place { color: var(--medal-silver); }
.early-badge.is-3 { border-color: var(--medal-bronze); }
.early-badge.is-3 .early-place { color: var(--medal-bronze); }

@media (max-width: 640px) { .early-strip { padding: 4px 14px 14px; } }

/* ---------- battle rows ----------
   A battle list that never said which ladder it was on, on a page where two
   separate competitions are mixed together. The chip says it, and the row
   carries a thin result edge so a run of wins reads at a glance. */

.battle-row td:first-child { position: relative; }
.battle-row td:first-child::before {
  content: ""; position: absolute; left: 0; top: 4px; bottom: 4px; width: 2px;
  border-radius: var(--r-sm); background: var(--edge, transparent);
}
.battle-row.is-win  { --edge: color-mix(in srgb, var(--good) 70%, transparent); }
.battle-row.is-loss { --edge: color-mix(in srgb, var(--bad) 55%, transparent); }

.ladder-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 9px; border: 1px solid var(--rule); border-radius: 999px;
  font: 600 var(--fs-1) var(--cond); letter-spacing: .07em; text-transform: uppercase;
  color: var(--mute); white-space: nowrap;
}
.ladder-chip.is-open { color: var(--gold); border-color: color-mix(in srgb, var(--gold) 40%, var(--rule)); }
.ladder-chip.is-common,   .ladder-chip.is-uncommon,
.ladder-chip.is-rare,     .ladder-chip.is-epic,
.ladder-chip.is-rarity    { color: var(--ink); }

/* ---------- letting the long pages breathe ----------

   A ball page is now eight sections and an item page six, and they ran
   together: a section heading looked exactly the same whether it introduced
   the next part of the same thought or a completely new subject. The rule is
   positional rather than per-page — a heading that follows actual content is
   a new subject and gets a divider, a heading that follows its own
   introductory note is not.

   `:not(...)` with a selector list, so this stays one rule rather than a list
   of every element type a section can end with. */

#view > :not(.note, .section-title, .empty, .group-title) + .section-title,
#view > :not(.note, .empty) + .first-owners {
  margin-top: 20px;
  padding-top: 26px;
  border-top: 1px solid var(--rule);
}
/* Its own heading then sits inside the divider rather than adding a second. */
.first-owners > .section-title { padding-top: 0; border-top: 0; margin-top: 0; }

/* The same for the group headings a profile uses, which already had a rule
   but sat right on top of it. */
#view > :not(.note, .empty) + .group-title { padding-top: 26px; }

/* A table followed by its own footnote should not have the footnote crowding
   the next section's divider. */
#view > .wrap + .note { padding-top: 10px; }

/* ---------- the merged "High elo" crest ----------

   This shipped with no CSS at all. `.div-crest` sizes an <img> with
   object-fit, and the merged badge is a <span> of three slices, so it
   inherited a 46x38 box that did nothing to contain three 138px images — the
   division dropdown overflowed with them.

   Each slice is a window of equal width onto a full-width copy of one
   division's badge, offset by its own index, so the three read as one emblem
   cut into thirds rather than as three badges squashed together. The hairlines
   between them are what say it is a composite and not a real eighth rank. */

.div-crest.is-merged {
  display: flex; width: 46px; height: 38px; overflow: hidden; padding: 0;
}
.crest-slice {
  position: relative; flex: 1 1 0; min-width: 0; height: 100%; overflow: hidden;
}
.crest-slice + .crest-slice { border-left: 1px solid color-mix(in srgb, var(--bg) 70%, transparent); }
.crest-slice img {
  position: absolute; top: 0; height: 100%; max-width: none;
  object-fit: contain; display: block;
}

/* The empty slot "All" gets, so its label lines up with every badged row. */
.crest-none { display: block; width: 46px; height: 38px; flex-shrink: 0; }

/* And the picker itself: a face is a badge and a name on one line, and the
   list is at least as wide as the button that opens it. */


/* ---------- raid effects as tiles ----------

   141 effects is a browse, not a read, and a three-column table is the wrong
   shape for browsing. Tiles lead; the table is one click away for anyone
   comparing wording side by side. */

.fx-modes { display: inline-flex; border: 1px solid var(--rule); border-radius: var(--r); overflow: hidden; }
.fx-modes button {
  background: transparent; border: 0; color: var(--mute); cursor: pointer;
  font: 700 var(--fs-1) var(--cond); letter-spacing: .1em; text-transform: uppercase;
  padding: 7px 12px; transition: background .14s var(--ease), color .14s var(--ease);
}
.fx-modes button + button { border-left: 1px solid var(--rule); }
.fx-modes button:hover { color: var(--ink); }
.fx-modes button[aria-pressed="true"] { background: var(--gold); color: var(--bg); }

.fx-grid {
  display: grid; gap: 1px; background: var(--rule);
  border-bottom: 1px solid var(--rule);
  grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
}
.fx-grid[hidden], .fx[hidden] { display: none; }
.fx-tile {
  background: var(--bg); padding: 13px 16px 15px;
  display: flex; flex-direction: column; gap: 8px;
  transition: background .16s var(--ease);
}
.fx-tile[hidden] { display: none; }
.fx-tile:hover { background: var(--row); }
.fx-tile-head { display: flex; align-items: center; gap: 10px; }
.fx-tile-head .fx-icon { width: 30px; height: 30px; border-radius: var(--r); flex: none; }
.fx-tile-head h3 { font: 600 var(--fs-3) var(--body); color: var(--ink); }
.fx-tile-desc { margin: 0; color: var(--mute); font-size: var(--fs-2); line-height: 1.5; }
.fx-tile-mymics {
  display: flex; flex-wrap: wrap; gap: 5px 10px; margin-top: auto;
  padding-top: 8px; border-top: 1px solid var(--rule);
}
.fx-tile-mymics .fx-mymic { font-size: var(--fs-2); }
.fx-tile-mymics .thumb { width: 20px; height: 20px; border-radius: var(--r-sm); }

@media (max-width: 760px) {
  .fx-modes { order: 3; }
}

/* ---------- the theme switch ----------
   Sits beside the account chip and looks like it: the same size, the same
   hairline, no label. One button that cycles auto, light, dark, because this
   is set once and does not deserve a menu. */

.themer {
  display: grid; place-items: center; width: 34px; height: 34px; flex: none;
  margin-right: 4px; padding: 0; background: transparent; color: var(--mute);
  border: 1px solid var(--rule); border-radius: 50%; cursor: pointer;
  transition: color .14s var(--ease), border-color .14s var(--ease), transform .18s var(--ease);
}
.themer:hover, .themer:focus-visible { color: var(--gold); border-color: var(--gold); }
.themer:hover { transform: rotate(-18deg); }
.themer svg { display: block; }

@media (max-width: 640px) { .themer { width: 30px; height: 30px; margin-right: 2px; } }

/* ---------- colour theme, on the account page ----------
   Was one icon button in the masthead that cycled through three states, which
   put a preference almost nobody sets twice next to the nav and made you
   click past the option you did not want. Three rows, one click each. */

.theme-choice { border: 0; padding: 0 var(--gutter) 8px; display: grid; gap: 8px; max-width: 460px; }
.theme-choice legend { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

.theme-option {
  display: flex; align-items: flex-start; gap: 11px; padding: 11px 13px;
  background: var(--panel); border: 1px solid var(--rule); border-radius: var(--r);
  cursor: pointer; transition: border-color .14s var(--ease), background .14s var(--ease);
}
.theme-option:hover { border-color: var(--mute); }
.theme-option.is-on { border-color: var(--gold); background: color-mix(in srgb, var(--gold) 8%, var(--panel)); }
.theme-option input { margin-top: 2px; accent-color: var(--gold); flex: none; width: 15px; height: 15px; }
.theme-option-text { display: grid; gap: 2px; min-width: 0; }
.theme-option b { font: 600 var(--fs-3) var(--body); color: var(--ink); }
.theme-option small { font-size: var(--fs-2); color: var(--mute); line-height: 1.45; }

/* On the sign-in page it sits in the right-hand column under the perks. */
.auth-appearance { margin-top: 26px; border-top: 1px solid var(--rule); padding-top: 4px; }
.auth-appearance .theme-choice { padding-inline: 0; }
.auth-appearance .section-title { padding-inline: 0; }

/* ---------- divisions compared ----------
   Seven divisions read top to bottom, highest first, because the interesting
   direction is where you are going rather than where you came from. */

.dv-curve {
  display: grid; gap: 1px; background: var(--rule);
  border-block: 1px solid var(--rule); margin-bottom: 4px;
}
.dv-curve-row {
  display: grid; grid-template-columns: minmax(120px, 160px) minmax(0, 1fr) 68px 52px;
  gap: 12px; align-items: center; padding: 7px var(--gutter); background: var(--bg);
}
.dv-curve-row:hover { background: var(--row); }
.dv-curve-name { display: flex; align-items: center; gap: 8px; min-width: 0; }
.dv-curve-name > span { font: 600 var(--fs-2) var(--body); white-space: nowrap; }
.dv-curve-name .div-crest { width: 30px; height: 25px; }
.dv-bar { display: block; height: 9px; background: var(--row); border-radius: 999px; overflow: hidden; }
.dv-bar > span {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--gold) 60%, transparent), var(--gold));
}
.dv-curve-n { font: 600 var(--fs-2) var(--mono); text-align: right; }
.dv-curve-share { font: normal var(--fs-1) var(--mono); color: var(--dim); text-align: right; }

.dv-grid {
  display: grid; gap: 1px; background: var(--rule);
  border-block: 1px solid var(--rule);
}
.dv-block { background: var(--bg); padding: 13px var(--gutter) 15px; display: grid; gap: 9px; }
.dv-block:hover { background: var(--row); }
.dv-head { display: flex; align-items: center; gap: 10px; }
.dv-head .div-crest { width: 34px; height: 28px; }
.dv-head-text { display: grid; gap: 1px; }
.dv-head-text h3 {
  font: 700 var(--fs-2) var(--cond); letter-spacing: .12em; text-transform: uppercase; color: var(--ink);
}
.dv-head-text span { font: var(--fs-1) var(--mono); color: var(--dim); }

.dv-line { display: grid; grid-template-columns: 62px minmax(0, 1fr); gap: 12px; align-items: start; }
.dv-label {
  font: 700 var(--fs-1) var(--cond); letter-spacing: .12em; text-transform: uppercase;
  color: var(--mute); padding-top: 5px;
}
.dv-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.dv-none { color: var(--dim); font: var(--fs-1) var(--mono); }
.dv-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 9px 3px 5px; border: 1px solid var(--rule); border-radius: 999px;
  background: var(--panel); color: var(--ink); font: 500 var(--fs-2) var(--body);
}
.dv-chip.is-art { cursor: pointer; }
.dv-chip.is-art:hover { border-color: var(--gold); }
.dv-chip .thumb { width: 22px; height: 22px; border-radius: 50%; flex: none; }
.dv-chip > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px; }
.dv-chip b { font: 600 var(--fs-1) var(--mono); color: var(--mute); }

/* How much of a division carries a tier-4 item, and how much does not. */
.dv-gear-wrap { display: flex; flex-direction: column; gap: 4px; padding-top: 3px; }
.dv-gear {
  display: flex; height: 16px; border-radius: var(--r-sm); overflow: hidden;
  border: 1px solid var(--rule); background: var(--panel);
}
.dv-gear-seg {
  display: flex; align-items: center; justify-content: center; min-width: 2px;
  font: 600 var(--fs-1) var(--mono); color: var(--bg);
}
.dv-gear-seg.is-p4 { background: var(--gold); }
.dv-gear-seg.is-lesser { background: var(--mute); }
.dv-gear-seg.is-bare { background: var(--rule); color: var(--mute); }
.dv-gear-key { font: var(--fs-1) var(--mono); color: var(--dim); }

@media (max-width: 640px) {
  .dv-curve-row { grid-template-columns: minmax(90px, 1fr) minmax(0, 1.4fr) 56px; padding: 7px 14px; }
  .dv-curve-share { display: none; }
  .dv-line { grid-template-columns: minmax(0, 1fr); gap: 4px; }
  .dv-label { padding-top: 0; }
  .dv-block { padding: 12px 14px 14px; }
}

/* ---------- what changed this patch ---------- */
.chg-note select { font: inherit; color: var(--ink); background: var(--panel); border: 1px solid var(--rule); border-radius: var(--r-sm); padding: 1px 4px; }
.chg-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; background: var(--rule); border-block: 1px solid var(--rule); }
.chg-col { background: var(--bg); padding: 12px var(--gutter) 14px; min-width: 0; }
.chg-col-title { font: 700 var(--fs-1) var(--cond); letter-spacing: .12em; text-transform: uppercase; color: var(--mute); margin: 0 0 6px; }
/* Four short columns: opt out of the site-wide 720px minimum that makes the
   wide stat tables scroll sideways on a phone. */
.chg-table { width: 100%; min-width: 0; border-collapse: collapse; font-size: var(--fs-2); table-layout: fixed; }
.chg-table th { cursor: default; font: 600 var(--fs-1) var(--mono); color: var(--dim); text-align: left; padding: 4px 6px; border-bottom: 1px solid var(--rule); }
.chg-table td { padding: 5px 6px; border-bottom: 1px solid var(--hairline); }
.chg-table .num { width: 62px; text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.chg-table tr.is-link { cursor: pointer; }
.chg-table tr.is-link:hover, .chg-table tr.is-link:focus-visible { background: var(--row); }
.chg-col-name { overflow: hidden; }
.chg-name { display: flex; align-items: center; gap: 7px; min-width: 0; }
.chg-name > .chips { flex: none; }
.chg-name > span:last-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chg-name .thumb { width: 24px; height: 24px; border-radius: 50%; flex: none; }
.chg-delta { font-weight: 600; }
.chg-delta.is-up { color: var(--good); }
.chg-delta.is-down { color: var(--bad); }
.chg-none { color: var(--dim); font: var(--fs-2) var(--mono); margin: 6px 0; }
.chg-shifts { display: grid; gap: 8px; padding: 12px var(--gutter) 4px; }
.chg-chip > .chg-name { gap: 5px; max-width: none; overflow: visible; }
.chg-chip .chg-name > span:last-child { max-width: 150px; }
.chg-chip .chg-name .thumb { width: 22px; height: 22px; }
@media (max-width: 760px) {
  .chg-grid { grid-template-columns: minmax(0, 1fr); }
  .chg-col, .chg-shifts { padding-left: var(--gutter); padding-right: var(--gutter); }
  .chg-table .num { width: 54px; }
}

/* The item's own sprite on a calculator tile. Small, because the item is the
   second thing deciding the fight and the ball is the first. */
.pvp-item-art { width: 18px; height: 18px; border-radius: var(--r-sm); flex: none; }

/* ------------------------------------------------------------ alliance wars ---
   Shared pieces of the war pages: the score header and the two sides
   (views/war-detail.js, views/alliances.js). The notebook war room's board,
   seats, forms and verdicts were removed on 2026-09-24 once no view drew
   them; the field lives under "one alliance war" below. */
.war-room { display: grid; gap: 16px; padding: 18px 0 30px; }

.war-head { display: grid; gap: 10px; justify-items: center; padding: 14px 16px;
  background: var(--panel); border: 1px solid var(--rule); border-radius: var(--r); }
.war-score { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center; gap: 14px; width: 100%; max-width: 560px; }
.war-side { display: grid; gap: 2px; justify-items: center; min-width: 0; text-align: center; }
.war-side-name { font: 600 var(--fs-2) var(--body); color: var(--mute); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.war-banners { font: 700 var(--fs-5) var(--mono); color: var(--ink); }
.war-banners.is-masked { color: var(--dim); }
.war-vs { font: 700 var(--fs-2) var(--cond); letter-spacing: 0.14em; text-transform: uppercase; color: var(--dim); }
.war-history { display: flex; gap: 8px; align-items: center; font: var(--fs-2) var(--body); color: var(--mute); }
.war-history select { background: var(--bg); color: var(--ink); border: 1px solid var(--rule);
  border-radius: var(--r); padding: 4px 8px; font: var(--fs-2) var(--body); max-width: 260px; }

.war-detail { display: grid; gap: 10px; }

.war-field { display: grid; gap: 4px; font: var(--fs-2) var(--body); color: var(--mute); min-width: 0; }

/* ---------- the war list, before any one war ---------- */

a.war-side-name { color: var(--mute); text-decoration: none; }
a.war-side-name:hover { color: var(--ink); text-decoration: underline; }


@media (max-width: 480px) {
  .war-banners { font-size: var(--fs-5); }
}

/* ---- how an account compares in its own division ---- */
/* Its own shape rather than the stat tiles beside it: each line carries three
   numbers that only mean something together — the value, where it sits, and
   what the division's average is — and a tile has room for one. */
/* The other cards in this row pad through their tiles; this one lays its
   rows out directly, so it had none at all and every figure sat 1px from the
   border. Matches the header's 14px. */
.cmp .cmp-list { display: grid; gap: 14px; padding: 14px; }
.cmp-row { display: grid; gap: 5px; }
.cmp-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.cmp-label { color: var(--mute); font-size: var(--fs-2); }
.cmp-you { font-variant-numeric: tabular-nums; font-size: var(--fs-4); }
.cmp-track { height: 7px; border-radius: var(--r-sm); background: var(--line); overflow: hidden; }
.cmp-fill { height: 100%; border-radius: var(--r-sm); transition: width .5s cubic-bezier(.2,.7,.3,1); }
.cmp-fill.is-high { background: var(--good); }
.cmp-fill.is-mid { background: var(--accent); }
.cmp-fill.is-low { background: var(--warn, var(--accent)); opacity: .8; }
.cmp-fill.is-bottom { background: var(--mute); }
.cmp-foot { display: flex; justify-content: space-between; gap: 10px;
  color: var(--dim); font-size: var(--fs-2); }
.cmp-foot i { font-style: normal; font-variant-numeric: tabular-nums; }
@media (max-width: 480px) {
  .cmp-foot { flex-direction: column; gap: 2px; }
}

/* ---- saved comparisons, on the calculator ---- */
.pvp-saved { margin: 0 0 18px; }
.pvp-saved h3 { margin: 0 0 8px; font-size: var(--fs-2); letter-spacing: .08em;
  text-transform: uppercase; color: var(--mute); font-weight: 600; }
.pvp-saved-list { display: flex; flex-wrap: wrap; gap: 8px; }
.pvp-saved-chip { display: flex; align-items: stretch;
  border: 1px solid var(--rule); border-radius: var(--r); overflow: hidden; background: var(--panel); }
.pvp-saved-open { background: none; border: 0; color: var(--ink); cursor: pointer;
  font: inherit; font-size: var(--fs-2); padding: 7px 10px; max-width: 220px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pvp-saved-open:hover, .pvp-saved-open:focus-visible { color: var(--gold); background: var(--row); }
.pvp-saved-drop { background: none; border: 0; border-left: 1px solid var(--hairline);
  color: var(--dim); cursor: pointer; font: inherit; padding: 0 9px; }
.pvp-saved-drop:hover, .pvp-saved-drop:focus-visible { color: var(--bad); background: var(--row); }
.pvp-save { margin-top: 18px; }
@media (pointer: coarse) {
  .pvp-saved-open, .pvp-saved-drop { min-height: 38px; }
}

/* ---- raid history, and what each player took ---- */
/* A run is a row of three: when, what was in it, who was in it. The party is
   last because it is the widest and the only part that opens something. */
.rh-list { display: grid; gap: 10px; }
.rh-row {
  display: grid; grid-template-columns: 116px minmax(0, 1fr) minmax(0, 1.1fr);
  gap: 14px; align-items: start;
  padding: 12px 14px; border: 1px solid var(--rule); border-radius: var(--r);
  background: var(--panel);
}
.rh-when { display: grid; gap: 3px; }
.rh-when b { font-size: var(--fs-2); font-variant-numeric: tabular-nums; }
.rh-stars { color: var(--gold); font-size: var(--fs-2); letter-spacing: 2px; }
.rh-what { display: grid; gap: 6px; min-width: 0; }
.rh-boss { font-size: var(--fs-3); font-weight: 600; color: var(--ink); text-decoration: none; }
.rh-boss:hover { color: var(--gold); }
.rh-boss.is-none { color: var(--dim); font-weight: 400; font-style: italic; }
.rh-foes { display: flex; flex-wrap: wrap; gap: 5px; }
.rh-foe {
  font-size: var(--fs-1); color: var(--mute); text-decoration: none;
  padding: 2px 7px; border: 1px solid var(--hairline); border-radius: 999px;
}
.rh-foe:hover { color: var(--gold); border-color: var(--rule); }
.rh-foe.is-more { color: var(--dim); border-style: dashed; }

.rh-party { display: flex; flex-wrap: wrap; gap: 6px; }
.rh-who { position: relative; }
.rh-who-btn {
  display: flex; align-items: center; gap: 6px; max-width: 160px;
  background: none; border: 1px solid var(--hairline); border-radius: 999px;
  color: var(--ink); cursor: pointer; font: inherit; font-size: var(--fs-2); padding: 3px 10px 3px 3px;
}
.rh-who-btn:hover, .rh-who-btn:focus-visible { border-color: var(--rule); background: var(--row); }
.rh-who-btn .thumb { width: 22px; height: 22px; border-radius: 50%; flex: none; }
.rh-who-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The popover is anchored to the chip and clamped to the viewport by its own
   max-width, rather than positioned in script: it only ever holds five lines
   and a short list, so it never needs to flip. */
.rh-pop {
  position: absolute; z-index: 40; top: calc(100% + 6px); left: 0;
  min-width: 210px; max-width: min(280px, 82vw);
  padding: 10px 12px; border: 1px solid var(--rule); border-radius: var(--r);
  background: var(--bg); box-shadow: var(--shadow-1);
}
.rh-pop-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.rh-pop-head .thumb { width: 28px; height: 28px; border-radius: 50%; flex: none; }
.rh-pop-head b { display: block; font-size: var(--fs-2); }
.rh-pop-head small { color: var(--dim); font-size: var(--fs-1); }
.rh-pop-lines { display: grid; gap: 3px; }
.rh-pop-line { display: flex; justify-content: space-between; gap: 12px; font-size: var(--fs-2); }
.rh-pop-line span { color: var(--mute); }
.rh-pop-line b { font-variant-numeric: tabular-nums; }
.rh-pop-items { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--hairline); display: grid; gap: 5px; }
.rh-pop-item { display: flex; align-items: center; gap: 7px; font-size: var(--fs-2); }
.rh-pop-item .thumb { width: 20px; height: 20px; flex: none; }
.rh-pop-item i { color: var(--dim); font-style: normal; margin-left: auto; }
.rh-pop-none { margin: 0; color: var(--dim); font-size: var(--fs-1); line-height: 1.5; }
.rh-more { margin-top: 14px; }

@media (max-width: 720px) {
  .rh-row { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .rh-when { display: flex; align-items: baseline; gap: 8px; }
}
@media (pointer: coarse) {
  .rh-who-btn { min-height: 34px; }
  .rh-foe { padding: 6px 9px; }
}

/* A row's own link: the row is still the mouse target, so the link should not
   announce itself as a second, differently-coloured thing inside it. */
.rowlink { color: inherit; text-decoration: none; }
tr.clickable:hover .rowlink { color: var(--gold); }
.rowlink:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; border-radius: var(--r-sm); }

/* ---- how an alliance's work is shared out ---- */
/* Three figures and a bar, all of it from member rows the page already has.
   The bar is the same fact as "how many hold half the alliance", drawn — a carried
   alliance is visibly one long block. */
.spread { display: flex; flex-wrap: wrap; gap: 26px; padding: 4px var(--gutter) 14px; }
.spread-cell { display: grid; gap: 2px; }
.spread-cell b { font-size: var(--fs-5); font-variant-numeric: tabular-nums; line-height: 1.15; }
.spread-cell span { font-size: var(--fs-2); color: var(--mute); }
.spread-bar {
  display: flex; height: 10px; margin: 0 var(--gutter) 18px; border-radius: var(--r-sm);
  overflow: hidden; background: var(--line); gap: 1px;
}
.spread-seg { background: var(--rule); min-width: 1px; }
.spread-seg.is-top { background: var(--accent); }
@media (max-width: 560px) {
  .spread { gap: 18px; }
  .spread-cell b { font-size: var(--fs-4); }
}

/* ---- Type matchup calculator (/types, public/views/typecalc.js) ---------
   Colour encodes a number here as everywhere else on the site: --good and
   --bad are the win-rate colours, reused for a chart net because it is the
   same question in a different unit. Everything is a token, so the light
   theme comes for free. */
.matchup { display: grid; gap: 12px; }
.matchup .lede { margin: 0; }

.mu-pickers { display: grid; gap: 8px; grid-template-columns: 1fr 1fr; }
@media (max-width: 640px) { .mu-pickers { grid-template-columns: 1fr; } }
.mu-pick { background: var(--panel); border: 1px solid var(--rule); border-radius: var(--r); padding: 8px 10px; }
.mu-pick-h { font: 700 var(--fs-2) var(--cond); text-transform: uppercase; letter-spacing: 0.06em;
             color: var(--mute); margin-bottom: 6px; }
.mu-pick-row { display: flex; gap: 8px; flex-wrap: wrap; }
.mu-select { flex: 1 1 130px; background: var(--row); color: var(--ink);
             border: 1px solid var(--rule); border-radius: var(--r); padding: 5px 7px; font-size: var(--fs-2); }

.mu-attack { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 7px; }
.mu-attack-label { font: 700 var(--fs-1) var(--cond); text-transform: uppercase;
                   letter-spacing: 0.06em; color: var(--dim); }
.mu-radio { display: inline-flex; align-items: center; gap: 5px; font-size: var(--fs-2); color: var(--mute); }
.mu-note { color: var(--dim); font-size: var(--fs-1); line-height: 1.45; margin-top: 4px; }
.mu-clear { justify-self: start; }

/* A pair, both directions. */
.mu-pair { display: grid; gap: 8px; }
.mu-pair-head .chip { font-size: var(--fs-2); padding: 4px 9px 4px 4px; }
.mu-pair-head .chip img { width: 19px; height: 19px; }
.mu-pair-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
                background: var(--panel); border: 1px solid var(--rule);
                border-left-width: 3px; border-radius: var(--r); padding: 8px 10px; }
.mu-pair-head.mu-good { border-left-color: var(--good); }
.mu-pair-head.mu-bad  { border-left-color: var(--bad); }
.mu-pair-head.mu-even { border-left-color: var(--rule); }
.mu-vs { font: 700 var(--fs-2) var(--cond); text-transform: uppercase; color: var(--dim); }
.mu-pair-verdict { margin-left: auto; font: 700 var(--fs-3) var(--cond); text-transform: uppercase;
                   letter-spacing: 0.04em; color: var(--ink); }

.mu-dirs { display: grid; gap: 8px; grid-template-columns: 1fr 1fr; }
@media (max-width: 640px) { .mu-dirs { grid-template-columns: 1fr; } }
.mu-dir { background: var(--panel); border: 1px solid var(--rule); border-radius: var(--r); padding: 8px 10px; }
.mu-dir-h { font: 700 var(--fs-2) var(--cond); text-transform: uppercase; letter-spacing: 0.05em;
            color: var(--mute); margin-bottom: 6px; }

.mu-headline { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.mu-net { font: 700 var(--fs-5) var(--mono); }
/* An average of two rolls is not a figure any ball lands on, so it is not
   drawn like one: dashed underline, muted, never the confident colour. */
.mu-avg { color: var(--mute); border-bottom: 2px dashed var(--dim); }
.mu-mult { font: 600 var(--fs-3) var(--mono); color: var(--mute); }
.mu-verdict { font-size: var(--fs-2); color: var(--dim); }
.mu-with { font-size: var(--fs-2); color: var(--mute); margin-top: 6px; }

.mu-parts-h { font: 700 var(--fs-1) var(--cond); text-transform: uppercase; letter-spacing: 0.06em;
              color: var(--dim); margin-top: 8px; }
.mu-parts { list-style: none; margin-top: 4px; display: grid; gap: 3px; }
.mu-parts li { display: flex; justify-content: space-between; gap: 10px;
               background: var(--row); border-radius: var(--r-sm); padding: 2px 8px; font-size: var(--fs-2); }
.mu-part-v { font-family: var(--mono); font-weight: 700; }
.mu-range, .mu-gear { font-size: var(--fs-1); color: var(--dim); margin-top: 8px; line-height: 1.5; }
.mu-warn { font-size: var(--fs-2); color: var(--mute); background: var(--row);
           border: 1px solid var(--rule); border-radius: var(--r); padding: 6px 10px; line-height: 1.45; }

/* The sweep: a scoreboard, then one line of chips per edge value.
   Louder than the rest of the site on purpose - this page states the game's
   own rules rather than measuring anything, so it is allowed to look like
   the game: the attribute colours at full strength, chunky badges, and the
   three-number scoreboard a player reads first. Every colour is still a
   token, so both themes come for free. */
.mu-hero { display: flex; align-items: center; justify-content: space-between;
           gap: 14px; flex-wrap: wrap; padding: 12px 14px; border-radius: var(--r-lg);
           background: linear-gradient(135deg, color-mix(in srgb, var(--gold) 12%, var(--panel)), var(--panel));
           border: 1px solid var(--rule); }
.mu-hero-who { display: flex; align-items: center; gap: 10px; min-width: 0; }
.mu-hero-who .chip { font-size: var(--fs-2); padding: 4px 9px 4px 4px; }
.mu-hero-who .chip img { width: 20px; height: 20px; }
.mu-hero-name { font: 700 var(--fs-4) var(--cond); text-transform: uppercase; letter-spacing: 0.04em; }

.mu-score { display: flex; gap: 8px; }
.mu-score-cell { display: grid; justify-items: center; gap: 1px; min-width: 62px;
                 padding: 5px 10px; border-radius: var(--r); background: var(--row);
                 border: 1px solid var(--rule); }
.mu-score-n { font: 700 var(--fs-4) var(--mono); line-height: 1; }
.mu-score-l { font: 700 var(--fs-1) var(--cond); text-transform: uppercase;
              letter-spacing: 0.08em; color: var(--dim); }
.mu-score-cell.is-good .mu-score-n { color: var(--good); }
.mu-score-cell.is-bad .mu-score-n { color: var(--bad); }
.mu-score-cell.is-good { border-color: color-mix(in srgb, var(--good) 40%, var(--rule)); }
.mu-score-cell.is-bad { border-color: color-mix(in srgb, var(--bad) 40%, var(--rule)); }

.mu-groups { display: grid; gap: 4px; margin-top: 4px; }
.mu-group { display: grid; grid-template-columns: 48px minmax(0, 1fr); gap: 10px;
            align-items: start; padding: 6px; border-radius: var(--r); background: var(--panel); }
/* The badge carries the grade, so a row's strength is legible before any
   number is read. Stronger advantage, stronger fill. */
.mu-cell-edge { font: 700 var(--fs-3) var(--mono); text-align: center; padding: 4px 0;
                border-radius: var(--r); color: var(--ink);
                background: color-mix(in srgb, var(--mute) 18%, transparent); }
.mu-cell-edge[data-grade="3"] { background: color-mix(in srgb, var(--good) 62%, transparent); color: #06130c; }
.mu-cell-edge[data-grade="2"] { background: color-mix(in srgb, var(--good) 40%, transparent); }
.mu-cell-edge[data-grade="1"] { background: color-mix(in srgb, var(--good) 22%, transparent); }
.mu-cell-edge[data-grade="-1"] { background: color-mix(in srgb, var(--bad) 22%, transparent); }
.mu-cell-edge[data-grade="-2"] { background: color-mix(in srgb, var(--bad) 40%, transparent); }
.mu-cell-edge[data-grade="-3"] { background: color-mix(in srgb, var(--bad) 62%, transparent); color: #1a0606; }

.mu-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.mu-link { display: inline-flex; align-items: center; gap: 6px; background: var(--row);
           border: 1px solid var(--rule); border-radius: var(--r); padding: 3px 8px 3px 4px;
           color: var(--ink); cursor: pointer; transition: transform 90ms ease, border-color 90ms ease; }
.mu-link .chip { font-size: var(--fs-1); padding: 3px 7px 3px 4px; }
.mu-link .chip img { width: 17px; height: 17px; }
.mu-link:hover, .mu-link:focus-visible { border-color: var(--gold); transform: translateY(-1px); outline: none; }
.mu-io { font: var(--fs-1) var(--mono); color: var(--dim); white-space: nowrap; }

@media (max-width: 640px) {
  .mu-hero { gap: 10px; }
  .mu-score-cell { min-width: 54px; }
  .mu-group { grid-template-columns: 42px minmax(0, 1fr); gap: 7px; }
}
.mu-good { color: var(--good); }
.mu-bad  { color: var(--bad); }
.mu-even { color: var(--mute); }
.mu-good-row .mu-cell-edge { color: var(--good); }
.mu-bad-row .mu-cell-edge  { color: var(--bad); }
.mu-sweep { display: grid; gap: 6px; }
.mu-part-t { color: var(--mute); }

.btn-tiny { padding: 2px 7px; font-size: var(--fs-1); }

/* Raid rewards on a profile's raid history: the item art a player recognises,
   with the names in the tooltip. */
.raid-drops { display: flex; flex-wrap: wrap; gap: 3px; align-items: center; }
.raid-drop { width: 22px; height: 22px; }
.raid-drop-name { color: var(--mute); font: var(--fs-1) var(--body); }

/* A ball as rolled: the attacking type first and at full strength, the second
   type dimmed behind it. The roll is decided at unlock and is what the ball
   swings with, so the page shows which is which rather than listing a pair. */
.roll { display: inline-flex; align-items: center; gap: 3px; }
.roll-primary .chip { font-weight: 700; }
.roll-secondary .chip { opacity: 0.55; }

/* Gear pickers on a type matchup: filter by the type an item resonates, then
   choose one. Two controls because people arrive with two questions - what
   does MY item do here, and what should I bring against this. */
.mu-item { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-top: 7px; }
.mu-item-label { font: 700 var(--fs-1) var(--cond); text-transform: uppercase;
                 letter-spacing: 0.06em; color: var(--dim); }
.mu-item-type { flex: 0 1 120px; }
.mu-item-pick { flex: 1 1 180px; }

/* The by-hand matchup on the PvP calculator: folded away, because the steps
   above are the main road and this is the side door. */
.pvp-manual { border: 1px solid var(--rule); border-radius: var(--r); background: var(--panel);
              padding: 10px 12px; margin-top: 12px; }
.pvp-manual summary { cursor: pointer; font: 700 var(--fs-2) var(--cond); text-transform: uppercase;
                      letter-spacing: 0.06em; color: var(--mute); }
.pvp-manual summary:hover { color: var(--ink); }
.pvp-manual-body { margin-top: 10px; }
.pvp-manual-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.pvp-manual-field { display: grid; gap: 3px; font: var(--fs-1) var(--body); color: var(--dim); }
.pvp-manual-field select { background: var(--row); color: var(--ink); border: 1px solid var(--rule);
                           border-radius: var(--r); padding: 5px 7px; font-size: var(--fs-2); }
.pvp-manual-out { margin-top: 10px; display: grid; gap: 4px; }
.pvp-manual-verdict { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.pvp-manual-verdict b { font: 700 var(--fs-3) var(--cond); text-transform: uppercase; }
.pvp-manual-verdict b.is-good { color: var(--good); }
.pvp-manual-verdict b.is-bad { color: var(--bad); }
@media (max-width: 560px) { .pvp-manual-grid { grid-template-columns: minmax(0, 1fr); } }

/* hover-scroll: a clipped name needs somewhere to travel, so the cut is made
   by the cell rather than by the text itself (public/lib/scrolltext.js). */
.cellflex .name, .cellflex .ball-link, .cellflex > a.plain { will-change: transform; }

/* The tier-list tile: art, name, then the roll — attacking type first and at
   full strength, the second dimmed behind it. */
.tl-roll { display: inline-flex; gap: 2px; margin-top: 1px; }
.tl-roll .chip { font-size: var(--fs-1); padding: 1px 4px 1px 2px; }
.tl-roll .chip img { width: 12px; height: 12px; }

/* The page for something half built (public/lib/wip.js). Plain and short:
   it is an explanation, not a feature. */
.wip { max-width: 60ch; margin: 40px auto; padding: 0 var(--gutter); }
.wip-title { font: 700 var(--fs-5) var(--cond); text-transform: uppercase; letter-spacing: 0.04em; }
.wip-what { color: var(--mute); font-size: var(--fs-3); line-height: 1.6; margin-top: 10px; }
.wip-label { font: 700 var(--fs-1) var(--cond); text-transform: uppercase; letter-spacing: 0.08em;
             color: var(--dim); margin-top: 20px; }
.wip-list { margin: 6px 0 0 18px; color: var(--mute); font-size: var(--fs-3); line-height: 1.7; }
.wip-foot { color: var(--dim); font-size: var(--fs-2); margin-top: 20px; }

/* When these figures were last worked out, in the filter strip rather than
   the footer: a reader wants it beside the numbers, not three screens down. */
.chrome-fresh { margin-left: auto; display: inline-flex; align-items: baseline; gap: 5px;
                font: var(--fs-1) var(--mono); color: var(--dim); white-space: nowrap; }
.chrome-fresh-label { font-family: var(--cond); text-transform: uppercase; letter-spacing: 0.08em; }
.chrome-fresh-next { color: var(--dim); }
.chrome-fresh b { color: var(--mute); font-weight: 600; }
@media (max-width: 640px) { .chrome-fresh { margin-left: 0; width: 100%; padding: 2px 12px 6px; } }

/* The ladder's shape: columns in ladder order with the curve over their tops.
   Stood up rather than stacked, because a stack of horizontal bars reads as a
   ranking and the point here is where the mass sits. */
.dv-dist { padding: 10px var(--gutter) 16px; }
.dv-dist-plot { position: relative; display: grid; grid-auto-flow: column;
                grid-auto-columns: minmax(0, 1fr); gap: 6px; align-items: end; }
.dv-col { display: grid; grid-template-rows: auto 1fr auto; gap: 4px; align-items: end;
          justify-items: center; min-width: 0; }
.dv-col-n { font: 600 var(--fs-1) var(--mono); color: var(--mute); }
.dv-col-track { width: 100%; height: 120px; display: flex; align-items: flex-end;
                background: var(--row); border-radius: var(--r-sm) var(--r-sm) 0 0; }
.dv-col-fill { width: 100%; background: linear-gradient(180deg, var(--gold), color-mix(in srgb, var(--gold) 35%, transparent));
               border-radius: var(--r-sm) var(--r-sm) 0 0; }
.dv-col-foot { display: grid; justify-items: center; gap: 1px; text-align: center; min-width: 0; }
.dv-col-name { font: var(--fs-1) var(--cond); text-transform: uppercase; letter-spacing: 0.06em;
               color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.dv-col-share { font: var(--fs-1) var(--mono); color: var(--mute); font-style: normal; }
.dv-col-foot .crest, .dv-col-foot img { width: 18px; height: 18px; }
/* The curve sits over the columns and ignores pointer events, so a hover
   still belongs to the column underneath it. */
/* Over the columns, not behind them: the columns are opaque and paint after
   the svg in document order, so the curve was being covered by the thing it
   describes. */
/* width: 100% and not just left/right — an svg has its own intrinsic size, so
   pinning both edges leaves it at that size rather than stretching it. */
.dv-dist-line { position: absolute; left: 0; bottom: 34px; width: 100%; height: 120px;
                pointer-events: none; overflow: visible; z-index: 2; }
.dv-dist-line polyline { fill: none; stroke: var(--ink); stroke-width: 2;
                         vector-effect: non-scaling-stroke; opacity: 0.8;
                         stroke-linejoin: round; stroke-linecap: round; }
@media (max-width: 560px) {
  .dv-col-track { height: 86px; }
  .dv-dist-line { height: 86px; }
  .dv-col-n, .dv-col-share { font-size: var(--fs-1); }
}

/* The heading and sentence served in the HTML for crawlers and for anyone on
   a slow connection. The app replaces the whole of <main> on load, so this is
   only ever seen before the first paint. */
.boot-h1 { font: 700 var(--fs-5) var(--cond); text-transform: uppercase; letter-spacing: 0.04em;
           padding: 18px var(--gutter) 0; }
.boot-p { color: var(--mute); font-size: var(--fs-3); line-height: 1.6; padding: 6px var(--gutter) 0; max-width: 70ch; }

/* Split-flap digits, for the moment the ladder actually moves (lib/flip.js).
   Only the digits that changed carry a card; the rest are plain text. */
.flip { display: inline-flex; gap: 1px; }
.flip-d {
  position: relative;
  display: inline-block;
  min-width: 0.62em;
  height: 1.15em;
  line-height: 1.15em;
  perspective: 120px;
  animation: flip-card 420ms ease-in both;
  transform-style: preserve-3d;
}
.flip-old, .flip-new {
  position: absolute;
  inset: 0;
  text-align: center;
  backface-visibility: hidden;
}
.flip-new { transform: rotateX(180deg); }
/* The card carries the pair; rotating the card swaps which face is toward the
   reader, so the two never cross-fade. */
@keyframes flip-card {
  from { transform: rotateX(0deg); }
  to   { transform: rotateX(180deg); }
}
.flip-d::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--gold) 22%, transparent);
  animation: flip-glow 620ms ease-out both;
  pointer-events: none;
}
@keyframes flip-glow { from { opacity: 1; } to { opacity: 0; } }

/* The no-change nudge (lib/flip.js `flutter`): this was re-read, nobody moved.
   Deliberately unlike a flap — a few degrees and back, no card, no glow, same
   digit. A flap that resolves to a new number and a twitch that resolves to
   the same one must not read alike, or the page stops distinguishing "nothing
   moved" from "something moved". */
.flutter {
  display: inline-block;
  transform-origin: 50% 55%;
  animation: flip-flutter 300ms cubic-bezier(.3, .7, .4, 1) both;
}
@keyframes flip-flutter {
  0%   { transform: rotateX(0deg); }
  38%  { transform: rotateX(-13deg); }
  70%  { transform: rotateX(4deg); }
  100% { transform: rotateX(0deg); }
}

/* The convention this file already follows in three other places. A reader who
   asked for less motion gets the new number, immediately, with no card. */
@media (prefers-reduced-motion: reduce) {
  .flutter { animation: none; transform: none; }
  .flip-d { animation: none; transform: none; }
  .flip-old { display: none; }
  .flip-new { position: static; transform: none; }
  .flip-d::after { animation: none; opacity: 0; }
}

/* What to bring to a raid Mymic (views/raids.js). Modelled from the chart,
   never counted — the hint on the column says so, and this styling must not
   make it look like a measured figure. */
.mt-bring { display: flex; align-items: center; gap: 4px; margin-top: 4px; }
.mt-bring-label {
  font-size: 0.72em;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--mute);
}
.mt-bring.is-doubled .mt-bring-label { color: var(--gold); font-weight: 600; }
.mt-double {
  margin-left: 5px;
  padding: 0 5px;
  border-radius: var(--r-sm);
  font-size: 0.72em;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--gold);
  border: 1px solid color-mix(in srgb, var(--gold) 40%, transparent);
}

/* The alliance ranking switch: our blend, or the game's own board. Both are
   offered because disagreeing with the number players know, silently, is
   worse than disagreeing with it out loud. */
.ally-sort { display: flex; gap: 6px; margin: 0 0 10px; }
.ally-sort-btn {
  padding: 4px 12px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--mute);
  font: inherit;
  font-size: 0.86em;
  cursor: pointer;
}
.ally-sort-btn:hover { color: var(--fg); border-color: var(--mute); }
.ally-sort-btn.is-on {
  color: var(--fg);
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}

/* A phone has 375px and this table wants 720. What scrolled off was Points —
   the one figure a ladder exists to show — while League repeated the choice
   the reader had just made in the control above it. Both go; the division
   arrows survive on the player's own page and in the Divisions tab. */
@media (max-width: 560px) {
  .drop-league { display: none; }
  /* `table` carries a blanket `min-width: 720px` so wide tables keep their
     shape and scroll rather than squashing. That floor is why hiding columns
     alone changed nothing: the table stayed 720px and Points stayed off the
     screen. `fits-narrow` is the opt-out, and a table may only claim it once
     it has `drop-*` columns to shed — otherwise it would compress eight
     columns into 375px, which is worse than the scroll it replaced. */
  table.fits-narrow { min-width: 0; }
}
@media (max-width: 430px) {
  .drop-div { display: none; }
}

/* Columns a phone does without. `sm` goes on a narrow phone, `xs` on the
   narrowest — two steps rather than one, because a table usually has one
   column it can lose and another it would rather keep. Which columns get
   these is a judgement about what the reader came for, so it lives with the
   page; `staticTable` only stamps the class on the header AND the body cells,
   since tagging one without the other silently misaligns the table. */
@media (max-width: 560px) { .drop-sm { display: none; } }
@media (max-width: 430px) { .drop-xs { display: none; } }

/* ---------- phone tables, 2026-09-24 ----------
   The rest of what it took for the stat, profile, ball, item, raid and
   alliance tables to fit 375px without a sideways scroll.

   `narrow-only` / `xs-only`: a spanning group header re-emitted with a
   smaller span for the widths where some of its columns are dropped (see
   `groupCells` in lib/table.js). Without them one "Win rate" header over
   three columns, two of them gone, swallows the next group's column. */
@media (min-width: 561px) { .narrow-only { display: none; } }
@media (min-width: 431px) { .xs-only { display: none; } }
@media (max-width: 560px) {
  /* A heading can take two lines on a phone. Auto layout only wraps it when
     the table would otherwise overflow, so a table that already fits keeps
     one-line headings. "Damage share" and "Own win rate" were each wider
     than any figure under them. */
  table.fits-narrow thead th { white-space: normal; }
  /* `flow: "sm"`: a cell whose width is a list — a party, a row of type
     chips — wraps rather than being dropped, so no item in it is lost. */
  .flow-sm { white-space: normal; }
  .flow-sm .chips, .flow-sm .cellflex { flex-wrap: wrap; }
}
@media (max-width: 430px) {
  /* The pinned name column may take 52vw, which on a 375px phone is 195px —
     and in a table that already sheds everything it can, that is what still
     overflowed (/alliances by 6-20px, the stat tables by 17, and by 5 more
     once POPULARITY sat over Play rate alone). Longer names truncate with an
     ellipsis, as they already did past 52vw. */
  .wrap > table.fits-narrow:not(.grid) th.rank + th,
  .wrap > table.fits-narrow:not(.grid) td.rank + td,
  .wrap > table.fits-narrow:not(.grid) thead th:first-child:not(.rank),
  .wrap > table.fits-narrow:not(.grid) tbody td:first-child:not(.rank) { max-width: 42vw; }
}
/* ---------- end phone tables ---------- */
/* Four and five sub-tabs overflowed a 375px screen by 3-9px (Players,
   a profile), which clipped the last tab's edge under the scroll fade for
   no gain. A little less padding and they sit on the screen. */
@media (max-width: 430px) { .subnav button { padding-left: 12px; padding-right: 12px; } }

/* A league column: the game's own emblem, with the name beside it.
   The badge art is a landscape box with wide transparent margins (see
   lib/ranks.js), so it is sized by height here and left to keep its ratio. */
.div-cell .cellflex { gap: 6px; align-items: center; }
.div-cell .div-crest { width: 26px; height: 18px; flex: none; }
.div-cell-name { white-space: nowrap; }
/* The division numeral, where a table carries one. It stays when the league's
   name goes, because the badge cannot say which quarter of the league it is. */
.div-cell-sub { font: 600 var(--fs-1) var(--mono); color: var(--dim); }

/* Below a phone's width the name is what goes, not the badge: the emblem is
   the faster read for anyone who plays, and it is the part that survives
   being small. */
@media (max-width: 560px) { .div-cell-name { display: none; } }

/* ---- one alliance war: the score, how it was earned, and the field ---- */

/* The score, split by how it was earned. */
.war-break { padding: 0 var(--gutter) 14px; }
.war-break h4 { margin: 0 0 6px; font: 700 var(--fs-2) var(--cond); }
.war-break-bar { display: flex; height: 10px; border-radius: 999px; overflow: hidden; background: var(--panel); }
.war-seg { display: block; min-width: 2px; background: var(--gold); }
.war-seg:nth-child(2n) { background: color-mix(in srgb, var(--gold) 62%, var(--bg)); }
.war-seg:nth-child(3n) { background: color-mix(in srgb, var(--gold) 38%, var(--bg)); }
.war-break-list { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 3px; }
.war-break-list li { display: flex; gap: 8px; align-items: baseline; font-size: var(--fs-2); }
.war-break-name { flex: 1; text-transform: capitalize; color: var(--mute); }

.war-side.is-winner .war-banners { color: var(--good); }
.war-side-sub { font: 500 var(--fs-1) var(--mono); color: var(--dim); }

/* ---- the masthead, once the nav grew a seventh item ----
   "Wars" became "Alliances" when the alliance pages landed, which added about
   60px and pushed the point where the nav starts scrolling from ~980px up to
   ~1100px — so an ordinary laptop got a nav whose last item was behind the
   search box. The nav is allowed to scroll on a phone; on a desktop it should
   not have to. The search field gives up the width, because a 250px box is a
   luxury and a nav item you cannot see is a broken link. */
@media (min-width: 641px) and (max-width: 1200px) {
  .nav a { padding-left: 13px; padding-right: 13px; }
  .omni input { width: 170px; }
}
@media (min-width: 641px) and (max-width: 1040px) {
  .omni input { width: 120px; }
}

/* A beta strip: a warning is only honest if it comes with somewhere to go. */
.beta-bar {
  display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline;
  padding: 9px var(--gutter); border-bottom: 1px solid var(--rule);
  background: color-mix(in srgb, var(--gold) 7%, transparent);
  font-size: var(--fs-2);
}
.beta-tag {
  font: 700 var(--fs-1) var(--cond); letter-spacing: 0.12em;
  color: var(--bg); background: var(--gold);
  padding: 2px 6px; border-radius: var(--r-sm); flex: none;
}
.beta-text { color: var(--mute); }
.beta-bar a { color: var(--gold); }

/* ------------------------------------------------------------ war hub ---
   The war room's hub (views/alliances.js → warRoom). Self-contained: every
   rule here is prefixed war-hub- and reads only the shared tokens, so it can
   be merged beside other sections without touching them.

   The alliance's own colour is the one liberty taken. It arrives as
   --war-hub-accent on the banner (checked as a hex before it gets there) and
   is only ever MIXED into the ground — a wash and a rule, never text — so an
   alliance that flies yellow or near-black cannot make the page unreadable.
   Colour still encodes no number here. */
.war-hub-banner {
  --war-hub-accent: var(--gold);
  position: relative; overflow: hidden;
  display: grid; gap: 16px 28px; align-items: center;
  grid-template-columns: minmax(0, 1fr) auto;
  padding: 24px var(--gutter) 18px;
  border-bottom: 1px solid var(--rule);
  background:
    radial-gradient(120% 140% at 0% 0%,
      color-mix(in srgb, var(--war-hub-accent) 18%, transparent) 0%, transparent 60%),
    linear-gradient(180deg, var(--faint), transparent);
}
/* The alliance's colour as a single rule down the left edge. */
.war-hub-banner::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--war-hub-accent);
}
.war-hub-who { display: flex; gap: 16px; align-items: center; min-width: 0; }
.war-hub-who .crest { filter: drop-shadow(0 6px 14px rgba(0,0,0,.35)); }
.war-hub-id { display: grid; gap: 3px; min-width: 0; }
.war-hub-kicker {
  font: 700 var(--fs-1) var(--cond); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--mute);
}
.war-hub-name {
  margin: 0; font: 700 var(--fs-5)/1.05 var(--cond); text-transform: uppercase; letter-spacing: 0.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.war-hub-name a { color: var(--ink); text-decoration: none; }
.war-hub-name a:hover { color: var(--gold); }
.war-hub-sub {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 0;
  font: 500 var(--fs-2) var(--mono); color: var(--mute);
}

/* Two standings, as big numerals divided by a hairline — not boxes. */
.war-hub-places { display: flex; align-items: stretch; }
.war-hub-place {
  display: grid; grid-template-columns: auto auto; column-gap: 8px; align-content: center;
  padding: 2px 20px; text-decoration: none; color: inherit;
  border-left: 1px solid var(--rule);
  transition: background-color 140ms ease;
  border-radius: var(--r);
}
.war-hub-place:first-child { border-left: 0; }
.war-hub-place:hover { background: var(--lift); }
.war-hub-place:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.war-hub-rank {
  grid-row: span 2; align-self: center;
  font: 700 var(--fs-5)/1 var(--cond); color: var(--ink); font-variant-numeric: tabular-nums;
}
.war-hub-hash { font-size: 0.55em; color: var(--mute); margin-right: 1px; vertical-align: 0.5em; }
.war-hub-place-label {
  align-self: end; font: 700 var(--fs-1) var(--cond); letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--mute); white-space: nowrap;
}
.war-hub-place-of { align-self: start; font: var(--fs-1) var(--mono); color: var(--dim); white-space: nowrap; }
.war-hub-place.is-unranked .war-hub-rank { font-size: var(--fs-4); color: var(--dim); }

.war-hub-figs {
  grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 6px 26px; margin: 0;
  padding-top: 12px; border-top: 1px solid var(--hairline-2);
}
.war-hub-fig { display: flex; align-items: baseline; gap: 8px; }
.war-hub-fig dt {
  font: 700 var(--fs-1) var(--cond); letter-spacing: 0.12em; text-transform: uppercase; color: var(--dim);
}
.war-hub-fig dd { margin: 0; font: 600 var(--fs-3) var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; }

/* Who carried the alliance: five short ranked lists that flow into columns. */
.war-hub-leaders { border-bottom: 1px solid var(--rule); padding-bottom: 14px; }
.war-hub-pending {
  margin: 0 var(--gutter) 6px; padding: 2px 0 2px 10px; border-left: 2px solid var(--acc-amber);
  font: var(--fs-2)/1.55 var(--mono); color: var(--mute); max-width: 78ch;
}
.war-hub-boards {
  display: grid; gap: 4px 28px; padding: 4px var(--gutter) 0;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.war-hub-board { min-width: 0; padding: 8px 0; }
.war-hub-board-title {
  margin: 0 0 6px; font: 700 var(--fs-1) var(--cond); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--mute);
}
.war-hub-board-empty { margin: 0; font: var(--fs-2) var(--mono); color: var(--dim); }
.war-hub-list { list-style: none; margin: 0; padding: 0; }
.war-hub-item {
  display: grid; grid-template-columns: 18px minmax(0, 1fr) auto; gap: 10px; align-items: center;
  padding: 6px 0; border-top: 1px solid var(--hairline-2);
}
.war-hub-item:first-child { border-top: 0; }
.war-hub-pos { font: 600 var(--fs-1) var(--mono); color: var(--dim); text-align: right; }
.war-hub-player { display: grid; min-width: 0; font: 500 var(--fs-3) var(--body); }
.war-hub-player > a, .war-hub-player-sub {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.war-hub-player a { color: var(--ink); text-decoration: none; }
.war-hub-player a:hover { color: var(--gold); }
.war-hub-player-sub { font: var(--fs-1) var(--mono); color: var(--dim); }
.war-hub-val { font: 700 var(--fs-3) var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; }
/* The leader of each list, lifted by a wash rather than a frame. */
.war-hub-item.is-first {
  margin: 0 -8px; padding: 8px; border-radius: var(--r); border-top: 0;
  background: linear-gradient(90deg, color-mix(in srgb, var(--gold) 12%, transparent), transparent 80%);
}
.war-hub-item.is-first + .war-hub-item { border-top: 0; }
.war-hub-item.is-first .war-hub-pos { color: var(--medal-gold); }
.war-hub-item.is-first .war-hub-val { font-size: var(--fs-4); color: var(--gold); }

@media (max-width: 760px) {
  .war-hub-banner { grid-template-columns: minmax(0, 1fr); padding-top: 20px; }
  .war-hub-places { justify-content: flex-start; }
  .war-hub-place:first-child { padding-left: 0; }
}
@media (max-width: 420px) {
  .war-hub-who { gap: 12px; }
  .war-hub-who .crest { width: 56px; height: 56px; }
  .war-hub-name { font-size: var(--fs-5); white-space: normal; overflow-wrap: anywhere; }
  .war-hub-place { padding: 2px 12px; }
  .war-hub-rank { font-size: var(--fs-5); }
  .war-hub-figs { gap: 6px 18px; }
  /* Five lists of five is a long scroll on a phone. Three each here; the
     full table directly below still has everyone. */
  .war-hub-item:nth-child(n+4) { display: none; }
}
@media (prefers-reduced-motion: reduce) { .war-hub-place { transition: none; } }

/* =====================================================================
   WAR INSTANCE PAGE — the field, side by side, and the sector popups
   (/war/{id}, public/views/war-detail.js). Prefixes: war-field-, war-cmp-.
   Everything that moves is inside `prefers-reduced-motion: no-preference`
   at the bottom of this section; with motion reduced the page is the same
   page, standing still.
   ===================================================================== */

/* Each side's colour. Fixed per slot rather than taken from the crest,
   because two crests can be the same red and the halves must never be. */
:root {
  --wf-a: #3FA7D6;
  --wf-b: #D2644F;
  --wf-fog: rgba(170, 182, 200, 0.16);
}
:root[data-theme="light"] { --wf-a: #1F6F99; --wf-b: #A8432F; --wf-fog: rgba(80, 96, 120, 0.14); }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) { --wf-a: #1F6F99; --wf-b: #A8432F; --wf-fog: rgba(80, 96, 120, 0.14); }
}

.war-field-wrap, .war-cmp-wrap { display: block; }
/* Capped and centred: a field stretched across a 1400px screen is a strip of
   wide flat boxes, which is exactly the look this replaced. */
.war-field-stage { position: relative; margin: 0 var(--gutter); }

/* The field: rounded, layered, one piece — not two boxes side by side. */
.war-field {
  position: relative; overflow: hidden; isolation: isolate;
  display: grid; gap: 0;
  padding: 10px 12px;
  border-radius: var(--r-lg);
  border: 1px solid var(--rule);
  background:
    radial-gradient(120% 60% at 50% 0%, color-mix(in srgb, var(--wf-a) 12%, transparent), transparent 70%),
    radial-gradient(120% 60% at 50% 100%, color-mix(in srgb, var(--wf-b) 12%, transparent), transparent 70%),
    var(--panel);
  box-shadow: inset 0 0 0 1px var(--hairline), var(--shadow-1);
}
.war-field-terrain { position: absolute; inset: 0; width: 100%; height: 100%; z-index: -1; pointer-events: none; }
.war-field-gridline { fill: none; stroke: var(--hairline-2); stroke-width: 1; vector-effect: non-scaling-stroke; }
.war-field-contours path { fill: none; stroke: var(--rule); stroke-width: 1; opacity: .55; vector-effect: non-scaling-stroke; }
.war-field-glowstop.is-a { stop-color: var(--wf-a); stop-opacity: .10; }
.war-field-glowstop.is-b { stop-color: var(--wf-b); stop-opacity: .10; }
.war-field-glowstop.is-none { stop-color: var(--panel); stop-opacity: 0; }

.war-field-half { display: grid; gap: 6px; justify-items: stretch; --side: var(--wf-a); }
.war-field-half.is-b { --side: var(--wf-b); }

.war-field-banner {
  display: flex; align-items: baseline; justify-content: center; gap: 8px; flex-wrap: wrap;
  padding: 4px 0; text-align: center; min-width: 0;
}
.war-field-banner b { font: 700 var(--fs-3) var(--cond); letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink); overflow-wrap: anywhere; }
.war-field-bsub { font: 500 var(--fs-1) var(--mono); color: var(--dim); }
.war-field-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; align-self: center;
  background: var(--side, var(--wf-a)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--side, var(--wf-a)) 22%, transparent); }

.war-field-rowcap { justify-self: center; font: 700 var(--fs-1) var(--cond); letter-spacing: .22em;
  text-transform: uppercase; color: var(--dim); }

/* A row of zones: centred, every tile the same width, so a two-zone front
   row sits in the middle of a three-zone row behind it. */
.war-field-row { display: flex; justify-content: center; gap: 8px; }

.war-field-zone {
  --tone: var(--side);
  flex: 0 1 calc((100% - 16px) / 3); min-width: 0;
  display: grid; align-content: start; gap: 5px; justify-items: center; text-align: center;
  padding: 8px 6px 7px;
  border-radius: var(--r-lg);
  border: 1px solid color-mix(in srgb, var(--tone) 38%, var(--rule));
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--tone) 14%, transparent), transparent 80%),
    color-mix(in srgb, var(--bg) 70%, var(--panel));
  color: var(--ink); font: inherit; cursor: pointer;
  position: relative; overflow: hidden;
  transition: transform .18s var(--ease), box-shadow .18s var(--ease), border-color .18s var(--ease);
}
.war-field-zone:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.war-field-zone:hover, .war-field-zone.is-pinned {
  border-color: var(--tone);
  box-shadow: 0 6px 18px color-mix(in srgb, var(--tone) 22%, transparent);
}
.war-field-zone.is-pinned { box-shadow: 0 0 0 2px var(--tone), 0 8px 22px color-mix(in srgb, var(--tone) 26%, transparent); }

.war-field-zhead { display: grid; gap: 2px; justify-items: center; }
.war-field-zhead b { font: 700 var(--fs-2) var(--cond); letter-spacing: .08em; text-transform: uppercase; }
.war-field-zstatus { font: 700 var(--fs-1) var(--cond); letter-spacing: .14em; text-transform: uppercase;
  padding: 1px 7px; border-radius: 999px; color: var(--mute);
  background: color-mix(in srgb, var(--tone) 12%, transparent); }
.war-field-zsub { font: 500 var(--fs-1) var(--mono); color: var(--mute); }
.war-field-zatk { font: 600 var(--fs-1) var(--mono); color: var(--dim); }
.war-field-zone.is-open .war-field-zstatus { color: var(--ink); background: color-mix(in srgb, var(--tone) 28%, transparent); }

/* Cleared: the colour drains out and a faint hatch goes over it — read at a
   glance, and still legible, because a cleared zone is still information. */
.war-field-zone.is-cleared {
  --tone: var(--dim);
  background:
    repeating-linear-gradient(135deg, var(--hairline-2) 0 2px, transparent 2px 9px),
    color-mix(in srgb, var(--bg) 82%, var(--panel));
}
.war-field-zone.is-cleared .war-field-zhead b { color: var(--mute); }
.war-field-zone.is-cleared .war-field-zstatus { color: var(--good); background: color-mix(in srgb, var(--good) 14%, transparent); }

/* Never attacked: fog, not emptiness. A soft cloud over a dashed edge — the
   quietest tile on the board, but plainly not a blank one. */
.war-field-zone.is-fogged {
  border-style: dashed;
  background:
    radial-gradient(60% 50% at 30% 40%, var(--wf-fog), transparent 70%),
    radial-gradient(55% 45% at 70% 65%, var(--wf-fog), transparent 70%),
    color-mix(in srgb, var(--bg) 78%, var(--panel));
  background-size: 180% 180%, 160% 160%, auto;
}
.war-field-zone.is-fogged .war-field-zhead b { color: var(--mute); }
.war-field-zone.is-fogged .war-field-zstatus { font-style: italic; }
.war-field-zone.is-unknown { opacity: .45; cursor: default; }
.war-field-zone.is-empty { display: grid; place-items: center; min-height: 48px; }

.war-field-squads { display: grid; gap: 3px; justify-items: center; }
.war-field-sq { display: flex; gap: 2px; }
.war-field-sq.is-out { opacity: .5; }
.war-field-pip {
  width: 16px; height: 16px; border-radius: 50%; display: grid; place-items: center;
  background: color-mix(in srgb, var(--tone) 16%, var(--bg));
  border: 1px solid color-mix(in srgb, var(--tone) 30%, transparent);
}
.war-field-pip .thumb { width: 14px; height: 14px; background: none; border: 0; border-radius: 50%; }
.war-field-pip.is-out { opacity: .35; filter: grayscale(1); }
/* A seat the game never revealed: drawn, because absent is not empty. */
.war-field-pip.is-hidden { background: none; border: 1px dashed var(--dim); opacity: .6; }

/* The front line: where the two boards meet, drawn as a seam of light. */
.war-field-line {
  position: relative; display: grid; place-items: center; height: 30px; margin: 4px 0;
}
.war-field-line::before {
  content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 2px; transform: translateY(-50%);
  background: linear-gradient(90deg, transparent, var(--wf-a) 22%, var(--gold) 50%, var(--wf-b) 78%, transparent);
  opacity: .55; border-radius: var(--r-sm);
}
.war-field-line span {
  position: relative; padding: 2px 10px; border-radius: 999px;
  font: 700 var(--fs-1) var(--cond); letter-spacing: .24em; text-transform: uppercase; color: var(--mute);
  background: var(--panel); border: 1px solid var(--rule);
}

.war-field-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; justify-content: center;
  padding: 10px var(--gutter) 0; font: var(--fs-1) var(--body); color: var(--dim); }
.war-field-key { display: inline-flex; align-items: center; gap: 5px; }
.war-field-key b { color: var(--mute); font-weight: 600; }
.war-field-key i { width: 12px; height: 12px; border-radius: var(--r-sm); border: 1px solid var(--wf-a);
  background: color-mix(in srgb, var(--wf-a) 22%, transparent); }
.war-field-key.is-cleared i { border-color: var(--dim);
  background: repeating-linear-gradient(135deg, var(--dim) 0 1px, transparent 1px 4px); }
.war-field-key.is-fogged i { border: 1px dashed var(--dim); background: var(--wf-fog); }

.war-field-feet { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; padding: 8px var(--gutter) 0; }
.war-field-foot { margin: 0; display: grid; gap: 2px; font: 500 var(--fs-1) var(--mono); color: var(--dim); }
.war-field-foot b { font: 700 var(--fs-1) var(--cond); letter-spacing: .08em; text-transform: uppercase; color: var(--mute); }
.war-field-foot.is-b { text-align: right; }

/* The popup: one per field, beside it rather than inside it. */
.war-field-pop {
  position: absolute; z-index: 40; max-height: min(460px, 70vh); overflow: auto;
  padding: 12px 14px 14px; border-radius: var(--r-lg);
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--rule); box-shadow: var(--shadow-3);
  overscroll-behavior: contain;
}
.war-field-pop[hidden] { display: none; }
.war-field-pop.is-pinned { border-color: color-mix(in srgb, var(--gold) 50%, var(--rule)); }
.war-field-pophead { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 8px; }
.war-field-pophead > div { flex: 1; min-width: 0; }
.war-field-pophead h3 { margin: 0; font: 700 var(--fs-4) var(--cond); letter-spacing: .04em; text-transform: uppercase; }
.war-field-popside { font: 500 var(--fs-1) var(--mono); color: var(--dim); }
.war-field-pill { font: 700 var(--fs-1) var(--cond); letter-spacing: .14em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 999px; background: var(--row); color: var(--mute); align-self: center; }
.war-field-pill.is-cleared { color: var(--good); }
.war-field-pill.is-open { color: var(--ink); }
.war-field-close { flex: none; width: 30px; height: 30px; border-radius: 50%; border: 1px solid var(--rule);
  background: var(--bg); color: var(--mute); font: var(--fs-4)/1 var(--body); cursor: pointer; }
.war-field-close:hover { color: var(--ink); border-color: var(--mute); }
.war-field-popnote { margin: 4px 0 8px; font: var(--fs-2)/1.5 var(--body); color: var(--mute); }
.war-field-popsquads { display: grid; gap: 8px; margin-bottom: 4px; }
.war-field-popsquad { display: grid; gap: 4px; }
.war-field-popowner { font: 600 var(--fs-1) var(--mono); color: var(--mute); }
.war-field-popowner.is-out { text-decoration: line-through; color: var(--dim); }
.war-field-popline, .war-field-hballs, .war-cmp-ex { display: flex; flex-wrap: wrap; gap: 4px; }
.war-field-pophist { margin: 10px 0 6px; font: 700 var(--fs-1) var(--cond); letter-spacing: .14em;
  text-transform: uppercase; color: var(--mute); }

.war-field-ball {
  display: inline-flex; align-items: center; gap: 4px; max-width: 100%;
  padding: 2px 7px 2px 2px; border-radius: 999px;
  background: var(--row); border: 1px solid var(--rule); font-size: var(--fs-1);
}
.war-field-ball .thumb, .war-field-ball-blank { width: 20px; height: 20px; border-radius: 50%; border: 0; background: var(--bg); }
.war-field-ball-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 14ch; }
.war-field-ball-cp { font: 500 var(--fs-1) var(--mono); color: var(--dim); }
.war-field-ball.is-out { opacity: .5; }
.war-field-ball.is-out .war-field-ball-name { text-decoration: line-through; }
.war-field-ball.is-hidden { border-style: dashed; color: var(--dim); }
.war-field-ball.is-won { border-color: color-mix(in srgb, var(--good) 55%, var(--rule)); }

.war-field-hist { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.war-field-hist li { display: grid; gap: 4px; padding: 8px 10px; border-radius: var(--r);
  background: color-mix(in srgb, var(--bg) 60%, var(--panel)); border: 1px solid var(--hairline-2); }
.war-field-hline { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; font-size: var(--fs-2); }
.war-field-htime { margin-left: auto; font: 500 var(--fs-1) var(--mono); color: var(--dim); }
.war-field-res { font: 700 var(--fs-1) var(--cond); letter-spacing: .12em; text-transform: uppercase;
  padding: 1px 7px; border-radius: 999px; background: var(--row); color: var(--mute); }
.war-field-res.is-zone, .war-field-res.is-squad { color: var(--good); background: color-mix(in srgb, var(--good) 14%, transparent); }
.war-field-kills { font: 500 var(--fs-1) var(--mono); color: var(--mute); }
.war-field-first { font: 700 var(--fs-1) var(--cond); letter-spacing: .12em; text-transform: uppercase; color: var(--gold); }
.war-field-pts { font: 600 var(--fs-1) var(--mono); color: var(--ink); }
.war-field-felled { font: var(--fs-1) var(--body); color: var(--dim); }

/* ---- side by side ---- */
.war-cmp {
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: 6px 12px;
  align-items: center; margin: 0 var(--gutter); padding: 12px 14px;
  border-radius: var(--r-lg); border: 1px solid var(--rule); background: var(--panel);
}
.war-cmp-name { display: flex; align-items: center; gap: 7px; min-width: 0;
  font: 700 var(--fs-2) var(--cond); letter-spacing: .06em; text-transform: uppercase; }
.war-cmp-name b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.war-cmp-name.is-a { --side: var(--wf-a); justify-content: flex-end; }
.war-cmp-name.is-b { --side: var(--wf-b); flex-direction: row-reverse; justify-content: flex-end; }
.war-cmp-mid { width: 0; }
.war-cmp-label { justify-self: center; text-align: center; max-width: 16ch;
  font: 600 var(--fs-1) var(--body); color: var(--mute); }
/* Bars grow outward from the centre column, so the two sides mirror. */
.war-cmp-val { position: relative; display: flex; align-items: center; min-height: 26px; }
.war-cmp-val b { position: relative; font: 700 var(--fs-4) var(--mono); padding: 0 6px; }
.war-cmp-val.is-a { justify-content: flex-end; --side: var(--wf-a); }
.war-cmp-val.is-b { justify-content: flex-start; --side: var(--wf-b); }
.war-cmp-bar { position: absolute; top: 4px; bottom: 4px; width: var(--w, 0%); border-radius: 999px;
  background: color-mix(in srgb, var(--side) 24%, transparent); }
.war-cmp-val.is-a .war-cmp-bar { right: 0; }
.war-cmp-val.is-b .war-cmp-bar { left: 0; }
.war-cmp-note { margin: 8px var(--gutter) 0; font: var(--fs-2)/1.5 var(--body); color: var(--dim); }

.war-cmp-items, .war-cmp-plans { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 10px var(--gutter) 0; }
.war-cmp-col, .war-cmp-plan { min-width: 0; padding: 10px 12px; border-radius: var(--r-lg);
  border: 1px solid var(--rule); background: var(--panel); }
.war-cmp-col.is-a, .war-cmp-plan.is-a { border-top: 2px solid var(--wf-a); }
.war-cmp-col.is-b, .war-cmp-plan.is-b { border-top: 2px solid var(--wf-b); }
.war-cmp-col h4, .war-cmp-plan h4 { margin: 0 0 8px; font: 700 var(--fs-1) var(--cond); letter-spacing: .1em;
  text-transform: uppercase; color: var(--mute); overflow-wrap: anywhere; }
.war-cmp-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.war-cmp-item { display: flex; align-items: center; gap: 8px; min-width: 0; }
.war-cmp-item .thumb { width: 30px; height: 30px; border-radius: var(--r); }
.war-cmp-iname { display: grid; min-width: 0; flex: 1; font-size: var(--fs-2); }
.war-cmp-iname a { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.war-cmp-istats { display: flex; gap: 8px; font: 500 var(--fs-1) var(--mono); color: var(--dim); }
.war-cmp-qty { font: 700 var(--fs-3) var(--mono); }
.war-cmp-empty { margin: 0; font: var(--fs-2) var(--body); color: var(--dim); }
.war-cmp-types { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 6px; }
.war-cmp-tlabel { font: 700 var(--fs-1) var(--cond); letter-spacing: .14em; text-transform: uppercase;
  color: var(--dim); min-width: 8ch; }
.war-cmp-type { display: inline-flex; align-items: center; gap: 4px; }
.war-cmp-type b { font: 600 var(--fs-1) var(--mono); }
.war-cmp-types.is-best .war-cmp-type b { color: var(--good); }
.war-cmp-types.is-worst .war-cmp-type b { color: var(--bad); }
.war-cmp-carriers { margin: 4px 0 6px; font: var(--fs-2)/1.45 var(--body); color: var(--mute); }

/* Wide screens: capped and centred. A field stretched across 1400px is a
   strip of wide flat boxes, which is exactly the look this replaced. Placed
   after the base rules so it wins over their side margins. */
@media (min-width: 900px) {
  .war-field-stage, .war-cmp, .war-cmp-items, .war-cmp-plans, .war-field-feet,
  .war-cmp-note, .war-field-legend {
    max-width: 820px; margin-left: auto; margin-right: auto;
  }
  .war-field-feet, .war-field-legend { padding-left: 0; padding-right: 0; }
}

/* Phones. The field keeps three zones abreast — the board's shape is the
   point — and the popup becomes a sheet at the bottom of the screen. */
@media (max-width: 640px) {
  .war-field-stage, .war-cmp, .war-cmp-items, .war-cmp-plans { margin-left: 12px; margin-right: 12px; }
  .war-cmp-note { margin-left: 12px; margin-right: 12px; }
  .war-field { padding: 8px 6px; border-radius: var(--r-lg); }
  .war-field-row { gap: 5px; }
  .war-field-zone { flex-basis: calc((100% - 10px) / 3); padding: 7px 3px 6px; border-radius: var(--r); }
  .war-field-zsub { font-size: var(--fs-1); }
  .war-field-pip { width: 13px; height: 13px; }
  .war-field-pip .thumb { width: 11px; height: 11px; }
  .war-field-sq { gap: 1px; }
  .war-field-feet { padding-left: 12px; padding-right: 12px; }
  .war-field-legend { padding-left: 12px; padding-right: 12px; }
  .war-field-pop {
    position: fixed; left: 8px !important; right: 8px; top: auto !important; bottom: 8px !important;
    width: auto !important; max-height: 72vh; border-radius: var(--r-lg) var(--r-lg) var(--r-lg) var(--r-lg);
  }
  .war-cmp { padding: 10px 8px; gap: 6px 6px; }
  .war-cmp-val b { font-size: var(--fs-3); }
  .war-cmp-items, .war-cmp-plans { grid-template-columns: 1fr; }
  .war-cmp-label { max-width: 11ch; font-size: var(--fs-1); }
}

@media (prefers-reduced-motion: no-preference) {
  .war-field-zone { animation: war-field-in .5s var(--ease) backwards; animation-delay: calc(var(--i, 0) * 32ms); }
  .war-field-half.is-b .war-field-zone { animation-name: war-field-in-up; }
  .war-field-zone:hover, .war-field-zone.is-pinned { transform: translateY(-2px); }
  .war-field-half.is-b .war-field-zone:hover, .war-field-half.is-b .war-field-zone.is-pinned { transform: translateY(2px); }
  .war-field-zone.is-fogged { animation: war-field-in .5s var(--ease) backwards, war-field-fog 14s ease-in-out infinite alternate; animation-delay: calc(var(--i, 0) * 32ms), 0s; }
  .war-field-half.is-b .war-field-zone.is-fogged { animation-name: war-field-in-up, war-field-fog; }
  .war-field-line::before { animation: war-field-pulse 4.5s ease-in-out infinite; }
  .war-field-pop:not([hidden]) { animation: war-field-pop .18s var(--ease) both; }
  .war-cmp-bar { transition: width .6s var(--ease); animation: war-cmp-grow .7s var(--ease) both; }
}
@keyframes war-field-in    { from { opacity: 0; transform: translateY(-8px) scale(.97); } to { opacity: 1; transform: none; } }
@keyframes war-field-in-up { from { opacity: 0; transform: translateY(8px) scale(.97); } to { opacity: 1; transform: none; } }
@keyframes war-field-fog   { from { background-position: 0% 0%, 100% 100%, 0 0; } to { background-position: 100% 60%, 0% 30%, 0 0; } }
@keyframes war-field-pulse { 0%, 100% { opacity: .35; } 50% { opacity: .8; } }
@keyframes war-field-pop   { from { opacity: 0; transform: translateY(4px) scale(.985); } to { opacity: 1; transform: none; } }
@keyframes war-cmp-grow    { from { width: 0; } }
/* ================================================ end WAR INSTANCE PAGE */

/* ================================================ ALLIANCE LEADERBOARDS
   Podium rows, row crests, and the leaderboard badges on an alliance page.
   The medal tokens already have a shade per theme, so the tint mix is the
   only thing each theme sets. The ground is mixed into --bg, not into
   transparent, because the rank and name cells are sticky on a phone and a
   see-through sticky cell shows the columns scrolling under it. */
:root { --podium-mix: 16%; --podium-mix-hover: 23%; }
:root[data-theme="light"] { --podium-mix: 15%; --podium-mix-hover: 22%; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) { --podium-mix: 15%; --podium-mix-hover: 22%; }
}
tbody tr.rank-1 { --podium: var(--medal-gold); }
tbody tr.rank-2 { --podium: var(--medal-silver); }
tbody tr.rank-3 { --podium: var(--medal-bronze); }
tbody tr.rank-1 td, tbody tr.rank-2 td, tbody tr.rank-3 td,
.wrap > table:not(.grid) tbody tr.rank-1 td,
.wrap > table:not(.grid) tbody tr.rank-2 td,
.wrap > table:not(.grid) tbody tr.rank-3 td {
  background: color-mix(in srgb, var(--podium) var(--podium-mix), var(--bg));
}
tbody tr.rank-1:hover td, tbody tr.rank-2:hover td, tbody tr.rank-3:hover td,
.wrap > table:not(.grid) tbody tr.rank-1:hover td,
.wrap > table:not(.grid) tbody tr.rank-2:hover td,
.wrap > table:not(.grid) tbody tr.rank-3:hover td {
  background: color-mix(in srgb, var(--podium) var(--podium-mix-hover), var(--panel));
}
tbody tr.rank-1 td.rank, tbody tr.rank-2 td.rank, tbody tr.rank-3 td.rank {
  color: var(--podium); font-weight: 700;
}
/* The crest in a leaderboard row stands where the colour dot did. */
.crest.is-row { width: 22px; height: 22px; }

.board-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.board-badge {
  --place: var(--mute);
  display: inline-flex; align-items: baseline; gap: 6px; padding: 4px 10px;
  border: 1px solid var(--rule); border-radius: 999px; background: var(--panel);
  color: var(--ink); text-decoration: none; font-size: var(--fs-2); line-height: 1.3;
}
.board-badge:hover, .board-badge:focus-visible { border-color: var(--place); outline: none; }
.board-badge-rank { font: 700 var(--fs-3) var(--cond); color: var(--place); font-variant-numeric: tabular-nums; }
.board-badge-label { color: var(--mute); }
.board-badge.is-place-1 { --place: var(--medal-gold); }
.board-badge.is-place-2 { --place: var(--medal-silver); }
.board-badge.is-place-3 { --place: var(--medal-bronze); }
.board-badge.is-place-1, .board-badge.is-place-2, .board-badge.is-place-3 {
  border-color: var(--place);
  background: color-mix(in srgb, var(--place) var(--podium-mix), var(--panel));
}
/* ================================================ end ALLIANCE LEADERBOARDS */

/* ======================================================== WAR ROOM LOCK
   The war room for a member without Premium (views/alliances.js → warLock).
   Every rule is prefixed war-lock- and reads only the shared tokens, so it
   follows light and dark on its own: the ghost fades out through a mask,
   which dissolves it into whatever ground --bg is, rather than laying a
   coloured veil over it that would need a second value per theme.

   The ghost is made-up shapes, never blurred real data — the API does not
   send the real leaders or wars to this reader at all.

   The root is war-lock-room, not war-lock: a bare .war-lock rule from the
   deleted notebook war room still sits in the WAR section above and would
   paint this whole block gold. Ghost and panel share one grid cell, so the
   panel sits over the fade and the block grows to whichever is taller —
   nothing absolute that a narrow screen could push out of its box. */
.war-lock-room { display: grid; margin-top: 18px; }
.war-lock-room > * { grid-area: 1 / 1; min-width: 0; }
.war-lock-ghost {
  pointer-events: none; user-select: none;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, rgba(0, 0, 0, .5) 34%, transparent 78%);
          mask-image: linear-gradient(to bottom, #000 0%, rgba(0, 0, 0, .5) 34%, transparent 78%);
}
.war-lock-cards {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px;
  margin: 10px var(--gutter) 22px;
}
.war-lock-card {
  display: grid; gap: 9px; padding: 12px;
  background: var(--panel); border: 1px solid var(--rule); border-radius: var(--r);
}
.war-lock-card-title { font: 600 var(--fs-2) var(--body); color: var(--mute); letter-spacing: .02em; }
.war-lock-line, .war-lock-row { display: flex; align-items: center; gap: 10px; min-width: 0; }
.war-lock-dot {
  flex: none; width: 18px; font: 600 var(--fs-1) var(--mono); font-style: normal;
  color: var(--dim); text-align: center;
}
.war-lock-bar {
  display: block; height: 9px; border-radius: var(--r-sm); width: var(--w, 60%);
  background: color-mix(in srgb, var(--mute) 26%, transparent);
}
.war-lock-val {
  display: block; flex: none; margin-left: auto; width: 34px; height: 9px; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--gold) 30%, transparent);
}
.war-lock-rows { display: grid; margin: 10px var(--gutter) 0; border-top: 1px solid var(--rule); }
.war-lock-row { padding: 12px 4px; border-bottom: 1px solid var(--rule); }
.war-lock-pill {
  display: block; flex: none; width: 52px; height: 16px; border-radius: var(--r);
  background: color-mix(in srgb, var(--good) 34%, transparent);
}
.war-lock-row:nth-child(3n) .war-lock-pill { background: color-mix(in srgb, var(--bad) 34%, transparent); }

.war-lock-panel {
  justify-self: center; align-self: start; margin: 110px 0 28px;
  width: min(520px, calc(100% - 24px)); box-sizing: border-box;
  display: grid; gap: 10px; justify-items: start;
  padding: 22px 22px 18px;
  background: var(--panel); color: var(--ink);
  border: 1px solid color-mix(in srgb, var(--gold) 45%, var(--rule));
  border-radius: calc(var(--r) * 2);
  box-shadow: 0 18px 48px -18px rgba(0, 0, 0, .45);
}
.war-lock-kicker {
  font: 700 var(--fs-1) var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--gold);
}
.war-lock-title { margin: 0; font: 600 var(--fs-4)/1.2 var(--body); color: var(--ink); }
.war-lock-lede { margin: 0; color: var(--ink); font-size: var(--fs-3); line-height: 1.45; }
.war-lock-perks { margin: 2px 0 6px; padding: 0; list-style: none; display: grid; gap: 7px; }
.war-lock-perks li {
  position: relative; padding-left: 20px; color: var(--mute); font-size: var(--fs-3); line-height: 1.4;
}
.war-lock-perks li::before {
  content: ""; position: absolute; left: 2px; top: .45em; width: 8px; height: 8px;
  border-radius: var(--r-sm); background: var(--gold);
}
.war-lock-cta { text-decoration: none; }
.war-lock-note { margin: 2px 0 0; color: var(--dim); font-size: var(--fs-2); line-height: 1.4; }
/* Open through a teammate's subscription: said once, under the banner. */
.war-lock-shared {
  display: inline-block; margin: 12px var(--gutter) 0; padding: 7px 11px;
  font-size: var(--fs-2); color: var(--mute); background: var(--panel);
  border: 1px solid var(--rule); border-left: 3px solid var(--gold); border-radius: var(--r);
}

@media (max-width: 640px) {
  .war-lock-cards { grid-template-columns: minmax(0, 1fr); }
  .war-lock-card:nth-child(n+2) { display: none; }
  .war-lock-panel { margin-top: 56px; padding: 18px 16px 16px; }
  .war-lock-title { font-size: var(--fs-4); }
}

@media (prefers-reduced-motion: no-preference) {
  .war-lock-panel { animation: war-lock-rise .45s var(--ease) both; }
}
@keyframes war-lock-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
/* ==================================================== end WAR ROOM LOCK */

/* ============================================ RECENT BATTLES FEED (bf-) */
/* public/views/battles.js: the ladder-wide match history. */
.bf-more { display: block; margin: 12px auto 24px; }
.bf-more[hidden] { display: none; }
.bf-status:empty { display: none; }
/* ========================================== end RECENT BATTLES FEED */

/* ============================================================ SSR FIGURES
   The small table of headline figures the Worker puts into the document for
   five pages (src/ssr.js), for crawlers that do not run JavaScript. The app
   replaces <main> on load, so this is only ever seen before the first paint
   or without JavaScript. Kept plain and narrow: no horizontal scroll at 375px. */
.ssr { padding: 10px var(--gutter) 0; max-width: 70ch; }
.ssr-lead { color: var(--mute); font-size: var(--fs-3); line-height: 1.6; margin: 0 0 8px; }
.ssr-table { border-collapse: collapse; width: 100%; font-size: var(--fs-2); }
.ssr-table caption { text-align: left; font: 700 var(--fs-2) var(--cond); text-transform: uppercase;
                     letter-spacing: 0.04em; padding: 0 0 6px; }
.ssr-table th, .ssr-table td { text-align: left; padding: 4px 8px 4px 0; border-bottom: 1px solid var(--rule);
                               overflow-wrap: anywhere; }
.ssr-table th { color: var(--mute); font-weight: 600; }
/* ======================================================== end SSR FIGURES */

/* ==================================================== ACCOUNT NEWS OPT-IN */
/* The news-email checkbox under the account's email row (src/blast.js). */
.account-news { flex-basis: 100%; display: flex; align-items: center; gap: 8px; font-size: var(--fs-2);
  color: var(--dim); cursor: pointer; }
.account-news input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent, currentColor); }
/* ==================================================== end ACCOUNT NEWS OPT-IN */

/* ==================================================== WAR STATS LINKS
 * "War stats": into any alliance's war room, from a war-ladder row and from
 * the alliance page's war section (public/views/alliances.js warStatsLink,
 * public/views/alliance.js warSection). Colours from the theme tokens only.
 */
.war-stats-link {
  margin-left: auto; flex: none; padding: 2px 8px;
  font: 600 var(--fs-1) var(--body); color: var(--mute); text-decoration: none; white-space: nowrap;
  border: 1px solid var(--rule); border-radius: var(--r); background: var(--panel);
}
.war-stats-link:hover, .war-stats-link:focus-visible { color: var(--ink); border-color: var(--gold); }
.section-title .war-stats-btn { margin-left: auto; padding: 5px 12px; font-size: var(--fs-2); }
/* A phone: a crossed-swords glyph in the same chip; the words are still
   there for a screen reader, clipped rather than removed. */
@media (max-width: 640px) {
  .war-stats-link { padding: 1px 6px; font-size: var(--fs-2); line-height: 1.3; }
  .war-stats-link::before { content: "\2694\FE0E"; }
  .war-stats-text {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }
}
/* The public war room's heading when there is no alliance to show. */
.war-room-intro-lede { margin: 6px 0 0; max-width: 60ch; color: var(--mute); font-size: var(--fs-3); line-height: 1.5; }
/* ==================================================== end WAR STATS LINKS */

/* ===================================================== UX REDESIGN (ux-)
   docs/ux-proposals-2026-09-24.md, P3 and P4, approved by the owner.

   The page was "a stats terminal": every control a bordered cell, every band
   its own rule, every label uppercase condensed. That idea is kept where it
   earns its place — the tables stay ruled, the brand and section titles keep
   the condensed caps — and taken out of the chrome and the cards, which is
   where "boxy, too straight" came from.

   Separation by space and tone instead of lines:
     - the masthead is one surface, divided from the page by a soft shadow;
     - nav items, sub-tabs and the ladder switch are pills with no cell edges,
       the current one filled;
     - the filter strip is a slightly lighter ground with no rules;
     - cards lose their border and sit on --panel with a small shadow.

   One label style: Outfit 600, sentence case, no tracking. Nav, tabs, filter
   labels, column headers and the freshness line all used the uppercase
   condensed treatment, so five levels of the hierarchy looked the same.

   Placed last so it wins by order, not by specificity: every rule here has
   the specificity of the rule it replaces. */

:root {
  --shadow-card: 0 1px 2px rgba(0,0,0,.35), 0 3px 10px rgba(0,0,0,.18);
  --shadow-bar: 0 1px 0 var(--hairline-2), 0 8px 18px -14px rgba(0,0,0,.9);
  --pill-on: color-mix(in srgb, var(--gold) 15%, transparent);
  --pill-hover: var(--lift);
}
:root[data-theme="light"] {
  --shadow-card: 0 1px 2px rgba(17,22,31,.07), 0 3px 10px rgba(17,22,31,.05);
  --shadow-bar: 0 1px 0 var(--rule), 0 8px 18px -14px rgba(17,22,31,.35);
  --pill-on: color-mix(in srgb, var(--gold) 12%, transparent);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --shadow-card: 0 1px 2px rgba(17,22,31,.07), 0 3px 10px rgba(17,22,31,.05);
    --shadow-bar: 0 1px 0 var(--rule), 0 8px 18px -14px rgba(17,22,31,.35);
    --pill-on: color-mix(in srgb, var(--gold) 12%, transparent);
  }
}

/* ---- masthead: one surface, no cell edges ---- */
.top { border-bottom: 0; box-shadow: var(--shadow-bar); align-items: center; }
.brand { border-right: 0; }
.nav { gap: 2px; padding: 6px 0; align-items: center; }
.nav a, .subnav button, .leagues button, .ladders button { border-radius: 999px; }
.nav a {
  border-right: 0; font-family: var(--body); font-weight: 600; font-size: var(--fs-3);
  letter-spacing: 0; text-transform: none; padding: 7px 14px;
}
.nav a:hover { background: var(--pill-hover); }
.nav a[aria-current="page"] { color: var(--gold); background: var(--pill-on); box-shadow: none; }
.omni { border-left: 0; align-self: stretch; display: flex; align-items: center; }
.omni input {
  background: var(--row); border-radius: 999px; padding: 8px 15px; margin: 0 6px;
}
.omni input:focus { background: var(--row); box-shadow: 0 0 0 2px color-mix(in srgb, var(--gold) 45%, transparent); }
.omni-results { border: 0; border-radius: var(--r-lg); box-shadow: var(--shadow-2); margin-top: 4px; }
.omni-row { border-bottom: 0; }
.omni-kind {
  border: 0; background: var(--lift); border-radius: var(--r-sm);
  font-family: var(--body); font-weight: 600; text-transform: none; letter-spacing: 0;
}
.acct { border-left: 0; align-self: stretch; }

@media (max-width: 900px) {
  .omni { border-top: 0; padding: 0 0 6px; }
  .nav a { padding: 7px 12px; }
}
@media (max-width: 640px) {
  .top .nav { border-top: 0; padding: 0 6px 5px; }
  .top .nav a { padding: 8px 4px; font-size: var(--fs-3); letter-spacing: 0; }
  .top .omni { padding: 5px 0 3px; }
  .omni input { margin: 0; }
}
@media (pointer: coarse) {
  .nav a { padding: 10px 14px; }
}

/* ---- the filter strip: a lighter ground, no rules ---- */
.chrome {
  border-bottom: 0; background: var(--row);
  padding: 6px 12px; gap: 6px 14px; align-items: center;
}
.tl-view-axes, .tl-scope { gap: 6px 14px; align-items: center; }
.tl-view-axes { background: transparent; }
.tl-scope { border-top: 0; background: transparent; padding-top: 6px; }
.tl-view-axes .subnav, .subnav { border-right: 0; }
.subnav { gap: 2px; align-items: center; padding: 2px 0; }
.subnav button {
  border-right: 0; font-family: var(--body); font-weight: 600; font-size: var(--fs-2);
  letter-spacing: 0; text-transform: none; padding: 6px 13px;
}
.subnav button:hover { background: var(--pill-hover); }
.subnav button[aria-pressed="true"] { color: var(--gold); background: var(--pill-on); box-shadow: none; }

/* The ladder switch: a segmented control, still filled when on, because Open
   and Rarity are separate competitions rather than two tabs of one. */
.ladders, .leagues { border-right: 0; gap: 2px; }
.ladders button, .leagues button {
  border-left: 0; font-family: var(--body); font-weight: 600; font-size: var(--fs-2);
  letter-spacing: 0; text-transform: none; padding: 6px 13px;
}
.flabel {
  border-left: 0; font-family: var(--body); font-weight: 600; font-size: var(--fs-1);
  letter-spacing: 0; text-transform: none; color: var(--mute); padding: 0 8px 0 2px;
}
.tl-tier-choose select { margin: 0; }
.chrome-fresh-label { font-family: var(--body); text-transform: none; letter-spacing: 0; }
@media (max-width: 640px) {
  .chrome { padding: 6px 10px; }
  .chrome-fresh { padding: 2px; }
}
@media (pointer: coarse) {
  .subnav button, .ladders button, .leagues button { padding: 10px 14px; }
}

/* ---- column headers ----
   Section titles keep the condensed caps: that is the site's voice. Column
   headers join the one label style and lose the rule above them — the band
   already says "header"; a line on each side said it twice. Tables keep
   their row rules. */
thead th {
  border-top: 0; font-family: var(--body); font-weight: 600; font-size: var(--fs-1);
  letter-spacing: 0; text-transform: none;
}
thead tr.group-row th { letter-spacing: 0; }

/* ---- cards: raised, not drawn ---- */
.mt, .home-more-card, .compart {
  border: 0; background: var(--panel); box-shadow: var(--shadow-card);
}
.mt, .compart, .home-more-card { border-radius: var(--r-lg); }
.mt:hover, .home-more-card:hover {
  background: var(--panel);
  box-shadow: var(--shadow-card), 0 0 0 1px color-mix(in srgb, var(--gold) 45%, transparent);
}
.home-more-grid { border: 0; gap: 10px; }
.compart-head { border-bottom: 0; }

/* Profile and ball panes: ground between them, not a ruled grid. */
.pgrid { background: transparent; gap: 10px; border: 0; border-radius: 0; overflow: visible; }
.pane { background: var(--panel); border-radius: var(--r-lg); box-shadow: var(--shadow-card); }

@media (prefers-reduced-motion: no-preference) {
  .nav a, .subnav button, .ladders button, .leagues button, .mt, .home-more-card {
    transition: background-color 160ms ease, color 160ms ease, box-shadow 160ms ease;
  }
}
/* The tier list's own axes share the line with the Balls strip where there
   is room, so the section tabs cost the page no extra row on a desktop. */
@media (min-width: 641px) {
  .chrome.is-tierlist { display: flex; flex-wrap: wrap; align-items: center; }
  .chrome.is-tierlist > .tl-scope { flex: 1 1 100%; }
}

/* ---- section strips (P1) ----
   The PVP Tools strip and its (i), on one line. */
.ux-tools-row {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px;
  min-width: 0; max-width: 100%;
}
.ux-tools-row .info { margin-left: 0; }
/* One line on a phone: the (i) beside it says the rest. */
@media (max-width: 640px) {
  .ux-tools-row { flex-wrap: nowrap; }
}

/* ---- the phone filter line (P2) ----
   One pill that says the slice — "Open · High elo · All time" — with the
   freshness beside it; tapping it opens the controls beneath. On a wider
   screen the pill is not drawn and the controls sit inline as before. */
.ux-scope-summary, .ux-fresh-short { display: none; }
.ux-scope:not(.tl-scope) { display: contents; }

@media (max-width: 640px) {
  .chrome, .chrome.is-tierlist { display: flex; flex-wrap: wrap; align-items: center; }
  .chrome > * { flex: 1 1 100%; }
  .chrome > .ux-scope-summary {
    display: inline-flex; align-items: center; gap: 6px; order: 10;
    flex: 0 1 auto; min-width: 0; max-width: 100%; min-height: 34px;
    padding: 6px 12px; border: 0; border-radius: 999px; cursor: pointer;
    background: var(--panel); color: var(--ink); box-shadow: var(--shadow-card);
    font: 600 var(--fs-2) var(--body);
  }
  .ux-scope-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
  .ux-scope-caret { color: var(--dim); flex: none; transition: transform 180ms ease; }
  .chrome.is-scope-open .ux-scope-caret { transform: rotate(180deg); color: var(--gold); }
  .ux-scope-summary:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

  .chrome > .chrome-fresh {
    order: 11; flex: 1 1 0; width: auto; min-width: max-content;
    margin-left: 0; padding: 0 2px; justify-content: flex-end; font-size: var(--fs-1);
  }
  /* "Updated 4:00 PM" rather than a labelled line of its own. A stale fold
     keeps its full label: that warning is the one thing worth the width. */
  .chrome-fresh:not(.chrome-fresh-stale) .chrome-fresh-label,
  .chrome-fresh .chrome-fresh-next,
  .chrome-fresh > b:not(.ux-fresh-short) { display: none; }
  .chrome-fresh > b.ux-fresh-short { display: inline; }
  .chrome-fresh:not(.chrome-fresh-stale) > b.ux-fresh-short::before {
    content: "Updated "; font-weight: 400; color: var(--dim);
  }

  .chrome > .ux-scope, .chrome > .ux-scope.tl-scope { order: 12; display: none; }
  .chrome.is-scope-open > .ux-scope {
    display: flex; flex-direction: column; align-items: stretch; gap: 8px;
    padding: 6px 0 4px;
  }
  .chrome.is-scope-open > .ux-scope > * { max-width: 100%; min-width: 0; }
}
@media (max-width: 640px) and (pointer: coarse) {
  .chrome > .ux-scope-summary { min-height: 40px; }
}
@media (max-width: 640px) and (prefers-reduced-motion: no-preference) {
  .chrome.is-scope-open > .ux-scope { animation: ux-rise 200ms ease both; }
}
@keyframes ux-rise {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

/* ================================================= end UX REDESIGN (ux-) */


/* ================================================ WAR ROOM ROSTER (wrs-)
   The war room's roster and overview modules (views/alliances.js →
   rosterModules), and the Alliance Sponsors line on the lock panel.

   Floating cards on the ux- tokens: no border, --panel ground, the card
   shadow and the large radius. Two columns on a desktop (the roster takes
   the wider one), stacked on a phone. The P4/P3 popups open on hover or
   focus with a pointer, and pin open on a tap (.is-pinned) everywhere.
   Colours come only from the theme tokens, so light and dark follow. */
.war-lock-promo {
  margin: 2px 0 4px; padding: 10px 12px; width: 100%; box-sizing: border-box;
  color: var(--ink); font-size: var(--fs-3); line-height: 1.45;
  background: color-mix(in srgb, var(--gold) 14%, var(--panel));
  border-left: 3px solid var(--gold); border-radius: var(--r-lg);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--gold) 28%, transparent);
}
.war-lock-promo strong { font-weight: 700; }
.war-lock-promo em { font-style: italic; }

.wrs-wrap { margin-top: 22px; }
.wrs-grid {
  display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: 16px; margin: 10px var(--gutter) 0; align-items: start;
}
.wrs-card {
  background: var(--panel); border-radius: var(--r-lg);
  box-shadow: var(--shadow-card), 0 14px 32px -22px rgba(0, 0, 0, .55);
  padding: 16px 16px 14px; min-width: 0;
}
.wrs-card-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.wrs-card-title { margin: 0; font: 600 var(--fs-4)/1.2 var(--body); color: var(--ink); }
.wrs-card-sub { color: var(--dim); font-size: var(--fs-2); }

.wrs-members { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.wrs-member {
  display: grid; grid-template-columns: minmax(96px, 150px) minmax(0, 1fr); gap: 12px;
  align-items: center; padding: 8px 10px; border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--ink) 3%, transparent);
}
.wrs-who { display: grid; min-width: 0; }
.wrs-name { font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wrs-role { color: var(--dim); font-size: var(--fs-1); text-transform: capitalize; }
.wrs-balls { display: flex; gap: 8px; flex-wrap: wrap; min-width: 0; }
.wrs-none { color: var(--dim); font-size: var(--fs-2); }
.wrs-ball {
  position: relative; display: grid; justify-items: center; gap: 2px; width: 52px;
  padding: 4px 2px 3px; border-radius: var(--r-lg); background: color-mix(in srgb, var(--ink) 6%, var(--panel));
}
.wrs-ball-art.thumb { width: 38px; height: 38px; object-fit: contain; }
.wrs-item {
  position: absolute; top: -3px; right: -3px; width: 20px; height: 20px;
  display: grid; place-items: center; overflow: hidden;
  border-radius: 999px; background: var(--panel); font: 600 var(--fs-1) var(--mono); color: var(--mute);
  box-shadow: 0 0 0 1px var(--rule);
}
.wrs-item .thumb { position: absolute; inset: 1px; width: 18px; height: 18px; object-fit: contain; background: var(--panel); border-radius: 999px; }
.wrs-item.is-p4 { box-shadow: 0 0 0 2px var(--gold); }
.wrs-item.is-p3 { box-shadow: 0 0 0 1px color-mix(in srgb, var(--gold) 55%, var(--rule)); }
.wrs-cp { font: 600 var(--fs-1) var(--mono); color: var(--mute); }

.wrs-figs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.wrs-fig {
  display: grid; gap: 2px; padding: 10px 12px; border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--ink) 3%, transparent);
}
.wrs-fig-val { font: 700 var(--fs-4)/1.1 var(--body); color: var(--ink); }
.wrs-fig-label { color: var(--mute); font-size: var(--fs-2); }
.wrs-pop { position: relative; padding: 0; }
.wrs-pop-btn {
  all: unset; box-sizing: border-box; display: grid; gap: 2px; width: 100%;
  padding: 10px 12px; border-radius: var(--r-lg); cursor: pointer;
}
.wrs-pop-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.wrs-pop-btn .wrs-fig-val { text-decoration: underline dotted color-mix(in srgb, var(--gold) 60%, transparent); text-underline-offset: 4px; }
.wrs-pop-card {
  position: absolute; z-index: 20; left: 0; top: calc(100% + 6px);
  min-width: 200px; max-width: min(280px, 80vw); max-height: 260px; overflow: auto;
  padding: 10px 12px; background: var(--panel); color: var(--ink);
  border-radius: var(--r-lg); box-shadow: 0 0 0 1px var(--rule), var(--shadow-card), 0 18px 40px -16px rgba(0, 0, 0, .6);
  opacity: 0; visibility: hidden; transform: translateY(-4px);
}
.wrs-pop.is-pinned .wrs-pop-card { opacity: 1; visibility: visible; transform: none; }
@media (hover: hover) {
  .wrs-pop:hover .wrs-pop-card, .wrs-pop:focus-within .wrs-pop-card { opacity: 1; visibility: visible; transform: none; }
}
.wrs-pop-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.wrs-pop-list li { display: flex; justify-content: space-between; gap: 12px; font-size: var(--fs-2); }
.wrs-pop-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wrs-pop-empty { margin: 0; color: var(--dim); font-size: var(--fs-2); }
.wrs-note { margin: 10px 0 12px; color: var(--dim); font-size: var(--fs-2); line-height: 1.4; }

.wrs-line { display: grid; grid-template-columns: 118px minmax(0, 1fr); gap: 10px; align-items: center; padding: 6px 0; }
.wrs-line-label { color: var(--mute); font-size: var(--fs-2); }
.wrs-line-val { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; }
.wrs-line-sub { color: var(--dim); font-size: var(--fs-1); }
.wrs-model {
  margin-top: 10px; padding: 10px 12px; border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--mute) 7%, transparent);
}
.wrs-model-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.wrs-model-tag {
  font: 700 var(--fs-1) var(--mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--mute); padding: 2px 7px; border-radius: 999px; box-shadow: 0 0 0 1px var(--rule);
}
.wrs-model-title { color: var(--mute); font-size: var(--fs-2); }

@media (max-width: 900px) {
  .wrs-grid { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 640px) {
  .wrs-grid { margin: 10px 12px 0; }
  .wrs-member { grid-template-columns: minmax(0, 1fr); gap: 6px; }
  .wrs-ball { width: 48px; }
  .wrs-line { grid-template-columns: minmax(0, 1fr); gap: 2px; }
}
@media (prefers-reduced-motion: no-preference) {
  .wrs-pop-card { transition: opacity .16s var(--ease), transform .16s var(--ease), visibility .16s; }
  .wrs-card { animation: war-lock-rise .4s var(--ease) both; }
}
/* ============================================ end WAR ROOM ROSTER (wrs-) */

/* ==================================================== WAR HUB LIVE BUTTON */
/* "This week's war": the banner's biggest control while a war is in flight. */
.war-hub-live {
  display: grid; grid-template-columns: 1fr auto; grid-template-areas: "k go" "vs go" "m go";
  align-items: center; gap: 2px 16px; margin-top: 16px; padding: 14px var(--gutter);
  border-radius: var(--r-lg); text-decoration: none; color: var(--ink);
  background: linear-gradient(120deg, color-mix(in srgb, var(--war-hub-accent, var(--gold)) 28%, var(--panel)), var(--panel));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--war-hub-accent, var(--gold)) 45%, transparent), 0 8px 22px rgba(0,0,0,.25);
  transition: transform .15s, box-shadow .15s;
}
.war-hub-live:hover { transform: translateY(-1px); box-shadow: 0 0 0 1px var(--war-hub-accent, var(--gold)), 0 12px 28px rgba(0,0,0,.3); }
.war-hub-live-kicker { grid-area: k; font: 600 var(--fs-1) var(--body); text-transform: uppercase; letter-spacing: .1em; color: var(--war-hub-accent, var(--gold)); }
.war-hub-live-vs { grid-area: vs; font: 700 var(--fs-4)/1.2 var(--body); }
.war-hub-live-meta { grid-area: m; font-size: var(--fs-2); color: var(--mute); }
.war-hub-live-go { grid-area: go; font-size: var(--fs-5); color: var(--war-hub-accent, var(--gold)); }
@media (prefers-reduced-motion: reduce) { .war-hub-live { transition: none; } .war-hub-live:hover { transform: none; } }
/* ================================================ end WAR HUB LIVE BUTTON */

/* ======================================== NAV SECTIONS 2026-09-24 (ux-nav-, ux-premium)
   The masthead split into seven sections (SECTIONS in lib/chrome.js), the
   "New!" badge on Wars, and the Premium shimmer on the tabs whose pages are
   Premium (PvP calculator, War room). */

/* Seven entries do not fit one row on a phone. Wrapped into two rows, every
   section stays visible; a scrolling row would hide the last two off the edge,
   which is the fault the phone nav row was made to fix. */
@media (max-width: 640px) {
  .top .nav { flex-wrap: wrap; row-gap: 2px; overflow-x: visible; }
  /* 22% puts four on the first row and three on the second, rather than
     five and a stretched-out two. */
  .top .nav a { flex: 1 1 22%; padding: 7px 8px; }
}

.ux-nav-new {
  display: inline-block; margin-left: 5px; padding: 1px 6px 2px;
  border-radius: 999px; vertical-align: 1px;
  font: 700 var(--fs-1) var(--body); line-height: 1.3; letter-spacing: 0;
  color: var(--bg); background: var(--gold);
}
@media (prefers-reduced-motion: no-preference) {
  .ux-nav-new { animation: ux-nav-new-pulse 2.4s ease-in-out infinite; }
}
@keyframes ux-nav-new-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--gold) 55%, transparent); }
  50%      { box-shadow: 0 0 0 5px color-mix(in srgb, var(--gold) 0%, transparent); }
}

/* Premium: the label in a moving blue gradient. Three stops per theme, each
   dark enough (light) or light enough (dark) to read as text on its own, so
   the word is legible at every frame of the sweep, and still under
   reduced motion, where the gradient simply holds. */
:root {
  --prem-a: #6FB2FF; --prem-b: #D2EAFF; --prem-c: #3F86FF;
}
:root[data-theme="light"] { --prem-a: #1450C8; --prem-b: #2C6EEA; --prem-c: #0B3A94; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) { --prem-a: #1450C8; --prem-b: #2C6EEA; --prem-c: #0B3A94; }
}
.ux-premium-text {
  background-image: linear-gradient(100deg, var(--prem-a) 0%, var(--prem-c) 30%,
    var(--prem-b) 50%, var(--prem-c) 70%, var(--prem-a) 100%);
  background-size: 250% 100%;
  background-position: 0% 50%;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
@media (prefers-reduced-motion: no-preference) {
  .ux-premium-text { animation: ux-premium-sweep 3.2s linear infinite; }
}
@keyframes ux-premium-sweep {
  from { background-position: 100% 50%; }
  to   { background-position: -150% 50%; }
}
/* Forced colours drop background images, which would leave the word invisible. */
@media (forced-colors: active) {
  .ux-premium-text { -webkit-text-fill-color: currentColor; color: inherit; background: none; }
}
/* PVP Tools has three tabs now, and on a phone its row is one line: the strip
   takes what is left and scrolls (centred on the current tab, lib/chrome.js),
   and the "Modelled" note and its (i) keep their size beside it. */
.ux-tools-row > .subnav { flex: 1 1 auto; min-width: 0; }
.ux-tools-row > .info { flex: none; }
/* The (i)'s phone touch target reaches 13px past it (.info::after); at the
   end of a full row that was page-wide horizontal scroll. */
.ux-tools-row > .info { margin-right: 13px; }
/* ================================================= end NAV SECTIONS (ux-nav-) */

/* ==== upd-: "the site was just updated" (app.js updatedNotice) ==== */
.upd-notice { display: flex; flex-direction: column; align-items: center; gap: .5rem; text-align: center; }
.upd-notice p { margin: 0; }
.upd-title { font-weight: 600; color: var(--ink); }
.upd-notice .btn { margin-top: .5rem; }

/* ==================================================== LIVE WAR (wlive-) */
/* A war in flight (views/war-detail.js liveStrip, lib/war-live.js): the
   "Live · updated Ns ago · phase · time left" line under the score, a faint
   glow on a section a refresh just changed, and the war room button's score. */
.wlive-strip {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 0 4px; margin: -4px 0 12px; font-size: var(--fs-2); color: var(--mute);
  font-variant-numeric: tabular-nums; text-align: center;
}
.wlive-word { color: var(--good); font-weight: 700; letter-spacing: .04em; text-transform: uppercase; font-size: var(--fs-1); }
.wlive-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--good); margin-right: 4px; flex: none; }
.wlive-strip.is-final .wlive-dot { background: var(--dim); }
.wlive-strip.is-final .wlive-word { color: var(--ink); }
.wlive-moved { border-radius: var(--r-lg); }
@media (prefers-reduced-motion: no-preference) {
  .wlive-strip:not(.is-final) .wlive-dot { animation: wlive-pulse 2s ease-out infinite; }
  .wlive-moved { animation: wlive-glow 1.6s ease-out; }
}
/* A section swapped in by a live refresh must not replay its entrance: the
   field's zones fly in on first paint, and replaying that every minute is
   exactly the flash a live page must not have. The fog drift stays. */
.wlive-quiet .war-field-half .war-field-zone { animation: none; }
@media (prefers-reduced-motion: no-preference) {
  .wlive-quiet .war-field-half .war-field-zone.is-fogged { animation: war-field-fog 14s ease-in-out infinite alternate; }
}
@keyframes wlive-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--good) 60%, transparent); }
  70%, 100% { box-shadow: 0 0 0 7px transparent; }
}
@keyframes wlive-glow {
  0% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--gold) 45%, transparent); }
  100% { box-shadow: 0 0 0 1px transparent; }
}
/* The war room button gains a score on the name's row: "vs Beta   812 – 640". */
.war-hub-live { grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: "k k go" "vs s go" "m m go"; }
.war-hub-live-vs { min-width: 0; overflow-wrap: anywhere; }
.war-hub-live-score {
  grid-area: s; display: inline-flex; align-items: baseline; gap: 6px;
  font: 700 var(--fs-4)/1.2 var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.war-hub-live-score[hidden], .war-hub-live-meta[hidden] { display: none; }
.wlive-them { color: var(--mute); }
.wlive-dash { color: var(--dim); font-weight: 400; }
/* ================================================ end LIVE WAR (wlive-) */

/* ============================================================ WAR PHASE */
/* The war cycle's phase and clock, at the top of the Wars pages (lib/war-phase.js). */
.war-phase {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px;
  margin: 0 0 16px; padding: 10px 14px; border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--gold) 10%, var(--panel));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--gold) 30%, transparent);
}
.war-phase-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--gold); align-self: center; }
.war-phase[data-phase="attack"] .war-phase-dot { background: var(--bad); animation: war-phase-pulse 1.6s ease-in-out infinite; }
.war-phase-title { font-size: var(--fs-3); }
.war-phase-next { font-size: var(--fs-2); color: var(--mute); }
@keyframes war-phase-pulse { 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .war-phase[data-phase="attack"] .war-phase-dot { animation: none; } }
/* ======================================================== end WAR PHASE */

/* ============================================================== WAR MAP */
/* The war page's Map view: the battlefield's real zone polygons as inline
   SVG (lib/war-map.js), beside the mirrored Field. Side colours are the
   field's own --wf-a / --wf-b, so both themes come with them. */
.wmap-toggle { display: inline-flex; margin: 0 var(--gutter) 10px; border: 1px solid var(--rule); border-radius: var(--r); overflow: hidden; }
.wmap-toggle button { padding: 6px 16px; }
.wmap { display: grid; gap: 6px; justify-items: center; }
.wmap-svg { display: block; width: 100%; max-width: 640px; height: auto; max-height: 82vh; overflow: visible; }
.wmap-side { margin: 0; display: flex; align-items: center; gap: 6px; font: 500 var(--fs-1) var(--mono); color: var(--dim); }
.wmap-side b { font: 700 var(--fs-2) var(--cond); letter-spacing: .06em; text-transform: uppercase; color: var(--ink); }
.wmap-side.is-a { --side: var(--wf-a); }
.wmap-side.is-b { --side: var(--wf-b); }
.wmap-note { margin: 18px 0; font: var(--fs-2) var(--body); color: var(--mute); text-align: center; }
.wmap-retry { margin-left: 6px; font: inherit; color: var(--gold); background: none; border: 1px solid var(--rule);
  border-radius: var(--r); padding: 2px 10px; cursor: pointer; }

.wmap-zone { --side: var(--wf-a); cursor: pointer; outline: none; }
.wmap-zone.is-b { --side: var(--wf-b); }
.wmap-shape { stroke: var(--panel); stroke-width: 2; stroke-linejoin: round; vector-effect: non-scaling-stroke;
  fill: color-mix(in srgb, var(--side) 34%, var(--panel)); }
/* Held: the side's colour, full. */
.wmap-zone.is-open .wmap-shape { fill: color-mix(in srgb, var(--side) 52%, var(--panel)); }
/* Cleared: drained and hatched, edged in the side's colour so it stays theirs. */
.wmap-zone.is-cleared .wmap-shape { fill: url(#wmap-hatch); stroke: color-mix(in srgb, var(--side) 55%, var(--panel)); }
.wmap-hatch-bg { fill: color-mix(in srgb, var(--bg) 82%, var(--panel)); }
.wmap-hatch-line { stroke: var(--dim); stroke-width: 1.4; opacity: .55; }
/* Never attacked: fog, not emptiness — clouded, with a dashed edge. */
.wmap-zone.is-fogged .wmap-shape { fill: url(#wmap-fog); stroke: color-mix(in srgb, var(--side) 60%, var(--panel));
  stroke-dasharray: 5 4; }
.wmap-fog-bg { fill: color-mix(in srgb, var(--side, var(--dim)) 10%, var(--panel)); }
.wmap-fog-dot { fill: var(--wf-fog); stroke: var(--dim); stroke-width: .5; opacity: .9; }
.wmap-zone.is-unknown .wmap-shape { fill: color-mix(in srgb, var(--side) 12%, var(--panel)); opacity: .7; }
.wmap-zone:hover .wmap-shape, .wmap-zone.is-pinned .wmap-shape { stroke: var(--ink); stroke-width: 2.5; }
.wmap-zone:focus-visible .wmap-shape { stroke: var(--gold); stroke-width: 3; }
.wmap-label { pointer-events: none; fill: var(--ink); font-family: var(--cond); font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  paint-order: stroke; stroke: color-mix(in srgb, var(--panel) 80%, transparent); stroke-width: 3px; stroke-linejoin: round; }
.wmap-lrow, .wmap-lstat { fill: var(--mute); font-weight: 600; }
.wmap-zone.is-cleared .wmap-lcol, .wmap-zone.is-fogged .wmap-lcol { fill: var(--mute); }
.wmap-zone.is-fogged .wmap-lstat { font-style: italic; }
.wmap-line { stroke: var(--gold); stroke-width: 2; stroke-dasharray: 8 6; vector-effect: non-scaling-stroke; opacity: .8; pointer-events: none; }
@media (max-width: 640px) {
  .wmap-toggle { margin-left: 12px; margin-right: 12px; }
}
@media (pointer: coarse) { .wmap-toggle button { padding: 10px 18px; } }
@media (prefers-reduced-motion: no-preference) {
  .wmap-shape { transition: fill .45s var(--ease), stroke .18s var(--ease); }
}
/* ========================================================== end WAR MAP */

/* ============================================================ WAR PRE-ATTACK */
.war-field-prenote { margin: 0 0 12px; padding: 10px 14px; border-radius: var(--r); background: var(--panel); color: var(--mute); font-size: var(--fs-2); }
/* ======================================================== end WAR PRE-ATTACK */

/* ======================================== WAR REWARDS AND POINTS (wr-) ===
   Placed just before SOFT TABLES because tests/soft-tables.test.mjs
   requires that section to be the last thing in the file.
   TODO items 14 and 18: the gift on a war-history row, "Rewards by result",
   and the points (i). The popup is the site's bottom sheet (dialog.ux-sheet);
   these rules only dress what goes inside it. lib/war-rewards.js. */
.wr-gift {
  display: inline-grid; place-items: center; vertical-align: middle;
  width: 26px; height: 26px; margin-left: 8px; padding: 0;
  border: 1px solid var(--rule); border-radius: 999px;
  background: transparent; color: var(--mute); cursor: pointer;
}
.wr-gift:hover, .wr-gift:focus-visible, .wr-gift[aria-expanded="true"] { color: var(--gold); border-color: var(--gold); }
.wr-gift-icon { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linejoin: round; }
.wr-link {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--gold); font: 600 var(--fs-1) var(--body); text-decoration: underline; text-underline-offset: 3px;
}
.wr-links { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px; margin: 10px 0 14px; }
.wr-links-item { display: inline-flex; align-items: center; gap: 6px; color: var(--mute); font: var(--fs-1) var(--body); }
.wr-info { margin-left: 8px; vertical-align: 1px; }

dialog.wr-pop { padding-top: 22px; }
.wr-pop-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 12px; }
.wr-pop-title { font: 700 var(--fs-4)/1.2 var(--cond); text-transform: uppercase; letter-spacing: .05em; }
.wr-pop-close {
  background: transparent; border: 1px solid var(--rule); border-radius: var(--r); color: var(--mute);
  padding: 6px 10px; cursor: pointer; font: var(--fs-1) var(--body);
}
.wr-pop-close:hover, .wr-pop-close:focus-visible { border-color: var(--gold); color: var(--gold); }
.wr-pop-wait, .wr-pop-note { color: var(--mute); font: var(--fs-1)/1.6 var(--body); }
.wr-pop-note { margin-top: 12px; }

.wr-side { padding: 12px 0; border-top: 1px solid var(--rule); }
.wr-side:first-child { border-top: 0; padding-top: 0; }
.wr-side-name { font: 700 var(--fs-3)/1.3 var(--body); }
.wr-side-tier { color: var(--mute); font-weight: 500; }
.wr-side-result { margin: 4px 0; font: 700 var(--fs-2) var(--cond); text-transform: uppercase; letter-spacing: .06em; color: var(--mute); }
.wr-side-result.is-win { color: var(--good); }
.wr-side-result.is-loss { color: var(--bad); }
.wr-side-line { font: var(--fs-2)/1.55 var(--body); }

.wr-tiers { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.wr-tier {
  padding: 4px 10px; border-radius: 999px; border: 1px solid var(--rule);
  background: transparent; color: var(--mute); cursor: pointer; font: 500 var(--fs-1) var(--body);
}
.wr-tier[aria-pressed="true"] { color: var(--ink); border-color: var(--gold); background: color-mix(in srgb, var(--gold) 14%, transparent); }
.wr-sub { margin: 12px 0 6px; font: 700 var(--fs-1) var(--cond); text-transform: uppercase; letter-spacing: .08em; color: var(--mute); }
table.wr-lines { width: 100%; min-width: 0; border-collapse: collapse; font: var(--fs-1) var(--body); }
.wr-lines th, .wr-lines td { padding: 6px 4px; text-align: right; border-bottom: 1px solid var(--hairline-2); }
.wr-lines th.left { text-align: left; }
.wr-lines thead th { color: var(--dim); font-weight: 500; }
.wr-lines td { font-variant-numeric: tabular-nums; }
.wr-lines tr.is-mark th, .wr-lines tr.is-mark td { color: var(--gold); font-weight: 700; }

.wr-points { list-style: none; padding: 0; margin: 0 0 14px; display: grid; gap: 8px; }
.wr-point { display: grid; grid-template-columns: minmax(0, 11fr) minmax(60px, 7fr) auto; align-items: center; gap: 10px; font: var(--fs-1)/1.35 var(--body); }
.wr-point-bar { height: 8px; border-radius: 999px; background: var(--hairline-2); overflow: hidden; }
.wr-point-fill { display: block; height: 100%; border-radius: 999px; background: var(--gold); }
.wr-point-value { min-width: 3ch; text-align: right; font-variant-numeric: tabular-nums; }
.wr-advice p { font: var(--fs-2)/1.55 var(--body); margin-top: 8px; }
@media (max-width: 430px) {
  .wr-point { grid-template-columns: minmax(0, 1fr) auto; }
  .wr-point-bar { grid-column: 1 / -1; grid-row: 2; }
}
/* ==================================== end WAR REWARDS AND POINTS (wr-) */
/* ======================================== RANK HISTORY + HEAD-TO-HEAD (rh-, h2h-)
   The ladder-history chart on a profile (lib/rank-chart.js) and the
   head-to-head cards on an alliance page (lib/head-to-head.js). The chart is
   drawn at its container's real pixel width, so these sizes are real sizes. */
.rh-block { padding: 10px var(--gutter) 6px; }
.rh-leagues { display: inline-flex; gap: 4px; margin-bottom: 8px; padding: 3px;
  border-radius: 999px; background: var(--hairline-2); }
.rh-league { border: 0; border-radius: 999px; padding: 5px 12px; cursor: pointer;
  font: 500 var(--fs-2) var(--body); color: var(--mute); background: transparent; }
.rh-league[aria-pressed="true"] { color: var(--ink); background: var(--panel); box-shadow: var(--shadow-card); }
.rh-league:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
.rh-caption { margin: 0 0 8px; font: var(--fs-3) var(--body); color: var(--mute); }
.rh-frame { width: 100%; min-height: 180px; }
.rh-chart { display: block; max-width: 100%; overflow: visible; }
.rh-band { fill: transparent; }
.rh-band.is-alt { fill: var(--hairline-2); }
.rh-tier, .rh-week { fill: var(--dim); font: var(--fs-1) var(--mono); }
.rh-line { fill: none; stroke: var(--gold); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.rh-dot { fill: var(--panel); stroke: var(--gold); stroke-width: 2; cursor: pointer; }
.rh-dot.is-up { stroke: var(--good); }
.rh-dot.is-down { stroke: var(--bad); }
.rh-dot.is-picked { fill: var(--gold); }
.rh-dot.is-up.is-picked { fill: var(--good); }
.rh-dot.is-down.is-picked { fill: var(--bad); }
.rh-dot:focus { outline: none; }
.rh-dot:focus-visible { stroke-width: 3; }
.rh-readout { margin: 6px 0 0; min-height: 1.4em; font: var(--fs-2) var(--body); color: var(--ink); }
@media (max-width: 560px) {
  .rh-block { padding: 8px var(--gutter) 4px; }
}
/* ======================================== end RANK HISTORY + HEAD-TO-HEAD */

/* ================================================= WAR MATCHUPS (wm-) ====
   TODO item 17: public/views/war-matchups.js. A thin row is dimmed, and
   its rate cell says "too few duels" in place of a number. */
.wm-head { margin: 4px 0 10px; }
.wm-intro, .wm-lede { max-width: 72ch; color: var(--mute); line-height: 1.5; }
.wm-lede { margin: 6px 0 12px; }
.wm-section { margin: 22px 0 8px; }
.wm-section .wrap + .wrap { margin-top: 14px; }
tr.wm-thin > td { opacity: 0.55; }
td.wm-thin-cell { color: var(--dim); font-style: italic; font-size: 0.85em; white-space: nowrap; }
.wm-against { white-space: nowrap; }
.wm-against .chips { vertical-align: middle; }
.wm-hi { color: var(--good); }
.wm-lo { color: var(--bad); }
/* ============================================== end WAR MATCHUPS (wm-) */

/* ======================================== LEFTOVERS 2026-09-25 (lo-) ====
   The page gutter as one token (docs/ux-proposals-2026-09-24.md, P3):
   20px on a desktop, 16px on a phone. It was a literal 18px typed into a
   hundred rules, with a phone block that pulled a dozen of them to 14px and
   left the rest at 18 — so one page could start its lines at two different
   distances from the edge. Every page-level horizontal padding and margin
   now reads this.
   Placed just before SOFT TABLES because tests/soft-tables.test.mjs
   requires that section to be the last thing in the file. */
:root { --gutter: 20px; }
@media (max-width: 640px) { :root { --gutter: 16px; } }

/* The patch picker is the shared icon dropdown now (lib/chrome.js
   `patchSelect`). A hollow pip, like Overall in the bracket list, and the
   patch's dates under its name in the open list only: the closed button
   stays one line in the filter strip. */
.patch-pip {
  width: 9px; height: 9px; border-radius: var(--r-sm); flex-shrink: 0;
  box-shadow: 0 0 0 1px var(--dim) inset;
}
.isel-row[aria-selected="true"] .patch-pip { background: var(--gold); box-shadow: none; }
.patch-when { display: block; color: var(--dim); font: var(--fs-1) var(--mono); margin-top: 1px; }
.isel-button .patch-when { display: none; }
.patch-choose .isel-button { min-width: 150px; }

/* The phone filter sheet rises in with a transform (`ux-rise`, fill mode
   `both`), and a transform makes a stacking context. With no z-index of its
   own that context painted under the page below it, so on a phone every
   dropdown in the sheet — Patch, Rarity bracket, Division — opened BEHIND the
   table: the list was there, but the rows showed through it and took the
   taps. Found while checking the patch picker at 375px, 2026-09-25. */
@media (max-width: 640px) {
  .chrome.is-scope-open > .ux-scope { position: relative; z-index: 20; }
}
/* ================================================ end LEFTOVERS (lo-) */

/* ================================= TODO 15: LAST ENDPOINTS (pg-, tc-, acct-)
   The profile's Progress section (lib/progress-block.js), territory captures
   on the Alliances section and alliance pages (lib/territory-captures.js),
   and the account page's Discord verification. */
.pg-block { padding: 10px var(--gutter) 14px; display: grid; gap: 10px; }
.pg-card { max-width: 720px; }
.pg-sub {
  margin: 6px 0 0; font-family: var(--cond); font-size: var(--fs-1); font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--mute);
}
.pg-achs { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px;
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); }
.pg-ach { display: flex; gap: 10px; align-items: flex-start; padding: 8px 10px;
  border-radius: var(--r); background: var(--hairline-2); }
.pg-ach[hidden] { display: none; }
.pg-mark { flex: none; width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center;
  font: 700 var(--fs-2) var(--cond); color: var(--mute); background: var(--panel); border: 1px solid var(--rule); }
.pg-ach.is-earned .pg-mark { color: var(--gold); border-color: color-mix(in srgb, var(--gold) 55%, transparent); }
.pg-ach-body { display: grid; gap: 2px; min-width: 0; }
.pg-ach-name { font: 600 var(--fs-3) var(--body); color: var(--ink); }
.pg-ach-desc { font: var(--fs-2) var(--body); color: var(--mute); overflow-wrap: anywhere; }
.pg-ach-meta { font: var(--fs-1) var(--mono); color: var(--dim); }
.pg-bar { display: block; height: 3px; margin-top: 4px; border-radius: var(--r-sm); background: var(--lift); }
.pg-bar > span { display: block; height: 100%; border-radius: var(--r-sm); background: var(--gold); }
.pg-more { justify-self: start; }
.pg-passes { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.pg-pass { display: flex; flex-wrap: wrap; gap: 4px 10px; font: var(--fs-2) var(--body); color: var(--mute); }
.pg-pass b { color: var(--ink); font-weight: 600; }

.tc-grid { display: grid; gap: 14px; }
.tc-side td.up { color: var(--good); }
.tc-side td.down { color: var(--bad); }
.tc-latest { margin-top: 8px; }
.tc-recent { list-style: none; margin: 0; padding: 0 var(--gutter) 12px; display: grid; gap: 2px; }
.tc-cap { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 2px 12px;
  padding: 6px 0; border-bottom: 1px solid var(--hairline-2); font: var(--fs-2) var(--body); color: var(--mute); }
.tc-cap a { color: var(--ink); }
.tc-when { font: var(--fs-1) var(--mono); color: var(--dim); }
.tc-rivals { padding: 0 var(--gutter) 10px; display: grid; gap: 4px; }
.tc-rival { margin: 0; font: var(--fs-2) var(--body); color: var(--mute); }
.tc-rival b { color: var(--ink); font-weight: 600; }
.tc-turnout[hidden] { display: none; }

.acct-discord-verify { margin-top: 12px; }
/* ================================= end TODO 15: LAST ENDPOINTS */

/* ================================================= SOFT TABLES (ux-t-)
   TODO item 8, 2026-09-24: the owner's mix of the four table mockups
   (docs/table-options-2026-09-24.md), built as ONE system in lib/table.js
   (`variant`) and lib/table-soft.js (drawer, sheet, bands):

     A  .ux-t-airy   a real table: no rules, rounded hover lift, a pill for
                     the headline rate, short rounded meters.
     B  .ux-t-cards  each row a card; the header row becomes the sort control.
     C  .ux-band     collapsible groups with a header that sticks.
     D  .ux-t-list   a few columns and a drawer with every figure; the A
                     tables use its bottom sheet below 560px.

   Scoped to those classes, so a table that has not opted in is untouched,
   and the base `tbody td` / `thead th` rules above keep their row rules.
   --ux-sticky-top is the masthead's height, kept current by table-soft.js. */

:root { --ux-sticky-top: 49px; }
.ux-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---- the drawer button, shared by A (phones only) and D (always) ---- */
.ux-t-more { display: none; }
thead th.ux-t-more { cursor: default; }
td.ux-t-more { width: 1%; padding-left: 2px; padding-right: 6px; text-align: right; }
.ux-t-open {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border: 0; border-radius: 999px; padding: 0;
  background: var(--lift); color: var(--ink); font: 600 var(--fs-4)/1 var(--body); cursor: pointer;
}
.ux-t-open:hover, .ux-t-open[aria-expanded="true"] { background: var(--gold); color: var(--bg); }
.ux-t-open:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* ---- A: airy rows (and the row look D's list shares) ---- */
table.ux-t-airy, table.ux-t-list { border-collapse: separate; border-spacing: 0 2px; }
.ux-t-airy thead th, .ux-t-list thead th,
.ux-t-airy thead tr.group-row th, .ux-t-list thead tr.group-row th {
  background: transparent; border-top: 0; border-bottom: 0; border-left: 0; border-right: 0;
}
.ux-t-airy thead tr:last-child th, .ux-t-list thead tr:last-child th { padding-bottom: 8px; color: var(--dim); }
.ux-t-airy thead th.primary, .ux-t-airy thead th:hover, .ux-t-airy thead th[data-dir],
.ux-t-list thead th:hover, .ux-t-list thead th[data-dir] { color: var(--ink); }
.ux-t-airy tbody td, .ux-t-list tbody td {
  border-bottom: 0; padding-top: 7px; padding-bottom: 7px;
  transition: background-color 150ms ease;
}
/* Two pixels less each side pays for the meters and the pill, so a table
   that fitted before still fits. */
.ux-t-airy tbody td, .ux-t-list tbody td, .ux-t-airy thead th, .ux-t-list thead th {
  padding-left: 10px; padding-right: 10px;
}
/* The drawer button's cell is only as wide as the button. Positioned, so the
   heading's screen-reader text stays inside the table's scroll box instead of
   widening the page. */
.ux-t-airy .ux-t-more, .ux-t-list .ux-t-more { position: relative; padding-left: 2px; padding-right: 6px; }
@media (max-width: 560px) {
  .ux-t-airy .ux-t-more, .ux-t-list .ux-t-more { padding-left: 0; padding-right: 2px; }
}
/* Whitespace separates the rows, and the hovered one lifts onto a rounded
   panel. The last visible cell is the one before the drawer button, except
   where the button shows. */
.ux-t-airy tbody td:first-child, .ux-t-list tbody td:first-child { border-radius: var(--r) 0 0 var(--r); }
.ux-t-airy tbody td:last-child, .ux-t-list tbody td:last-child,
.ux-t-airy.ux-t-has-more tbody td:nth-last-child(2) { border-radius: 0 var(--r) var(--r) 0; }
.ux-t-airy.ux-t-has-more tbody td:last-child { border-radius: 0; }
.ux-t-airy tbody tr.is-open td, .ux-t-list tbody tr.is-open td { background: var(--panel); }

/* The headline rate as a pill with its cautious bound inside it. Only this
   figure is tinted; a thin row's pill is an outline, since it is unranked. */
.ux-t-airy .wr-pill, .ux-t-list .wr-pill {
  display: inline-flex; align-items: baseline; gap: 5px; padding: 2px 8px;
  border-radius: 999px; background: var(--hairline-2);
}
.ux-t-airy .wr-pill b, .ux-t-list .wr-pill b { display: inline; font-size: var(--fs-2); }
.ux-t-airy .wr-pill .wr-bound, .ux-t-list .wr-pill .wr-bound { display: inline; }
.ux-t-airy .wr-pill.is-hi, .ux-t-list .wr-pill.is-hi { background: color-mix(in srgb, var(--good) 18%, transparent); }
.ux-t-airy .wr-pill.is-lo, .ux-t-list .wr-pill.is-lo { background: color-mix(in srgb, var(--bad) 16%, transparent); }
.ux-t-airy tr.thin .wr-pill, .ux-t-list tr.thin .wr-pill { background: none; box-shadow: inset 0 0 0 1px var(--rule); }

/* Play rate as a short rounded meter instead of a bar behind the figure. */
.ux-t-airy td.bar::before, .ux-t-list td.bar::before { display: none; }
.ux-t-airy td.bar > span, .ux-t-list td.bar > span {
  display: inline-grid; grid-template-columns: 40px auto; align-items: center; gap: 8px;
}
.ux-t-airy td.bar > span::before, .ux-t-list td.bar > span::before {
  content: ""; height: 6px; border-radius: 999px;
  background: linear-gradient(to right, color-mix(in srgb, var(--gold) 55%, transparent) var(--w, 0%),
    var(--hairline-2) var(--w, 0%));
}
/* Games: the confidence bar as the same meter, beside the count rather than
   under it, so the row is two lines tall instead of three. */
.ux-t-airy .conf-box, .ux-t-list .conf-box {
  grid-template-columns: 40px minmax(7ch, auto); column-gap: 8px; align-items: center;
}
.ux-t-airy .conf-box > b, .ux-t-list .conf-box > b { grid-column: 2; grid-row: 1; }
.ux-t-airy .conf-box > .conf-swap, .ux-t-list .conf-box > .conf-swap,
.ux-t-airy .conf-box > .conf-pm, .ux-t-list .conf-box > .conf-pm { grid-column: 2; grid-row: 2; justify-self: end; }
.ux-t-airy .conf-track, .ux-t-list .conf-track {
  grid-column: 1; grid-row: 1 / span 2; margin: 0;
  height: 6px; width: 40px; border-radius: 999px;
  background: var(--hairline-2); overflow: hidden;
}
.ux-t-airy .conf-fill, .ux-t-list .conf-fill {
  border-radius: 999px; background: color-mix(in srgb, var(--gold) 55%, transparent); opacity: 1;
}

@media (max-width: 560px) {
  /* A phone keeps what it kept before, plus a way to everything it dropped. */
  .ux-t-airy .ux-t-more { display: table-cell; }
  .ux-t-airy.ux-t-has-more tbody td:nth-last-child(2) { border-radius: 0; }
  .ux-t-airy.ux-t-has-more tbody td:last-child { border-radius: 0 var(--r) var(--r) 0; }
  /* The meters go on a phone: the figure beside them stays, and the width
     they took is what pays for the drawer button. */
  .ux-t-airy td.bar > span, .ux-t-list td.bar > span { display: inline; }
  .ux-t-airy td.bar > span::before, .ux-t-list td.bar > span::before { display: none; }
  .ux-t-airy .conf-box, .ux-t-list .conf-box { grid-template-columns: auto; }
  .ux-t-airy .conf-box > *, .ux-t-list .conf-box > * { grid-column: 1; }
  .ux-t-airy .conf-track, .ux-t-list .conf-track { display: none; }
  td.ux-t-more { padding-left: 0; padding-right: 4px; }
  .ux-t-open { width: 24px; height: 24px; }
  .ux-t-airy .wr-pill, .ux-t-list .wr-pill {
    flex-direction: column; align-items: flex-end; gap: 0; border-radius: var(--r); padding: 3px 8px;
  }
}

/* ---- D: compact list and drawer ---- */
.ux-t-list .ux-detail { display: none; }
/* A list is a few columns by design, beside a drawer: the 720px floor that
   keeps a wide table from squashing would only invent a sideways scroll. */
table.ux-t-list { min-width: 0; }
.ux-t-list .ux-t-more { display: table-cell; }
.ux-t-list tbody tr { cursor: pointer; }
.ux-t-split { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }
.ux-drawer { display: none; }
@media (min-width: 900px) {
  .ux-t-split { grid-template-columns: minmax(0, 1fr) 300px; }
  .ux-drawer {
    display: block; position: sticky; top: calc(var(--ux-sticky-top) + 10px);
    max-height: calc(100vh - var(--ux-sticky-top) - 20px); overflow: auto;
    padding: 16px; border-radius: var(--r-lg); background: var(--panel); box-shadow: var(--shadow-card);
  }
}
.ux-drawer-head { display: flex; align-items: flex-start; gap: 10px; }
.ux-drawer-title { flex: 1; min-width: 0; margin: 0; font: 600 var(--fs-3)/1.3 var(--body); color: var(--ink); }
.ux-drawer-title .cellflex { flex-wrap: wrap; }
.ux-drawer-title:focus { outline: none; }
.ux-drawer-title:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.ux-drawer-close {
  flex: none; width: 28px; height: 28px; border: 0; border-radius: 999px; padding: 0;
  background: var(--lift); color: var(--ink); font: 600 var(--fs-4)/1 var(--body); cursor: pointer;
}
.ux-drawer-close:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.ux-drawer-grab { display: none; }
.ux-drawer-note { margin: 10px 0 0; color: var(--mute); font-size: var(--fs-2); }
.ux-drawer-empty { margin: 0; color: var(--dim); font-size: var(--fs-2); }
.ux-drawer-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin: 12px 0 0; }
.ux-drawer-fact { min-width: 0; padding: 8px 10px; border-radius: var(--r); background: var(--bg); }
.ux-drawer-fact > dt { font: 500 var(--fs-1) var(--body); color: var(--dim); }
/* A copy of the cell, class and all, so its colour comes with it — and so
   does a phone's `drop-sm`, which must not hide it here. */
.ux-drawer-fact > dd {
  display: block; margin: 2px 0 0; padding: 0; text-align: left; white-space: normal; overflow-wrap: anywhere;
  font: 600 var(--fs-3) var(--mono); font-variant-numeric: tabular-nums; color: var(--ink);
}
.ux-drawer-fact > dd.support { color: var(--mute); }
/* A word rather than a figure (an item, a date, a role) reads in the body face. */
.ux-drawer-fact > dd.left { font: 500 var(--fs-3) var(--body); }
.ux-drawer-fact > dd.hi, .ux-drawer-fact b.hi { color: var(--good); }
.ux-drawer-fact > dd.lo, .ux-drawer-fact b.lo { color: var(--bad); }
.ux-drawer-fact .cell-detail, .ux-drawer-fact .conf-pm { opacity: 1; }
/* A phone hides a division's name beside its crest to save a column; the
   drawer has the room, and the crest alone is not an answer. */
.ux-drawer-fact .div-cell-name { display: inline; }
.ux-drawer-fact .conf-swap { display: grid; gap: 1px; }
.ux-drawer-fact .conf-swap > * { grid-area: auto; }
.ux-drawer-fact .conf-box { justify-items: start; }
.ux-drawer-fact .cellflex, .ux-drawer-fact .chips, .ux-drawer-fact .roll { flex-wrap: wrap; }
.ux-drawer-link { display: inline-block; margin-top: 12px; color: var(--gold); font-weight: 600; }

/* The phone's bottom sheet: one modal <dialog>; centred on a wider screen. */
dialog.ux-sheet {
  position: fixed; inset: auto 0 0 0; margin: 0 auto; width: 100%; max-width: 560px;
  max-height: 85vh; overflow: auto; border: 0;
  padding: 18px 16px calc(16px + env(safe-area-inset-bottom));
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: var(--panel); color: var(--ink); box-shadow: var(--shadow-3);
}
dialog.ux-sheet::backdrop { background: var(--scrim); }
.ux-sheet .ux-drawer-grab {
  display: block; position: absolute; left: 50%; top: 6px; transform: translateX(-50%);
  width: 36px; height: 4px; border-radius: 999px; background: var(--rule);
}
@media (min-width: 561px) {
  dialog.ux-sheet { inset: 0; margin: auto; max-width: 420px; height: fit-content; border-radius: var(--r-lg); }
}
@media (prefers-reduced-motion: no-preference) {
  dialog.ux-sheet[open] { animation: ux-sheet-in 220ms var(--ease) both; }
}
@keyframes ux-sheet-in { from { transform: translateY(24px); opacity: 0; } }

/* ---- B: row cards ---- */
table.ux-t-cards, .ux-t-cards > thead, .ux-t-cards > tbody { display: block; }
table.ux-t-cards { min-width: 0; }
/* The header is the sort control: one pill per sortable column. */
.ux-t-cards thead tr { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 0 0 10px; }
.ux-t-cards thead tr::before { content: "Sort by"; margin-right: 2px; font: 500 var(--fs-1) var(--body); color: var(--dim); }
.ux-t-cards thead tr:not(:has(th:not(.nosort))) { display: none; }
.wrap > table.ux-t-cards:not(.grid) thead th:nth-child(n) {
  display: inline-flex; position: static; max-width: none; padding: 4px 10px;
  border: 0; border-radius: 999px; background: var(--panel); color: var(--mute);
}
.wrap > table.ux-t-cards:not(.grid) thead th.nosort:nth-child(n) { display: none; }
.ux-t-cards thead th { align-items: center; gap: 5px; width: auto; min-width: 0; }
.ux-t-cards thead th .info { flex: none; margin: 0; }
.ux-t-cards thead th[data-dir] { color: var(--ink); box-shadow: inset 0 0 0 1px var(--rule); }
.ux-t-cards tbody { display: grid; gap: 8px; }
/* The cards sit in the page's gutter, with room for their shadow inside the
   scroll box that would otherwise clip it. */
.wrap.ux-t-wrap-cards { padding: 2px var(--gutter) 4px; }
.ux-t-cards tbody tr {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px; padding: 12px 16px;
  border-radius: var(--r-lg); background: var(--panel); box-shadow: var(--shadow-card);
  /* A cell's own absolutely placed marks (the battle log's result edge)
     land on the card rather than on the page. */
  position: relative;
}
.ux-t-cards tbody tr.clickable:hover { box-shadow: var(--shadow-card), inset 0 0 0 1px var(--rule); }
.ux-t-cards tbody tr:has(:focus-visible) { outline: 2px solid var(--gold); outline-offset: 2px; }
/* The break between the card's top line and its figures. */
.ux-t-cards tbody tr::after { content: ""; order: 4; flex-basis: 100%; height: 0; }
/* Specific enough to undo the phone's pinned first column (sticky, a ground
   colour, a seam) and the podium's cell tint: a card is one surface. */
.wrap > table.ux-t-cards:not(.grid) tbody td:nth-child(n) {
  display: block; order: 5; position: static; max-width: none; min-width: 0;
  padding: 0; border: 0; background: none; text-align: left; white-space: normal;
}
.ux-t-cards td[data-label]::before {
  content: attr(data-label); display: block; margin-bottom: 1px;
  font: 500 var(--fs-1) var(--body); color: var(--dim);
}
.wrap > table.ux-t-cards:not(.grid) tbody td.ux-c-lead:nth-child(n) { order: 1; }
.wrap > table.ux-t-cards:not(.grid) tbody td.ux-c-head:nth-child(n),
.wrap > table.ux-t-cards:not(.grid) tbody td.ux-c-vs:nth-child(n) {
  order: 2; font: 500 var(--fs-3) var(--body);
}
.ux-t-cards td.ux-c-vs::before { content: "vs"; margin-right: 8px; font: 500 var(--fs-1) var(--body); color: var(--dim); }
.wrap > table.ux-t-cards:not(.grid) tbody td.ux-c-big:nth-child(n) {
  order: 3; margin-left: auto; text-align: right; font-size: var(--fs-3);
}
.ux-t-cards tbody tr.rank-1, .ux-t-cards tbody tr.rank-2, .ux-t-cards tbody tr.rank-3 {
  background: color-mix(in srgb, var(--podium) var(--podium-mix), var(--panel));
}
/* The podium tint belongs to the card. The table rule also tints each cell,
   which on a card left blocks of colour behind every figure. */
.wrap > table.ux-t-cards:not(.grid) tbody tr.rank-1 td,
.wrap > table.ux-t-cards:not(.grid) tbody tr.rank-2 td,
.wrap > table.ux-t-cards:not(.grid) tbody tr.rank-3 td,
.wrap > table.ux-t-cards:not(.grid) tbody tr.rank-1:hover td,
.wrap > table.ux-t-cards:not(.grid) tbody tr.rank-2:hover td,
.wrap > table.ux-t-cards:not(.grid) tbody tr.rank-3:hover td { background: transparent; }
/* An alliance's colour edge belongs to the card, not to its first figure. */
.ux-t-cards tbody tr.has-ally-color { box-shadow: inset 3px 0 0 var(--ally), var(--shadow-card); }
.ux-t-cards tbody tr.has-ally-color td:first-child { box-shadow: none; }
@media (max-width: 560px) {
  /* A card stacks rather than dropping figures, so nothing is hidden. */
  .wrap > table.ux-t-cards:not(.grid) tbody td.drop-sm:nth-child(n),
  .wrap > table.ux-t-cards:not(.grid) tbody td.drop-xs:nth-child(n) { display: block; }
  .ux-t-cards .div-cell-name { display: inline; }
  .ux-t-cards tbody tr { padding: 12px; gap: 6px 14px; }
  .wrap.ux-t-wrap-cards { padding: 2px 14px 4px; }
}

/* ---- C: grouped bands ---- */
.ux-bands { display: grid; gap: 10px; margin: 0 0 16px; padding: 2px var(--gutter) 0; }
.ux-band { border-radius: var(--r-lg); background: var(--panel); box-shadow: var(--shadow-card); }
.ux-band-head {
  list-style: none; cursor: pointer; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px;
  padding: 11px 16px; position: sticky; top: var(--ux-sticky-top); z-index: 4;
  border-radius: var(--r-lg); background: var(--panel);
}
.ux-band[open] > .ux-band-head { border-radius: var(--r-lg) var(--r-lg) 0 0; }
.ux-band-head::-webkit-details-marker { display: none; }
.ux-band-head::before {
  content: "\203a"; width: 10px; font: 600 var(--fs-4)/1 var(--body); color: var(--dim);
  transition: transform 150ms ease;
}
.ux-band[open] > .ux-band-head::before { transform: rotate(90deg); }
.ux-band-head:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
.ux-band-dot { flex: none; width: 8px; height: 8px; border-radius: 999px; background: var(--dim); }
.ux-band.is-hi .ux-band-dot { background: var(--good); }
.ux-band.is-lo .ux-band-dot { background: var(--bad); }
.ux-band.is-s .ux-band-dot { background: var(--band-s); }
.ux-band.is-a .ux-band-dot { background: var(--band-a); }
.ux-band.is-b .ux-band-dot { background: var(--band-b); }
.ux-band.is-c .ux-band-dot { background: var(--band-c); }
.ux-band.is-d .ux-band-dot { background: var(--band-d); }
.ux-band.is-f .ux-band-dot { background: var(--band-f); }
/* The tier list's bands are letters: the letter is the headline, in its
   band's colour. */
.ux-band.is-s .ux-band-title, .ux-band.is-a .ux-band-title, .ux-band.is-b .ux-band-title,
.ux-band.is-c .ux-band-title, .ux-band.is-d .ux-band-title, .ux-band.is-f .ux-band-title {
  font-size: var(--fs-4); letter-spacing: .04em;
}
.ux-band.is-s .ux-band-title { color: var(--band-s); }
.ux-band.is-a .ux-band-title { color: var(--band-a); }
.ux-band.is-b .ux-band-title { color: var(--band-b); }
.ux-band.is-c .ux-band-title { color: var(--band-c); }
.ux-band.is-d .ux-band-title { color: var(--band-d); }
.ux-band.is-f .ux-band-title { color: var(--band-f); }
.ux-band-title { font: 700 var(--fs-3) var(--cond); letter-spacing: .08em; text-transform: uppercase; color: var(--ink); }
.ux-band-title .chips { display: inline-flex; vertical-align: -3px; margin-right: 8px; }
.ux-band-count { font: 500 var(--fs-1) var(--mono); color: var(--dim); }
.ux-band-tag {
  padding: 1px 8px; border-radius: 999px; background: var(--hairline-2);
  font: 600 var(--fs-1) var(--body); color: var(--mute);
}
.ux-band-note { margin-left: auto; font: 500 var(--fs-1) var(--mono); color: var(--dim); text-align: right; }
.ux-band-body { padding: 0 8px 8px; }
.ux-band-body > .wrap > table { min-width: 0; }
@media (max-width: 560px) {
  /* The note is the band's stated boundary, so it wraps rather than goes. */
  .ux-band-note { flex-basis: 100%; margin-left: 20px; text-align: left; }
  .ux-band-head { padding: 10px 12px; }
  .ux-band-body { padding: 0 4px 6px; }
  .ux-bands { padding: 2px var(--gutter) 0; }
  /* A band is narrower than the page, so a name wraps under its type chips
     rather than being cut off at the pinned column's edge. */
  .ux-band-body tbody td.left, .ux-band-body tbody td:first-child { white-space: normal; }
  .ux-band-body tbody .cellflex { flex-wrap: wrap; row-gap: 2px; }
}
/* ---- A where there is no table: the divisions page's blocks ----
   The same airy treatment: no rules between the divisions, a rounded panel
   on the one under the pointer, and each share in a quiet pill. */
.dv-grid.ux-airy-blocks { gap: 4px; background: none; border-block: 0; }
.ux-airy-blocks > .dv-block { border-radius: var(--r-lg); background: transparent; transition: background-color 150ms ease; }
.ux-airy-blocks > .dv-block:hover { background: var(--panel); }
.ux-airy-blocks .dv-chip { border-color: transparent; background: var(--hairline-2); }
.ux-airy-blocks .dv-chip.is-art:hover { border-color: var(--gold); }
.ux-airy-blocks .dv-chip b {
  padding: 1px 6px; border-radius: 999px; color: var(--ink);
  background: color-mix(in srgb, var(--gold) 14%, transparent);
}
.ux-airy-blocks .dv-gear { border-radius: 999px; overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .ux-band-head::before, .ux-t-airy tbody td, .ux-t-list tbody td, .ux-airy-blocks > .dv-block { transition: none; }
}
/* ================================================= end SOFT TABLES (ux-t-) */
