/* ============================================================
   Tier list page — the computed list and the maker.

   Self-contained like css/hero-detail.css: every custom property has a
   fallback, so the page stands up whether or not css/style.css loaded.
   Tokens mirror style.css so the two pages read as one site.
   ============================================================ */
/* Tier colours are NOT declared here. Rows can be renamed, recoloured, added
   and removed, so there's no fixed set to write rules for — each row carries
   its own --tl-tier (and the ink that reads on it) as an inline custom
   property, written by rowHTML() in js/tier-list.js and by its twin in
   scripts/make-tierlist-page.py. The default five live in DEFAULT_TIERS in
   both of those files. */

.tl-wrap { max-width: 1180px; margin: 0 auto; padding: 0 20px 80px; }

/* How much of the tray stays on screen when it's collapsed into a sheet below
   720px. Read by the sheet's own transform and by the page's bottom padding, so
   the board can always be scrolled clear of it. */
:root { --tray-peek-h: 52px; }
@keyframes tl-tray-pulse {
  0% { background: transparent; }
  30% { background: var(--gold-soft, rgba(242, 199, 68, 0.16)); }
  100% { background: transparent; }
}

/* ---------- controls ---------- */
/* Pinned, and the site nav on this page deliberately is not (see nav() in
   scripts/make-tierlist-page.py). Two bars stuck at top:0 stack on each other,
   and of the two this is the one you reach for while scrolling a five-row
   board: rank, role, the toggles and Download. The nav is a thing you use once.

   The negative side margins cancel .tl-wrap's 20px gutters so the pinned bar's
   background reaches the full width of the column — without them, cards scroll
   through two transparent 20px strips either side of it. */
.tl-controls {
  position: sticky; top: 0; z-index: 50;
  display: flex; flex-wrap: wrap; gap: 14px 18px; align-items: flex-end;
  margin: 0 -20px 20px; padding: 14px 20px 16px;
  background: var(--bg, #0b0e1a);
  border-bottom: 1px solid var(--line, #2a3158);
}
/* The phone summary line, the sheet's Done button and the tray's peek bar don't
   exist above 720px, where the controls are a bar that says what it is and the
   tray is just the last row of the board. */
.tl-summary, .tl-sheet-back, .tl-sheet-done, .tl-tray-peek { display: none; }

.tl-field { display: flex; flex-direction: column; gap: 6px; }
.tl-field > span {
  font: 600 11px/1 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-dim, #8a92b2);
}
.tl-seg { display: flex; flex-wrap: wrap; gap: 6px; }
.tl-seg button {
  background: var(--bg-panel, #151b3a);
  border: 1px solid var(--line, #2a3158);
  color: var(--ink-dim, #8a92b2);
  border-radius: 999px; padding: 7px 13px; font: inherit; font-size: 14px;
  cursor: pointer; transition: color .15s, border-color .15s, background .15s;
}
.tl-seg button:hover { color: var(--ink, #edf0fa); border-color: #3d4784; }
.tl-seg button[aria-pressed="true"] {
  background: var(--gold, #f2c744); border-color: var(--gold, #f2c744);
  color: #1a1400; font-weight: 600;
}
.tl-seg button:focus-visible {
  outline: 2px solid var(--gold, #f2c744); outline-offset: 2px;
}

.tl-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-left: auto; }
.tl-btn {
  background: var(--bg-panel, #151b3a); border: 1px solid var(--line, #2a3158);
  color: var(--ink, #edf0fa); border-radius: 10px; padding: 9px 16px;
  font: inherit; font-weight: 600; cursor: pointer;
  transition: border-color .15s, background .15s;
}
.tl-btn:hover { border-color: var(--gold, #f2c744); }
.tl-btn.primary { background: var(--gold, #f2c744); color: #1a1400; border-color: var(--gold, #f2c744); }
.tl-btn:focus-visible { outline: 2px solid var(--gold, #f2c744); outline-offset: 2px; }

.tl-btn:disabled { opacity: .4; cursor: default; }
.tl-btn:disabled:hover { border-color: var(--line, #2a3158); }

/* ---------- win-rate switch ----------
   Deliberately not another pill: it's a persistent setting, not an action, and
   when it sat in the same row styled the same way it read as a fourth button
   you press to do something once. */
.tl-switch {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 7px 14px 7px 11px; border-radius: 10px;
  background: transparent; border: 1px solid transparent;
  color: var(--ink-dim, #8a92b2); font: inherit; font-size: 14px;
  cursor: pointer;
  transition: color .15s, border-color .15s;
}
.tl-switch:hover { color: var(--ink, #edf0fa); }
.tl-switch:focus-visible { outline: 2px solid var(--gold, #f2c744); outline-offset: 2px; }
.tl-switch .tl-track {
  position: relative; width: 34px; height: 19px; flex: none;
  border-radius: 999px; background: #232a52;
  border: 1px solid var(--line, #2a3158);
  transition: background .18s var(--ease, ease), border-color .18s var(--ease, ease);
}
.tl-switch .tl-track::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--ink-dim, #8a92b2);
  transition: transform .18s var(--ease, ease), background .18s var(--ease, ease);
}
.tl-switch[aria-checked="true"] { color: var(--ink, #edf0fa); }
.tl-switch[aria-checked="true"] .tl-track {
  background: var(--gold, #f2c744); border-color: var(--gold, #f2c744);
}
.tl-switch[aria-checked="true"] .tl-track::after {
  transform: translateX(15px); background: #1a1400;
}

/* ---------- provenance ----------
   Sits under the board, not above it. It's the footnote that backs the
   numbers up, and at the top it pushed the thing people came for below the
   fold. */
.tl-asof {
  display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: baseline;
  margin: 30px 0 0; padding-top: 16px; font-size: 14px;
  color: var(--ink-dim, #8a92b2);
  border-top: 1px solid var(--line, #2a3158);
}
.tl-asof strong { color: var(--ink, #edf0fa); font-weight: 600; }
.tl-asof .tl-stale { color: var(--gold, #f2c744); }

/* ---------- tier rows ---------- */
.tl-tiers { display: flex; flex-direction: column; gap: 10px; }
.tl-row {
  display: grid; grid-template-columns: 74px 1fr; gap: 0;
  background: var(--bg-raise, #121732);
  border: 1px solid var(--line, #2a3158);
  border-radius: 12px; overflow: hidden;
}
/* A button, not a label: clicking the tier's own name is how you rename and
   recolour it, so the thing you want to change is the thing you click. */
.tl-row-label {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  font: 700 30px/1 var(--font-display, "Rajdhani", sans-serif);
  color: var(--tl-tier-ink, #14100a); background: var(--tl-tier, #888);
  letter-spacing: 0.02em;
  border: 0; margin: 0; padding: 8px 5px; width: 100%; min-height: 100%;
  text-align: center; overflow-wrap: anywhere; cursor: pointer;
  transition: filter .15s;
}
.tl-row-label:hover { filter: brightness(1.09); }
.tl-row-label:focus-visible { outline: 2px solid var(--ink, #edf0fa); outline-offset: -4px; }
/* The pencil only appears on hover/focus — a permanent one on five rows was
   more chrome than the board could carry. */
.tl-row-label::after {
  content: "\270E"; position: absolute; top: 3px; right: 5px;
  font-size: 11px; opacity: 0; transition: opacity .15s;
}
.tl-row-label:hover::after, .tl-row-label:focus-visible::after { opacity: .8; }
/* Names are free text in a narrow column, so the size steps down with length
   (bucketed by labelLen() in js/tier-list.js) rather than being fixed. */
.tl-row-label[data-len="m"] { font-size: 21px; }
.tl-row-label[data-len="l"] { font-size: 15px; letter-spacing: 0; line-height: 1.12; }
.tl-row-label[data-len="xl"] { font-size: 12px; letter-spacing: 0; line-height: 1.18; }

.tl-drop {
  display: flex; flex-wrap: wrap; gap: 8px; padding: 10px;
  min-height: 96px; align-content: flex-start;
  transition: background .15s;
}
.tl-empty {
  align-self: center; color: var(--ink-dim, #8a92b2); font-size: 14px;
  font-style: italic; padding-left: 4px;
}
/* Only meaningful while editing — a drop target the pointer is over. */
.tl-drop.tl-over { background: rgba(242, 199, 68, 0.09); }

/* ---------- hero card ---------- */
.tl-hero {
  position: relative; display: flex; flex-direction: column; align-items: center;
  width: 78px; padding: 6px 4px 5px;
  background: var(--bg-panel, #151b3a);
  border: 1px solid var(--line, #2a3158);
  border-top: 3px solid var(--tl-role, var(--line, #2a3158));
  border-radius: 9px; text-align: center;
}
.tl-hero[data-role="vanguard"] { --tl-role: var(--vanguard, #ffa53e); }
.tl-hero[data-role="duelist"] { --tl-role: var(--duelist, #ff4d6d); }
.tl-hero[data-role="strategist"] { --tl-role: var(--strategist, #3fd2ff); }

.tl-hero img {
  width: 54px; height: 54px; border-radius: 7px; display: block;
  background: #0c1020; object-fit: cover;
}
/* Two lines, and always a two-line box.
   Most of the roster is two words — Scarlet Witch, Rocket Raccoon, Cloak &
   Dagger — and a single 78px line clipped nearly all of them to an ellipsis.
   The height is fixed rather than growing to fit, because slotIndex() works out
   which slot the pointer is over by dividing the container by one card's
   height: cards of two different heights in the same row make that arithmetic
   wrong, and the drag starts dropping heroes a slot off. */
.tl-name {
  font: 600 11px/1.2 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.02em; text-transform: uppercase;
  color: var(--ink, #edf0fa); margin-top: 4px;
  width: 100%; overflow: hidden;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  height: 2.4em; overflow-wrap: break-word;
}
/* Only rendered for a hero the stats report under several roles (Deadpool), so
   their two or three cards are tellable apart.

   Absolutely positioned, and that's structural rather than cosmetic: in flow it
   made those cards ~18px taller than every other card, and slotIndex() works
   out which slot the pointer is over by dividing the row by ONE card's height.
   A row where some cards are taller than the one it measured put the drop a
   slot out. As a badge over the portrait it costs no height, so every card on
   the board is the same size and that arithmetic is exact. */
.tl-role-tag {
  position: absolute; top: 3px; left: 3px;
  font: 600 9px/1 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--tl-role, var(--ink-dim, #8a92b2));
  background: rgba(11, 14, 26, .85);
  border: 1px solid currentColor; border-radius: 999px;
  padding: 2px 5px;
}
.tl-stat {
  font-size: 11px; color: var(--ink-dim, #8a92b2); line-height: 1.3;
  font-variant-numeric: tabular-nums;
}
.tl-stat b { color: var(--ink, #edf0fa); font-weight: 600; }
.tl-delta { font-size: 10px; font-variant-numeric: tabular-nums; }
.tl-delta.up { color: #7ed957; }
.tl-delta.down { color: #ff8095; }
/* Shrunk toward the mean for a small sample — the card says so rather than
   presenting an adjusted number as if it were the raw one. */
.tl-hero.tl-lowconf .tl-stat b { color: var(--gold, #f2c744); }

.tl-info {
  position: absolute; top: 3px; right: 3px; width: 17px; height: 17px;
  border-radius: 50%; border: 1px solid var(--line, #2a3158);
  background: rgba(11, 14, 26, 0.85); color: var(--ink-dim, #8a92b2);
  font: 600 11px/1 var(--font-display, "Rajdhani", sans-serif);
  cursor: pointer; padding: 0; opacity: 0; transition: opacity .15s, color .15s;
}
.tl-hero:hover .tl-info, .tl-info:focus-visible { opacity: 1; }
.tl-info:hover { color: var(--gold, #f2c744); border-color: var(--gold, #f2c744); }
@media (hover: none) { .tl-info { opacity: 1; } }

/* ---------- dragging ----------
   Always available — there is no edit mode to enter. */
/* Deliberately NO touch-action: none here (follow-ups #32). Cards are most of
   the board's surface, so claiming every touch that starts on one meant an
   ordinary swipe-to-read-the-rest didn't scroll the page — it lifted a hero,
   dragged him under the finger and saved wherever the swipe ended. A touch now
   stays a tap until a long press arms it, and js/tier-list.js blocks the scroll
   from that moment on (blockTouchScroll) — the same rule .hero-pick follows in
   style.css. -webkit-touch-callout keeps iOS from raising its own long-press
   menu over the gesture we just claimed. */
.tl-hero {
  cursor: grab; user-select: none; -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.tl-hero:active { cursor: grabbing; }
/* The portrait is an <img>, which the browser will happily drag itself. That
   native drag cancels the pointer stream, so a press starting on the portrait
   — most of the card — would never become one of our drags. */
.tl-hero img { -webkit-user-drag: none; user-drag: none; pointer-events: none; }
/* Suppress hover lift mid-drag so the row doesn't twitch as cards reflow
   underneath the pointer. */
.tl-page.tl-dragging .tl-hero:hover { transform: none; }
.tl-hero:hover { transform: translateY(-2px); }
.tl-hero { transition: transform .16s var(--ease, ease), border-color .16s, box-shadow .16s; }

/* Tap-to-move fallback: touch users pick a hero, then tap a row. Drag still
   works, but it must not be the only way to place a card. */
.tl-hero.tl-armed {
  border-color: var(--gold, #f2c744);
  box-shadow: 0 0 0 2px var(--gold-soft, rgba(242, 199, 68, 0.16));
}
.tl-hero.tl-ghost {
  position: fixed; z-index: 300; pointer-events: none; margin: 0;
  opacity: .95; rotate: -3deg; scale: 1.06;
  box-shadow: 0 14px 34px rgba(0, 0, 0, .55);
  transition: none;
}
/* The source stays in flow as its own placeholder, so the gap travels with the
   pointer instead of collapsing and reopening. */
.tl-hero.tl-source {
  opacity: .25; filter: grayscale(1);
  transition: opacity .15s, filter .15s;
}

/* Toggled by a class, not by re-rendering. Scoped to <html> rather than the
   page wrapper for two reasons: it has to cover the drag ghost, which is
   appended to document.body and would otherwise show the numbers again on the
   one card you're actually looking at; and <html> is the only element that
   exists when the page head applies these, which is what stops a board with a
   toggle off from painting the numbers and then removing them. */
.tl-hide-winrates .tl-stat { display: none; }
/* The delta is its own switch. It answers a different question from the win
   rate — "how far from average", not "how often does this hero win" — and it's
   the one people turn off first when they want a clean board to share. */
.tl-hide-deltas .tl-delta { display: none; }
.tl-hide-names .tl-name { display: none; }
/* With all three off the card is just the portrait, so tighten it up rather
   than leaving the space the text used to occupy. */
.tl-hide-names.tl-hide-winrates.tl-hide-deltas .tl-hero { padding-bottom: 6px; }

/* The hero tray. Always on screen — it's a permanent part of the board, not a
   panel that appears once you start editing, so its dashed edge marks it as a
   holding area rather than a rank. */
.tl-tray {
  margin-top: 14px; border: 1px dashed var(--line, #2a3158);
  border-radius: 12px; background: rgba(18, 23, 50, .5);
}
.tl-tray h2 {
  font: 600 12px/1 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-dim, #8a92b2);
  margin: 0; padding: 12px 12px 0;
}
.tl-tray .tl-drop { min-height: 74px; }
/* Translucent so an empty tray reads as a hint rather than as content, and
   centred in the row: it's the only text on the board that isn't attached to
   something on the left, so left-aligning it just looked like a stray
   paragraph. */
.tl-tray-hint {
  opacity: .55; font-style: normal; max-width: 62ch; align-self: center;
  margin: 0 auto; text-align: center; padding-left: 0;
  font-size: 13px; line-height: 1.45;
}

/* Adding a row. Sits directly under the rows because that's where the new one
   lands, and styled quietly — it's an occasional action, not one of the board's
   main controls. */
.tl-addrow { margin: 10px 0 0; display: flex; }
.tl-addrow .tl-btn {
  font-size: 14px; padding: 8px 15px; color: var(--ink-dim, #8a92b2);
  background: transparent; border-style: dashed;
}
.tl-addrow .tl-btn:hover:not(:disabled) { color: var(--ink, #edf0fa); border-style: solid; }

.tl-hint {
  margin: 12px 0 0; padding: 10px 14px; font-size: 14px;
  color: var(--ink-dim, #8a92b2);
  background: var(--bg-raise, #121732); border-radius: 10px;
  border-left: 3px solid var(--gold, #f2c744);
}

/* ---------- tier edit dialog ----------
   Built on demand by js/tier-list.js (see ensureTierModal), so this is the only
   place its look is described. Deliberately not styled like the hero popover:
   that one is a reference card you read, this is a form you act in, and it's
   narrow enough to sit over the board rather than cover it — the row previews
   every change live behind it. */
.tl-modal[hidden] { display: none; }
.tl-modal {
  position: fixed; inset: 0; z-index: 400;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.tl-modal-backdrop {
  position: absolute; inset: 0; background: rgba(6, 8, 18, .62);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
}
.tl-modal-panel {
  position: relative; width: min(370px, 100%);
  background: var(--bg-raise, #121732);
  border: 1px solid var(--line, #2a3158); border-radius: 14px;
  padding: 16px 18px 18px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
  animation: tl-modal-in .18s var(--ease, ease);
}
@keyframes tl-modal-in {
  from { opacity: 0; transform: translateY(8px) scale(.98); }
}
.tl-modal-head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
/* A live swatch of the row being edited: its name, in its colour. */
.tl-modal-chip {
  flex: none; max-width: 120px; min-width: 42px; height: 34px; padding: 0 8px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 8px; overflow: hidden;
  font: 700 17px/1 var(--font-display, "Rajdhani", sans-serif);
  white-space: nowrap; text-overflow: ellipsis;
}
.tl-modal-title {
  margin: 0; font: 700 19px/1.2 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: .02em;
}
.tl-modal-close {
  margin-left: auto; width: 28px; height: 28px; flex: none;
  background: transparent; border: 1px solid var(--line, #2a3158);
  border-radius: 8px; color: var(--ink-dim, #8a92b2);
  font-size: 12px; cursor: pointer; line-height: 1;
}
.tl-modal-close:hover { color: var(--ink, #edf0fa); border-color: #3d4784; }

.tl-modal-label {
  display: block; margin: 0 0 6px;
  font: 600 11px/1 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-dim, #8a92b2);
}
.tl-modal-input {
  width: 100%; margin-bottom: 16px; padding: 9px 11px;
  background: var(--bg, #0b0e1a); color: var(--ink, #edf0fa);
  border: 1px solid var(--line, #2a3158); border-radius: 9px;
  font: inherit; font-size: 15px;
}
.tl-modal-input:focus { outline: none; border-color: var(--gold, #f2c744); }

.tl-swatches {
  display: grid; grid-template-columns: repeat(8, 1fr); gap: 6px; margin-bottom: 12px;
}
.tl-swatch {
  aspect-ratio: 1; border-radius: 7px; cursor: pointer; padding: 0;
  background: var(--sw); border: 2px solid transparent;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .28);
  transition: transform .13s var(--ease, ease);
}
.tl-swatch:hover { transform: scale(1.12); }
.tl-swatch[aria-pressed="true"] { border-color: var(--ink, #edf0fa); transform: scale(1.12); }
.tl-swatch:focus-visible { outline: 2px solid var(--gold, #f2c744); outline-offset: 2px; }

.tl-modal-custom {
  display: inline-flex; align-items: center; gap: 9px; cursor: pointer;
  font-size: 14px; color: var(--ink-dim, #8a92b2);
}
.tl-modal-custom input[type="color"] {
  width: 34px; height: 28px; padding: 0; cursor: pointer;
  background: transparent; border: 1px solid var(--line, #2a3158); border-radius: 7px;
}
.tl-modal-custom:hover { color: var(--ink, #edf0fa); }

.tl-modal-actions {
  display: flex; gap: 8px; align-items: center;
  margin-top: 20px; padding-top: 14px;
  border-top: 1px solid var(--line, #2a3158);
}
/* Destructive, so it sits apart from Cancel/Save rather than beside them. */
.tl-modal-actions .tl-danger { margin-right: auto; color: #ff8095; }
.tl-modal-actions .tl-danger:hover:not(:disabled) { border-color: #ff8095; }
.tl-modal-actions .tl-btn { padding: 8px 14px; font-size: 14px; }

@media (max-width: 420px) {
  .tl-swatches { grid-template-columns: repeat(6, 1fr); }
  .tl-modal-actions { flex-wrap: wrap; }
  .tl-modal-actions .tl-danger { margin-right: 0; width: 100%; }
}

/* ---------- method / prose ----------
   Used by /tier-list-method, which loads this stylesheet for the tokens and
   the button styles it shares with the board. */
.tl-method { margin-top: 34px; }
.tl-method h2 {
  font: 700 24px/1.2 var(--font-display, "Rajdhani", sans-serif);
  margin: 30px 0 10px;
}
.tl-method h2:first-child { margin-top: 0; }

.faq { margin-top: 44px; }
.faq h2 { font: 700 24px/1.2 var(--font-display, "Rajdhani", sans-serif); margin: 0 0 10px; }
.faq dt { font-weight: 600; margin-top: 16px; }
.faq dd { margin: 4px 0 0; color: var(--ink-dim, #8a92b2); max-width: 72ch; }

/* A link styled as a button — the cross-page moves out of the board. */
.tl-more { margin: 26px 0 0; }
.tl-more .tl-btn { display: inline-block; text-decoration: none; }
.tl-method p, .tl-method li { color: var(--ink-dim, #8a92b2); max-width: 72ch; }
.tl-method strong { color: var(--ink, #edf0fa); }
.tl-method code {
  background: var(--bg-panel, #151b3a); padding: 1px 5px; border-radius: 4px;
  font-size: 13px; color: var(--ink, #edf0fa);
}

/* ---------- the formulas ----------
   Native MathML — see the comment above the markup in
   scripts/make-tierlist-page.py for why there's no formatting library here.
   Each formula is a <figure> so the expression and the sentence explaining it
   are one unit. Moved here from css/style.css with the markup, when the maths
   joined the prose that justifies it. */
.formula {
  margin: 0 0 16px; padding: 14px 18px; max-width: 72ch;
  background: var(--bg-raise, #121732); border: 1px solid var(--line, #2a3158);
  border-left: 2px solid var(--gold-soft, rgba(242, 199, 68, 0.16)); border-radius: 10px;
}
/* The ex-height of a math font runs small next to Barlow, so display maths
   needs the bump to read as the same size as the 16px prose around it. Long
   expressions scroll inside the card rather than widening the page — the worked
   example overflows a narrow phone. */
.formula math[display="block"] {
  color: var(--ink, #edf0fa); font-size: 21px;
  overflow-x: auto; overflow-y: hidden; padding-bottom: 2px;
}
.formula figcaption {
  margin-top: 12px; font-size: 15px; line-height: 1.6; max-width: 72ch;
  color: var(--ink-dim, #8a92b2);
}
.formula figcaption strong { color: var(--ink, #edf0fa); }
/* The piecewise tier cut. Stands in for a cases brace, which can't be drawn
   reliably — see the comment above the markup. The gold rule is the same device
   .tl-method's left borders use: a group marker that costs no glyph. */
.cases-lead { margin: 0 0 6px; }
.cases {
  border-collapse: collapse; margin: 0;
  border-left: 2px solid var(--gold-soft, rgba(242, 199, 68, 0.16)); padding-left: 0;
}
.cases th {
  font-family: var(--font-display, "Rajdhani", sans-serif); font-weight: 700; font-size: 17px;
  color: var(--gold, #f2c744); text-align: left; white-space: nowrap;
  padding: 3px 16px 3px 12px; width: 1px;
}
.cases td { padding: 3px 0; }
/* Inline maths in running prose (the symbols in the intro, the c=1 in step 2).
   Must not inherit the display size or it would push the line boxes apart. */
.tl-method p math, .formula figcaption math { font-size: 1em; color: var(--ink, #edf0fa); }
/* The cases rows are display maths that happens not to be display="block", so
   they opt back out of the inline size above — .cases-lead is a <p>, so the
   rule needs the extra class to outrank `.tl-method p math`. */
.formula .cases-lead math, .formula .cases math { font-size: 19px; color: var(--ink, #edf0fa); }

/* ---------- responsive ---------- */
@media (max-width: 720px) {
  .tl-wrap { padding: 0 12px 60px; }

  /* ---------- the controls become a bottom sheet (follow-ups #32a) ----------
     Nine rank pills, four roles, three switches and three buttons wrap into a
     ~460px stack here. Pinned, that covered more than half an 812px screen;
     static — which is what this used to be — it still pushed the first tier row
     to y=917, so the board was entirely below the fold on the page whose whole
     job is showing you the board.

     Static-vs-pinned was the wrong axis. The answer is FEWER CONTROLS ON A
     PHONE: .tl-summary is one sticky line saying what you're looking at, and
     the full set moves off-flow into a sheet it opens. Same markup, same ids,
     same handlers — only the presentation differs, which is what keeps
     syncButtons() and the browser tests working unchanged on both. */
  .tl-summary {
    display: block; position: sticky; top: 0; z-index: 40;
    margin: 0 -12px 14px; padding: 8px 12px;
    background: var(--bg, #0b0e1a); border-bottom: 1px solid var(--line, #2a3158);
  }
  .tl-summary-btn {
    display: flex; align-items: center; gap: 10px; width: 100%;
    min-height: 44px; padding: 6px 12px; text-align: left;
    background: var(--bg-panel, #151b3a); border: 1px solid var(--line, #2a3158);
    border-radius: 10px; color: var(--ink, #edf0fa); font: inherit; cursor: pointer;
  }
  .tl-summary-btn:focus-visible { outline: 2px solid var(--gold, #f2c744); outline-offset: 2px; }
  .tl-summary-text { flex: 1; min-width: 0; }
  .tl-summary-view {
    display: block; font: 600 15px/1.25 var(--font-display, "Rajdhani", sans-serif);
    letter-spacing: 0.02em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .tl-summary-role { display: block; font-size: 12px; color: var(--ink-dim, #8a92b2); }
  .tl-summary-cue {
    flex: none; font-size: 13px; font-weight: 600; color: var(--gold, #f2c744);
  }
  .tl-summary-cue::after { content: " ▾"; }

  .tl-sheet-back {
    display: block; position: fixed; inset: 0; z-index: 60;
    background: rgba(6, 8, 18, .62);
    backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
  }
  .tl-sheet-back[hidden] { display: none; }

  /* Off-flow, so the board starts where the page starts. visibility (not just
     the transform) is what keeps a closed sheet from eating drags aimed at the
     rows behind where it would be. */
  .tl-controls {
    position: fixed; inset: auto 0 0 0; z-index: 70;
    flex-direction: column; align-items: stretch; gap: 16px;
    margin: 0; padding: 14px 14px calc(16px + env(safe-area-inset-bottom, 0px));
    max-height: min(78vh, 620px); overflow-y: auto;
    border: 1px solid var(--line, #2a3158); border-bottom: 0;
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -18px 50px rgba(0, 0, 0, .5);
    transform: translateY(100%); visibility: hidden;
    /* visibility is stepped, not eased, and the delay is the whole trick: it
       flips to visible at 0s on open (so the Done button can take focus in the
       same task the class is added — focus() on a visibility:hidden element
       silently does nothing) and only back to hidden after the slide out. */
    transition: transform .22s var(--ease, ease), visibility 0s linear .22s;
  }
  .tl-controls.is-open {
    transform: none; visibility: visible;
    transition: transform .22s var(--ease, ease), visibility 0s;
  }
  .tl-sheet-done {
    display: block; align-self: flex-end; min-height: 44px; padding: 8px 18px;
    background: transparent; border: 0; color: var(--gold, #f2c744);
    font: inherit; font-weight: 600; font-size: 15px; cursor: pointer;
  }
  .tl-sheet-done:focus-visible { outline: 2px solid var(--gold, #f2c744); outline-offset: 2px; }
  /* Room to press. In the sheet there is width to spend, and these were 33px. */
  .tl-seg button { min-height: 44px; padding: 9px 15px; }
  .tl-switch { min-height: 44px; }

  /* ---------- the tray becomes a bottom sheet ----------
     Same gesture as the team builder's team-up panel (see wireSheet() in
     js/app.js and the 960px block in css/style.css): a peek bar that is the
     whole drag handle, flick or tap to expand, and the contents scrolling
     inside. What it buys here is different, though. The tray is a DROP TARGET
     that used to sit at the bottom of a ~2800px page, so parking a hero meant
     dragging him through the entire board on the auto-scroller. Fixed, it is
     always under your thumb, and a drop on the collapsed handle counts as a
     drop in the tray (dropAt() in js/tier-list.js maps it).

     THE ONE DELIBERATE DIFFERENCE FROM THE BUILDER'S SHEET: no backdrop. That
     panel is a list you read, so dimming the page behind it is right. This one
     exists to move heroes between itself and the board, and a backdrop that
     takes pointer events is a wall across exactly that gesture — elementFromPoint
     would answer "the backdrop" for every drop aimed at a row. */
  .tl-wrap { padding-bottom: calc(60px + var(--tray-peek-h)); }
  .tl-tray {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto; z-index: 50;
    display: flex; flex-direction: column;
    margin: 0; max-height: 52vh; max-height: 52dvh;
    background: var(--bg-raise, #121732);
    border: 1px solid var(--line, #2a3158); border-bottom: 0;
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -14px 40px rgba(0, 0, 0, .45);
    transform: translateY(calc(100% - var(--tray-peek-h)));
    transition: transform 340ms var(--ease, ease);
  }
  .tl-tray.is-open { transform: translateY(0); }
  /* The peek bar is the sheet's header, so the in-panel heading is redundant. */
  .tl-tray h2 { display: none; }
  .tl-tray-peek {
    display: flex; align-items: center; gap: 9px;
    flex: none; width: 100%; height: var(--tray-peek-h);
    padding: 6px 14px 0; position: relative;
    background: transparent; border: 0; color: var(--ink, #edf0fa);
    font: inherit; text-align: left;
    /* The whole bar is the handle: claim vertical gestures so they move the
       sheet rather than scrolling the page, and don't select the label. */
    touch-action: none; user-select: none; -webkit-user-select: none;
    cursor: grab;
  }
  .tl-tray-peek:active { cursor: grabbing; }
  .tl-tray-peek:focus-visible { outline: 2px solid var(--gold, #f2c744); outline-offset: -2px; }
  .tl-tray-grip {
    position: absolute; top: 6px; left: 50%; transform: translateX(-50%);
    width: 34px; height: 4px; border-radius: 3px; background: var(--line, #2a3158);
  }
  .tl-tray-title {
    font: 600 12px/1 var(--font-display, "Rajdhani", sans-serif);
    letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-dim, #8a92b2);
  }
  .tl-tray-count { font-size: 13px; color: var(--ink-dim, #8a92b2); }
  .tl-tray.has-heroes .tl-tray-count { color: var(--gold, #f2c744); font-weight: 600; }
  .tl-tray-caret {
    margin-left: auto; color: var(--ink-dim, #8a92b2); font-size: 12px;
    transition: transform 300ms var(--ease, ease);
  }
  .tl-tray.is-open .tl-tray-caret { transform: rotate(180deg); }
  /* Says "something landed in here" when a drop arrives while it's shut — the
     count changed somewhere the user can't see. Borrowed from .sheet-peek.pulse. */
  .tl-tray-peek.pulse { animation: tl-tray-pulse 640ms var(--ease, ease); }
  .tl-tray .tl-drop {
    flex: 1; min-height: 0; overflow-y: auto;
    padding: 4px 12px calc(14px + env(safe-area-inset-bottom, 0px));
  }
  /* A drag hovering the CLOSED tray highlights .tl-drop, which is off screen —
     so the hover has to read on the handle instead, or dropping onto it is a
     leap of faith. :has() is the whole mechanism; where it's missing the drop
     still works and only the highlight is lost. */
  .tl-tray:has(.tl-drop.tl-over) .tl-tray-peek {
    background: rgba(242, 199, 68, 0.12);
  }
  .tl-tray:has(.tl-drop.tl-over) .tl-tray-title { color: var(--gold, #f2c744); }

  .tl-row { grid-template-columns: 52px 1fr; }
  /* 52px of text column rather than 68px, so the two longest names on the
     roster ("Elsa Bloodstone", "Jeff the Land Shark") still fit two lines
     without dropping a card per row to buy the space. */
  .tl-name { font-size: 9.5px; letter-spacing: 0; }
  .tl-row-label { font-size: 23px; padding: 6px 3px; }
  .tl-row-label[data-len="m"] { font-size: 16px; }
  .tl-row-label[data-len="l"] { font-size: 12px; }
  .tl-row-label[data-len="xl"] { font-size: 10px; }
  /* No hover on touch, so the pencil would never appear — show it always. */
  .tl-row-label::after { opacity: .55; }
  .tl-hero { width: 62px; }
  .tl-hero img { width: 44px; height: 44px; }
  .tl-actions { margin-left: 0; width: 100%; }
  .tl-actions .tl-btn { flex: 1; min-height: 44px; }

  /* Vertical rhythm, tightened (follow-ups #32a). The card was 115px tall for a
     44px portrait — 49px of it text under the face — and a 27-hero tier is
     seven lines of that. This takes ~14px off every card without touching the
     two numbers the 720px block was tuned around: the 62px width and the 9.5px
     name that fits "Elsa Bloodstone" in it at four cards per row. Buying a
     fifth card per row needs a name size that doesn't fit those, which is the
     trade the comment above refuses — so height is where the space comes from.
     .tl-name keeps a FIXED two-line box: slotIndex() divides the row by one
     card's height, and cards of two heights put the drop a slot out. */
  .tl-name { margin-top: 3px; height: 2.2em; line-height: 1.1; }
  .tl-stat { font-size: 10px; line-height: 1.2; }
  .tl-delta { font-size: 9px; line-height: 1.2; }
  .tl-hero { padding: 5px 4px 4px; }
  .tl-drop { gap: 7px; padding: 8px; min-height: 84px; }
}

/* ---------- touch hit targets (follow-ups #32a) ----------
   17×17 in the corner of a 62px card, pinned permanently visible because touch
   has no hover: well under the ~44px guidance, and every mis-tap opened a
   popover over the board. The glyph grows to 24px and its hit box to 36 via an
   inset pseudo-element, so the *pressable* area grows without the badge eating
   more of a card that is also the drag surface.

   It overhangs by 6px rather than reaching a full 44: the card's own body is
   the tap-to-pick-up target and the gap to the next card is 7px, so a 44px box
   would take taps meant for the neighbour. 36px on a deliberate corner control
   is the honest compromise, and it is 4.5x the area it had.

   The row-label pencil is NOT the same problem, despite reading like one: the
   pencil is a decorative ::after on .tl-row-label, and the button is the whole
   52px-wide label column — already one of the largest targets on the page. */
@media (hover: none) {
  .tl-info {
    width: 24px; height: 24px; top: 2px; right: 2px;
    font-size: 14px; line-height: 22px;
  }
  .tl-info::after {
    content: ""; position: absolute; inset: -6px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .tl-hero.tl-ghost { transform: none; }
  .tl-seg button, .tl-btn, .tl-drop, .tl-info { transition: none; }
  /* The sheet still has to end up hidden rather than merely off-screen, so this
     keeps the stepped visibility and drops only the slide. */
  .tl-controls, .tl-controls.is-open { transition: visibility 0s; }
  .tl-tray, .tl-tray-caret { transition: none; }
  .tl-tray-peek.pulse { animation: none; }
  .tl-modal-panel { animation: none; }
  .tl-swatch { transition: none; }
  .tl-swatch:hover { transform: none; }
}
