/* PalDuels - styled to match catvtuber.com (TheHealingWhisperer).
   Palette, fonts, radii and the pink glow-shadow language are lifted from that site's
   own custom properties so the two read as one brand. */

@import url('https://fonts.googleapis.com/css2?family=Bagel+Fat+One&family=JetBrains+Mono:wght@400;500;700&family=Zen+Maru+Gothic:wght@400;500;700;900&display=swap');

:root {
  --bg-deep:  #0a0612;
  --bg-1:     #15091f;
  --bg-2:     #1d0d2c;
  --panel:    #1a0d27;
  --panel-2:  #25103a;
  --line:     #3a1d54;
  --line-2:   #54286f;
  --ink:      #fdeaff;
  --ink-dim:  #c4a8d6;
  --ink-mute: #9a7db2;   /* lifted from #8a6ea0: that failed 4.5:1 on the
                            card background for the IV labels, the "not
                            recorded" string and the /300 denominator */
  --accent:   #ff3aa8;
  --accent-2: #a06bff;
  --accent-3: #ffd6ec;
  --good:     #6bf0c4;
  --warn:     #ffd84d;
  --frill:    #ffb8e0;
  --loss:     #f4655e;   /* 14.4 dE from --good under deuteranopia at #ff7a9c,
                            36.3 dE here. W and L counts sit side by side. */
  --maxw:     1180px;

  /* The pink glow is an ACCENT, for things that are meant to stand proud: the primary
     button, the active nav pill, an S-tier badge. It is deliberately NOT the panel
     shadow. A 70px blur offset 30px down, repeated on every panel in a column 16px
     apart, lands each panel's glow on the one below it and fills every seam with a
     pink smear. Panels get depth instead, from a plain dark shadow. */
  --glow-sm:  0 8px 24px -8px rgba(255, 58, 168, .7);
  --glow-md:  0 22px 44px -20px rgba(255, 58, 168, .7);
  --shadow-panel: 0 1px 1px rgba(0, 0, 0, .5), 0 10px 24px -14px rgba(0, 0, 0, .85);

  --font-display: "Bagel Fat One", "Zen Maru Gothic", system-ui, sans-serif;
  --font-body:    "Zen Maru Gothic", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono:    "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  color-scheme: dark;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
/* Reserve the scrollbar gutter on every page, scrolling or not.
   /live is short enough not to scroll while / and /history are not, so the scrollbar
   appeared and disappeared between pages. `.wrap` is centred, so that took ~15px out of
   the viewport and shifted every centred thing - including the header - by half of it.
   That is the few-pixel jump when moving between pages. */
html { scrollbar-gutter: stable; }

body {
  background:
    radial-gradient(1100px 560px at 12% -10%, rgba(160, 107, 255, .16) 0%, transparent 62%),
    radial-gradient(900px 500px at 90% 2%,   rgba(255,  58, 168, .13) 0%, transparent 58%),
    var(--bg-deep);
  background-attachment: fixed;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 15.5px;
  line-height: 1.6;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent-3); text-decoration: none; }
a:hover { color: #fff; text-decoration: none; }

.wrap { width: min(var(--maxw), 100% - 2.5rem); margin-inline: auto; }

/* ------------------------------------------------------------------ topbar */
.topbar {
  border-bottom: 1px solid var(--line);
  background: rgba(10, 6, 18, .86);
  backdrop-filter: blur(12px);
  position: sticky; top: 0; z-index: 40;
}
.topbar-in { display: flex; align-items: center; gap: 1rem; min-height: 62px; flex-wrap: wrap; row-gap: 0.375rem; }
.topbar-in .nav { margin-right: auto; }
/* The bar is position:sticky, so any height it takes is subtracted from every scroll
   position. Wrapped to three rows it stood 161px tall at 360px. */
@media (max-width: 560px) {
  .topbar-in { gap: 0.5rem; row-gap: 0.25rem; padding: 0.5rem 0; min-height: 0; }
  .brand { font-size: 1.125rem; }
  .nav { gap: 0.125rem; order: 3; width: 100%; justify-content: space-between; }
  .nav a { padding: 0.25rem 0.5rem; font-size: 0.8125rem; }
  .topbar-right { margin-left: auto; }
}
.brand {
  font-family: var(--font-display);
  font-size: 1.32rem;
  color: var(--ink);
  display: inline-flex; align-items: center; gap: 0.5rem;
  text-shadow: 0 0 18px rgba(255, 58, 168, .45);
}
.brand:hover { color: var(--accent-3); }
.brand-mark { color: var(--accent); }
/* One segmented control rather than four loose pills. The group carries the border and
   background; each item is flat until it is hovered or current, so the bar reads as a
   single object instead of scattered buttons. */
.nav {
  display: flex; gap: 0.125rem; flex-wrap: wrap;
  padding: 0.25rem; border-radius: 999px;
  background: rgba(255, 255, 255, .03); border: 1px solid var(--line);
}
.nav a {
  color: var(--ink-dim); padding: 0.375rem 0.75rem; border-radius: 999px;
  /* 29px tall was under every thumb-target guideline; this is the site's primary
     navigation and the one place on a phone where a miss costs a page load. */
  min-height: 2.5rem; display: inline-flex; align-items: center;
  font-weight: 700; font-size: 0.875rem; letter-spacing: .01em;
  transition: background .14s ease, color .14s ease;
}
.nav a:hover { background: rgba(255, 255, 255, .06); color: var(--ink); }
.nav a.on {
  background: linear-gradient(180deg, var(--accent), #d81d86);
  color: #fff; box-shadow: 0 2px 10px -2px rgba(255, 58, 168, .8);
}
.topbar-right { display: flex; gap: 0.375rem; align-items: center; }

.chip {
  display: inline-flex; align-items: center; gap: 0.375rem;
  padding: 0.375rem 0.75rem; border-radius: 999px; font-size: .84rem; font-weight: 700;
  border: 1px solid var(--line-2); color: var(--ink-dim); white-space: nowrap;
}
.chip:hover { border-color: var(--accent); color: var(--ink); }
.chip-cta {
  background: linear-gradient(180deg, var(--accent), #d81d86);
  border-color: transparent; color: #fff; box-shadow: var(--glow-sm);
}
.chip-cta:hover { color: #fff; filter: brightness(1.07); }
.chip-me { background: var(--panel-2); color: var(--ink); }
.chip-ghost { border-style: dashed; }

/* ------------------------------------------------------------------ blocks */
main.wrap { flex: 1; padding: 1.5rem 0 2.5rem; }

h1 {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 4.2vw, 2.5rem);
  margin: 0 0 0.375rem; line-height: 1.2; font-weight: 400;
  text-shadow: 0 0 26px rgba(255, 58, 168, .35);
}
h2 { font-family: var(--font-display); font-size: 1.15rem; margin: 0 0 0.75rem; font-weight: 400; }
.sub { color: var(--ink-dim); margin: 0; }

.panel {
  background: linear-gradient(180deg, var(--panel), var(--bg-1));
  border: 1px solid var(--line);
  border-radius: 20px;
  padding: 1.25rem;
  box-shadow: var(--shadow-panel);
}
/* Vertical rhythm belongs to the container. `.panel + .panel` only worked while panels
   were literally adjacent, so every .grid-2 between two panels dropped the gap to 0 and
   two bordered cards shared a doubled seam. */
main.wrap > * + * { margin-top: 1rem; }
main.wrap > h1 + .sub { margin-top: 0.375rem; }
/* There is deliberately no `.panel + .panel` rule. It also matched the SECOND panel
   inside a .grid-2 and pushed it a row lower than its neighbour, so side-by-side
   panels sat visibly misaligned. Grid gap spaces a row; the container rule stacks. */
.panel-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; margin-bottom: 0.75rem; flex-wrap: wrap;
}
.panel-head h2 { margin: 0; }
.panel-head a { font-size: .85rem; font-weight: 700; color: var(--accent-3); }

.grid-2 {
  display: grid; grid-template-columns: 1fr; gap: 1rem;
  /* Panels size to their own content. Stretching left a large empty area under the
     shorter of two side-by-side panels. */
  align-items: start;
}
/* Opt-in equal heights. The default is content-sized, because stretching two panels
   whose contents differ a lot leaves a large void under the shorter one. Where the two
   really are peers - the home page's Current duel and Up next - matching heights reads
   tidier, so those grids ask for it. */
.grid-2.eq { align-items: stretch; }
.grid-2.eq > .panel { display: flex; flex-direction: column; }
.grid-2.eq > .panel > .empty { margin: auto 0; }

/* Grid items are min-width:auto by default, so a child's min-content width becomes a
   floor the track cannot go below. `table { min-width: 560px }` then stretched the whole
   page instead of letting .tbl-scroll scroll inside it - 246px of horizontal overflow on
   the home page at 360px, and again at 920-983px. Declared once here rather than relying
   on per-table inline overrides that two of four call sites had forgotten. */
.grid-2 > * { min-width: 0; }
/* A grid cell that stacks panels instead of holding one. align-content:start stops the
   stack itself stretching, which is what left the hole in the first place. */
.grid-2 > .col { display: grid; gap: 1rem; align-content: start; min-width: 0; }
@media (min-width: 920px) {
  .grid-2 { grid-template-columns: 1.4fr 1fr; }
  /* EQUAL halves for the home row. 1.4fr gave the duel panel 679px against the queue's
     485 at 1440, which was sized for the thin fighter strip that panel used to hold; it
     now holds a card pair capped at 240px each, so the extra 194px was margin either
     side of an arena that had stopped growing into it. Scoped by :has to that one row -
     the recent-duels / top-duelists row below it still wants the wider left column,
     because a duel row genuinely is wider than a leaderboard line. */
  .grid-2:has(> .queue-panel) { grid-template-columns: 1fr 1fr; }
}

/* ------------------------------------------------------------------ live */
.livebadge {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: .7rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: #fff; border: 1px solid rgba(255, 58, 168, .55);
  background: rgba(255, 58, 168, .18); padding: 0.25rem 0.5rem; border-radius: 999px;
  box-shadow: 0 0 18px rgba(255, 58, 168, .45);
}
.livebadge::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
  animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .3; transform: scale(.75); } }
@media (prefers-reduced-motion: reduce) { .livebadge::before { animation: none; } }

.idlebadge {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: .7rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-mute); border: 1px solid var(--line); padding: 0.25rem 0.5rem; border-radius: 999px;
}

/* The third state: no duel running, but a pool is taking money. It is gold rather than
   the arena's pink so it never reads as "a duel is live", and it says which of the two
   betting states it is in words - a viewer deciding whether they can still stake cannot
   be asked to tell two glows apart. The dot is a filled square, not the live badge's
   pulsing circle, so the shape carries the difference as well as the hue. */
.betbadge {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: .7rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: #ffe89a; border: 1px solid rgba(255, 203, 61, .55);
  background: rgba(255, 203, 61, .16); padding: 0.25rem 0.5rem; border-radius: 999px;
  box-shadow: 0 0 18px rgba(255, 203, 61, .3);
}
.betbadge::before {
  content: ""; width: 7px; height: 7px; border-radius: 0; background: #ffcb3d;
}

.arena { display: grid; grid-template-columns: 1fr auto 1fr; gap: 1rem; align-items: start; }
@media (max-width: 640px) { .arena { grid-template-columns: 1fr; } .vs { justify-self: center; } }

.vs {
  font-family: var(--font-display); color: var(--accent); font-size: 1.4rem;
  align-self: center; text-shadow: 0 0 20px rgba(255, 58, 168, .6);
}

.fighter { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; text-align: center; min-width: 0; }
.fighter-away { order: 3; }
.portrait {
  position: relative;
  width: 136px; height: 136px; border-radius: 20px;
  background: radial-gradient(circle at 50% 30%, var(--panel-2), var(--bg-deep));
  border: 1px solid var(--line-2);
  display: grid; place-items: center; overflow: hidden;
}
.portrait img { width: 100%; height: 100%; object-fit: contain; padding: 0.5rem; }
.portrait-none { color: var(--ink-mute); font-size: 2.2rem; font-family: var(--font-display); }
/* Gold, not green: green means "home side" everywhere else on the site, so a green
   ring on the arena portrait would read as a side marker rather than a result. */
.is-win .portrait {
  border-color: rgba(255, 210, 77, .6);
  box-shadow: 0 0 0 3px rgba(255, 210, 77, .15), 0 0 24px -8px rgba(255, 210, 77, .55);
}
.is-loss .portrait { opacity: .55; filter: saturate(.4); }
.crown {
  position: absolute; top: 5px; right: 8px; font-size: 1.15rem;
  color: #ffd24d; text-shadow: 0 0 8px rgba(255, 210, 77, .7);
}

/* The arena says which side it is looking at. Position and a health-bar hue were the
   only two cues, and the two bars measure 23.6 dE apart under deuteranopia. */
.fighter-tag {
  display: inline-block; margin-bottom: 0.375rem;
  font-family: var(--font-mono); font-size: 0.625rem; font-weight: 700;
  letter-spacing: .12em; padding: 0.125rem 0.5rem; border-radius: 6px;
}
.fighter-home .fighter-tag { color: #b6ffe2; background: rgba(55, 201, 127, .16); border: 1px solid rgba(55, 201, 127, .45); }
.fighter-away .fighter-tag { color: #ff8f88; background: rgba(244, 101, 94, .14); border: 1px solid rgba(244, 101, 94, .45); }

.fighter-meta { min-width: 0; width: 100%; }
.actor { font-weight: 700; font-size: .98rem; display: block; overflow-wrap: anywhere; color: var(--ink); }
.actor:hover { color: var(--accent-3); }
.palname { color: var(--ink-dim); font-size: .88rem; }
.types { display: flex; gap: 0.25rem; justify-content: center; flex-wrap: wrap; margin-top: 0.375rem; }
.type {
  font-family: var(--font-mono);
  font-size: .64rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  padding: 0.125rem 0.5rem; border-radius: 6px; border: 1px solid var(--line-2); color: var(--ink-dim);
}
.type-fire     { color: #ffb0a8; border-color: #7a2f45; background: rgba(255, 176, 168, .1); }
.type-water    { color: #9ddcff; border-color: #2c4a7a; background: rgba(157, 220, 255, .1); }
.type-grass    { color: #9ff2c4; border-color: #2b6b57; background: rgba(159, 242, 196, .1); }
.type-electric { color: #ffe98a; border-color: #7a6420; background: rgba(255, 233, 138, .1); }
.type-ice      { color: #b6f0ff; border-color: #2a6a80; background: rgba(182, 240, 255, .1); }
.type-ground   { color: #e6c9a8; border-color: #6b5238; background: rgba(230, 201, 168, .1); }
.type-dark     { color: #c9a8ff; border-color: var(--line-2); background: rgba(201, 168, 255, .1); }
.type-dragon   { color: #ffa8e8; border-color: #6a1a55; background: rgba(255, 168, 232, .1); }
.type-neutral  { color: #ddd0e8; border-color: var(--line); background: rgba(221, 208, 232, .1); }

.hpbar {
  margin-top: 0.5rem; height: 9px; border-radius: 999px;
  background: var(--bg-deep); border: 1px solid var(--line-2); overflow: hidden;
}
.hpfill { height: 100%; background: linear-gradient(90deg, var(--good), #9ddcff); transition: width .4s ease; }
.hpbar[data-side="away"] .hpfill {
  background:
    repeating-linear-gradient(135deg,
      rgba(255, 255, 255, .30) 0 2px, rgba(255, 255, 255, 0) 2px 6px),
    linear-gradient(90deg, var(--accent), var(--accent-2));
}

/* Sits beside the element icons, never on a row of its own. The word is the channel -
   the gold is decoration - so it is never abbreviated to a code. */
.status {
  font-family: var(--font-mono);
  font-size: 0.5625rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  padding: 0.125rem 0.375rem; border-radius: 6px; white-space: nowrap;
  background: rgba(255, 216, 77, .13); color: var(--warn); border: 1px solid rgba(255, 216, 77, .35);
}
/* The overflow chip carries the rest in its tooltip; it is a count, not a status. */
.status-more { background: rgba(255, 255, 255, .05); color: var(--ink-mute);
  border-color: var(--line-2); }

/* 2.5rem here plus the panel's own 1.25rem put two lines of text inside 120px of
   vertical padding, which made every idle panel on the home page look cavernous. */
.empty { text-align: center; color: var(--ink-mute); padding: 1.5rem 1rem; }
.empty strong { color: var(--ink-dim); display: block; font-size: 1rem; margin-bottom: 0.25rem; font-family: var(--font-display); font-weight: 400; }

/* ------------------------------------------------------------------ tables */
.tbl-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { width: 100%; border-collapse: collapse; font-size: .9rem; min-width: 560px; }
th, td { text-align: left; padding: 0.5rem 0.5rem; border-bottom: 1px solid var(--line); }
th {
  font-family: var(--font-mono);
  color: var(--ink-mute); font-size: .68rem; text-transform: uppercase;
  letter-spacing: .1em; font-weight: 700;
}
tbody tr:hover { background: var(--panel-2); }
td.num, th.num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.w { color: var(--good); font-weight: 700; }
.l { color: var(--loss); font-weight: 700; }
.muted { color: var(--ink-mute); }
/* Outcome, kept distinct from side colour: green is "home", so a win is gold. */
.res-win { color: #ffd24d; font-weight: 700; text-shadow: 0 0 8px rgba(255, 210, 77, .4); }
.res-loss { color: var(--ink-mute); font-weight: 700; }
.mini-img { width: 28px; height: 28px; object-fit: contain; vertical-align: middle; margin-right: 0.375rem; }

/* ------------------------------------------------- duel sides and outcomes */
/* HOME IS GREEN, AWAY IS RED, matching the stream. These are the bot's own values:
   panel.js:2090 (--home #37c97f / --away #f4655e), overlay.js:1323-1330 for the name
   colours, and panel.js:2717-2718 for the badge tints. Anyone who has watched the
   overlay already reads green as home without being told.

   Because green is spent on "home", the WINNER cannot also be green or the two
   meanings collide. The winner is marked with a gold crown, full weight and full
   opacity; the loser is dimmed. Outcome and side stay independently readable. */
.side { display: inline-flex; align-items: center; gap: 0.5rem; min-width: 0; }
.side-t { display: grid; gap: 0.125rem; min-width: 0; }
.side-l { display: flex; align-items: center; gap: 0.25rem; min-width: 0; }
/* The pal, under the chatter who fielded it, the same order the duel cards use. */
.side-p { font-size: 0.75rem; color: var(--ink-dim); overflow-wrap: anywhere; }
.side.is-lost .side-p { color: var(--ink-mute); }
/* A beaten pal greys out here too, so the table agrees with the cards. */
.side.is-lost .mini-img { filter: grayscale(.8) brightness(.72); }
.duel-pair { display: inline-flex; align-items: center; gap: 0.375rem; flex-wrap: wrap; }
.side-n { font-weight: 700; overflow-wrap: anywhere; }
/* Named for assistive tech at every width; painted only when the table stacks and
   the column headers are gone. */
.side-tag {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%);
}
.side-home .side-tag { color: #b6ffe2; background: rgba(55, 201, 127, .16); border: 1px solid rgba(55, 201, 127, .42); }
.side-away .side-tag { color: #ff8f88; background: rgba(244, 101, 94, .14); border: 1px solid rgba(244, 101, 94, .42); }
.side-home .side-n { color: #b6ffe2; }
.side-away .side-n { color: #f4655e; }
.side-home .side-n:hover, .side-away .side-n:hover { color: #fff; }

/* Winner: gold, not green. Green already means "home". */
.side-win { color: #ffd24d; font-size: 0.9rem; line-height: 1; text-shadow: 0 0 8px rgba(255, 210, 77, .55); }
.side.is-won .side-tag { box-shadow: 0 0 0 1px rgba(255, 210, 77, .55); }
.side.is-won .side-n { text-shadow: 0 0 10px rgba(255, 210, 77, .3); }
.side.is-lost { opacity: .5; }
.side.is-lost .side-n { color: var(--ink-mute); }

/* Below this the two sides cannot sit side by side: the away column fell off the
   screen behind 251px of horizontal scroll, so half of every duel was hidden. The row
   becomes a block, the sides stack, and the side word turns visible because there is
   no column header left to name them. */
@media (max-width: 560px) {
  .hist thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .hist, .hist tbody, .hist tr, .hist td { display: block; width: 100%; }
  .hist { min-width: 0; }
  .hist tr {
    padding: 0.5rem 0.25rem; border-bottom: 1px solid var(--line);
  }
  .hist td { border: 0; padding: 0.125rem 0; }
  .hist td.when { font-size: 0.6875rem; margin-bottom: 0.25rem; }
  .hist .side { width: 100%; }
  .hist .side-tag {
    position: static; width: auto; height: auto; overflow: visible; clip-path: none;
    flex: none; align-self: stretch; display: grid; place-items: center;
    writing-mode: vertical-rl; text-orientation: upright;
    font-family: var(--font-mono); font-size: 0.6875rem; font-weight: 700;
    letter-spacing: -0.4em; line-height: 1;
    padding: 0.25rem 0.125rem; border-radius: 6px;
  }
  .hist .side-home .side-tag { color: #b6ffe2; background: rgba(55, 201, 127, .16); border: 1px solid rgba(55, 201, 127, .45); }
  .hist .side-away .side-tag { color: #ff8f88; background: rgba(244, 101, 94, .14); border: 1px solid rgba(244, 101, 94, .45); }
}

/* A phone cannot hold six or eight columns: all three boards were hiding 251px
   behind a horizontal scroll, with the ranking metric itself off the right edge. The
   columns that go are the ones a reader can get back: Duels is W plus L, and Power is
   printed on every pal card. Nothing that defines a board's order is dropped. */
@media (max-width: 560px) {
  .opt { display: none; }
  /* The 560px floor above exists so .tbl-scroll scrolls instead of stretching the
     page. With the optional columns gone the table fits, so the floor has to come off
     or it holds the old width and scrolls anyway. */
  table { font-size: 0.8125rem; min-width: 0; }
  th, td { padding: 0.375rem 0.25rem; }
  .mini-img { width: 22px; height: 22px; margin-right: 0.25rem; }
  .el-mini { display: none; }
  .crown-title { font-size: 0.5rem; letter-spacing: .02em; }
  /* Long chatter names are what is left pushing the table wide, so they may break
     anywhere. Figures may NOT: anywhere-breaking split 139 wins across two lines and
     it read as 13 over 9. */
  td { overflow-wrap: anywhere; }
  td.num, th.num { overflow-wrap: normal; word-break: keep-all; white-space: nowrap; }
}

/* The narrowest phones still in circulation are 320px. At that width the four nav
   items no longer fit one line and Leaderboard dropped to a second row inside the
   pill, which reads as broken rather than responsive. Smaller type and tighter
   padding fit all four in ~260px of the ~288px available. */
@media (max-width: 400px) {
  .nav a { font-size: 0.8125rem; padding: 0.25rem 0.5rem; }
  /* Strongest pals is the widest board even with its optional columns gone, because
     a pal name is long. At 320 it was still 17px over; this closes that without
     dropping another column. */
  table { font-size: 0.75rem; }
  th, td { padding: 0.375rem 0.125rem; }
  .mini-img { width: 18px; height: 18px; margin-right: 0.125rem; }
  /* the tracking on six uppercase headers is the last few pixels */
  th { letter-spacing: 0; }
  .dl-pal .tier, td .tier { width: 17px; height: 20px; font-size: 0.625rem; }
}

/* ------------------------------------------------------------------ queue */
.queue-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }

/* The queue follows the duel panel's height; it must never set it.

   An equal-height row is sized by the TALLER of the two, so eight queue rows at their
   natural height made the queue the driver and stretched the arena to match - the queue
   ran past the bottom of the betting panel beside it. The fix is to stop the queue
   contributing a content height at all: `height: 0` takes it out of the row's
   content-based sizing, `min-height: 100%` then stretches it to whatever height the
   duel panel set, and the list inside scrolls for anything that does not fit.

   Rows still grow into spare space up to 6rem, so a short duel panel does not leave a
   hole under the list. Scoped to the breakpoint where the two are actually side by
   side; below it they stack and each gets the room it needs. */
@media (min-width: 920px) {
  /* ALWAYS the duel panel's height, and never the thing that sets it. The gate used to
     be on five entries, which left the two panels visibly unequal below that - measured
     at 1440: 211px against 651 with an empty queue, 129 with one entry, 291 with four.
     Dropping the gate makes them equal at every length.

     `height: 0` is what keeps the duel panel in charge. Removing it (leaving a plain
     stretch) also equalises, but then a long queue DRIVES the row: measured, eight
     entries took both panels to 907px and twelve to 1323, which puts 670px of empty
     panel under the duel card. Pinned at 0 with min-height:100%, the list scrolls
     inside instead - twelve entries fit in the 651 the duel panel set. */
  .grid-2.eq > #up-next { height: 0; min-height: 100%; overflow: hidden; }
  .grid-2.eq > #up-next .queue-list { flex: 1; min-height: 0; overflow-y: auto; }
  /* The rows share the slack only once there are enough of them for that to look
     deliberate. Four entries in a 711px panel put 81px of air between 96px rows and two
     put 435px between them - the list stopped reading as a list and became a few
     stranded cards - so below five they keep their natural size at the top. */
  .grid-2.eq > #up-next:has(.queue-item:nth-child(5)) .queue-list {
    grid-auto-rows: minmax(3rem, 6rem);
    align-content: space-between;
  }

  /* /live's queue is the other way round: it MAY drive its row. Nothing precious is
     beside it - "Just finished" is five cards - and twelve queue entries are taller than
     those five, so the height:0 + overflow:hidden the home row uses would silently clip
     the tail off the list. Plain stretch instead: a long queue makes the row taller, a
     short one takes the slack, and the rows spread into it from five entries up, the
     same floor the home row uses. */
  .grid-2.eq > #queue:has(.queue-item:nth-child(5)) .queue-list {
    flex: 1; min-height: 0;
    grid-auto-rows: minmax(3rem, 6rem);
    align-content: space-between;
  }
}
.queue-item {
  display: flex; align-items: center; gap: 0.75rem;
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 14px; padding: 0.5rem 0.75rem;
}
.queue-pos {
  width: 28px; height: 28px; flex: none; border-radius: 14px;
  background: var(--bg-deep); border: 1px solid var(--line-2);
  display: grid; place-items: center; font-family: var(--font-mono);
  font-weight: 700; font-size: .76rem; color: var(--accent-3);
}
.queue-names { font-weight: 700; min-width: 0; overflow-wrap: anywhere; }
/* The arena's own two colours, so a queued match reads as the same thing it will become.
   Position and the "vs" between them are what actually carry the sides; this is the
   redundant channel, not the load-bearing one. */
.q-home, .q-away { text-decoration: none; border-bottom: 1px solid transparent; }
.q-home { color: #8ef0c0; }
.q-away { color: #ff9a93; }
.q-home:hover, .q-away:hover { border-bottom-color: currentColor; }
.queue-note { font-weight: 400; font-size: .82rem; color: var(--ink-mute); }

/* ------------------------------------------------------------------ stats */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 0.5rem; }
.stat {
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 14px; padding: 0.75rem;
}
.stat-v {
  font-family: var(--font-display); font-size: 1.5rem; line-height: 1.15;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.stat-v.good { color: var(--good); }
.stat-v.bad  { color: var(--loss); }
.stat-k {
  font-family: var(--font-mono);
  font-size: .64rem; text-transform: uppercase; letter-spacing: .1em;
  color: var(--ink-mute); font-weight: 700;
}

/* ------------------------------------------------- one rhythm in the live panel
   Every piece in here carried its own margin: the verdict 16 below, the odds bar 20
   above, the note 16 above and nothing below, the movesets 20 above. On /live the panel
   body is a block, so those collapsed into an accidental 20/16/0; on the home teaser it
   is a flex column, where margins do NOT collapse, so the verdict and the odds bar sat
   36px apart while the note touched the arena with 0 between them.

   Space that varies for no reason is worse than too much or too little of it, so the
   panel sets ONE gap and the children stop bringing their own. */
#live-body { display: flex; flex-direction: column; gap: 0.75rem; }
.grid-2.eq > #live-panel { gap: 0.75rem; }
#live-panel > *, #live-body > * { margin-top: 0; margin-bottom: 0; }

/* ------------------------------------------------- the duel fighter card
   A duel used to render as a thumbnail with a name under it while a profile got the
   full card. Same pal, two classes of citizen. This is the roster card - tier rule,
   art, elements, POWER, three bars, passives - with the stream overlay's own furniture
   on top, so the site and the stream read as one thing.

   Everything here rides ON .pal, never replaces it: change the card once and the
   roster, the home strip and the arena all move together. */
/* Capped and centred. In a full-width 1fr|auto|1fr the two cards centre in columns
   600px wide and end up on opposite sides of the screen, with the VS marooned between
   them - the opposite of the overlay, where the pair is one tight object. */
/* width:100% is load-bearing, not belt and braces. The home panel is a COLUMN flex
   container, and an auto inline margin on a flex item sizes it to its content instead of
   centring a full-width box - which is how the arena came out 353px inside a 1180px
   panel, and 32px when it had size containment on top. */
/* STRETCH, not start. The two cards do not always hold the same blocks - a butchered
   pal shows the stamp where the other shows three stat rows, which is 42px against 72 -
   and side by side that left one card ending 30px above the other (408 against 438 at
   1440, 365 against 395 at 390). Stretching makes the row size both to the taller, and
   the auto top margins below drop the slack ABOVE the last block rather than leaving a
   gap under the card's own footer.
   Only bites on the two-column arena; stacked, each card is its own row and its natural
   height is right. */
.duel-arena { align-items: stretch; width: 100%; max-width: 640px; margin-inline: auto; }
/* A pal card is a 215px object in the roster. Stretched across half a 1440px page it
   stops being a card, so the column centres it at a size the art was drawn for. */
.duel-arena > .pal { max-width: 240px; margin-inline: auto; width: 100%; }

/* HOME/AWAY as a solid bar across the top, the way .dhead sits on the overlay card.
   The WORD carries the side; the green/coral pair behind it is decoration, because
   those two measure 23.6 dE apart under deuteranopia and a viewer with money on the
   duel cannot be asked to tell them apart. Sits below the 3px tier rule rather than
   over it - the rule is the card's other identity and both fit. */
.pal.duel-pal { padding-top: 2rem; }
.duel-head {
  position: absolute; top: 3px; left: 0; right: 0; z-index: 2;
  text-align: center; padding: 0.125rem 0;
  font-family: var(--font-mono); font-size: 0.6875rem; font-weight: 700;
  letter-spacing: .18em;
}
.duel-home .duel-head { color: #06240f; background: linear-gradient(90deg, #2ab970, #66e6a8, #2ab970); }
.duel-away .duel-head { color: #3a0a10; background: linear-gradient(90deg, #ef5a53, #ff8f83, #ef5a53); }
/* The corner marks clear the bar rather than sitting under it. Written as
   .pal.duel-pal, not .duel-pal: the two class selectors weigh the same and .pal is
   declared further down the sheet, so the base card was winning and the HOME bar
   landed on top of both the tier badge and the art. */
.pal.duel-pal > .tier, .pal.duel-pal .unbeaten { top: 2rem; }

/* The level the streamer set. It is on the ART, not in the stats, because it is a
   property of the DUEL and not of the pal: a lv12 pal in a lv40 duel fights at 40, and
   a pal's own level is meaningless here - which is why the roster card never shows one. */
.duel-lv {
  position: absolute; left: 0; bottom: 0; z-index: 2;
  font-family: var(--font-mono); font-size: 0.625rem; font-weight: 700;
  padding: 0.125rem 0.375rem; border-radius: 6px;
  background: rgba(8, 4, 16, .82); color: var(--accent-2);
  box-shadow: 0 0 6px rgba(0, 0, 0, .85);
}

/* Health, while they are fighting. It is the only number on the card that is moving,
   and it is why somebody has the page open, so it sits directly under the name rather
   than down with the talents. The away fill is hatched, never hue alone. */
.duel-hp { display: flex; align-items: center; gap: 0.5rem; width: 100%; }
.duel-hp .hpbar { flex: 1; }
.duel-hpv {
  flex: none; min-width: 2.5rem; text-align: right;
  font-family: var(--font-mono); font-size: 0.75rem; font-weight: 700; color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}
.duel-cast {
  width: 100%; text-align: center;
  font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: .06em;
}
.duel-cast { color: var(--ink-mute); }
/* NOT on the home teaser. What a pal is casting changes every couple of seconds and is
   only worth reading while you are watching the fight; on a page somebody landed on it
   is a line that flickers and says nothing they can act on.
   In CSS rather than in the card, because ONE rendered arena serves both pages - that is
   what stops the first paint and the two-second repaint from drifting - so suppressing
   it server-side for home would just be undone by the next poll. data-skills marks the
   /live panel; the teaser has no such attribute. */
#live-panel:not([data-skills]) .duel-cast { display: none; }
.duel-cast strong { color: var(--accent-2); font-weight: 700; }
/* BUTCHERED. The stream puts the same word on the same pal the moment it loses, in the
   same red, so a viewer moving between the two is looking at one event and not two.

   The WORD does the work - the card is already greyed and the art desaturated, and the
   red is the third channel rather than the only one. The line under it says what the
   word means for anyone who has not watched a duel end before. */
.butchered {
  position: relative; z-index: 0;
  display: block; font-family: var(--font-mono); font-weight: 700; font-style: normal;
  font-size: 0.9375rem; letter-spacing: .12em; color: #ff5064;
  text-shadow: 0 0 11px rgba(255, 40, 60, .55), 0 2px 3px #000;
}
/* It LANDS: the word slams down out of nothing and a red splash blows out behind it.
   Once, not on a loop - this sits beside a duel a viewer is watching, and a stamp that
   pulsed forever would pull the eye off the fight it is describing.

   --butch-age is a NEGATIVE delay set inline: the page repaints every two seconds and
   an innerHTML swap restarts every animation on it, so without this the stamp would
   re-slam once a second for as long as the card was up. A negative delay starts the
   animation part-way through, so it plays exactly once from the moment the pal actually
   went down. The overlay resyncs its pal reel the same way. */
@keyframes butch-slam {
  0%   { transform: scale(2.4); opacity: 0; }
  55%  { transform: scale(.92); opacity: 1; }
  100% { transform: scale(1);   opacity: 1; }
}
@keyframes butch-splash {
  0%   { transform: scale(.25); opacity: .8; }
  100% { transform: scale(2.8); opacity: 0; }
}
.is-fresh-kill .butchered {
  animation: butch-slam .5s cubic-bezier(.2, 1.9, .4, 1) both;
}
.is-fresh-kill .butchered::after {
  content: ""; position: absolute; inset: -60% -20%; z-index: -1;
  border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle, rgba(255, 40, 60, .6), rgba(255, 40, 60, 0) 70%);
  animation: butch-splash .9s ease-out both;
}

/* THE BLEED. Butchering is the moment the duel is decided, and until now the card just
   quietly went grey for it. One element over the whole card, two things on it:

     - a hit flash, gone in a quarter second, so the eye is pulled to the card that just
       lost rather than to the one still standing;
     - blood running down from the top edge, which wipes down over about a second and
       then drains away, leaving the drained card underneath.

   Cheap on purpose: transform and opacity only, no filters, no shadows, no layout - so
   it composites on the GPU and cannot make the two-second repaint judder. Both run off
   the card's --butch-age, so they play once at the real moment of death. */
@keyframes bleed-run {
  0%   { transform: scaleY(0);   opacity: .9; }
  55%  { transform: scaleY(1);   opacity: .7; }
  100% { transform: scaleY(1);   opacity: 0; }
}
@keyframes kill-flash {
  0%   { opacity: .85; }
  100% { opacity: 0; }
}
.bleed {
  position: absolute; inset: 0; z-index: 4; pointer-events: none;
  border-radius: inherit; overflow: hidden;
}
.bleed::before {
  content: ""; position: absolute; inset: 0;
  transform-origin: top; will-change: transform, opacity;
  background: linear-gradient(180deg,
    rgba(220, 16, 40, .95) 0%, rgba(190, 10, 32, .72) 38%,
    rgba(150, 4, 24, .38) 70%, rgba(120, 0, 20, 0) 100%);
  animation: bleed-run 1.1s cubic-bezier(.25, .8, .3, 1) both;
}
.bleed::after {
  content: ""; position: absolute; inset: 0;
  background: rgba(255, 46, 66, .55);
  animation: kill-flash .3s ease-out both;
}
/* THE DRIP, once the run of blood has drained. Two drops on a long, offset cycle, so
   what a viewer sees is the occasional bead running down a dead card rather than rain.

   Two pseudo-elements on one node, transform and opacity only - the same compositor-only
   budget the bleed keeps, because this one never stops. The delay is
   calc(--drip-age + 1.1s): --drip-age is the NEGATIVE age of the kill and 1.1s is the
   length of the run, so the drip begins where the run ends and, on a card that died a
   minute ago, resumes mid-cycle rather than restarting on every repaint. The loop wants
   continuous wall-clock phase; the one-shot above wants to fire on load. Two clocks. */
@keyframes drip-run {
  0%   { transform: scaleY(0);   opacity: 0; }
  6%   { opacity: .95; }
  50%  { transform: scaleY(1);   opacity: .8; }
  78%  { transform: scaleY(1);   opacity: .3; }
  100% { transform: scaleY(1);   opacity: 0; }
}
@keyframes drip-bead {
  0%   { transform: translateY(0) scaleY(.55); opacity: 0; }
  10%  { opacity: .9; }
  70%  { opacity: .7; }
  100% { transform: translateY(280px) scaleY(1.3); opacity: 0; }
}
.drip {
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  border-radius: inherit; overflow: hidden;
}
/* Four runs of blood down the face of the card, each a different width and length so
   they read as streaks rather than as a ruled grid. They GROW downward - transform-origin
   is the top edge and scaleY goes 0 to 1 - then fade out and go again, which is the
   shape of blood running down a surface rather than a bar filling up.

   Four background layers on ONE element: no extra nodes, and the whole thing animates on
   transform and opacity alone, so it stays on the compositor next to a panel that
   repaints every two seconds. */
.drip::before {
  content: ""; position: absolute; inset: 0;
  transform-origin: top; will-change: transform, opacity;
  background-repeat: no-repeat;
  background-image:
    linear-gradient(180deg, rgba(225, 20, 45, .95), rgba(150, 4, 24, .35) 70%, transparent),
    linear-gradient(180deg, rgba(210, 14, 38, .85), rgba(140, 4, 22, .28) 65%, transparent),
    linear-gradient(180deg, rgba(235, 26, 52, .95), rgba(150, 4, 24, .32) 75%, transparent),
    linear-gradient(180deg, rgba(200, 12, 34, .8),  rgba(130, 2, 20, .25) 60%, transparent);
  background-size: 3px 82%, 2px 55%, 2px 96%, 3px 68%;
  background-position: 18% 0, 37% 0, 62% 0, 81% 0;
  animation: drip-run 5.2s ease-out infinite;
  animation-delay: calc(var(--drip-age, 0ms) + 1100ms);
}
/* The bead at the head of the longest run, so a line looks drawn rather than switched on. */
.drip::after {
  content: ""; position: absolute; top: 0; left: 62%;
  width: 4px; height: 11px; margin-left: -1px;
  border-radius: 40% 40% 55% 55% / 30% 30% 70% 70%;
  background: linear-gradient(180deg, rgba(235, 26, 52, .95), rgba(140, 4, 22, .35));
  will-change: transform, opacity;
  animation: drip-bead 5.2s ease-out infinite;
  animation-delay: calc(var(--drip-age, 0ms) + 1100ms);
}

/* The word and the drained card carry the meaning; the blood is decoration, and
   decoration is exactly what a reduced-motion setting asks to be dropped. */
@media (prefers-reduced-motion: reduce) {
  .bleed, .drip { display: none; }
}
/* The word is the message; the slam is decoration, and decoration is what a reduced
   motion setting is asking us to drop. */
@media (prefers-reduced-motion: reduce) {
  .is-fresh-kill .butchered, .is-fresh-kill .butchered::after { animation: none; }
}
.butchered-why {
  display: block; margin-top: 0.125rem;
  font-size: 0.625rem; letter-spacing: .04em; color: var(--ink-mute); font-style: normal;
}

/* Said once, in place of three rows that each say "not recorded". */
/* margin-top:auto for the same reason .pal-ivs has it: whatever slack the stretch above
   hands this card lands before this block, so the note and the passives footer stay
   pinned to the bottom the way the stat rows do on the card beside it. */
.duel-unknown {
  width: 100%; margin: auto 0 0; text-align: center;
  font-size: 0.6875rem; font-style: italic; color: var(--ink-mute);
}
/* ---- BEATEN and WON, as two ends of one scale.
   The old treatment moved the art's saturation a little and the card's opacity a little
   and left the two cards looking much the same at a glance. These are the states a
   viewer reads from across the room, so they are drawn like it.

   The whole card drains, not just the portrait: art, name, bars, chips. Filtering the
   CHILDREN rather than the card lets the head bar keep its colour, which matters because
   the head bar is where the word is - grey out the label too and the only thing left
   saying which pal lost would be that it looks washed out. */
.pal.duel-pal.is-down > *:not(.duel-head):not(.is-butchered):not(.bleed):not(.drip),
.pal.duel-pal.is-loss > *:not(.duel-head):not(.is-butchered):not(.bleed):not(.drip) { filter: grayscale(1); }
/* DOWN is the harder of the two: the pal is out RIGHT NOW, mid-fight, next to a card
   that is still going. LOST is history and only has to read as the quieter card. */
.pal.duel-pal.is-down > *:not(.duel-head):not(.is-butchered):not(.bleed):not(.drip) { opacity: .45; }
.pal.duel-pal.is-loss > *:not(.duel-head):not(.is-butchered):not(.bleed):not(.drip) { opacity: .62; }
.duel-pal.is-down, .duel-pal.is-loss {
  border-color: var(--line);
  background: linear-gradient(180deg, #1a1720, #100e15);
}
/* The tier rule is painted by ::before, which no child selector reaches. */
.duel-pal.is-down::before, .duel-pal.is-loss::before { background: var(--line); }

/* The winner lifts instead of merely not being grey: a solid gold rule where the tier
   colour was, a doubled gold edge, and a portrait a touch brighter than a card that is
   not winning anything. The word on the head bar is still what states it. */
.duel-pal.is-win {
  border-color: #ffcb3d;
  box-shadow: 0 0 0 1px #ffcb3d, 0 0 30px rgba(255, 203, 61, .45);
  background: linear-gradient(180deg, #33254a, var(--bg-1));
}
.duel-pal.is-win::before { background: linear-gradient(90deg, #ffe89a, #ffcb3d, #ffe89a); }
.pal.duel-pal.is-win .pal-art img { filter: saturate(1.12) brightness(1.06); }

/* The arena had come out 353px inside a 679px panel, and the cards 164px with a 29px
   bar. The cause was not the panel: the home panel is a COLUMN flex container, and an
   auto inline margin on a flex item sizes it to its CONTENT rather than centring a
   full-width box. width:100% above fixes it, and the teaser now gets the same 240px
   cards and 105px bars the live page does, in half the row.

   Two approaches that collapsed the layout, written down so they are not retried:
   container-type on .pal drops its min-content contribution and its 1fr column falls to
   26px; the same on .duel-arena takes the arena to 32px. */

@media (max-width: 640px) {
  /* The arena stacks, so a card that filled the column would be enormous. */
  .duel-arena > .pal { max-width: 260px; }
}

/* THE TEASER STACKS BEFORE ITS CARDS GET TOO NARROW.
   Half of a two-column row is not always enough for two cards side by side. Measured on
   the home row: the card tracks the column down to 239px at 1440 and 231 at 1200, but
   187 at 1024 and 161 at 920 - and the element row needs a 184px content box to hold two
   element icons, a status chip and its +N, so at 1024 that row hung 21px past the card
   and at 920 it hung 47px.

   Stacking is the only fix that loses nothing: shrinking the chips would truncate the
   status word, which is the channel, and dropping them would be a silent cap. Stacked,
   each card gets the whole column and returns to its 240px cap. 1150 is where the card
   crosses 208px (240 less its own 24px of padding, plus a little margin), read off that
   same measurement rather than picked.

   /live is untouched: its panel is the full row, so its arena is 640 and its cards 240
   at every desktop width. */
@media (min-width: 641px) and (max-width: 1150px) {
  .grid-2 #live-panel .duel-arena { grid-template-columns: 1fr; }
  .grid-2 #live-panel .duel-arena .vs { justify-self: center; }
}

/* ------------------------------------------------- the verdict banner
   What the stream's duel-stats overlay leads with, and the one thing this page could
   not say: is this a fair fight. It NAMES the side - "STRONG EDGE" on its own made a
   viewer guess which half of the screen it meant, which is the last thing a banner over
   a live betting pool should do.

   The WORD carries the side; the green/coral behind it is decoration, because those two
   sit 23.6 dE apart under deuteranopia and somebody with money on the duel cannot be
   asked to tell them apart. */
.verdict {
  width: 100%; text-align: center; border-radius: 14px;
  padding: 0.375rem 0.75rem; margin-bottom: 1rem;
  border: 1px solid var(--line-2); background: rgba(160, 107, 255, .1);
}
.vd-h    { border-color: rgba(58, 208, 122, .5); background: rgba(58, 208, 122, .12); }
.vd-a    { border-color: rgba(244, 101, 94, .5); background: rgba(244, 101, 94, .12); }
.vd-even { border-color: rgba(160, 107, 255, .45); background: rgba(160, 107, 255, .12); }
.vd-line {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 0.375rem; line-height: 1.2;
}
.vd-label {
  font-family: var(--font-mono); font-size: 0.875rem; font-weight: 700;
  letter-spacing: .1em; color: var(--ink);
}
.vd-side {
  font-family: var(--font-mono); font-size: 0.625rem; font-weight: 700;
  letter-spacing: .1em; border-radius: 6px; padding: 0.125rem 0.375rem;
}
.vd-side-home { color: #06240f; background: linear-gradient(90deg, #66e6a8, #2ab970); }
.vd-side-away { color: #3a0a10; background: linear-gradient(90deg, #ff8f83, #ef5a53); }
.vd-name { font-weight: 700; color: var(--ink); }
.vd-why {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: center;
  gap: 0.75rem; margin-top: 0.125rem;
  font-size: 0.6875rem; color: var(--ink-dim);
}
.vd-rank b { color: var(--ink); font-variant-numeric: tabular-nums; }
/* The banner scores SPECIES against SPECIES. Two pals of one species can still fight
   very differently once their rolls are in, so the qualifier is printed rather than left
   for a viewer to infer - the numbers under it are the ones that carry the individual. */
.vd-note { color: var(--ink-mute); font-style: italic; }

/* ------------------------------------------------- who wins each stat
   Two bars of slightly different length, sixty pixels apart with a VS between them, is
   exactly the comparison a viewer at a glance gets wrong. The winning row grows a solid
   caret pointing ACROSS at the other fighter and the losing row's number steps back to
   the dim ink. Direction and brightness, never hue: it survives a greyscale capture.
   An equal stat gets no caret on either side, so even reads as even. */
.st-row { position: relative; }
.st-cx {
  position: absolute; top: 50%; width: 0; height: 0;
  border-top: 5px solid transparent; border-bottom: 5px solid transparent;
}
/* Inside the card's own padding, so nothing has to grow to make room and .pal's
   overflow:hidden never clips it. */
.duel-home .st-cx {
  right: -0.5rem; transform: translateY(-50%);
  border-left: 6px solid #66e6a8; border-right: 0;
}
.duel-away .st-cx {
  left: -0.5rem; transform: translateY(-50%);
  border-right: 6px solid #ff8f83; border-left: 0;
}
.st-lose .st-v { color: var(--ink-mute); }
.st-lose .st-bar i { opacity: .55; }

/* ------------------------------------------------- the two movesets
   The other half of what the overlay shows and this page did not: what these two can
   actually DO. Level-gated against the fight level, because a lv50 move on a lv40 duel
   is not in the fight, and the strongest one they CAN use is marked - that single number
   decides most fights.

   Cooldown is deliberately absent. The streamer had it taken off the overlay as a number
   nobody reads mid-fight, and this column is narrower than that one. */
/* Held to the arena's own width so the panel reads as ONE block rather than a card
   floating over a table twice its size. It costs nothing: measured against the widest
   skill name that exists here - "Double Blizzard Spike", 115px, the same string
   statsoverlay.js found - a column needs about 250px before the name starts to
   ellipsise, and 640 gives each of them 313. width:100% is not decoration either: an
   auto inline margin without it shrink-wraps to content inside a column flex parent,
   which is how the arena once collapsed to 353px. */
.skl-wrap {
  display: flex; align-items: stretch; gap: 0.75rem;
  width: 100%; max-width: 640px; margin: 1.25rem auto 0;
}
.skl {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; gap: 0.125rem;
  background: rgba(255, 255, 255, .03); border: 1px solid var(--line);
  border-radius: 14px; padding: 0.5rem 0.75rem;
}
.skl-mid { flex: none; width: 1px; background: var(--line); }
/* row-reverse is what MIRRORS the away column: SKILLS lands on the outer edge and POWER
   over its own column on both sides, which a text-align swap alone does not give. */
/* The COLUMN keeps its own direction. Naming .skl-away here as well turned the away
   panel itself into a row, which laid its ten skills out side by side and ran them
   straight out of the card - only the two rows INSIDE it mirror. */
.skl-away .skl-hd, .skl-away .skl-row { flex-direction: row-reverse; }
.skl-hd {
  display: flex; align-items: center; gap: 0.375rem;
  padding-bottom: 0.25rem; margin-bottom: 0.125rem;
  border-bottom: 1px solid var(--line);
}
.skl-k {
  font-family: var(--font-mono); font-size: 0.5625rem; font-weight: 700;
  letter-spacing: .1em; color: var(--ink-mute);
}
.skl-n {
  flex: 1; text-align: center; font-size: 0.625rem; color: var(--ink-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.skl-n b { color: var(--ink); }
.skl-row {
  display: flex; align-items: center; gap: 0.375rem;
  font-size: 0.75rem; line-height: 1.5; border-radius: 6px;
  padding: 0 0.25rem;
}
/* Dimmed and nothing else. The padlock the overlay used to draw here was stealing 54px
   from every name on every row; the unlock level is already in the first column. */
.skl-row.is-off { opacity: .45; }
/* The finisher is an INSET shadow, not a border and not padding: both of those change
   the row's content width, which pushes that one row's POWER column out of line with
   the others. The word is in the row's tooltip, so the ring is not the only channel. */
.skl-row.is-fin {
  background: rgba(255, 203, 61, .12);
  box-shadow: inset 0 0 0 1px rgba(255, 203, 61, .75);
}
.skl-lv {
  flex: none; width: 1.25rem; font-family: var(--font-mono); font-size: 0.5625rem;
  color: var(--ink-mute); font-variant-numeric: tabular-nums;
}
.skl-away .skl-lv { text-align: right; }
.skl-el { flex: none; width: 18px; height: 18px; object-fit: contain; border-radius: 50%; }
.skl-nm {
  flex: 0 1 auto; min-width: 0; font-weight: 700;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.skl-gap { flex: 1 1 auto; min-width: 0; }
.skl-p {
  flex: none; width: 2.5rem; font-family: var(--font-mono); font-size: 0.6875rem;
  font-weight: 700; color: #ffb27a; font-variant-numeric: tabular-nums; text-align: right;
}
.skl-away .skl-p { text-align: left; }
/* The same element colours the panel and the fight HUD use, so a skill reads the same
   here as it does on stream. The element ICON sits beside every one of them, so the hue
   is never carrying the element on its own. */
.el-t-fire     { color: #ff8f5e; }
.el-t-water    { color: #5ec8ff; }
.el-t-grass    { color: #6ee08a; }
.el-t-electric { color: #ffd84d; }
.el-t-ice      { color: #8fe6ff; }
.el-t-ground   { color: #d2a56a; }
.el-t-dark     { color: #c08cff; }
.el-t-dragon   { color: #9d8cff; }
.el-t-neutral  { color: #cfd6e2; }

/* The species record, under the column it belongs to. Both halves, unlike the UNBEATEN
   badge on the card above: that one CELEBRATES a pal and a ranked loser gets butchered,
   this one REPORTS. The two are deliberately different and were settled that way. */
/* Held to the same 640 as the columns above it. Left full width it drew a rule right
   across the panel under a 640px table, which read as a divider for the whole section
   rather than as the footer of this one. */
.skl-rec {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: 0.5rem; width: 100%; max-width: 640px; margin: 0.5rem auto 0;
  padding-top: 0.5rem; border-top: 1px solid var(--line);
}
.skl-rec-k {
  font-family: var(--font-mono); font-size: 0.5625rem; font-weight: 700;
  letter-spacing: .1em; color: var(--ink-mute); text-align: center;
}
.skl-rec-k small {
  display: block; font-family: var(--font-body); font-size: 0.625rem;
  font-weight: 400; letter-spacing: 0; color: var(--ink-mute); opacity: .8;
}
.skl-rec-v {
  font-size: 0.875rem; font-weight: 700; font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.skl-rec-v:first-child { text-align: right; }
.skl-rec-v:last-child  { text-align: left; }
/* W and L as LETTERS beside their numbers is why this is not a colour-only claim: a
   greyscale capture still reads "9 W 3 L". */
.skl-rec-v .ok { color: #66e6a8; }
.skl-rec-v .no { color: #ff8f83; }
.skl-rec-v u {
  text-decoration: none; font-size: 0.625rem; font-weight: 700;
  color: var(--ink-mute); margin-left: 0.125rem;
}
.skl-none { font-size: 0.6875rem; font-weight: 400; color: var(--ink-mute); font-style: italic; }

@media (max-width: 640px) {
  /* Two ten-row tables side by side at 360px gives each name about eleven characters,
     and every one of them ellipsises. Stacked, each gets the full width. */
  .skl-wrap { flex-direction: column; gap: 0.5rem; }
  .skl-mid { display: none; }
  .skl-away .skl-hd, .skl-away .skl-row { flex-direction: row; }
  .skl-away .skl-lv, .skl-away .skl-p { text-align: right; }
  .skl-away .skl-p { text-align: right; }
  .skl-rec { grid-template-columns: 1fr auto 1fr; gap: 0.375rem; }
}

/* ------------------------------------------------------------------ titles */
.titles { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.title-badge {
  display: inline-flex; align-items: center; gap: 0.375rem;
  padding: 0.375rem 0.75rem; border-radius: 999px;
  font-size: .82rem; font-weight: 700;
  background: linear-gradient(180deg, rgba(255, 58, 168, .18), rgba(160, 107, 255, .12));
  border: 1px solid var(--line-2); color: var(--accent-3);
}
.title-badge .ico { font-size: .95rem; }
.title-badge.rare { border-color: var(--accent); box-shadow: 0 0 16px -4px rgba(255, 58, 168, .6); color: #fff; }
.title-badge.locked { opacity: .38; filter: grayscale(.6); }
.title-when { font-family: var(--font-mono); font-size: .68rem; color: var(--ink-mute); }

/* ------------------------------------------------------------------- crowns */
.crowns { display: flex; flex-wrap: wrap; gap: 0.5rem; }
/* Crowns in the profile header, under the name. Compact: the badge carries the claim
   and the title attribute carries the reason, because the header is not the place for
   a sentence per crown. */
.head-crowns {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.375rem;
  margin: -0.5rem 0 1.25rem;
}
.head-crown {
  display: inline-flex; align-items: center;
  padding: 0.25rem 0.5rem; border-radius: 999px;
  background: var(--panel-2); border: 1px solid var(--line);
}
.head-crown:hover { border-color: var(--accent); }
.head-crowns-note { font-size: 0.75rem; color: var(--ink-mute); margin-left: 0.25rem; }
/* Four crowns across on the home page, folding to two and then one. */
.crown-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(232px, 1fr)); gap: 0.5rem; }
.crown-grid .crown-card { flex-wrap: wrap; }
.crown-who { font-weight: 700; font-size: 0.9375rem; }
.crown-stat { font-family: var(--font-mono); font-size: 0.75rem; color: var(--ink-mute); margin-left: auto; }
/* -------------------------------------------------------------------- slider */
/* Five cards across, fifteen in the track. A real overflow region with scroll-snap
   rather than a transform carousel: a phone then flicks it natively, keyboard focus
   scrolls it, and there is no state to get out of step with the DOM. */
.slider { min-width: 0; }
.slide-track {
  display: grid; grid-auto-flow: column;
  grid-auto-columns: calc((100% - 4 * 0.75rem) / 5);
  gap: 0.75rem; overflow-x: auto; scroll-snap-type: x mandatory;
  /* No scrollbar: the dots below already say where you are, and a track scrolled by
     flicking or by tapping a dot has nothing to gain from one. */
  scrollbar-width: none; -ms-overflow-style: none;
}
.slide-track::-webkit-scrollbar { display: none; }
.slide-track > .pal { scroll-snap-align: start; }
.slide-track:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 14px; }

.slide-dots { display: flex; justify-content: center; gap: 0.375rem; margin-top: 0.5rem; }
.slide-dot {
  width: 28px; height: 6px; padding: 0; border: 0; border-radius: 999px; cursor: pointer;
  background: var(--line-2); transition: background .2s ease, width .2s ease;
}
.slide-dot:hover { background: var(--accent-2); }
.slide-dot.on { width: 40px; background: linear-gradient(90deg, var(--accent-2), var(--accent)); }
.slide-dots.as-count { font-family: var(--font-mono); font-size: 0.75rem; color: var(--ink-mute); }
/* A 6px bar is not a tap target, so the hit area is padded out beyond the paint. */
@media (max-width: 560px) {
  .slide-dots { gap: 0.5rem; margin-top: 0.75rem; }
  .slide-dot { height: 8px; box-shadow: 0 12px 0 -6px transparent; padding: 0.5rem 0; background-clip: content-box; }
}
/* The owner is the point of this strip, so it sits above the art, not under it. */
/* The tier badge is absolutely positioned at the card's top-left, so the owner has to
   keep clear of it or a long name runs underneath. */
/* The card makes room at the top so the owner clears both corner marks, and then the
   name gets the FULL width. Squeezed between them it had 56px on a 218px card, which
   wrapped TangerineButterChimkin and three others onto a second line. */
/* The slider card keeps the same tight padding as the roster card. The owner takes
   the top line at full width, so the two corner marks drop below it and sit over the
   art instead of competing with the name for the same row. */
/* The slider runs the card tighter than the roster does, through the same two
   variables rather than by overriding the padding outright. */
.slide-track { --pal-pad: 0.5rem; --pal-gap: 0.5rem; }
.pal.has-owner > .tier,
.pal.has-owner .unbeaten { top: calc(2.5rem + 0.375rem); }
/* A fixed height, so the badges below it can be placed once instead of per
   breakpoint: the mobile tap-target padding was making this line 30px there against
   16px on the desk, and a single offset cannot clear both. It doubles as the tap
   target, which is why the mobile rule no longer has to pad it. */
.pal-owner {
  display: flex; align-items: center; justify-content: center;
  width: 100%; min-height: 28px; text-align: center;
  font-family: var(--font-mono); font-size: 0.8125rem; font-weight: 700;
  letter-spacing: .02em; color: var(--accent-3); line-height: 1.25;
  overflow-wrap: anywhere; margin-bottom: 0.375rem;
}
.pal-owner:hover { color: #fff; }

/* A species that has never lost. Spelled out, because a gold pill alone would be one
   more colour to decode on a card that already carries a tier ramp and an element. */
/* The card's other corner mark. It sits opposite the tier badge and out of the flow,
   so it costs the card no height and reads as part of the same furniture. */
.unbeaten {
  position: absolute; top: 0.5rem; right: 0.5rem; z-index: 2;
  font-family: var(--font-mono); font-size: 0.5rem; font-weight: 700; letter-spacing: .04em;
  padding: 0.25rem 0.375rem; border-radius: 6px;
  color: #3a2a00; background: linear-gradient(180deg, #ffe89a, #ffcb3d);
  box-shadow: 0 0 10px rgba(255, 203, 61, .45);
}
@media (max-width: 1100px) { .slide-track { grid-auto-columns: calc((100% - 2 * 0.75rem) / 3); } }
@media (max-width: 720px)  { .slide-track { grid-auto-columns: calc((100% - 0.75rem) / 2); } }
@media (max-width: 460px)  { .slide-track { grid-auto-columns: 100%; } }
.crown-card {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.5rem 0.75rem; border-radius: 14px;
  background: var(--panel-2); border: 1px solid var(--line);
}
.crown-card:hover { border-color: var(--accent); }
.crown-card-week  { box-shadow: inset 3px 0 0 #9ddcff; }
.crown-card-month { box-shadow: inset 3px 0 0 #ffb27a; }
.crown-why { font-size: 0.8125rem; color: var(--ink-dim); }

/* ------------------------------------------------------------------ periods */
/* The window a board is counted over. Kept visually apart from the pal filters, which
   are a different kind of control and are switched off. */
.periods {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.375rem;
  margin: 0 0 1.25rem;
}
/* The same chips, but living in a panel head rather than above the page, so each
   board carries its own switch. */
.ptabs { display: inline-flex; flex-wrap: wrap; gap: 0.25rem; }
.ptabs .pchip { font-family: inherit; cursor: pointer; }
/* Every window is rendered; one is shown. */
.ppane { display: none; }
.ppane.on { display: block; }
.pchip {
  padding: 0.375rem 0.75rem; border-radius: 999px;
  border: 1px solid var(--line-2); background: rgba(255, 255, 255, .03);
  color: var(--ink-dim); font-size: 0.8125rem; font-weight: 700; line-height: 1.4;
}
.pchip:hover { border-color: var(--accent); color: var(--ink); }
.pchip.on { background: linear-gradient(180deg, var(--accent), #d81d86); color: #fff; border-color: transparent; }
.periods-note {
  font-family: var(--font-mono); font-size: 0.6875rem; color: var(--ink-mute);
  margin-left: 0.25rem;
}
/* A period crown is the site's own, so it is gold like the all-time ones but tinted
   by window: a week crown is worth less than a month crown and should not look equal. */
.crown-week  { color: #9ddcff; }
.crown-month { color: #ffb27a; }

/* --------------------------------------------------------------- rank badges */
/* A place is only worth defending if it looks scarce, so the top three are marked
   individually and the rest collapse into bands. */
.rk {
  display: inline-flex; align-items: center; gap: 0.25rem;
  padding: 0.25rem 0.5rem; border-radius: 999px;
  font-family: var(--font-mono); font-size: 0.6875rem; font-weight: 700;
  border: 1px solid var(--line-2); color: var(--ink-dim); white-space: nowrap;
}
.rk-1 { color: #3a2a00; background: linear-gradient(180deg, #ffe89a, #ffcb3d); border-color: transparent; box-shadow: 0 0 12px rgba(255, 203, 61, .55); }
.rk-2 { color: #1b2230; background: linear-gradient(180deg, #e8eef6, #b9c6d6); border-color: transparent; }
.rk-3 { color: #3a1e05; background: linear-gradient(180deg, #f0c08a, #cf9152); border-color: transparent; }
.rk-10 { color: var(--accent-3); border-color: var(--accent); background: rgba(255, 58, 168, .12); }
.rk-25 { color: var(--ink-dim); }
.rk-out { color: var(--ink-mute); border-color: var(--line); }
tr.rk-row { background: rgba(255, 203, 61, .06); }
.crown-title {
  display: inline-block; margin-top: 0.125rem;
  font-family: var(--font-mono); font-size: 0.625rem; font-weight: 700;
  letter-spacing: .06em; color: #ffcb3d;
}

/* Standings sit inside the Duel record panel, which had spare room under its stat
   grid. The denominator is deliberately not printed: "#15 of 96" reads like a score,
   when the point is simply the position held. */
/* A rank badge is already a pill. Wrapping each one in a second pill left 4px between
   two 999px radii, so the inner corners cut the outer curve and first place's glow bled
   straight through the border. The badge carries its own chrome; grouping is done with
   spacing instead - tight inside an item, wide between them. */
.stand {
  display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; align-items: center;
  margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid var(--line);
}
.stand-h {
  font-family: var(--font-mono); font-size: 0.625rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .1em; color: var(--ink-mute);
}
.stand-i { display: inline-flex; align-items: center; gap: 0.5rem; }
.stand-l { font-size: 0.8125rem; font-weight: 700; color: var(--ink-dim); }

/* ------------------------------------------------- global title progress */
.tprog { margin-bottom: 0.75rem; }
.tprog-bar {
  height: 10px; border-radius: 999px; background: var(--bg-deep);
  border: 1px solid var(--line-2); overflow: hidden;
}
.tprog-bar i {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent-2), var(--accent), #ffcb3d);
}
.tprog-t { margin-top: 0.375rem; font-size: 0.8125rem; color: var(--ink-dim); }
.tprog-t strong { color: var(--ink); }

/* ------------------------------------------------------------------ filters */
.filters { display: grid; gap: 0.5rem; margin-bottom: 0.75rem; }
.filter-row { display: flex; flex-wrap: wrap; gap: 0.25rem; align-items: center; }
.filter-k {
  font-family: var(--font-mono); font-size: 0.625rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .1em; color: var(--ink-mute);
  width: 4rem; flex: none;
}
.fchip {
  display: inline-flex; align-items: center; gap: 0.25rem;
  padding: 0.25rem 0.75rem; border-radius: 999px;
  border: 1px solid var(--line-2); background: rgba(255, 255, 255, .03);
  color: var(--ink-dim); font-size: 0.75rem; font-weight: 700; line-height: 1.4;
}
.fchip:hover { border-color: var(--accent); color: var(--ink); }
.fchip.on { background: linear-gradient(180deg, var(--accent), #d81d86); color: #fff; border-color: transparent; }
.fchip img { display: block; }
/* The tier chips carry their own ramp so the filter reads like the badges it filters. */
.tier-chip { font-family: var(--font-mono); min-width: 2rem; justify-content: center; }
.tier-chip.tier-s { color: #ffcb3d; border-color: rgba(255, 203, 61, .5); }
.tier-chip.tier-a { color: #ff6ee0; border-color: rgba(255, 110, 224, .5); }
.tier-chip.tier-b { color: #3ad07a; border-color: rgba(58, 208, 122, .5); }
.tier-chip.tier-c { color: #7aa6ff; border-color: rgba(122, 166, 255, .5); }
.tier-chip.tier-d { color: #9c93b3; border-color: rgba(156, 147, 179, .5); }
.tier-chip.on { color: #fff; }
.el-mini { vertical-align: middle; margin-left: 0.25rem; }
@media (max-width: 560px) {
  .filter-k { width: 100%; }
  .el-chip .el-name { display: none; }
}

/* -------------------------------------------------------------- title quests */
/* The nearest unearned titles, as fillable goals. Only countable conditions get a
   bar, so every bar here is a real fraction rather than a guess at "how close". */
.quests { display: grid; grid-template-columns: repeat(auto-fit, minmax(216px, 1fr)); gap: 0.75rem; }
.quest {
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 14px; padding: 0.75rem;
}
.quest-top { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; }
.quest-n { font-weight: 700; font-size: 0.8125rem; display: inline-flex; align-items: center; gap: 0.375rem; }
.quest-n .ico { font-size: 0.9rem; }
.quest-c {
  font-family: var(--font-mono); font-size: 0.75rem; font-weight: 700;
  color: var(--accent-3); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.quest-c small { color: var(--ink-mute); font-weight: 400; }
.quest-bar {
  margin-top: 0.5rem; height: 8px; border-radius: 999px;
  background: var(--bg-deep); border: 1px solid var(--line); overflow: hidden;
}
.quest-bar i {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent-2), var(--accent));
}
.quest-d { margin-top: 0.375rem; font-size: 0.6875rem; color: var(--ink-mute); }

/* ------------------------------------------------------------------ roster */
/* A pal card reads top to bottom: art carrying its tier, alpha and sex, then name,
   elements, the three talent bars, and a footer of total plus passives. A tier-tinted
   rule across the top edge makes the grid scannable by tier without reading a badge. */
.roster {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(clamp(138px, 20vw, 215px), 1fr));
  gap: 0.75rem;
  --pal-pad: 0.75rem;
  --pal-gap: 0.5rem;
}

.pal {
  position: relative;
  display: flex; flex-direction: column; align-items: center;
  /* Fallbacks, not declarations: --pal-pad and --pal-gap are set by .roster and tuned
     per breakpoint there, so declaring them ON .pal would win over that inheritance and
     freeze the card. Written as var() defaults instead, a card dropped anywhere else -
     the home slider - still gets its own spacing rather than none. It had none: every
     element inside a slider card was touching the next. */
  gap: var(--pal-gap, 0.5rem);
  /* The +3px is not a spacing value: it is the height of the tier rule painted across
     the top edge by ::before, so the gap BELOW that rule is exactly --pal-pad. */
  padding: calc(var(--pal-pad, 0.75rem) + 3px) var(--pal-pad, 0.75rem) var(--pal-pad, 0.75rem);
  background: linear-gradient(180deg, var(--panel-2), var(--bg-1));
  border: 1px solid var(--line);
  border-radius: 20px;
  overflow: hidden;
  transition: border-color .16s ease, box-shadow .16s ease, transform .16s ease;
}
.pal::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--tier-c, var(--line-2));
}
.pal:hover {
  border-color: var(--accent);
  box-shadow: 0 6px 18px -10px rgba(255, 58, 168, .8);
  transform: translateY(-2px);
}

.pal-tx { --tier-c: var(--line-2); }
.pal-ts { --tier-c: linear-gradient(90deg, #ffcb3d, #ffe89a); }
.pal-ta { --tier-c: linear-gradient(90deg, #ff6ee0, #ffb9f0); }
.pal-tb { --tier-c: linear-gradient(90deg, #3ad07a, #b6ffe2); }
.pal-tc { --tier-c: linear-gradient(90deg, #7aa6ff, #cfe0ff); }
.pal-td { --tier-c: linear-gradient(90deg, #9c93b3, #d8d2e6); }

.pal-art {
  position: relative; width: 120px; height: 120px;
  display: grid; place-items: center;
  background: radial-gradient(circle at 50% 38%, rgba(160, 107, 255, .2), transparent 70%);
  border-radius: 50%;
}
.pal-art img {
  width: 120px; height: 120px; object-fit: contain; display: block;
  /* The source renders carry their own square backdrop, which showed as a hard
     edge against the round glow behind them. */
  border-radius: 14px;
}
.pal-none { color: var(--ink-mute); font-size: 2.25rem; font-family: var(--font-display); }

.alpha-tag {
  position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  color: var(--warn); font-size: 1.125rem; line-height: 1;
  text-shadow: 0 0 10px rgba(255, 216, 77, .9);
}
.gender-tag { position: absolute; right: -2px; bottom: 4px; }
.g {
  display: inline-grid; place-items: center;
  width: 26px; height: 26px; border-radius: 50%;
  font-size: 1.0625rem; font-weight: 700; line-height: 1;
  border: 1px solid var(--line-2); background: var(--bg-deep);
}
.g-m { color: #7db8ff; border-color: #2c4a7a; }
.g-f { color: #ff8fc8; border-color: #6a1a55; }

/* The tier badge is a hex chip: the one place the grade is spelled out. */
.tier {
  display: inline-grid; place-items: center;
  width: 28px; height: 32px;
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  font-family: var(--font-display); font-size: 1rem; line-height: 1;
  color: #1a0b12;
}
.pal > .tier { position: absolute; top: 0.5rem; left: 0.5rem; z-index: 2; }
/* Exactly the stream's ramp: statsoverlay.js:478-480 and panel.js:2243-2247 agree.
   B is GREEN on stream; it was purple here, which was the one that disagreed. */
.tier-s { background: linear-gradient(180deg, #ffe89a, #ffcb3d); color: #3a2a00; box-shadow: 0 0 14px rgba(255, 203, 61, .7); }
.tier-a { background: linear-gradient(180deg, #ffb9f0, #ff6ee0); color: #45003a; box-shadow: 0 0 12px rgba(255, 110, 224, .55); }
.tier-b { background: linear-gradient(180deg, #b6ffe2, #3ad07a); color: #03301a; }
.tier-c { background: linear-gradient(180deg, #cfe0ff, #7aa6ff); color: #06204a; }
.tier-d { background: linear-gradient(180deg, #d8d2e6, #9c93b3); color: #241d33; }

.pal-n {
  margin: 0; font-family: var(--font-body);
  font-weight: 700; font-size: 0.9375rem; line-height: 1.25;
  text-align: center; overflow-wrap: anywhere; color: var(--ink);
}

/* nowrap on purpose: a status chip that wrapped to a second line would grow the card
   exactly the way the old status row did, just less obviously. The chip count is capped
   so it never has to. */
.pal-els {
  display: flex; gap: 0.375rem; justify-content: center; align-items: center;
  min-height: 34px; flex-wrap: nowrap;
}
.pal-els > * { flex: none; }
.el {
  display: inline-grid; place-items: center;
  width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--line-2); background: var(--bg-deep);
}
.el img { width: 26px; height: 26px; object-fit: contain; display: block; border-radius: 50%; }
.el-fire { border-color: #7a2f45; background: rgba(255, 176, 168, .12); }
.el-water { border-color: #2c4a7a; background: rgba(157, 220, 255, .12); }
.el-grass { border-color: #2b6b57; background: rgba(159, 242, 196, .12); }
.el-electric { border-color: #7a6420; background: rgba(255, 233, 138, .12); }
.el-ice { border-color: #2a6a80; background: rgba(182, 240, 255, .12); }
.el-ground { border-color: #6b5238; background: rgba(230, 201, 168, .12); }
.el-dark { border-color: var(--line-2); background: rgba(201, 168, 255, .12); }
.el-dragon { border-color: #6a1a55; background: rgba(255, 168, 232, .12); }
.el-neutral { border-color: var(--line); background: rgba(221, 208, 232, .12); }

/* POWER first, then the three stats. The big number is what the pal fights with; the
   bar beside it is the 0-100 talent roll. Base is a tooltip rather than a fourth
   column, because four numbers per row stopped being readable at card width. */
.pwr {
  width: 100%; display: grid; grid-template-columns: auto 1fr auto;
  align-items: center; gap: 0.375rem;
  padding: 0.375rem 0.5rem; border-radius: 14px;
  background: rgba(255, 178, 122, .1); border: 1px solid rgba(255, 178, 122, .28);
}
.pwr-k {
  font-family: var(--font-mono); font-size: 0.5625rem; font-weight: 700;
  letter-spacing: .1em; color: #ffb27a;
}
.pwr-bar { height: 7px; border-radius: 999px; background: var(--bg-deep); border: 1px solid var(--line); overflow: hidden; }
.pwr-bar i { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, #ffb27a, #ff8a3d); }
.pwr-v {
  font-family: var(--font-mono); font-size: 0.75rem; font-weight: 700;
  color: #ffcda6; font-variant-numeric: tabular-nums;
}

.pal-ivs { width: 100%; display: grid; gap: 0.375rem; margin-top: auto; }
.st-row { display: grid; grid-template-columns: 1.75rem 2.9rem 1fr 1.4rem; align-items: center; gap: 0.25rem; }
.st-k {
  font-family: var(--font-mono); font-size: 0.5625rem; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-mute); font-weight: 700;
}
.st-v {
  font-family: var(--font-mono); font-size: 0.75rem; font-weight: 700;
  color: var(--ink); text-align: right; font-variant-numeric: tabular-nums;
}
.st-v-none { color: var(--ink-mute); }
.st-bar { height: 7px; border-radius: 999px; background: var(--bg-deep); border: 1px solid var(--line); overflow: hidden; }
/* One neutral fill: the bar measures the STAT, so its colour must not also try to say
   something about roll quality. The roll gets its own tinted chip instead. */
.st-bar i {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent-2), var(--accent));
}
.st-r {
  font-family: var(--font-mono); font-size: 0.625rem; font-weight: 700;
  text-align: center; font-variant-numeric: tabular-nums;
  border-radius: 6px; padding: 0.125rem 0;
}
.rq-s { color: #3a2a00; background: linear-gradient(180deg, #ffe89a, #ffcb3d); }
.rq-a { color: #03301a; background: linear-gradient(180deg, #b6ffe2, #3ad07a); }
.rq-m { color: #06204a; background: linear-gradient(180deg, #cfe0ff, #7aa6ff); }
.rq-c { color: var(--ink-mute); background: rgba(255, 255, 255, .05); }
.rq-none { color: var(--ink-mute); }
.st-none { grid-column: 3 / span 2; font-size: 0.5625rem; color: var(--ink-mute); font-style: italic; }

.pal-foot {
  width: 100%; display: flex; flex-wrap: wrap; gap: 0.25rem;
  align-items: center; justify-content: center;
  padding-top: var(--pal-gap); border-top: 1px solid var(--line);
  min-height: 2rem;
}
/* No talent total here. Each stat row already shows its own roll, and one more
   number on the card only competed with them. */
.pass-none { font-size: 0.6875rem; color: var(--ink-mute); font-style: italic; }
.pass {
  font-size: 0.625rem; font-weight: 700; padding: 0.125rem 0.5rem; border-radius: 6px;
  background: rgba(160, 107, 255, .16); border: 1px solid var(--line-2); color: var(--accent-3);
}

@media (max-width: 820px) {
  .pal-art, .pal-art img { width: 96px; height: 96px; }
}
@media (max-width: 560px) {
  .roster { gap: 0.5rem; --pal-pad: 0.5rem; }
  .pal-art, .pal-art img { width: 84px; height: 84px; }
  .el { width: 28px; height: 28px; }
  .el img { width: 20px; height: 20px; }
  .pal-els { min-height: 28px; }
  .pal-n { font-size: 0.875rem; }
  .panel { padding: 1rem 0.75rem; }
}

/* ------------------------------------------------------------------ forms */
.form { display: flex; gap: 0.5rem; flex-wrap: wrap; }
input[type="text"] {
  flex: 1 1 240px; min-width: 0;
  background: var(--bg-deep); border: 1px solid var(--line-2); color: var(--ink);
  border-radius: 999px; padding: 0.75rem 1rem; font: inherit; font-family: var(--font-body);
}
input[type="text"]::placeholder { color: var(--ink-mute); }
input[type="text"]:focus { outline: 2px solid var(--accent); outline-offset: 2px; border-color: transparent; }
button {
  background: linear-gradient(180deg, var(--accent), #d81d86);
  color: #fff; border: 0; border-radius: 999px;
  padding: 0.75rem 1.5rem; font: inherit; font-family: var(--font-body);
  font-weight: 700; cursor: pointer; box-shadow: var(--glow-sm);
}
button:hover { filter: brightness(1.08); }
.err { color: var(--loss); font-size: .88rem; margin-top: 0.75rem; margin-bottom: 0; }
/* The UA gives <p> a 1em bottom margin, which does not collapse out of a parent
   with padding - so every panel ending in a note sat 13px bottom-heavy. */
.note { color: var(--ink-mute); font-size: .84rem; margin-top: 1rem; margin-bottom: 0; }
/* A link inside a note is a real tap target on a phone, and the note's own type size
   leaves it 19px tall - under the 24px floor the rest of the site holds to. Padding on
   an inline box grows the hit area outside the line box, so nothing around it moves. */
.note a { padding-block: 0.25rem; }

/* Name links in the dense lists measured 22-23px tall on a phone, under any sane
   floor for a thumb. Padding would reflow the tables, so the height comes from
   line-height, which grows the box without moving anything around it. */
@media (max-width: 560px) {
  td a, .dl-n, .panel-head a, .stand a { display: inline-block; line-height: 2; }
  /* .pal-owner is not listed here: it carries its own min-height, which is both the
     tap target and what keeps the corner badges clear of it. */
}

/* ------------------------------------------------------------------ footer */
.foot { border-top: 1px solid var(--line); padding: 1.25rem 0; color: var(--ink-mute); font-size: .8rem; }
.foot-in { display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.foot-sync { font-family: var(--font-mono); }
.foot-sync.warn { color: var(--warn); }

.pager { display: flex; gap: 0.5rem; margin-top: 1.25rem; justify-content: center; flex-wrap: wrap; }
.pager a, .pager span {
  padding: 0.375rem 1rem; border-radius: 999px; border: 1px solid var(--line-2);
  font-size: .85rem; font-weight: 700; color: var(--ink-dim);
}
.pager a:hover { border-color: var(--accent); color: var(--ink); }
.pager span { opacity: .35; }


/* Result strip: last ten outcomes, oldest first.
   Named .form-strip, NOT .form - it collided with the real search/login form rule of
   the same name at equal specificity, and being declared later it won, stripping
   flex-wrap from every form on the site. */
.form-strip { display: inline-flex; gap: 0.25rem; align-items: center; }
.form-strip i { width: 9px; height: 14px; border-radius: 6px; display: block; }
.form-strip .fw { background: var(--good); }
.form-strip .fl { background: var(--line-2); }

/* Live betting split. Deliberately shows the SPLIT only - never pool size or headcount. */
.oddsbar {
  display: flex; margin-top: 1.25rem; height: 26px; border-radius: 999px;
  overflow: hidden; border: 1px solid var(--line-2); font-family: var(--font-mono);
  font-size: .72rem; font-weight: 700;
}
.oddsbar > div { display: grid; place-items: center; transition: width .5s ease; min-width: 0; }
.odds-h { background: linear-gradient(90deg, var(--good), #4bc8a4); color: #06231a; }
.odds-a {
  background:
    repeating-linear-gradient(135deg,
      rgba(255, 255, 255, .28) 0 2px, rgba(255, 255, 255, 0) 2px 6px),
    linear-gradient(90deg, var(--accent-2), var(--accent));
  color: #fff;
  border-left: 2px solid rgba(255, 255, 255, .92);
}

/* Visible to assistive tech only - used to name things a sighted reader gets from
   position or an icon (the IV total, the alpha star, the gender glyph). */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* The tier badge is defined once, with the roster styles above. An earlier copy lived
   here and, being later in the file, silently won: C and D were painted in panel and
   background colours, so those chips were all but invisible on the card. */

/* --------------------------------------------------------------- duel cards */
/* A duel listing is a fight card, not a table row: two portraits facing each other
   with the chatter over the pal they fielded, and the money on the right. A table
   made the reader rebuild the pairing from separate columns; this shows it. */
/* The list is its own query container. What a duel card needs to know is how wide the
   PANEL is, not how wide the window is: the same list runs full width on a profile and
   in a third of the page on /live, so a viewport media query gets one of the two wrong
   every time. */
.dl {
  list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem;
  container-type: inline-size;
}
.dl-row {
  display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 1rem;
  padding: 0.5rem 0.75rem; border-radius: 14px;
  background: var(--panel-2); border: 1px solid var(--line);
}
/* Capped, otherwise the two fighters are pushed to the far edges of a wide panel and
   the "vs" floats in a void the eye has to cross to pair them up. */
.dl-mu {
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: 0.75rem; min-width: 0; max-width: 36rem;
}
/* Each half carries its own colour, washing out towards the middle so the two never
   meet in a hard seam. Green is home and red is away everywhere on the site, the same
   pairing the stream overlay uses. */
.dl-f {
  display: flex; align-items: center; gap: 0.5rem; min-width: 0;
  padding: 0.25rem; border-radius: 14px;
}
.dl-f { --side-a: .20; --side-b: .03; }
.dl-home { background: linear-gradient(90deg, rgba(55, 201, 127, var(--side-a)), rgba(55, 201, 127, var(--side-b)) 92%); }
/* The away side mirrors, so both portraits sit on the outer edges and the two names
   meet in the middle around the "vs". */
.dl-away {
  flex-direction: row-reverse;
  background:
    repeating-linear-gradient(135deg,
      rgba(255, 255, 255, .045) 0 3px, rgba(255, 255, 255, 0) 3px 8px),
    linear-gradient(270deg, rgba(244, 101, 94, var(--side-a)), rgba(244, 101, 94, var(--side-b)) 92%);
}

/* Home is flat, away is hatched. Under full greyscale that texture is the only thing
   left telling the two halves apart, which is the point. */

/* HOME and AWAY set upright, one letter per line, down the outer edge.
   In upright vertical text each glyph advances by the font's VERTICAL metric, which
   here is about 1.5em - so at 8px the four letters spread over 49px and the spine was
   mostly gap. letter-spacing works on that block axis, so a negative value pulls the
   letters back together and pays for a much larger type size in the same height. */
.dl-tag {
  flex: none; align-self: stretch;
  display: grid; place-items: center;
  writing-mode: vertical-rl; text-orientation: upright;
  font-family: var(--font-mono); font-size: 0.9rem; font-weight: 700;
  letter-spacing: -0.4em; line-height: 1;
  /* 0 on the leading edge, 0.375rem on the trailing one: the negative tracking is
     applied after the LAST letter too, so the glyph ink runs past the end of the
     text box and the bottom pad is what buys it back. */
  padding: 0.125rem 0.125rem 0.375rem 0; border-radius: 6px;
}
.dl-home .dl-tag { color: #b6ffe2; background: rgba(55, 201, 127, .16); border: 1px solid rgba(55, 201, 127, .45); }
.dl-away .dl-tag { color: #ff8f88; background: rgba(244, 101, 94, .14); border: 1px solid rgba(244, 101, 94, .45); }

.dl-home .dl-port { border-style: solid; }
.dl-away .dl-port { border-style: dashed; }

.dl-port {
  position: relative;
  display: grid; place-items: center; flex: none;
  width: 56px; height: 56px; border-radius: 14px;
  background: rgba(255, 255, 255, .04); border: 1px solid var(--line);
}
/* The art is square and the frame is rounded, so without a radius of its own the
   image's corners poke out past the frame at the bottom right. overflow:hidden on the
   frame is not an option: the winner's crown hangs outside it on purpose. */
.dl-img { width: 52px; height: 52px; object-fit: contain; display: block; border-radius: 14px; }
.dl-noimg { font-family: var(--font-mono); font-size: 1.25rem; color: var(--ink-mute); }

.dl-t { display: grid; gap: 0.125rem; min-width: 0; flex: 1 1 auto; }
.dl-who { display: flex; align-items: center; gap: 0.25rem; min-width: 0; justify-content: flex-end; }
.dl-pal { display: flex; align-items: center; gap: 0.25rem; min-width: 0; justify-content: flex-end; }
.dl-home .dl-t { text-align: right; }
/* row-reverse, so flex-end is the LEFT edge: the away name packs against the "vs" too. */
.dl-away .dl-who, .dl-away .dl-pal { flex-direction: row-reverse; }

/* Clipped on one line, never broken mid-word. overflow-wrap:anywhere breaks at whatever
   character happens to reach the edge, which is how TangerineButterChimkin became
   "TangerineButterCh / imkin" and ReluctantWatchman ended a line on a bare "n" - both
   read as typos rather than as long names. An ellipsis says "there is more"; the full
   name is on the element as a title. min-width:0 on the flex parents is what lets the
   text box actually shrink to the column. */
.dl-n {
  font-weight: 700; font-size: 0.9375rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.dl-home .dl-n { color: #b6ffe2; }
.dl-away .dl-n { color: #f4655e; }
.dl-n:hover { color: #fff; }

/* Pinned to the portrait's outer corner, so it is unambiguous which fighter it crowns. */
.dl-crown {
  position: absolute; top: -8px; z-index: 2;
  font-size: 0.875rem; line-height: 1; color: #ffd24d;
  text-shadow: 0 0 8px rgba(255, 210, 77, .8), 0 1px 2px rgba(0, 0, 0, .9);
}
.dl-home .dl-crown { left: -7px; }
.dl-away .dl-crown { right: -7px; }

.dl-paln {
  font-size: 0.8125rem; color: var(--ink-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.dl-pal .tier { flex: none; width: 20px; height: 23px; font-size: 0.6875rem; }
.dl-pal-none { font-size: 0.75rem; color: var(--ink-mute); font-style: italic; }

.dl-vs {
  flex: none; font-family: var(--font-mono); font-size: 0.6875rem; font-weight: 700;
  letter-spacing: .1em; color: var(--ink-mute);
}

/* Losing takes the colour out of the whole half, not just the text. Dimming the text
   alone left the wash, the spine and the pal art at full strength, so a beaten side
   still read as loud as the winner - and a beaten side that was also the BACKED one
   measured BRIGHTER than the winner. */
.dl-f.is-lost { --side-a: .05; --side-b: .01; }
.dl-f.is-lost .dl-port, .dl-f.is-lost .dl-t { opacity: .45; }
.dl-f.is-lost .dl-n, .dl-f.is-lost .dl-paln { color: var(--ink-mute); }
.dl-f.is-lost .dl-img { filter: grayscale(.8) brightness(.72); }
.dl-f.is-lost .dl-tag { opacity: .4; background: transparent; }
.dl-f.is-lost .tier { filter: grayscale(.7) brightness(.8); }

.dl-f.is-won .dl-n { text-shadow: 0 0 10px rgba(255, 210, 77, .3); }
.dl-f.is-won .dl-port { border-color: rgba(255, 210, 77, .55); box-shadow: 0 0 12px rgba(255, 210, 77, .25); }

/* The side the row is about. Without it a card shows two fighters and no clue which
   one the stake or the result belongs to. */
.dl-f.is-lost.is-mark .dl-port, .dl-f.is-lost.is-mark .dl-t { opacity: .6; }
.dl-f.is-mark .dl-port { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(255, 58, 168, .3); }
.dl-mark {
  flex: none; font-family: var(--font-mono); font-size: 0.5rem; font-weight: 700;
  letter-spacing: .08em; color: var(--accent-3);
  padding: 0.125rem 0.25rem; border-radius: 6px;
  background: rgba(255, 58, 168, .14); border: 1px solid rgba(255, 58, 168, .35);
}

.dl-out { display: flex; align-items: center; gap: 0.75rem; flex: none; }
.dl-when { font-family: var(--font-mono); font-size: 0.6875rem; color: var(--ink-mute); white-space: nowrap; }
.dl-figs { display: flex; align-items: center; gap: 0.75rem; }
.dl-fig { display: grid; gap: 0.125rem; text-align: right; min-width: 4.25rem; }
.dl-k {
  font-family: var(--font-mono); font-size: 0.5625rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; color: var(--ink-mute);
}
.dl-v { font-family: var(--font-mono); font-size: 0.9375rem; font-weight: 700; }

.wchip {
  display: grid; justify-items: center; gap: 0.125rem;
  font-family: var(--font-mono); font-size: 0.625rem; font-weight: 700;
  line-height: 1; white-space: nowrap;
  padding: 0.375rem 0.5rem; border-radius: 14px;
}
.wchip-i { font-size: 0.875rem; line-height: 1; }
.wchip-s { letter-spacing: .06em; }
.wchip-w { font-size: 0.5rem; letter-spacing: .12em; opacity: .75; }
.wchip-void { padding: 0.25rem 0.5rem; border-radius: 999px; }
.wchip-home { color: #b6ffe2; background: rgba(55, 201, 127, .14); border: 1px solid rgba(55, 201, 127, .38); }
.wchip-away { color: #ff8f88; background: rgba(244, 101, 94, .14); border: 1px solid rgba(244, 101, 94, .38); }
.wchip-void { color: var(--ink-mute); background: transparent; border: 1px solid var(--line-2); }

/* Below this the money no longer fits beside the matchup, so it drops to its own line
   rather than crushing the names. */
@container (max-width: 600px) {
  .dl-row { grid-template-columns: 1fr; gap: 0.375rem; }
  .dl-out { justify-content: space-between; }
  /* On its own row the chip has width to spare and height to save, so it lies flat. */
  .wchip { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.25rem 0.5rem; border-radius: 999px; }
  .wchip-i { font-size: 0.75rem; }
  .wchip-w { font-size: 0.625rem; letter-spacing: .06em; }
}
@container (max-width: 520px) {
  .dl-port { width: 46px; height: 46px; }
  .dl-img { width: 42px; height: 42px; }
  .dl-mu { gap: 0.5rem; }
  .dl-n { font-size: 0.875rem; }
}
/* On a phone the two fighters stop facing each other and stack, each getting the full
   width. Side by side, a name like TangerineButterChimkin had about 75px to live in and
   wrapped into a row five lines deep. Stacked, the spine still labels each side, so
   nothing is lost but the mirroring. */
@container (max-width: 410px) {
  .dl-row { padding: 0.5rem; }
  .dl-mu { grid-template-columns: 1fr; gap: 0.25rem; max-width: none; }
  .dl-port { width: 44px; height: 44px; }
  .dl-img { width: 40px; height: 40px; }
  .dl-n { font-size: 0.875rem; }
  .dl-paln { font-size: 0.75rem; }
  .dl-figs { gap: 0.5rem; }
  .dl-fig { min-width: 3.25rem; }

  .dl-away {
    flex-direction: row;
    background:
      repeating-linear-gradient(135deg,
        rgba(255, 255, 255, .045) 0 3px, rgba(255, 255, 255, 0) 3px 8px),
      linear-gradient(90deg, rgba(244, 101, 94, var(--side-a)), rgba(244, 101, 94, var(--side-b)) 92%);
  }
  .dl-home .dl-t { text-align: left; }
  .dl-who, .dl-pal { justify-content: flex-start; }
  .dl-away .dl-who, .dl-away .dl-pal { flex-direction: row; }
  .dl-away .dl-crown { right: auto; left: -7px; }
  .dl-vs { text-align: center; }
}

/* Container queries are the whole layout here, so a browser without them would get a
   single squeezed line. This is the floor for that case only. */
@supports not (container-type: inline-size) {
  @media (max-width: 900px) {
    .dl-row { grid-template-columns: 1fr; gap: 0.375rem; }
    .dl-out { justify-content: space-between; }
  }
}

/* ------------------------------------------------- the smallest labels, on a phone
   0.5625rem is 9px, which is under the floor for something read at arm's length. These
   four are the smallest type on the site and every one of them labels a number, so
   losing the label costs the number its meaning. 10px on the phone only - the desk has
   the room to stay compact.

   AT THE END OF THE SHEET on purpose: .pwr-k and .st-k are declared some five hundred
   lines below where this override first sat, so it lost on source order and changed
   nothing. Same weight, so the last one wins. */
@media (max-width: 640px) {
  .pwr-k, .st-k, .skl-k, .skl-lv { font-size: 0.625rem; }
  /* A 27px standalone link in a panel head. The row links are dense inline text and stay
     as they are, but this one is on its own and costs nothing to make hittable. */
  .panel-head a { padding: 0.375rem 0; }
}
