/* ==========================================================================
   Hanami VGC Analytics — stylesheet
   Color tokens come from the validated data-viz reference palette.
   Type-chip pairs (bg / fg) were computed for >= 4.5:1 in both modes.
   ========================================================================== */

:root {
  color-scheme: light;

  /* surfaces & ink */
  --surface-1: #fcfcfb;
  --plane: #f9f9f7;
  --sunken: #f2f2ef;
  --ink: #0b0b0b;
  --ink-2: #52514e;
  --ink-3: #898781;
  --line: #e1e0d9;
  --axis: #c3c2b7;
  --ring: rgba(11, 11, 11, 0.10);

  /* sequential blue ramp — magnitude */
  --seq-100: #cde2fb;
  --seq-200: #9ec5f4;
  --seq-300: #6da7ec;
  --seq-450: #2a78d6;
  --seq-600: #184f95;
  /* SAKURA IS CHROME, NOT DATA. The accent paints the active nav pill, focus
     rings, sort indicators, hovers and the primary button — it never paints a
     mark that encodes a number. That separation is what lets the brand be pink
     while the charts stay on the validated blue ramp, and it is worth keeping:
     a pink that also meant "series 1" would sit next to --neg (loss) and --s5
     at ~7 dE and stop being readable as either brand or data.
     Measured, OKLab dE x100 against the data palette: --neg 13.2, --s5 19.6,
     --s2 19.6. Contrast on #fcfcfb is 5.84:1 — the blue it replaces was 4.0. */
  --accent: #b32d6a;
  --accent-soft: #fce9f0;

  /* Badge ink. --ink-3 sits on its own soft chip at 3.20:1, under AA for text
     this small (11px, and 650 weight is not enough to make it large text), so
     the chip gets its own darker pair. Measured against #fce9f0 and #f2f2ef:
     6.27 and 6.50. (The accent itself now clears AA on its own chip at 5.16,
     where the old blue did not — but the pair stays, because the chip should
     not depend on the accent never being lightened again.) */
  --badge-hi: #9d275c;
  --badge-lo: #63615c;

  /* categorical slots — identity, for multi-line charts. Fixed order: the
     ordering IS the colourblind-safety mechanism, not a decoration.

     Four slots, not six, and validated on the ALL-PAIRS list rather than the
     adjacent one. The adjacent list is the right test for a stacked bar, where
     only neighbours touch. It is the wrong test for this site's one multi-line
     chart, where the reader picks the members and any two lines can cross. On
     the all-pairs list the old six failed in both themes — worst dark pair
     #d55181 against #199e70 at CVD dE 1.6, which is one line, not two, for a
     deuteranopic reader; and #c98500 against #d95926 at dE 10.6 unsimulated,
     under the 15 floor for everyone else. Four is not a rounding down of six:
     six all-pairs-safe hues do not exist inside the lightness band at this
     chroma without surrendering dark-surface contrast, so the honest move is
     fewer lines, and small multiples if more are ever wanted.

     Slot 4 is the one that changed: amber #eda100, a second warm neighbour of
     the orange, became purple. Blue, orange and green were already safe as a
     trio and are untouched.

     Worst all-pairs, both themes: CVD dE 9.2 light / 9.4 dark (target 8),
     unsimulated 19.3 / 19.5 (floor 15). Re-check with the palette validator
     before touching any value here. */
  --s1: #2a78d6;
  --s2: #eb6834;
  --s3: #1baf7a;
  --s4: #8c399e;
  /* Not part of the categorical theme — no chart may reach for these. Kept
     because the accent's distance note below is measured against --s5. */
  --s5: #e87ba4;
  --s6: #008300;

  /* diverging poles — polarity around 50% */
  --pos: #2a78d6;
  --neg: #e34948;
  --mid: #f0efec;
  /* The same two poles as channel triples, so a heatmap can vary alpha over
     the page surface instead of introducing a second, unvalidated ramp. */
  --pos-rgb: 42, 120, 214;
  --neg-rgb: 227, 73, 72;

  --good: #0ca30c;
  --good-ink: #006300;
  --warn: #fab219;
  /* The readable step of --neg, for the same reason --good-ink exists: --neg on
     a pale --neg-tinted surface measures 3.30:1, and the one place that pairing
     occurs is the alarm chip — the state that must not be the weakest thing on
     screen. 8.31:1 light. Dark needs no separate step (--neg measures 4.63). */
  --neg-ink: #a81715;

  --shadow: 0 1px 2px rgba(11, 11, 11, .04), 0 4px 16px rgba(11, 11, 11, .05);
  --r: 12px;
  --r-sm: 8px;

  /* Header height. Exactly two rules depend on it — the bar's own height and
     the sticky offset of the filter row below it — and when those two drift
     apart the filters either float over the page or hide under the header. So
     it is one number, not two. */
  --head-h: 58px;

  --t-normal-b: #efeee6; --t-normal-f: #6c6b4e;
  --t-fire-b: #fae8db; --t-fire-f: #9d5520;
  --t-water-b: #e4ebf9; --t-water-f: #4768ad;
  --t-electric-b: #fbf5da; --t-electric-f: #806c17;
  --t-grass-b: #e7f4df; --t-grass-f: #49772e;
  --t-ice-b: #ecf6f5; --t-ice-f: #517574;
  --t-fighting-b: #f3dbd9; --t-fighting-f: #ba2c26;
  --t-poison-b: #eedeed; --t-poison-f: #9c3c9b;
  --t-ground-b: #f8f2e3; --t-ground-f: #7f6b39;
  --t-flying-b: #efebfa; --t-flying-f: #705ea0;
  --t-psychic-b: #fce1e8; --t-psychic-f: #b33d61;
  --t-bug-b: #eef1d7; --t-bug-f: #677310;
  --t-rock-b: #f1eddb; --t-rock-f: #786a24;
  --t-ghost-b: #e6e2eb; --t-ghost-f: #735797;
  --t-dragon-b: #e5dcfb; --t-dragon-f: #6d34f7;
  --t-dark-b: #e6e2de; --t-dark-f: #705746;
  --t-steel-b: #f1f1f4; --t-steel-f: #6a6a77;
  --t-fairy-b: #f6e9ef; --t-fairy-f: #925a76;
}

@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) { color-scheme: dark;
    --surface-1: #1a1a19; --plane: #0d0d0d; --sunken: #232321;
    --ink: #ffffff; --ink-2: #c3c2b7; --ink-3: #898781;
    --line: #2c2c2a; --axis: #383835; --ring: rgba(255,255,255,0.10);
    --seq-100: #16324f; --seq-200: #1c5cab;
    --seq-300: #256abf; --seq-450: #3987e5; --seq-600: #86b6ef;
    --accent: #f08fb4; --accent-soft: #3d1f2d;
    /* 7.89 on #3d1f2d and 8.47 on #232321 — see the light block. */
    --badge-hi: #f5a8c6; --badge-lo: #9d9b94;
    --s1: #3987e5; --s2: #d95926; --s3: #199e70;
    --s4: #9846ab; --s5: #d55181; --s6: #008300;
    --pos: #3987e5; --neg: #e66767; --mid: #383835;
  --pos-rgb: 57, 135, 229; --neg-rgb: 230, 103, 103;
    --good: #0ca30c; --good-ink: #0ca30c; --warn: #fab219;
  --neg-ink: #e66767;
    --shadow: 0 1px 2px rgba(0,0,0,.3), 0 4px 16px rgba(0,0,0,.35);
    --t-normal-b: #39392e; --t-normal-f: #a8a77a;
    --t-fire-b: #49311e; --t-fire-f: #ee8130;
    --t-water-b: #2a3448; --t-water-f: #739bf2;
    --t-electric-b: #4b421d; --t-electric-f: #f7d02c;
    --t-grass-b: #2f4024; --t-grass-f: #7ac74c;
    --t-ice-b: #354443; --t-ice-f: #96d9d6;
    --t-fighting-b: #3f1e1c; --t-fighting-f: #d6716d;
    --t-poison-b: #382237; --t-poison-f: #bf78bd;
    --t-ground-b: #463e2a; --t-ground-f: #e2bf65;
    --t-flying-b: #393449; --t-flying-f: #ab91f3;
    --t-psychic-b: #4b2731; --t-psychic-f: #fa6693;
    --t-bug-b: #393d19; --t-bug-f: #a6b91a;
    --t-rock-b: #3c381f; --t-rock-f: #b6a136;
    --t-ghost-b: #2e2735; --t-ghost-f: #9d89b6;
    --t-dragon-b: #2d204b; --t-dragon-f: #9d76fd;
    --t-dark-b: #2d2723; --t-dark-f: #9e8d81;
    --t-steel-b: #3d3d41; --t-steel-f: #b7b7ce;
    --t-fairy-b: #43323a; --t-fairy-f: #d787af;
  }
}
:root[data-theme="dark"] { color-scheme: dark;
  --surface-1: #1a1a19; --plane: #0d0d0d; --sunken: #232321;
  --ink: #ffffff; --ink-2: #c3c2b7; --ink-3: #898781;
  --line: #2c2c2a; --axis: #383835; --ring: rgba(255,255,255,0.10);
  --seq-100: #16324f; --seq-200: #1c5cab; --seq-300: #256abf;
  --seq-450: #3987e5; --seq-600: #86b6ef;
  --accent: #f08fb4; --accent-soft: #3d1f2d;
  /* 7.89 on #3d1f2d and 8.47 on #232321 — see the light block. */
  --badge-hi: #f5a8c6; --badge-lo: #9d9b94;
  --s1: #3987e5; --s2: #d95926; --s3: #199e70;
  --s4: #9846ab; --s5: #d55181; --s6: #008300;
  --pos: #3987e5; --neg: #e66767; --mid: #383835;
  --pos-rgb: 57, 135, 229; --neg-rgb: 230, 103, 103;
  --good: #0ca30c; --good-ink: #0ca30c; --warn: #fab219;
  --neg-ink: #e66767;
  --shadow: 0 1px 2px rgba(0,0,0,.3), 0 4px 16px rgba(0,0,0,.35);
    --t-normal-b: #39392e; --t-normal-f: #a8a77a;
    --t-fire-b: #49311e; --t-fire-f: #ee8130;
    --t-water-b: #2a3448; --t-water-f: #739bf2;
    --t-electric-b: #4b421d; --t-electric-f: #f7d02c;
    --t-grass-b: #2f4024; --t-grass-f: #7ac74c;
    --t-ice-b: #354443; --t-ice-f: #96d9d6;
    --t-fighting-b: #3f1e1c; --t-fighting-f: #d6716d;
    --t-poison-b: #382237; --t-poison-f: #bf78bd;
    --t-ground-b: #463e2a; --t-ground-f: #e2bf65;
    --t-flying-b: #393449; --t-flying-f: #ab91f3;
    --t-psychic-b: #4b2731; --t-psychic-f: #fa6693;
    --t-bug-b: #393d19; --t-bug-f: #a6b91a;
    --t-rock-b: #3c381f; --t-rock-f: #b6a136;
    --t-ghost-b: #2e2735; --t-ghost-f: #9d89b6;
    --t-dragon-b: #2d204b; --t-dragon-f: #9d76fd;
    --t-dark-b: #2d2723; --t-dark-f: #9e8d81;
    --t-steel-b: #3d3d41; --t-steel-f: #b7b7ce;
    --t-fairy-b: #43323a; --t-fairy-f: #d787af;
}

/* --------------------------------------------------------------- base --- */

* { box-sizing: border-box; }

/* The UA rule for [hidden] is `display: none` at the weakest specificity, so
   any class here that sets a display wins over it and the element stays on
   screen. Something marked hidden is hidden. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--plane);
  color: var(--ink);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
  font-synthesis-weight: none;
}
a { color: inherit; text-decoration: none; }
img { max-width: 100%; }
button { font: inherit; color: inherit; }
h1, h2, h3 { margin: 0; font-weight: 650; letter-spacing: -0.011em; }
h1 { font-size: 1.6rem; }
h2 { font-size: 1.05rem; }
h3 { font-size: .82rem; text-transform: uppercase; letter-spacing: .06em;
     color: var(--ink-3); font-weight: 650; }
.muted { color: var(--ink-3); }
.dim { color: var(--ink-2); }
.num { font-variant-numeric: tabular-nums; }
.wrap { max-width: 1180px; margin: 0 auto; padding: 0 20px; }

/* ------------------------------------------------------------- header --- */

.top {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--surface-1) 88%, transparent);
  backdrop-filter: saturate(1.6) blur(12px);
  border-bottom: 1px solid var(--line);
}
.top .wrap { display: flex; align-items: center; gap: 18px;
             height: var(--head-h); }
/* The name is "Hanami VGC Analytics" and the wordmark says all of it, but not
   at one weight: "Hanami" is the name and takes the accent, the rest is the
   description and steps back to --ink-2. Nineteen characters instead of
   thirteen — the nav below is the part that gives ground for it. */
.brand { display: flex; align-items: baseline; gap: 6px; font-weight: 550;
         letter-spacing: -.01em; font-size: .86rem; white-space: nowrap;
         color: var(--ink-2); }
.brand em { font-style: normal; font-weight: 750; font-size: 1.06rem;
            letter-spacing: -.02em; color: var(--accent); }
/* The nav is the only part of the header allowed to give ground. Twelve links
   is more than fits between the brand and the theme button at 1060px, and a
   flex row whose items all refuse to shrink pushes the last one — the theme
   button — off the right edge of the document, which is where the site's
   stray horizontal scrollbar came from. `min-width: 0` lets it shrink; the
   overflow makes the shrinking scroll the links instead of clipping them. */
.nav { display: flex; gap: 2px; margin-left: 6px;
       min-width: 0; overflow-x: auto; scrollbar-width: none; }
.nav::-webkit-scrollbar { display: none; }
.nav a { flex: 0 0 auto; }
/* 9px, not 11px. The longer wordmark took 41px off the nav's box and pushed it
   into its own scroller at *full* width, which is the one width where nothing
   should have to scroll. Four pixels a link across thirteen links buys 52px
   back — more than the name cost. */
.nav a {
  padding: 7px 9px; border-radius: var(--r-sm); font-size: .88rem;
  font-weight: 550; color: var(--ink-2);
}
.nav a:hover { background: var(--sunken); color: var(--ink); }
.nav a[aria-current] { background: var(--accent-soft); color: var(--accent); }
.top .spacer { flex: 1; }

.icon-btn {
  width: 34px; height: 34px; flex: 0 0 auto;
  display: grid; place-items: center;
  border: 1px solid var(--line); background: var(--surface-1);
  border-radius: var(--r-sm); cursor: pointer; color: var(--ink-2);
}
.icon-btn:hover { color: var(--ink); border-color: var(--axis); }

/* Between 700px — where the nav is dropped entirely — and 1180px, where .wrap
   stops growing, twelve links are wider than the gap between the brand and the
   theme button. Tighten them across that band so the row fits; the scroll on
   .nav is only the last resort behind this. */
@media (max-width: 1180px) {
  .top .wrap { gap: 12px; }
  .nav a { padding: 7px 8px; font-size: .84rem; }
}

/* --------------------------------------------------------- filter bar --- */

.filters {
  position: sticky; top: var(--head-h); z-index: 30;
  background: var(--plane);
  border-bottom: 1px solid var(--line);
}
.filters .wrap { display: flex; flex-wrap: wrap; align-items: center;
                 gap: 10px; padding-top: 12px; padding-bottom: 12px; }

.seg { display: inline-flex; background: var(--sunken); padding: 3px;
       border-radius: 10px; gap: 2px; }
.seg button {
  border: 0; background: transparent; cursor: pointer; color: var(--ink-2);
  padding: 6px 12px; border-radius: 7px; font-size: .84rem; font-weight: 550;
  white-space: nowrap;
}
.seg button:hover { color: var(--ink); }
.seg button[aria-pressed="true"] {
  background: var(--surface-1); color: var(--ink);
  box-shadow: 0 1px 2px rgba(0,0,0,.08); font-weight: 620;
}
.seg-label { font-size: .74rem; text-transform: uppercase; letter-spacing: .06em;
             color: var(--ink-3); font-weight: 650; margin-right: 2px; }

/* Freshness chip. Five states, and only three of them are a problem — a closed
   regulation is complete, not stale, so it reads as success. Colour is never
   the only signal: every state carries its own glyph and its own words, which
   is also what keeps it legible in forced-colors and to a CVD reader. */
.fresh {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  padding: 5px 10px; border-radius: 999px; font-size: .76rem; font-weight: 600;
  text-decoration: none; border: 1px solid var(--line);
  background: var(--sunken); color: var(--ink-2);
}
.fresh:hover { border-color: var(--ink-3); color: var(--ink); }
.fresh .fdot { font-size: .7em; line-height: 1; }
.fresh.live .fdot { color: var(--good-ink); }
.fresh.lagging {
  background: color-mix(in srgb, var(--warn) 14%, var(--surface-1));
  border-color: color-mix(in srgb, var(--warn) 45%, var(--line));
}
.fresh.stale, .fresh.unknown, .fresh.frozen {
  background: color-mix(in srgb, var(--neg) 12%, var(--surface-1));
  border-color: color-mix(in srgb, var(--neg) 45%, var(--line));
  color: var(--ink);
}
.fresh.stale .fdot, .fresh.unknown .fdot, .fresh.frozen .fdot {
  font-size: .82em; font-weight: 800; color: var(--neg-ink);
}
.fresh.lagging .fdot { font-size: .82em; font-weight: 800; }
/* In a card header the chip is a status badge, not a control: pushed right,
   and not a link, so it gets no hover affordance. */
.card > header .fresh { margin-left: auto; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
        font-size: .84em; }

.search { position: relative; flex: 1; min-width: 190px; max-width: 340px; }
.search input {
  width: 100%; padding: 9px 12px 9px 34px; font: inherit; font-size: .9rem;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface-1); color: var(--ink);
}
.search input:focus { outline: 2px solid var(--accent); outline-offset: -1px;
                      border-color: transparent; }
.search svg { position: absolute; left: 11px; top: 50%; translate: 0 -50%;
              color: var(--ink-3); pointer-events: none; }

/* ---------------------------------------------------------- structure --- */

main { padding: 24px 0 72px; }
.page-head { display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap;
             margin-bottom: 16px; }
.page-head p { margin: 4px 0 0; color: var(--ink-3); font-size: .88rem; }

/* The tournament section's tab strip. It exists because that section is five
   pages and the header row is full: measured at full width the thirteen nav
   links need exactly the 858px they are given, so a fourteenth breaks the
   header. A second level inside the section is the better shape anyway — these
   pages share one population and one set of caveats, and the strip is what
   holds them together.

   The accent does chrome work here, which is the rule the rebrand turns on: it
   marks which tab you are on and never encodes a quantity. */
/* overflow-y is pinned rather than left alone: a box with `overflow-x: auto`
   computes overflow-y to `auto` too, and the underline below is enough to
   overflow it by a pixel — which Windows answers with a full vertical
   scrollbar sitting in the corner of the tab strip. */
.subnav {
  display: flex; gap: 2px; margin-bottom: 18px;
  overflow-x: auto; overflow-y: hidden;
  border-bottom: 1px solid var(--line);
}
.subnav a {
  padding: 9px 13px 11px; font-size: .88rem; font-weight: 550;
  color: var(--ink-2); text-decoration: none; white-space: nowrap;
}
.subnav a:hover { color: var(--ink); background: var(--sunken);
                  border-radius: var(--r-sm) var(--r-sm) 0 0; }
/* The underline is an inset shadow rather than a border, so it draws inside the
   tab's own box and needs no negative margin to sit on the strip's rule.
   Never colour alone: the current tab is heavier and underlined as well as
   tinted, so it survives a CVD reader and forced-colors. */
.subnav a[aria-current="page"] {
  color: var(--accent); font-weight: 680;
  box-shadow: inset 0 -2px 0 var(--accent);
}

/* Both strips scroll sideways when they do not fit, and a scroller with no
   visible edge is a scroller nobody scrolls. Measured at max-content: the
   ladder strip is 617px across nine tabs, tournaments 366 and Champions 332,
   against roughly 343px of usable width on a 375px phone - so the ladder
   always overflows there and the other two sit right on the line.

   The fade is a mask rather than an overlay because an overlay has to know the
   colour behind it, and these strips sit on two different surfaces in two
   themes. The classes are set from JS against real scroll positions, so the
   fade appears only on the side that actually has more to show. */
.nav.more, .subnav.more {
  mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
}
.nav.less, .subnav.less {
  mask-image: linear-gradient(to right, transparent, #000 24px);
}
.nav.more.less, .subnav.more.less {
  mask-image: linear-gradient(to right, transparent, #000 24px,
                              #000 calc(100% - 24px), transparent);
}

/* An event row navigates; a standings or archetype row does not. The table
   rule sets a pointer on every row, so the ones that go nowhere say so. */
.tbl tbody tr.flat { cursor: default; }
/* A six inside a table cell stays a six. `.team` wraps by design where it sits
   in a card at full width, but in a column it wrapped 5 + 1 and turned a
   registered team into two rows of sprites that read as two groups. The cell
   is given the width the six actually need instead. `width: max-content` is
   load-bearing: a nowrap flex row of sprites reports a min-content width of
   one sprite, so the cell sized itself to 58px and the six spilled across the
   next column. Stating max-content makes that the cell's minimum, and the 1%
   then means "no wider than the six" rather than "as narrow as possible". */
.tbl td .team { flex-wrap: nowrap; width: max-content; }
.tbl td .team > * { flex: none; }
.tbl td:has(> .team) { width: 1%; }
.nowrap { white-space: nowrap; }
.evname { color: var(--ink); text-decoration: none; font-weight: 560; }
.evname:hover { color: var(--accent); }
/* A country code, not a flag: Windows renders regional-indicator pairs as the
   two letters anyway, so the letters are what everyone actually sees. */
.cc { margin-left: 7px; font-size: .7rem; font-weight: 650; letter-spacing: .04em;
      color: var(--ink-3); font-variant-numeric: tabular-nums; }

.card {
  background: var(--surface-1); border: 1px solid var(--line);
  border-radius: var(--r); box-shadow: var(--shadow);
}
/* Deliberately NOT `overflow: hidden`: that makes the card a scroll container,
   which becomes the containing block for the sticky <thead> and pins it to the
   card's top edge instead of the viewport's. Round the last row by hand
   instead — the only thing that actually needed clipping. */
.card > :last-child { border-radius: 0 0 var(--r) var(--r); }
.tbl tbody tr:last-child td:first-child { border-bottom-left-radius: 11px; }
.tbl tbody tr:last-child td:last-child { border-bottom-right-radius: 11px; }
.card > header {
  border-radius: var(--r) var(--r) 0 0;
  display: flex; align-items: center; gap: 10px;
  padding: 13px 16px; border-bottom: 1px solid var(--line);
}
.card > header p { margin: 0; font-size: .76rem; color: var(--ink-3); }
.card .body { padding: 14px 16px; }
.grid { display: grid; gap: 16px; }
/* Grid items default to `min-width: auto`, which is their min-content size -
   so a card holding a wide table refuses to shrink and pushes the whole grid,
   and with it the page, wider than the screen. The `.scroll-x` inside is then
   never narrower than its table and never scrolls, because it is never asked
   to; the overflow lands on the document instead.

   Measured on a 375px phone before this rule: the calculator's threat table
   needs 772px of minimum content, which set `.calc-out`'s only column to 774
   and gave the page 419px of horizontal overflow. Allowed to shrink, the table
   scrolls inside its own card and the page does not move. Every other page
   escaped this only because its widest table happened to fit.

   Deliberately stopped at 1180, where `.scroll-x` turns `overflow-x: visible`
   to buy sticky table headers (see the note above that breakpoint). There a
   card that can shrink is a card a wide table spills out of, through a border
   `.card` cannot clip - so above 1180 the items keep their content minimum and
   the grid widens instead, which is what makes the no-scrollport trade safe. */
@media (max-width: 1179px) {
  .grid > *, .calc-out > * { min-width: 0; }
}

@media (min-width: 860px) { .cols-2 { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1000px) { .cols-3 { grid-template-columns: repeat(3, 1fr); } }

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

.tbl { width: 100%; border-collapse: collapse; font-size: .88rem; }
.tbl th {
  background: var(--surface-1); text-align: right; font-weight: 600;
  font-size: .74rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-3); padding: 10px 12px; white-space: nowrap;
  border-bottom: 1px solid var(--line);
}
.tbl th:first-child, .tbl th.l { text-align: left; }
.tbl th[data-sort] { cursor: pointer; user-select: none; }
.tbl th[data-sort]:hover { color: var(--ink); }
.tbl th[aria-sort] { color: var(--accent); }
.tbl th .caret { opacity: 0; margin-left: 3px; }
.tbl th[aria-sort] .caret { opacity: 1; }
.tbl td { padding: 7px 12px; text-align: right; border-bottom: 1px solid var(--line);
          font-variant-numeric: tabular-nums; }
.tbl td:first-child, .tbl td.l { text-align: left; font-variant-numeric: normal; }
.tbl tbody tr { cursor: pointer; }
.tbl tbody tr:hover { background: var(--sunken); }
.tbl tbody tr:last-child td { border-bottom: 0; }
.rank { color: var(--ink-3); font-size: .8rem; width: 1%; }
.scroll-x { overflow-x: auto; }
/* A horizontal scrollport would become the containing block for a sticky thead
   and pin it to the wrong edge, so the page-level sticky header is bought by
   giving up the x-scroll. That trade is only safe while the widest table on the
   page actually FITS: with no scrollport and no way to shrink, a table wider
   than its card does not scroll, it spills out through the card's border, and
   .card cannot clip it (see the note above `.card > :last-child`).

   The widest table is the usage board at ~970px of minimum content. 1180px is
   `.wrap`'s own max-width, so at this breakpoint the content box is a full
   1140px and the fit is guaranteed rather than estimated. Below it, every table
   scrolls instead - which costs nothing when the table already fits, because no
   scrollbar appears. Do not lower this number to win back sticky headers on
   narrower screens without first re-measuring the board.

   The board used to need a `.tbl.board` block tightening its cells to 10px and
   its tracks to 56px, because it carried five bar columns. Three of those bars
   encoded nothing - Bring and Lead sat in narrow mid-track bands and Mega drew
   43 values into six pixels - so they became figures instead, which returned
   enough width that the board now fits at the site's ordinary density with
   ~170px to spare. If a column is ever added back, budget it there first. */
@media (min-width: 1180px) {
  .scroll-x { overflow-x: visible; }
  .tbl thead th { position: sticky; top: 115px; z-index: 10; }
}

/* ------------------------------------------------------------ species --- */

.mon { display: flex; align-items: center; gap: 9px; min-width: 0; }
.mon img { width: 40px; height: 40px; flex: none; object-fit: contain;
           image-rendering: -webkit-optimize-contrast; }
.mon .nm { font-weight: 570; white-space: nowrap; overflow: hidden;
           text-overflow: ellipsis; }
.mon.sm img { width: 28px; height: 28px; }
.mon.sm .nm { font-weight: 500; font-size: .86rem; }

.types { display: flex; gap: 4px; flex-wrap: wrap; }
.type {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  font-size: .69rem; font-weight: 650; letter-spacing: .03em;
  text-transform: uppercase; white-space: nowrap;
}
.type[data-t="normal"] { background: var(--t-normal-b); color: var(--t-normal-f); }
.type[data-t="fire"] { background: var(--t-fire-b); color: var(--t-fire-f); }
.type[data-t="water"] { background: var(--t-water-b); color: var(--t-water-f); }
.type[data-t="electric"] { background: var(--t-electric-b); color: var(--t-electric-f); }
.type[data-t="grass"] { background: var(--t-grass-b); color: var(--t-grass-f); }
.type[data-t="ice"] { background: var(--t-ice-b); color: var(--t-ice-f); }
.type[data-t="fighting"] { background: var(--t-fighting-b); color: var(--t-fighting-f); }
.type[data-t="poison"] { background: var(--t-poison-b); color: var(--t-poison-f); }
.type[data-t="ground"] { background: var(--t-ground-b); color: var(--t-ground-f); }
.type[data-t="flying"] { background: var(--t-flying-b); color: var(--t-flying-f); }
.type[data-t="psychic"] { background: var(--t-psychic-b); color: var(--t-psychic-f); }
.type[data-t="bug"] { background: var(--t-bug-b); color: var(--t-bug-f); }
.type[data-t="rock"] { background: var(--t-rock-b); color: var(--t-rock-f); }
.type[data-t="ghost"] { background: var(--t-ghost-b); color: var(--t-ghost-f); }
.type[data-t="dragon"] { background: var(--t-dragon-b); color: var(--t-dragon-f); }
.type[data-t="dark"] { background: var(--t-dark-b); color: var(--t-dark-f); }
.type[data-t="steel"] { background: var(--t-steel-b); color: var(--t-steel-f); }
.type[data-t="fairy"] { background: var(--t-fairy-b); color: var(--t-fairy-f); }

/* ---------------------------------------------------------------- bars --- */

/* Sequential magnitude: one hue, grows from a single baseline.
   4px rounded data-end, square at the baseline. */
.bar { display: flex; align-items: center; gap: 8px; justify-content: flex-end; }
/* 96px, not 76, and the number is the board's leftover width rather than a
   taste. With the tracks at 76/64 the board's min-content is 1054px inside a
   1138px column, so 84px was going spare as cell padding - width the bars had
   already been denied. 96/80 spends 52 of it and leaves min-content at 1106,
   a 32px margin kept deliberately for platforms whose font metrics run wider.
   110/92 is the first pair that overflows, and above 1180 the board does not
   scroll, it spills - so re-measure before raising these. */
.bar .track {
  position: relative; height: 8px; width: 96px; flex: none;
  background: var(--sunken); border-radius: 4px; overflow: hidden;
}
.bar .fill {
  position: absolute; inset: 0 auto 0 0; background: var(--seq-450);
  border-radius: 0 4px 4px 0;
}
.bar .v { min-width: 46px; text-align: right; font-variant-numeric: tabular-nums; }

/* Diverging: win% is polarity around 50 — blue above, red below,
   neutral gray midpoint, equal arms. */
.wr { display: flex; align-items: center; gap: 8px; justify-content: flex-end; }
/* 80px against the sequential bar's 96 - the arms are half-tracks, so an
   80px diverging track draws 40px per side. See the budget note above. */
.wr .track { position: relative; height: 8px; width: 80px; flex: none;
             background: var(--mid); border-radius: 4px; }
.wr .track::before { content: ""; position: absolute; left: 50%; top: -2px;
                     bottom: -2px; width: 1px; background: var(--axis); }
.wr .fill { position: absolute; top: 0; bottom: 0; border-radius: 2px; }
.wr .v { min-width: 46px; text-align: right; font-variant-numeric: tabular-nums; }
.wr.up .v { color: var(--ink); font-weight: 600; }

/* --------------------------------------------------------- stat tiles --- */

.tiles { display: grid; gap: 10px;
         grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); }
.tile {
  background: var(--surface-1); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: 12px 14px;
}
.tile .label { font-size: .74rem; color: var(--ink-3); font-weight: 550; }
.tile .value { font-size: 1.5rem; font-weight: 650; letter-spacing: -.02em;
               margin-top: 2px; line-height: 1.15; }
.tile .sub { font-size: .74rem; color: var(--ink-3);
             font-variant-numeric: tabular-nums; }
.hero { font-size: 3rem; font-weight: 680; letter-spacing: -.03em; line-height: 1; }

/* --------------------------------------------------------- detail page --- */

.mon-hero { display: flex; gap: 22px; align-items: center; flex-wrap: wrap; }
.mon-hero img { width: 132px; height: 132px; object-fit: contain; flex: none; }
.mon-hero .meta { min-width: 200px; }
.mon-hero .statbars { flex: 1; min-width: 240px; max-width: 420px; }
.dexno { font-size: .78rem; color: var(--ink-3); font-variant-numeric: tabular-nums; }

.statbars { display: grid; gap: 6px; }
.statbar { display: grid; grid-template-columns: 34px 34px 1fr;
           align-items: center; gap: 10px; font-size: .8rem; }
.statbar .k { color: var(--ink-3); font-weight: 600; text-transform: uppercase;
              font-size: .7rem; letter-spacing: .04em; }
.statbar .n { text-align: right; font-variant-numeric: tabular-nums;
              font-weight: 600; }
.statbar .track { height: 8px; background: var(--sunken); border-radius: 4px;
                  overflow: hidden; }
.statbar .fill { height: 100%; border-radius: 0 4px 4px 0;
                 background: var(--seq-450); }

/* A query container, so the rows inside can size their bars to the panel they
   landed in rather than to the viewport. The same `.rows` is used at 528px in
   a half-width card and at 1106px full-width, and a media query cannot tell
   those apart. */
.rows { display: grid; container-type: inline-size; }
.row {
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  gap: 10px; padding: 7px 0; border-bottom: 1px solid var(--line);
  font-size: .87rem;
}
.row:last-child { border-bottom: 0; }
.row .k { min-width: 0; overflow: hidden; text-overflow: ellipsis;
          white-space: nowrap; }
.row .pct { display: flex; align-items: center; gap: 8px; }
/* The starting 56px is the narrow case, not the intended one. In a 528px card
   row it left the bar drawing 10% of the width while the key column was handed
   ~400px to render a name whose widest measured need is 157px - so two values a
   point apart were separated by well under a pixel of bar. The steps below hand
   that unused width to the encoding.

   Only the track grows; the `1fr auto` split is untouched. That matters because
   `.row` is also the shape of the tournament list, whose keys are event names
   needing 700px+ and which carry no track at all - those rows keep every pixel
   they have today. Capped at 132 so a full-width panel does not draw a 300px
   hairline; past that the bar stops gaining resolution and starts looking like
   a rule. */
.row .track { position: relative; height: 6px; width: 56px; flex: none;
              background: var(--sunken); border-radius: 3px; overflow: hidden; }
@container (min-width: 380px) { .row .track { width: 96px; } }
@container (min-width: 500px) { .row .track { width: 132px; } }
.row .fill { position: absolute; inset: 0 auto 0 0; background: var(--seq-300);
             border-radius: 0 3px 3px 0; }
.row .v { min-width: 44px; text-align: right; font-variant-numeric: tabular-nums;
          color: var(--ink-2); }

.lift { font-size: .7rem; font-weight: 650; padding: 1px 5px; border-radius: 4px;
        font-variant-numeric: tabular-nums; }
.lift.hi { background: var(--accent-soft); color: var(--badge-hi); }
.lift.lo { background: var(--sunken); color: var(--badge-lo); }

/* ---------------------------------------------------------- item tells --- */

/* The badge is accent/neutral rather than the pos/neg pair the win rates and
   matchup deltas use. Those two really are good and bad; a move showing up less
   often behind an item is not worse, it is just less of a tell, and painting it
   red would put a verdict on a direction that has none. */
.tell { min-width: 0; }
.tell-h {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; padding-bottom: 5px; margin-bottom: 2px;
  border-bottom: 1px solid var(--line);
}
.tell-h strong { font-size: .88rem; }
.tell-h .dim { font-size: .75rem; color: var(--ink-3); white-space: nowrap;
               font-variant-numeric: tabular-nums; }

/* -------------------------------------------------------------- teams --- */

.team { display: flex; gap: 2px; flex-wrap: wrap; }
.team img { width: 42px; height: 42px; object-fit: contain; }
.team a { border-radius: 6px; display: block; line-height: 0; }
.team a:hover { background: var(--sunken); }

/* ------------------------------------------------------------ players --- */

/* A player name is arbitrary user text — it can be forty characters of no
   spaces — so it gets a width to live in and breaks inside a word rather than
   pushing the table sideways. */
.pname { font-weight: 570; overflow-wrap: anywhere; display: inline-block;
         max-width: 18ch; }

/* Sprite over its share, because the share is the point: three species with
   78% / 41% / 33% is a signature and three with 24% / 21% / 19% is not, and
   the reader can only tell those apart if the number is on the page. */
.sig { display: flex; gap: 4px; }
.sig a { display: flex; flex-direction: column; align-items: center; gap: 1px;
         border-radius: 6px; padding: 2px 3px; }
.sig a:hover { background: var(--sunken); }
.sig img { width: 34px; height: 34px; object-fit: contain; display: block; }
.sig .v { font-size: .68rem; color: var(--ink-3); line-height: 1;
          font-variant-numeric: tabular-nums; }

/* -------------------------------------------------------- tournaments --- */

/* An event winner is a caption plus a team, and the team is the part being
   compared down the list — so the caption sits above it at full width rather
   than beside it in a column that would squeeze six sprites into four. */
.winner { padding: 10px 0; border-bottom: 1px solid var(--line); }
.winner:last-child { border-bottom: 0; }
.wmeta { display: flex; align-items: baseline; justify-content: space-between;
         gap: 12px; margin-bottom: 6px; flex-wrap: wrap; }
.wev { font-size: .84rem; font-weight: 600; min-width: 0;
       overflow-wrap: anywhere; }
.wmeta .dim { font-size: .75rem; color: var(--ink-3); white-space: nowrap;
              font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------- misc --- */

.chips { display: flex; gap: 6px; flex-wrap: wrap; }
.chip { font-size: .74rem; padding: 3px 9px; border-radius: 999px;
        background: var(--sunken); color: var(--ink-2); font-weight: 550; }
.chip.on { background: var(--accent-soft); color: var(--accent); }
.chip.btn { cursor: pointer; border: 0; }

/* Names the group of ability chips that follows it, so they are not misread
   as more legality badges. Recessive on purpose — it is a label, not data. */
.chip-label {
  font-size: .64rem; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 650; align-self: center;
  margin-left: 6px; cursor: help;
}

/* Known Abilities. A divider-ruled list, the same shape as every other panel
   on the page — `.rows`/`.row` in the markup's place, with the name and its
   declared share on one line and the description under it.
 *
 * This was a grid of bordered tiles, and boxes-inside-a-box is a distinction
 * the content does not have: the card already says these belong together, and
 * drawing a second frame around each one only competed with the card's own.
 * A hairline is what the rest of the site uses to say "next item", so that is
 * what this uses. Vertical padding is a little over `.row`'s 7px because these
 * entries are two lines, not one.
 *
 * The one thing the tiles were right about: at full card width a description
 * would run a 1100px measure, which is unreadable. The row spans the card, the
 * prose inside it does not. */
.abil-list { display: grid; }
.abil { padding: 10px 0; border-bottom: 1px solid var(--line); }
.abil:first-child { padding-top: 2px; }
.abil:last-child { padding-bottom: 0; border-bottom: 0; }
.abil-head { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.abil-name { font-weight: 650; font-size: .92rem; }
.abil-desc {
  margin: 6px 0 0; font-size: .8rem; line-height: 1.55; color: var(--ink-2);
  max-width: 78ch;
}
/* A declared share, or the absence of one. The two must not be told apart by
   colour alone: "Not declared" says so in words, and drops its fill for a
   dashed outline so an absence of data looks like an absence. */
.abil-ev {
  font-size: .68rem; font-weight: 650; padding: 2px 8px; border-radius: 999px;
  margin-left: auto; white-space: nowrap; cursor: help;
  border: 1px solid transparent;
}
.abil-ev.yes {
  color: var(--good-ink);
  background: color-mix(in srgb, var(--good) 14%, var(--surface-1));
}
.abil-ev.blind {
  color: var(--badge-lo); background: none; border-color: var(--line);
  border-style: dashed;
}

/* Nature effect, beside the name in a ranked row. Arrows would be ambiguous at
   this size; the sign is spelled and the two halves are separately coloured. */
.nat-eff { display: inline-flex; gap: 5px; margin-left: 8px; font-size: .7rem;
  font-weight: 650; letter-spacing: .01em; }
.nat-eff .up { color: var(--good-ink); }
.nat-eff .dn { color: var(--badge-lo); }
.nat-eff.neutral { color: var(--ink-3); font-weight: 500; font-style: italic; }

.note {
  font-size: .78rem; color: var(--ink-3); line-height: 1.5;
  border-left: 2px solid var(--line); padding-left: 10px; margin: 10px 0 0;
}
.warn-note {
  font-size: .78rem; line-height: 1.5; margin: 0 0 10px;
  padding: 8px 10px; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--warn) 14%, var(--surface-1));
  color: var(--ink-2);
  border-left: 3px solid var(--warn);
}
.empty { padding: 44px 20px; text-align: center; color: var(--ink-3); }
.loading { padding: 60px 20px; text-align: center; color: var(--ink-3); }
.loading::after { content: ""; display: block; width: 22px; height: 22px;
  margin: 12px auto 0; border: 2px solid var(--line);
  border-top-color: var(--accent); border-radius: 50%;
  animation: spin .7s linear infinite; }
@keyframes spin { to { rotate: 360deg; } }
@media (prefers-reduced-motion: reduce) { * { animation: none !important; } }

footer { border-top: 1px solid var(--line); background: var(--surface-1);
         padding: 24px 0 40px; font-size: .8rem; color: var(--ink-3); }
footer p { margin: 0 0 8px; max-width: 70ch; line-height: 1.6; }

.dexgrid { display: grid; gap: 10px;
           grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); }
.dexcard {
  background: var(--surface-1); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: 10px; text-align: center;
  transition: border-color .12s, transform .12s;
  /* Dual-type chips wrap on narrow cards; pin the chip row to the bottom so
     one- and two-type cards still line up across a row. */
  display: flex; flex-direction: column;
}
.dexcard .types { margin-top: auto; }
.dexcard:hover { border-color: var(--accent); transform: translateY(-1px); }
.dexcard img { width: 72px; height: 72px; object-fit: contain; }
.dexcard .nm { font-size: .82rem; font-weight: 570; margin-top: 2px;
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dexcard .types { justify-content: center; margin-top: 5px; }

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

/* A label above its control. Smaller and quieter than .seg-label because there
   are dozens of them on this page and they must not compete with the numbers. */
.mini-label {
  display: block; font-size: .64rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-3); font-weight: 650;
  margin-bottom: 3px;
}

.calc-field-bar { display: flex; flex-wrap: wrap; gap: 12px 18px;
                  align-items: flex-end; }
/* .ctl is the same shape outside the calculator — a labelled control that
   fills whatever column it lands in. */
.calc-field, .ctl { display: block; min-width: 0; }
.calc-field select, .calc-field input,
.ctl select, .ctl input { width: 100%; }

.calc-mon .body { display: grid; gap: 12px; }
.calc-species { display: flex; align-items: center; gap: 12px; }
.calc-species img { width: 64px; height: 64px; flex: none; object-fit: contain; }
.calc-species .grow { flex: 1; min-width: 0; display: grid; gap: 6px; }

.calc-mega { display: flex; align-items: center; gap: 8px;
             background: var(--sunken); border-radius: var(--r-sm);
             padding: 7px 10px; }
.calc-mega .mini-label { margin: 0; cursor: help; }
.calc-mega select { flex: 1; min-width: 0; }

.calc-grid { display: grid; gap: 10px;
             grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); }

.calc-sub { display: flex; align-items: baseline; justify-content: space-between;
            gap: 8px; }
.calc-sub .mini-label { margin: 0; }
.ev-total { font-size: .72rem; color: var(--ink-3);
            font-variant-numeric: tabular-nums; }
.ev-total.over { color: var(--neg); font-weight: 650; cursor: help; }

.calc-stats { display: grid; grid-template-columns: repeat(6, 1fr); gap: 5px; }
.calc-stat { min-width: 0; }
.calc-stat .k { display: block; font-size: .62rem; letter-spacing: .04em;
                text-transform: uppercase; color: var(--ink-3);
                font-weight: 650; text-align: center; margin-bottom: 2px; }
.calc-stat input, .calc-stat select { width: 100%; text-align: center;
                                      padding-left: 2px; padding-right: 2px; }

/* Shared control skin. The native widgets are kept — they are the accessible,
   keyboard-navigable, mobile-native option — and only re-surfaced to match. */
.calc-mon select, .calc-mon input, .calc-field-bar select,
.calc-field-bar input, .calc-add input, .ctl select, .ctl input {
  font: inherit; font-size: .82rem; color: var(--ink);
  background: var(--surface-1); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: 6px 8px; min-width: 0;
}
.calc-mon select:focus, .calc-mon input:focus, .calc-field-bar select:focus,
.calc-field-bar input:focus, .calc-add input:focus, .ctl select:focus,
.ctl input:focus {
  outline: 2px solid var(--accent); outline-offset: -1px;
  border-color: transparent;
}
/* Chrome renders number spinners at a size that wrecks a 6-across stat row. */
.calc-stat input::-webkit-outer-spin-button,
.calc-stat input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.calc-stat input { -moz-appearance: textfield; }

.combo-wrap { display: block; min-width: 0; }
.combo { width: 100%; }

.calc-toggles { display: flex; flex-wrap: wrap; gap: 6px 14px; }
/* nowrap so the row breaks between checkboxes and never inside one. Without it
   "Choice Scarf" broke over two lines and its box, centred against the pair,
   read as an unlabelled checkbox followed by loose text. The parent already
   carries flex-wrap, so a chip that will not fit moves down whole. */
.chk { display: inline-flex; align-items: center; gap: 5px; font-size: .8rem;
       color: var(--ink-2); cursor: pointer; user-select: none;
       white-space: nowrap; }
.chk input { accent-color: var(--accent); margin: 0; }
.chk:hover { color: var(--ink); }

.linkish { background: none; border: 0; padding: 0; cursor: pointer;
           font-size: .72rem; color: var(--ink-3); font-weight: 600;
           text-align: left; justify-self: start; }
.linkish:hover { color: var(--accent); }

.calc-acts { display: flex; justify-content: center; flex-wrap: wrap;
             gap: 10px; margin: 14px 0; }
.calc-mon > header { display: flex; align-items: flex-start; gap: 12px; }
.calc-mon > header .grow { flex: 1; min-width: 0; }
.calc-mon > header .btn { flex: none; }

/* The two sides are stretched to the same height, and every control exists on
   both, so a row in one panel is the same row in the other. Comparing two
   Pokemon is the whole job of this page — a spread that sits one line lower
   than the spread it is being read against costs more than the space saved. */
.calc-pair { align-items: stretch; }
.calc-pair > .card { display: flex; flex-direction: column; }
.calc-pair > .card > .scroll-x { flex: 1; }
.calc-mon .body { align-content: start; }
.calc-mega select:disabled { color: var(--ink-3); cursor: not-allowed; }

.calc-tgroup { display: grid; gap: 5px; }
.calc-tgroup .mini-label { margin: 0; }

/* Nothing in the damage table navigates anywhere, so it must not claim to. */
.calc-results tbody tr { cursor: default; }
.calc-results td { vertical-align: middle; }
.calc-results .x {
  background: none; border: 0; cursor: pointer; color: var(--ink-3);
  font-size: 1rem; line-height: 1; padding: 0 4px; border-radius: 4px;
}
.calc-results .x:hover { color: var(--neg); background: var(--sunken); }
.calc-ko { font-size: .82rem; color: var(--ink-2); cursor: help; }

/* The roll band: the track is the defender's full HP, the fill spans the low
   roll to the high roll. Rounded on both ends, because both ends are data. */
.bar.dmg .track { width: 92px; }
.bar.dmg .fill { inset: 0 auto 0 auto; border-radius: 3px; }
.bar.dmg .v { min-width: 92px; }

.calc-add { display: flex; align-items: center; gap: 10px;
            border-top: 1px solid var(--line); }
.calc-add .mini-label { margin: 0; white-space: nowrap; }
.calc-add .combo-wrap { flex: 1; max-width: 280px; }
.calc-note { margin: 14px 0 0; font-size: .78rem; line-height: 1.55;
             max-width: 70ch; }

/* ---- metagame threats ---- */

.calc-threat > header { display: flex; align-items: flex-start;
                        flex-wrap: wrap; gap: 12px; }
.calc-threat > header .grow { flex: 1 1 240px; min-width: 0; }
.calc-threat > header .calc-field { flex: 0 0 auto; min-width: 150px; }
.threat-mon { display: inline-flex; align-items: center; gap: 8px;
              color: inherit; text-decoration: none; }
.threat-mon img { width: 32px; height: 32px; object-fit: contain; flex: none; }
.threat-mon:hover span { color: var(--accent); }
.threat-move { display: block; font-size: .68rem; color: var(--ink-3);
               margin-top: 2px; }
.trade { font-size: .78rem; font-weight: 600; white-space: nowrap; }
.trade.good { color: var(--pos); }
.trade.bad { color: var(--neg); }
.trade.even { color: var(--ink-3); }
.threat-note { padding-top: 12px; border-top: 1px solid var(--line);
               font-size: .75rem; line-height: 1.55; margin: 0; }

/* ---- speed ---- */

/* Two numbers and a sentence. The pair is laid out left/right to match the two
   panels above it, so the side that moves first is the side of the screen that
   lights up — the verdict is legible before the sentence is read. */
.calc-out { display: grid; gap: 16px; }
.calc-speed { margin-bottom: 0; }
.speed-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
             padding-bottom: 8px; }
.speed-side { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 8px;
              padding: 8px 12px; border-radius: 8px; background: var(--sunken);
              border: 1px solid transparent; }
.speed-side.first { border-color: var(--accent); background: var(--accent-soft,
                    var(--sunken)); }
.speed-side .n { font-size: 1.35rem; font-weight: 700; color: var(--ink); }
.speed-side .who { font-size: .84rem; color: var(--ink-2); min-width: 0;
                   overflow-wrap: anywhere; }
.speed-side .tag { margin-left: auto; font-size: .66rem; font-weight: 700;
                   letter-spacing: .05em; text-transform: uppercase;
                   color: var(--accent); }
.speed-verdict { margin: 0; padding: 0 16px 14px; font-size: .8rem;
                 color: var(--ink-2); }

/* ---- Showdown import ---- */

.calc-import { margin-bottom: 14px; }
.calc-import > header { display: flex; align-items: center; gap: 12px; }
.calc-import > header .grow { flex: 1; min-width: 0; }
.import-body { display: grid; gap: 12px; }

/* Monospaced, because export text is column-aligned by eye — a proportional
   font makes a correctly pasted spread look crooked. */
.calc-import textarea {
  width: 100%; resize: vertical; font-size: .78rem; line-height: 1.5;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--ink); background: var(--sunken);
  border: 1px solid var(--line); border-radius: var(--r-sm); padding: 10px 12px;
}
.calc-import textarea:focus { outline: 2px solid var(--accent);
                              outline-offset: -1px; border-color: transparent; }
.calc-import textarea::placeholder { color: var(--ink-3); }

/* The redraw target. Empty until something parses, and it must not leave the
   grid gap behind when it is. */
.import-out:empty { display: none; }

.import-sets { display: grid; gap: 8px; }
.import-set { display: flex; align-items: flex-start; gap: 12px;
              padding: 10px 12px; background: var(--sunken);
              border-radius: var(--r-sm); }
.import-set img { width: 44px; height: 44px; flex: none; object-fit: contain; }
.import-set .grow { flex: 1; min-width: 0; }
.import-set .t { font-size: .88rem; }
.import-set .sub { margin: 2px 0 0; font-size: .74rem; color: var(--ink-2); }

/* Corrections the parser made. Quiet by default — most pastes produce one or
   two, and they are read rather than scanned — and only a set that cannot be
   used at all turns red. */
.import-set .note { margin: 3px 0 0; font-size: .72rem; line-height: 1.45;
                    color: var(--ink-3); }
.import-set .note.bad { color: var(--neg); }

.import-acts { display: flex; flex-direction: column; gap: 6px; flex: none; }
.import-acts .chip { white-space: nowrap; }

/* ---- team report ---- */

/* Same monospace reasoning as the calculator's importer: a Showdown export is
   column-aligned by eye, and a proportional font makes a correct paste look
   crooked. Deliberately not the same selector — the two boxes sit on different
   pages and one is not the other's variant. */
.paste-box {
  width: 100%; resize: vertical; min-height: 180px;
  font-size: .78rem; line-height: 1.5;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--ink); background: var(--sunken);
  border: 1px solid var(--line); border-radius: var(--r-sm); padding: 10px 12px;
}
.paste-box:focus { outline: 2px solid var(--accent); outline-offset: -1px;
                   border-color: transparent; }
.paste-box::placeholder { color: var(--ink-3); }

.paste-bar { display: flex; align-items: center; gap: 8px; margin-top: 10px;
             flex-wrap: wrap; }
.btn {
  font-size: .8rem; font-weight: 600; padding: 7px 14px; cursor: pointer;
  border-radius: var(--r-sm); border: 1px solid var(--line);
  background: var(--surface-1); color: var(--ink-2);
}
.btn:hover { border-color: var(--ink-3); color: var(--ink); }
/* One action on this page is the action; the others are conveniences. It is a
   tinted button rather than a solid accent one because white on --accent
   measures 4.46:1 light and 3.67:1 dark, and 12.8px/600 is not large text by
   any definition that would excuse either. --badge-hi on --accent-soft is the
   pairing this file already measured for exactly this problem: 5.96:1. */
.btn.primary { background: var(--accent-soft); color: var(--badge-hi);
               border-color: color-mix(in srgb, var(--accent) 35%, var(--line)); }
.btn.primary:hover { border-color: var(--accent); color: var(--badge-hi); }

/* The generated build card.

   `.spec` is `.row` with a third column in front of it: the category the
   choice belongs to. Without it the reader gets "Black Glasses 36.8%" and has
   to infer that the first row is an item, the third a nature and the rest
   moves; the labels also let the move rows say whether their number is a rate
   or a floor, which is a distinction the file carries and the page must not
   flatten. Fixed at 74px rather than `auto` so the six labels do not shift the
   value column as their widths vary down the card. */
.rows.spec .row { grid-template-columns: 74px auto 1fr auto; row-gap: 1px; }
.rows.spec .sk { grid-column: 1; grid-row: 1; font-size: .72rem;
                 font-weight: 620; color: var(--ink-3); letter-spacing: .02em;
                 white-space: nowrap; }
.rows.spec .k { grid-column: 2; grid-row: 1; font-weight: 540; }
.rows.spec .v { grid-column: 4; grid-row: 1; min-width: 44px;
                text-align: right; font-variant-numeric: tabular-nums;
                color: var(--ink-2); }
/* Second line by default. The aside is "then Focus Sash 10.4%" or
   "priority +1" - real, but secondary - and on a phone there is not room for
   it and the name on one line without the name being the one that loses. */
.rows.spec .sub { grid-column: 2 / 5; grid-row: 2; font-size: .74rem;
                  color: var(--ink-3); }
/* Inline once the row is wide enough to hold both. 460 is where the widest
   measured pairing - a 144px move name beside a 119px "then <item> <pct>" -
   clears the 74px label and the 44px value with room left. A container query
   rather than a media query because this table also has to work inside a
   half-width card, which a viewport width cannot tell apart from a phone. */
@container (min-width: 460px) {
  /* min-width:0 because a grid item's automatic minimum is its min-content
     width, and with `justify-self: end` an aside wider than its track does not
     clip - it grows leftwards out of the track and prints on top of the value
     beside it. Measured at a 471px container: a 331px aside in a 250px track,
     starting at x=123 with the item name occupying 121-194. */
  .rows.spec .sub { grid-column: 3; grid-row: 1; justify-self: end;
                    min-width: 0; white-space: nowrap; overflow: hidden;
                    text-overflow: ellipsis; }
  /* Except in a registered set, where the aside is not a footnote. On the
     assembled build it is one runner-up - "then Focus Sash 10.4%", 119px - but
     a registered set lists every variant above 12%, and Espathra's Calm Mind
     carries three: "then Colbur Berry 25.0%, Mental Herb 25.0%, Kasib Berry
     12.5%" measures 331px and fits its track only past a 500px card. Clipped,
     what the ellipsis eats is the third variant, which is the answer and not a
     decoration; this table is three rows, so keeping the second line costs at
     most three of them and deletes nothing. */
  .rows.spec.vars .sub { grid-column: 2 / 5; grid-row: 2; justify-self: start;
                         white-space: normal; overflow: visible; }
}

/* The Showdown paste. Monospace and preserved whitespace because that is the
   format's actual grammar - "EVs: 32 HP / 32 Atk" is parsed, not decorative -
   and because a reader comparing it to their own team wants the lines to sit
   under each other.

   `overflow-x: auto` on the <pre> and not the wrapper: the copy button is
   pinned to the wrapper, and a scrolling parent would carry it out of the
   corner as soon as a long move name pushed the block wide. */
.paste { position: relative; background: var(--sunken);
         border: 1px solid var(--line); border-radius: var(--r-sm);
         margin: 12px 0 0; }
.paste pre {
  margin: 0; padding: 12px 14px; overflow-x: auto;
  font-size: .78rem; line-height: 1.55; color: var(--ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
/* Room for the button, so a nine-line paste never runs under it. */
.paste pre { padding-right: 92px; }
.paste .btn.sm { position: absolute; top: 8px; right: 8px; }
.btn.sm { font-size: .72rem; padding: 4px 10px; }

/* Generated paragraphs. Narrower measure than the card body would otherwise
   give them: at a full-width card these run past 120 characters, and prose is
   the one thing on this site that is read left-to-right rather than scanned. */
/* The reading behind one registered set. Collapsed for every set after the
   first: Sinistcha registers three and three open writeups is a page nobody
   scrolls to the end of. The summary has to look like a control rather than a
   heading, hence the marker and the pointer. */
.setprose { margin-top: 14px; border-top: 1px solid var(--line);
            padding-top: 10px; }
.setprose > summary {
  cursor: pointer; font-size: .76rem; font-weight: 620;
  letter-spacing: .02em; color: var(--ink-2); padding: 2px 0;
  list-style: none;
}
.setprose > summary::-webkit-details-marker { display: none; }
.setprose > summary::before {
  content: "\25b8"; display: inline-block; width: 14px;
  color: var(--ink-3); transition: transform .12s ease;
}
.setprose[open] > summary::before { transform: rotate(90deg); }
.setprose > summary:hover { color: var(--ink); }
.setprose > .sub-h:first-of-type { margin-top: 12px; }

.prose { margin: 0 0 10px; font-size: .87rem; line-height: 1.65;
         color: var(--ink-2); max-width: 74ch; }
.prose:last-child { margin-bottom: 0; }

/* A heading inside a card body, under the card's own <h2>. */
.sub-h { margin: 18px 0 8px; font-size: .82rem; font-weight: 650;
         letter-spacing: .01em; color: var(--ink-2); }

/* One member's pasted set beside what the field runs. A row rather than a card
   each: six of these stack, and six cards would push the comparison off screen. */
.set-row { display: flex; gap: 14px; align-items: flex-start;
           padding: 10px 0; border-top: 1px solid var(--line); }
.set-row:first-of-type { border-top: 0; }
.set-mon { flex: none; width: 150px; }
.set-facts { flex: 1; min-width: 0; display: grid; gap: 3px; }
.set-line { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap;
            font-size: .8rem; }
.set-line .mini-label { margin: 0; flex: none; width: 52px; }
.set-val { font-weight: 600; }

@media (max-width: 640px) {
  .set-row { flex-direction: column; gap: 6px; }
  .set-mon { width: auto; }
}

/* ---- speed tiers ---- */

.speed-controls { display: grid; gap: 12px 18px; align-items: end;
                  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }

.speed-hero { display: flex; align-items: center; gap: 16px; margin-top: 14px;
              padding: 12px 16px; border-radius: var(--r-sm);
              background: var(--accent-soft); }
.speed-hero .n { font-size: 2.4rem; font-weight: 700; letter-spacing: -.03em;
                 line-height: 1; color: var(--accent); }
.speed-hero .meta { min-width: 0; }
.speed-hero .who { font-weight: 620; }
.speed-hero .sub { font-size: .78rem; color: var(--ink-2); margin-top: 2px; }
.speed-hero + .tiles { margin-top: 12px; }

/* The Speed column is the only place on this page where a colour means
   anything: blue you beat, red beats you, amber a coin flip. */
.speed-tbl .spe { font-weight: 650; width: 1%; white-space: nowrap; }
.speed-tbl .spe.slower { color: var(--pos); }
.speed-tbl .spe.faster { color: var(--neg); }
.speed-tbl .spe.tied { color: var(--warn); }

.speed-tbl tr.you { background: var(--accent-soft); }
.speed-tbl tr.you:hover { background: var(--accent-soft); }
.speed-tbl tr.you td { border-top: 2px solid var(--accent);
                       border-bottom-color: var(--accent); }
.speed-tbl tr.you .spe { color: var(--accent); font-size: 1.05rem; }
.speed-tbl tr.you .tag { margin-left: 7px; font-size: .62rem; font-weight: 700;
                         letter-spacing: .07em; text-transform: uppercase;
                         color: var(--accent); }

.spread-tag { font-size: .7rem; font-weight: 650; padding: 2px 7px;
              border-radius: 5px; background: var(--sunken); color: var(--ink-2);
              font-variant-numeric: tabular-nums; }

/* ---- charts ---- */

.chart { position: relative; width: 100%; }
.chart svg { display: block; }
/* Axis ticks align vertically, so they get tabular figures; nothing else on
   the chart does. Text on a chart wears ink tokens, never a series colour —
   the coloured dot beside a label is what carries identity. */
.chart text.ax { font-size: .68rem; fill: var(--ink-3);
                 font-variant-numeric: tabular-nums; }
.chart text.lbl { font-size: .72rem; font-weight: 600; fill: var(--ink-2); }

.chart-tip { position: absolute; z-index: 3; pointer-events: none;
             min-width: 150px; padding: 8px 10px; border-radius: var(--r-sm);
             background: var(--surface-1); border: 1px solid var(--line);
             box-shadow: var(--shadow); font-size: .76rem; }
.chart-tip .wk { font-weight: 650; margin-bottom: 5px; }
.chart-tip .r { display: flex; align-items: center; gap: 7px; padding: 1px 0; }
.chart-tip .r i { width: 9px; height: 9px; border-radius: 2px; flex: none; }
.chart-tip .r .n { flex: 1; min-width: 0; white-space: nowrap;
                   overflow: hidden; text-overflow: ellipsis; color: var(--ink-2); }
.chart-tip .r .v { font-weight: 650; font-variant-numeric: tabular-nums; }
.chart-tip .r .s { color: var(--ink-3); font-variant-numeric: tabular-nums; }

.legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-bottom: 10px; }
.legend span { display: inline-flex; align-items: center; gap: 6px;
               font-size: .78rem; color: var(--ink-2); }
.legend i { width: 10px; height: 10px; border-radius: 3px; flex: none; }

/* The table view. Required rather than optional: three of the light-mode
   series colours sit below 3:1 against the surface, and the relief for that
   is readable values, not a better guess at the hue. */
.chart-table { margin-top: 12px; }
.chart-table summary { cursor: pointer; font-size: .78rem; color: var(--ink-2);
                       padding: 4px 0; }
.chart-table summary:hover { color: var(--ink); }
.chart-table .scroll-x { margin-top: 8px; }

/* ---- trends ---- */

.pick { flex: none; width: 22px; height: 22px; padding: 0; line-height: 1;
        border: 1px solid var(--line); border-radius: 6px; cursor: pointer;
        background: var(--surface-1); color: var(--ink-3); font-size: .8rem;
        font-weight: 700; }
.pick:hover { color: var(--ink); border-color: var(--axis); }
.pick[aria-pressed="true"] { color: #fff; border-color: transparent; }
.pick[disabled] { opacity: .35; cursor: default; }

.mover .k { display: flex; align-items: center; gap: 8px; min-width: 0; }

/* ---- matchups ---- */

/* The grid scrolls inside itself; the page body never scrolls sideways. Both
   header bands are sticky, so a cell 30 columns in still says what it is. */
.mx-wrap { overflow: auto; max-height: 76vh; border: 1px solid var(--line);
           border-radius: var(--r-sm); }
.mx { border-collapse: separate; border-spacing: 0; font-size: .74rem; }
.mx th { font-weight: 600; background: var(--surface-1); }
.mx thead th { position: sticky; top: 0; z-index: 2; padding: 5px 4px;
               border-bottom: 1px solid var(--axis); }
.mx thead th:first-child { left: 0; z-index: 3; }
.mx tbody th { position: sticky; left: 0; z-index: 1; text-align: left;
               white-space: nowrap; padding: 3px 10px 3px 6px;
               border-right: 1px solid var(--axis); }
.mx tbody th a { color: inherit; text-decoration: none; }
.mx tbody th a:hover { color: var(--accent); }
.mx td { text-align: center; padding: 5px 4px; min-width: 46px;
         font-variant-numeric: tabular-nums; color: var(--ink);
         border-bottom: 1px solid var(--line); }
.mx td.na { color: var(--ink-3); }
.mx td.self { background: var(--sunken); color: var(--ink-3); }
.mx img { width: 30px; height: 30px; display: block; margin: 0 auto; }
.mx tbody th img { width: 22px; height: 22px; display: inline-block;
                   vertical-align: middle; margin-right: 6px; }

/* Legend for the fill. The number is in every cell as well, so the colour is
   reinforcement rather than the only carrier of the value. */
.mx-key { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
          font-size: .78rem; color: var(--ink-2); margin: 2px 0 12px; }
.mx-key .ramp { display: flex; height: 12px; border-radius: 3px;
                overflow: hidden; }
.mx-key .ramp span { width: 22px; }

.mx-pick { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
           margin-bottom: 12px; }

/* The phone. Two things here were wrong rather than merely tight.

   The nav used to be `display: none` below this width, with nothing put in its
   place - so on a phone the site had no navigation at all beyond whatever
   links a page happened to contain, and no way to reach Tournaments,
   Champions, the Pokedex or the tools. It now takes a second header row and
   scrolls, which is what it already did between 700 and 1180. The arithmetic
   for why 700 is the breakpoint: brand 151 + gap + nav 439 + theme 34, plus
   32px of wrap padding, is ~692px on one row, so 700 is the last width where
   one row holds and the first where a second is needed.

   The filter bar stops being sticky here. Its seven controls need ~1077px and
   wrap to three rows on a phone; stuck under a two-row header they would stand
   on roughly 40% of a 667px viewport before any data was visible. It scrolls
   away with the page instead, and the header - which is how you leave the
   page - is what stays. */
@media (max-width: 700px) {
  .top .wrap { height: auto; flex-wrap: wrap; gap: 8px 12px;
               padding-top: 9px; padding-bottom: 9px; }
  .nav { order: 3; flex-basis: 100%; margin-left: 0; }
  .nav a { padding: 6px 8px; }
  .filters { position: static; }
  /* The one control that gains from the full width: a search field narrow
     enough to hide its own query is worse than one that wraps. */
  .filters .search { flex: 1 1 100%; }
  .hide-sm { display: none !important; }
  .tbl th { position: static; }
  .calc-stats { grid-template-columns: repeat(3, 1fr); }
  .bar.dmg .track { width: 60px; }
  .import-set { flex-wrap: wrap; }
  .import-acts { flex-direction: row; width: 100%; }
  .speed-hero .n { font-size: 1.9rem; }
  /* The hero, compacted. Measured on a 390x780 phone before this block: the
     card stood 442px tall and pushed the detail page's tab strip to y=844,
     entirely below the fold - so a reader landing on a Pokemon could not see
     that the page had tabs at all, which is most of what tabbing it was for.

     Three changes, none of which drop anything: the sprite is decoration next
     to the name and typing, so it comes down from 132px; the name block stops
     insisting on 200px so it sits beside the sprite rather than under it; and
     six stat bars become three rows of two. */
  /* Top-aligned here, not centred: against a 207px name block a centred 92px
     sprite floats in the middle of it. Aligned to the top it sits with the dex
     number and the name, which is what it labels. */
  .mon-hero { gap: 14px; align-items: flex-start; }
  .mon-hero img { width: 92px; height: 92px; }
  .mon-hero .meta { min-width: 0; flex: 1 1 180px; }
  .mon-hero .statbars { flex-basis: 100%; min-width: 0; max-width: none;
                        grid-template-columns: 1fr 1fr; column-gap: 14px; }
}
