/* pages.css — the generated content pages (/breed/…, /pals/…, /guide/…,
   /farm/…, /loot/…, /skills/…).

   "Open Skies", the content-page half. styles.css owns the shell: the sky
   gradient (painted on .shell, not on .masthead), the rail, the scene, the
   display type, the footer, and every colour token. Nothing is redeclared
   here and there is not one literal colour in this file — only tokens and
   neutral black/white shadow alphas.

   Two rules govern the whole sheet:

   1. One definition per component, in catalogue order. Earlier append-only
      passes left contradictory duplicates; everything below is folded.
   2. --tint is the page's own colour. It is the element colour where the
      page has one and the brand green where it does not, and it is read all
      the way down the page — so a Fire page and an Ice page are not
      byte-identical below the fold.

   Text colour has exactly two tiers here: --body for prose and --dim for
   labels, meta and captions. --faint (2.8:1) and --fainter (2.2:1) are
   below AA on daylight paper, so nothing readable is set in them —
   hierarchy comes from size, weight and case instead of from fading text
   out until it stops passing.                                              */

/* ══ 0 · page-level custom properties ═══════════════════════════════════ */

.content-page {
  --tint: var(--dex-tint, var(--accent));
  --panel: var(--accent-2);
  --on-panel: var(--page);
  --on-panel-2: color-mix(in srgb, var(--page) 66%, var(--accent-2));
  --hero-h: 430px;

  /* ── spacing, in px, because the root is 15px ────────────────────────────
     styles.css pins html/body to `font: 15px/1.55`, so 1rem = 15px and the
     0.125rem step everyone reaches for is 1.875px — a value Chrome paints as
     1 or 2 depending on where the box lands. That is what makes identical
     rows alternate 8px, 7px, 8px down a long list. Spacing in this sheet is
     therefore written in even px, which is the only unit that IS the 2px
     grid at this root, and the recurring insets are tokens so four
     components cannot drift into four different answers again. */
  --row-pad: 8px 16px;    /* dense list rows: .prow, .barrow */
  --card-pad: 14px 16px;  /* text blocks: .tcard, .hubcard */
  --row-gap: 8px;         /* one gap between stacked rows */
  /* The face slot a table row reserves so every item name shares one left
     edge (.srcpal, including the empty .iph placeholder). Anything that has
     to line up UNDER that name indents by the pair. */
  --face: 34px;
  --face-gap: 10px;

  /* One reading measure, as a length. It used to be `70ch` on a group of
     selectors that each resolve ch against their OWN font-size, so a list ran
     34px wider than the paragraph above it and a .note stopped 87px earlier —
     four rag edges in one column. */
  --measure: 640px;

  /* How much of the bottom of the hero the hill line occupies, and therefore
     how much the hero body has to keep clear of it. In styles.css the hills
     are a PERCENTAGE of the masthead, which is why padding alone can never
     fix type landing on them — every pixel reserved grows the thing being
     reserved against. On a content page the band is a fixed strip and the
     hero body keeps exactly that strip plus a line of air, so hero type lands
     in the sky at every width. Read by BOTH hero bodies, full and compact. */
  --hill-band: 128px;

  /* Filled buttons take --accent-hi, not --accent: --accent-ink on --accent
     measures 4.07:1 in daylight, --accent-hi measures 4.76:1. It is the same
     pair the podium plinths already use. The hover step leans on --accent-2,
     which is near-black in daylight and cream after dark, so the button gets
     darker in one theme and lighter in the other — the direction that raises
     contrast against the ink in each case. */
  --btn-fill: var(--accent-hi);
  --btn-fill-hover: color-mix(in srgb, var(--accent-hi) 84%, var(--accent-2));
  /* --accent-text on --accent-soft is 4.43:1 in daylight. Pulling it toward
     --text darkens it on paper and lightens it after dark. */
  --accent-text-aa: color-mix(in srgb, var(--accent-text) 78%, var(--text));
}
.content-page.page-farmitem,
.content-page.page-lootpage,
.content-page.page-skill,
.content-page.page-guidepage { --hero-h: 330px; }
.content-page.page-pal { --hero-h: 380px; }

/* The horizon is the masthead's bottom edge and the sky/paper seam is
   --sky-h; if the hero is shortened both have to move together or the two
   lines separate. */
.content-page .shell { --sky-h: var(--hero-h); }
.content-page .masthead { min-height: var(--hero-h); }

/* Nine palettes. These sit on <body> (whole page), on a masthead, or on a
   single card — --dex-tint inherits, --tint is re-pinned so a tinted card
   inside a tinted page uses its own colour, not the page's. */
.el-bg-neutral  { --dex-tint: var(--el-neutral);  --tint: var(--el-neutral); }
.el-bg-fire     { --dex-tint: var(--el-fire);     --tint: var(--el-fire); }
.el-bg-water    { --dex-tint: var(--el-water);    --tint: var(--el-water); }
.el-bg-grass    { --dex-tint: var(--el-grass);    --tint: var(--el-grass); }
.el-bg-electric { --dex-tint: var(--el-electric); --tint: var(--el-electric); }
.el-bg-ice      { --dex-tint: var(--el-ice);      --tint: var(--el-ice); }
.el-bg-ground   { --dex-tint: var(--el-ground);   --tint: var(--el-ground); }
.el-bg-dark     { --dex-tint: var(--el-dark);     --tint: var(--el-dark); }
.el-bg-dragon   { --dex-tint: var(--el-dragon);   --tint: var(--el-dragon); }

/* ══ 1 · skip link and screen-reader text ═══════════════════════════════ */

.skip {
  position: absolute; left: -999px; top: .6rem; z-index: 50;
  padding: 8px 14px; border-radius: var(--r-pill);
  background: var(--accent); color: var(--accent-ink);
  font: 700 .82rem/1 var(--f-body); text-decoration: none;
}
.skip:focus-visible { left: .6rem; }

.vh {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -2px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ══ 2 · the rail, on a static page ═════════════════════════════════════
   styles.css styles both <button data-tab> and <a data-tab>, so the only
   thing left here is the brand link's UA underline. */
.rail a.brand { text-decoration: none; }

/* The chip tint darkens the card under it, which drops inherited --dim to
   4.45:1 in daylight and 4.15:1 after dark. --body clears AA on both. */
.rail-note code {
  font: 600 .9em/1 var(--num); color: var(--body);
  background: var(--glass-hi); padding: 2px 4px; border-radius: 5px;
}
.rail-cta {
  display: block; margin-top: 8px; padding: 8px 8px;
  text-align: center; text-decoration: none;
  background: var(--btn-fill); color: var(--accent-ink);
  border-radius: var(--r-pill); font: 700 .74rem/1.3 var(--f-body);
  transition: background .13s;
}
.rail-cta:hover { background: var(--btn-fill-hover); }

/* ══ 3 · type scale ═════════════════════════════════════════════════════
   The old sheet set h2 and h3 at the same ~0.95rem, so a 130 KB page had
   exactly one level of hierarchy. h2 is now the display face at a real
   size; h3 is a smaller display line; .rlab is demoted from
   heading-shaped-caption-bar to an actual micro-label. */

.content { padding: 32px var(--shell) 56px; max-width: 74rem; margin: 0 auto; }

/* ══ 3a · the second track ══════════════════════════════════════════════
   The column is 966px wide at a 1280 viewport and running prose is capped at
   --measure, so two thirds of the page carried text and the right third
   carried nothing — while the headings, tables and card grids in between ran
   the full width. Two right edges down one page, which reads as a mistake
   rather than as a measure. On a wide monitor it got worse, not better: once
   the 74rem cap binds at ~1362px the empty band is 417px.

   /breed/<pal>/ never had this, and the reason is the whole fix: .dexcols
   (further down this sheet) splits its column into a main track and a sticky
   side track, and its main track is 616px — under the measure, so the cap
   never binds and prose fills its column edge to edge. This generalises that
   to every other content route. 966 - 300 - 40 = 626px, the same shape.

   The rail is emitted by tools/build-pages.mjs (railFor), which is also why
   this is keyed on :has(> .pagerail) rather than on .content: a page that
   grows no rail keeps the old single column instead of a 300px hole. */
@media (min-width: 1100px) {
  .content:has(> .pagerail) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    column-gap: 40px;
    align-items: start;
  }
  .content:has(> .pagerail) > :not(.pagerail) { grid-column: 1; min-width: 0; }
  /* `span 1000`, NOT `1 / -1`. In a grid whose rows are all implicit, -1 is
     the end of the EXPLICIT grid — which is line 1 — so `1 / -1` silently
     collapsed to a single row. Row 1 then sized to the tallest thing in it,
     the 455px rail, and every page whose first block is a short lede opened
     with a ~380px hole before its first heading. The rows the span invents
     past the end of the content are empty and there is no row-gap, so they
     cost nothing. */
  .content > .pagerail {
    grid-column: 2; grid-row: 1 / span 1000;
    position: sticky; top: 16px; align-self: start;
    max-height: calc(100vh - 32px); overflow-y: auto;
    display: flex; flex-direction: column; gap: 14px;
  }
  /* The intro band lays its CTA out beside the prose above 1040px. That was
     measured against the old full-width column; inside a 718px track it
     squeezes the opening paragraph into ~430px and strands the button. With a
     rail on the page the band goes back to one column. */
  .content:has(> .pagerail) .legal-tldr { display: block; }
  .content:has(> .pagerail) .legal-tldr > p:last-child:not(:first-child) {
    grid-column: auto; margin-top: 20px; max-width: var(--measure);
  }
  /* The track IS the measure now, so the cap has nothing left to do. Left in
     place rather than deleted: below 1100px there is no rail and the single
     column still needs it. */
  .content:has(> .pagerail) section > p,
  .content:has(> .pagerail) section > ul,
  .content:has(> .pagerail) section > ol,
  .content:has(> .pagerail) > p,
  .content:has(> .pagerail) .note { max-width: none; }
}
/* Below the split the page is a single column at a comfortable width already,
   and a jump list stacked above the prose would push the first paragraph off
   the screen. The rail's content is navigation, not information — nothing is
   lost by leaving it out. */
@media (max-width: 1099px) { .pagerail { display: none; } }

/* Every rule here is written .content-first on purpose. The rail lives inside
   main.content, so it is up against `.content h2`, `.content p` and
   `.content a` — all (0,1,1) and all declared LATER in this sheet. A bare
   `.railcard h2` ties on specificity and loses on source order, which is how
   the rail's micro-labels came out as 27px display headings and its jump
   links came out link-green. */
.railcard {
  background: var(--surface); border-radius: var(--r-card);
  padding: 14px 16px; box-shadow: var(--card-sh);
}
/* The micro-label voice, same as .rlab elsewhere in this sheet. */
.content .raillab {
  margin: 0 0 10px;
  font: 700 .62rem/1.2 var(--num); letter-spacing: .12em;
  text-transform: uppercase; color: var(--dim);
}
.railjump { display: flex; flex-direction: column; gap: 2px; }
.content .railjump a {
  display: block;
  padding: 5px 8px; border-radius: var(--r-sm);
  color: var(--body); text-decoration: none;
  font-size: .82rem; line-height: 1.35;
}
.content .railjump a:hover { background: var(--surface-2); color: var(--text); }
/* Matches the tint the .rlab bullet uses, so "where you are" reads the same
   on the rail as it does on the section it points at. */
.content .railjump a.active { background: var(--accent-soft); color: var(--text); font-weight: 700; }
.content .railcta p { margin: 0 0 10px; font-size: .82rem; line-height: 1.5; color: var(--body); max-width: none; }

/* ── the reading measure ──────────────────────────────────────────────────
   One rule for all running prose. Without it the page was half 64ch-capped
   fragments and half paragraphs running the full 74rem — the inconsistency
   read as broken linebreaks. Tables, grids and cards keep the full column;
   only bare text is measured.

   The cap is --measure (one length), not 70ch: ch resolves against each
   selector's own font-size, so the same "one measure" produced 639px on a
   paragraph, 673px on a list, 606px on a .pull and 552px on a .note. */
.content section > p,
.content section > ul,
.content section > ol,
.content > p,
.content .note,
.content .pull,
.content .callout,
.content .flavor { max-width: var(--measure); }

/* The opening statement of a hub. Prose reads down the left at one measure;
   the CTA sits out to the right so the band is filled on purpose instead of
   trailing 400px of nothing beside a narrow column.

   It was briefly set in two columns — that broke sentences mid-phrase into
   40-character ribbons AND still left the dead space, so it is gone. Text
   that is one thought stays one column. */
.content .legal-tldr { max-width: none; margin: 24px 0 6px; }
.content .legal-tldr p { max-width: var(--measure); font-size: 1.02rem; line-height: 1.65; color: var(--body); }
.content .legal-tldr p:first-child { font-size: 1.1rem; color: var(--text); }
.content .legal-tldr p + p { margin-top: 16px; }
/* :not(:first-child) throughout — a band that is one paragraph long has a
   :last-child that is ALSO its :first-child, and the trailing-CTA treatment
   was capturing it. */
.content .legal-tldr p:last-child:not(:first-child) { margin-top: 24px; }

@media (min-width: 1040px) {
  .content .legal-tldr:has(> p + p) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: clamp(32px, 5vw, 72px);
    align-items: start;
  }
  .content .legal-tldr > p { grid-column: 1; }
  /* The CTA rides the right column, level with the opening claim. There is no
     explicit row template here, so `grid-row: 1 / -1` resolved inside a
     one-line grid and meant nothing; row 1 is what it always rendered as and
     what the line above asks for, so it is now what it says. */
  .content .legal-tldr > p:last-child:not(:first-child) {
    grid-column: 2; grid-row: 1;
    align-self: center; margin: 0; max-width: none;
  }
}

.content h2 {
  margin: 40px 0 12px;
  font: var(--dis-w) clamp(1.35rem, 2.1vw, 1.8rem)/1.08 var(--f-dis);
  letter-spacing: var(--dis-ls); color: var(--text);
}
.content h3 {
  margin: 26px 0 8px;
  font: var(--dis-w) 1.06rem/1.25 var(--f-dis);
  letter-spacing: var(--dis-ls); color: var(--text);
}
.content p, .content li, .content dd { color: var(--body); font-size: .95rem; line-height: 1.68; }
.content p { margin: 0 0 14px; }
.content ul, .content ol { margin: 0 0 14px; padding-left: 18px; }
.content li { margin-bottom: 6px; }

/* Lists that are LAYOUT, not prose. The two rules above are `.content ul`
   (0,1,1) and `.content li` (0,1,1); a bare component class is (0,1,0), so
   `.combolist { padding: 0 }` never won and every combo card on a dex page
   sat 17px right of the single left edge its own section heading, filter bar,
   table and CTA all share — while `.content li` added 6px under each card, so
   the grid's row gap was 14px against a 8px column gap. One reset, matched on
   specificity, for every list-shaped component in this sheet. */
.content ul.combolist, .content ul.droplist,
.content ul.moves, .content ol.route {
  list-style: none; padding-left: 0;
}
.content .combolist > li, .content .droplist > li,
.content .moves > li, .content .route > li { margin-bottom: 0; }
.content a { color: var(--link); text-underline-offset: 2px; }
.content b, .content strong { color: var(--text); }
.content code {
  font: 600 .84em/1 var(--num); background: var(--surface-2);
  padding: 2px 6px; border-radius: 5px;
  /* A Windows/Proton save path is one 90-character token with no break
     opportunity in it; inline, it ran 100px past the column on a phone. Only
     bites when the token cannot otherwise fit. */
  overflow-wrap: anywhere;
}
/* The reduced offset is for the FIRST BLOCK ON THE PAGE. :first-of-type only
   means "first <section>", so on a hub — where an intro band and a lead card
   come first — it fired on the third block down and gave that one boundary a
   21px gap where every other section boundary is 39px. */
.content > section:first-child h2 { margin-top: 22px; }
/* Anchor targets clear whatever is stuck above them (secnav on dex pages). */
.content section[id] { scroll-margin-top: calc(var(--sticky-h, 0px) + 16px); }

/* One visible focus treatment for everything that can be focused, including
   the links that live outside main.content (hero chrome, podium, crumbs). */
.content a:focus-visible,
.content button:focus-visible,
.filterbar :focus-visible,
.secnav a:focus-visible,
.masthead a:focus-visible,
.podium a:focus-visible,
.hero-top a:focus-visible,
.skip:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sq);
}
/* Pill-shaped controls keep a pill-shaped ring. */
.content a.linkchip:focus-visible, .content a.linpair:focus-visible,
.content a.binchip:focus-visible, .content a.cta-btn:focus-visible,
.content a.step:focus-visible, .content a.mapopen:focus-visible,
.masthead a.fact:focus-visible { border-radius: var(--r-pill); }

/* The micro-label voice. .rlab carries the page's own colour as its bullet,
   which is one more place --tint shows below the fold. */
.content h3.rlab, .content .rlab {
  display: flex; align-items: center; gap: 8px;
  margin: 28px 0 8px;
  font: 700 .62rem/1.2 var(--num); letter-spacing: .12em; text-transform: uppercase;
  color: var(--dim);
}
.content h3.rlab::before, .content .rlab::before {
  content: ""; width: .45rem; height: .45rem; border-radius: 50%;
  background: var(--tint); flex: none;
}
.mulabel, .chainlab, .tlab, .cotag, .sbtag,
.content h2.chainlab, .content h3.chainlab,
.content .mulabel, .content .tlab, .content .cotag, .content .sbtag {
  margin: 0 0 12px;
  font: 700 .63rem/1.2 var(--f-body);
  letter-spacing: .08em; text-transform: uppercase; color: var(--dim);
}
/* These three live inside a tile / a combo card / a stat-bar row, all of
   which own their own spacing — so they take the shared label typography
   above and drop its bottom margin. Deliberate, not a leftover. */
.content .tlab, .content .cotag, .content .sbtag { margin: 0; }

/* Ledes. The lede is a statement, not a callout box — styles.css's .card
   and .legal-tldr treatments are both stripped here on purpose. */
/* styles.css's .card brings a hover lift, a border and overflow:hidden; a
   lede is neither a card nor a thing you can click, so all of it is unset. */
.content .lede.card, .content .legal-tldr {
  background: transparent; box-shadow: none; border: 0; padding: 0;
  overflow: visible; transition: none;
}
.content .lede.card:hover, .content .legal-tldr:hover {
  transform: none; box-shadow: none; border-color: transparent;
}
.content .lede.card { margin: 0 0 8px; }
.content .lede.card p { margin: 0; font-size: 1rem; color: var(--body); }
/* Sizing, measure and rhythm for .legal-tldr live in ONE place — the intro
   band block near the top of this file. A second definition here used to win
   on source order and silently re-clamp it to 64ch, which is why the intro
   sat in a narrow column while everything under it ran full width. */

/* Real game text, set apart from our own prose. */
.content .flavor { font-style: italic; color: var(--dim); font-size: .95rem; line-height: 1.6; }
.content .pskill { font-size: .92rem; }
.content .note { font-size: .82rem; color: var(--dim); line-height: 1.55; }
.content .note a { color: var(--link); }

/* ══ 4 · sky hero — full ════════════════════════════════════════════════
   styles.css gives .mast-body no horizontal padding, because the app's
   headline deliberately hugs the rail. On a content page the hero type has
   to line up with main.content instead, so it is re-inset to the same
   74rem / var(--shell) column. */

.content-page .mast-body {
  width: 100%; max-width: 74rem; margin: 0 auto;
  padding: 32px var(--shell) max(24px, var(--hill-band)); flex: 1;
  display: flex; flex-direction: column; justify-content: center;
}
.content-page .mast-body > :not(.facts) { max-width: 70ch; }
.content-page .mast-body h1 {
  font-size: clamp(34px, 4.4vw, 60px); line-height: 1; letter-spacing: -.03em;
}
.content-page .mast-line {
  margin-top: 14px; font-size: 1.04rem; font-weight: 600; color: var(--body); max-width: 64ch;
}
.content-page .mast-sub { margin-top: 8px; font-size: .88rem; color: var(--dim); max-width: 66ch; }
.mast-body .eyebrow { display: block; margin-bottom: 8px; }
/* The kicker above the H1 and the dateline below it were typographically
   IDENTICAL — same size, weight, tracking, case, family and colour in both
   themes — so the hero read as a headline sandwiched between two copies of
   one label. --faint is also what the plain .eyebrow already resolves to, so
   the intended step down did nothing. The dateline stops shouting instead:
   sentence case, normal tracking, one weight lighter. It is metadata, not a
   second kicker. */
.mast-body .eyebrow.dateline {
  margin: 12px 0 0; color: var(--dim);
  text-transform: none; letter-spacing: .01em; font-weight: 500;
}

/* The hill line as a fixed band rather than a proportion of the hero — see
   --hill-band. These are the styles.css percentages resolved against a
   mid-size hero, so the horizon sits where it always did. */
.content-page .scene .hill.h1 { height: 96px; }
.content-page .scene .hill.h2 { height: 112px; }
.content-page .scene .hill.h3 { height: 68px; }

/* Pals stand ON the hill crest, largest first — the site's signature. */
.masthead .scene-pals { right: clamp(1.2rem, 5vw, 4.5rem); bottom: 13%; }
.masthead .scene-pal img {
  border-radius: 14%;
  filter: drop-shadow(0 8px 16px rgb(0 0 0 / .32));
  /* Solid at the feet, fading toward the top corners: renders whose source
     art touches the bitmap frame (or bakes in a background) melt into the
     sky instead of standing there as hard-edged tiles. */
  -webkit-mask-image: radial-gradient(108% 108% at 50% 96%, #000 48%, transparent 90%);
  mask-image: radial-gradient(108% 108% at 50% 96%, #000 48%, transparent 90%);
}

@media (max-width: 900px) {
  /* On phones the text owns the band; the cast steps back into the scene. */
  .masthead .scene-pals { opacity: .38; bottom: 6%; }
  .masthead .scene-pal { width: 48px !important; }
  .masthead .scene-pal:first-child { width: 64px !important; }
  .masthead .scene-pal:nth-child(n+3) { display: none; }
}
/* On a phone the headline spans the whole band, so the sun/moon steps up
   into the corner and dims — display type never sits on the orb core. */
@media (max-width: 700px) {
  .content-page .scene .orb {
    left: auto; right: -26px; top: 46px; margin: 0;
    width: 92px; height: 92px; opacity: .7;
  }
  /* Nothing reserved the hill band, so on a phone — where the dek runs to
     seven or eight lines — its last four lines were set ON the green crest.
     Padding alone could not fix it: the hills are a PERCENTAGE of the
     masthead, so every pixel of reserved space grew the thing being reserved
     against. At phone width the band is a fixed height instead, and the hero
     body reserves exactly that band plus a line of air. */
  .content-page { --hill-band: 96px; }
  .content-page .scene .hill.h1 { height: 68px; }
  .content-page .scene .hill.h2 { height: 80px; }
  .content-page .scene .hill.h3 { height: 52px; }
}

/* ══ 5 · sky hero — compact (6b / 6c) ═══════════════════════════════════
   One large portrait floated right, no hill-line cast, tighter type. */
.masthead.hero-c .mast-body { padding-top: 30px; padding-bottom: max(16px, var(--hill-band)); }
/* ~26ch: a long article title wraps before it reaches the orb and the
   scene-pal zone instead of running its full stop into the moon. */
.masthead.hero-c .mast-body h1 { font-size: clamp(28px, 3.4vw, 44px); max-width: 26ch; }
.masthead.hero-c .scene-pals { right: clamp(1rem, 4vw, 3rem); bottom: 16%; gap: 0; }
.masthead.hero-c .scene-pal:nth-child(n+2) { display: none; }
.masthead.hero-c .scene-pal:first-child { width: 128px !important; }
@media (max-width: 760px) { .masthead.hero-c .scene-pals { opacity: .3; } }

/* ══ 6 · sky hero — portrait (the dex hero, 6a) ═════════════════════════
   The element colour is a WASH OVER the shell's sky, never a slab painted
   on top of it: two stops, so the horizon still reads as sky and the pal
   still reads as lit. */
.masthead.dexhero { position: relative; justify-content: center; }
.masthead.dexhero::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg,
      color-mix(in srgb, var(--tint) 26%, transparent) 0%,
      color-mix(in srgb, var(--tint) 8%, transparent) 62%,
      transparent 100%);
}
.masthead.dexhero .stars { display: none; }

.dexhero-row {
  position: relative; z-index: 2;
  width: 100%; max-width: 74rem; margin: 0 auto;
  padding: 20px var(--shell) 18px;
  /* The art column tracks the art: 210px was the ceiling of a 128px bitmap.
     :has() keeps the old width for the 30 pals still on an icon, so the two
     never sit at mismatched sizes on neighbouring pages. */
  display: grid; grid-template-columns: minmax(0, 210px) minmax(0, 1fr);
  gap: clamp(16px, 3vw, 30px); align-items: center;
}
/* The hero art ships inside a <picture>, so the picture — not the img — is
   the flex/grid item that has to be placed. */
.masthead picture:has(> .hero-art) {
  display: block; position: relative; z-index: 2;
  align-self: center; width: 100%; max-width: 210px; margin-inline: auto;
}
/* Both ceilings lift together for a real render — the <picture> is the grid
   item, so raising only .hero-art's max-width would do nothing.

   The COLUMN WIDTHS are locked inside the desktop query. `:has()` scores
   (0,3,0) against the (0,1,0) of the single-column mobile reset at the foot of
   this sheet, and @media adds no specificity — so on every pal with a real
   render the two-column hero survived down to 390px, the art track ate all
   276px of it, the text track computed to 0, and the name, the element badge
   and every fact pill were pushed off the right edge of a page that then
   scrolled sideways by 189px. A rule that only makes sense with two columns
   now only exists where there are two columns. */
@media (min-width: 901px) {
  .dexhero-row:has(.hero-art.has-render) { grid-template-columns: minmax(0, 264px) minmax(0, 1fr); }
}
.masthead picture:has(> .hero-art.has-render) { max-width: 264px; }
/* No element halo behind a render. The glow was drawn to give a flat 128px
   cut-out some presence against the sky; a lit full-body render already has
   form and its own contact shadow, so the halo only adds a third soft tinted
   shape on top of that shadow and the hill line — three overlapping washes
   that read as a grey smudge around the pal rather than as light. */
.masthead picture:has(> .hero-art.has-render)::before { content: none; }
/* The element wash the design puts behind pal art, painted deliberately and
   LARGER than the bitmap — the source renders carry a faint baked wash of
   their own, and against the night sky that read as a grey box unless it sits
   inside a designed glow that outsizes it. */
.masthead picture:has(> .hero-art)::before {
  content: ""; position: absolute; z-index: -1;
  inset: -14% -18%; pointer-events: none;
  background: radial-gradient(50% 50% at 50% 48%,
      color-mix(in srgb, var(--tint) 34%, transparent) 0%,
      color-mix(in srgb, var(--tint) 14%, transparent) 58%,
      transparent 78%);
}
.hero-art {
  position: relative; z-index: 2;
  width: 100%; max-width: 210px; height: auto; margin-inline: auto;
  aspect-ratio: 1 / 1; object-fit: contain;
  filter: drop-shadow(0 22px 26px rgb(0 0 0 / .30));
  /* The 128px source bitmaps clip the pal mid-body; the vignette fades the
     cut sides into the glow instead of exposing a guillotined edge. */
  -webkit-mask-image: radial-gradient(75% 75% at 50% 44%, #000 56%, transparent 97%);
  mask-image: radial-gradient(75% 75% at 50% 44%, #000 56%, transparent 97%);
}
/* A 512/1024 full-body render (tools/build-pal-renders.mjs) has the whole pal
   inside the frame with real alpha, so there is no cut edge to hide — the
   vignette above would fade its feet out instead. It also earns a bigger
   ceiling: 210px was the honest limit of a 128px bitmap, not a design choice.
   The drop-shadow stays; it grounds the pal against the sky wash. */
.hero-art.has-render {
  max-width: 264px;
  -webkit-mask-image: none;
  mask-image: none;
  /* The render is lit over a shadow catcher and carries its own ray-traced
     contact shadow in the alpha; drop-shadow() would blur a copy of that
     shadow underneath itself. The grounding the comment above wanted is
     already in the pixels. */
  filter: none;
}
/* Same for the two mid-size render slots on the hubs. */
.leadcard img.pal-render, .pod img.pal-render { filter: none; }
.hero-top {
  position: absolute; inset: 0 0 auto 0; z-index: 3;
  display: flex; justify-content: space-between; align-items: center;
  width: 100%; max-width: 74rem; margin: 0 auto;
  padding: 16px var(--shell); color: var(--band-fg);
}
.hero-back { color: inherit; text-decoration: none; font: 700 .8rem/1 var(--f-body); }
.hero-back:hover { text-decoration: underline; }
.hero-no { font: 600 .82rem/1 var(--num); letter-spacing: .04em; color: var(--band-dim); }

/* Skill hero: the same wash, so the band works in both themes without an
   ink override. */
.masthead.skillhero { position: relative; }
.masthead.skillhero::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg,
      color-mix(in srgb, var(--tint) 24%, transparent) 0%,
      color-mix(in srgb, var(--tint) 7%, transparent) 64%,
      transparent 100%);
}
.masthead.skillhero .stars { display: none; }

/* Element hub hero: the element mixed INTO the sky (the map-hub zone-tint
   recipe) — strongest at the horizon behind the pal line-up. */
.masthead.elhub {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--tint) 14%, var(--sky-b)),
    color-mix(in srgb, var(--tint) 44%, var(--sky-a)));
}

/* The dex body is an ordinary content column now: with the sky on .shell
   there is nothing left to curtain off. --sticky-h stays — the secnav
   offset is load-bearing. */
.dexbody { --sticky-h: 2.9rem; position: relative; z-index: 2; }

.dex-id h1 {
  margin: 0 0 8px;
  font: var(--dis-w) clamp(30px, 4.4vw, 52px)/1.02 var(--f-dis);
  letter-spacing: -.03em; color: var(--text);
}
.dex-id h1 small {
  display: block; margin-bottom: 4px;
  font: 700 .66rem/1 var(--num); letter-spacing: .14em; text-transform: uppercase;
  color: var(--dim);
}
.dexhero-row .dex-id h1 { color: var(--band-fg); }
.dexhero-row .dex-id h1 small { color: var(--band-dim); }
.dexhero-row .dex-id h1 { margin-bottom: 12px; }

/* ══ 7 · hero chip row (.facts) ═════════════════════════════════════════ */
.facts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.fact {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--glass); backdrop-filter: blur(6px);
  border-radius: var(--r-pill); padding: 6px 12px;
  font: 700 .74rem/1.25 var(--f-body); color: var(--body); white-space: nowrap;
  text-decoration: none;
}
.content a.fact { color: var(--body); }
.content a.fact:hover { color: var(--text); }
.fact i.dot { width: .5rem; height: .5rem; border-radius: 50%; background: var(--tint); flex: none; }
/* --warn-text rather than --amber-text: the amber pair is 4.27:1 in daylight,
   the warn pair is 5.69:1 on the same background. */
.fact-amber  { background: var(--amber-bg);  color: var(--warn-text);   box-shadow: inset 0 0 0 1px var(--amber-line); }
.fact-violet { background: var(--violet-bg); color: var(--violet-text); box-shadow: inset 0 0 0 1px var(--violet-line); }
.fact-info   { background: var(--info-bg);   color: var(--info-text);   box-shadow: inset 0 0 0 1px var(--info-line); }
.content .fact-amber, .content .fact-violet, .content .fact-info { color: inherit; }

/* ══ 8 · breadcrumb ═════════════════════════════════════════════════════ */
.mast-body .crumbs {
  font: 700 .76rem/1.3 var(--f-body); color: var(--dim); margin-bottom: 14px;
}
.mast-body .crumbs a { color: var(--link); text-decoration: none; }
.mast-body .crumbs a:hover { text-decoration: underline; }
.mast-body .crumbs > span:last-child { color: var(--text); }

/* ══ 9 · element badges ═════════════════════════════════════════════════
   The element tints are fixed across both themes and --on-el is the ink
   that clears 4.5:1 on all nine. */
.el {
  display: inline-flex; align-items: center;
  padding: 2px 8px; border-radius: var(--r-pill);
  font: 700 .68rem/1.35 var(--f-body); letter-spacing: .03em;
  color: var(--on-el); white-space: nowrap;
}
.el-neutral  { background: var(--el-neutral); }
.el-fire     { background: var(--el-fire); }
.el-water    { background: var(--el-water); }
.el-grass    { background: var(--el-grass); }
.el-electric { background: var(--el-electric); }
.el-ice      { background: var(--el-ice); }
.el-ground   { background: var(--el-ground); }
.el-dark     { background: var(--el-dark); }
.el-dragon   { background: var(--el-dragon); }
.badges { display: flex; flex-wrap: wrap; gap: 4px; }
a.el, .content a.el { text-decoration: none; color: var(--on-el); }
a.el:hover { filter: brightness(1.07); }

/* ══ 10 · internal-link chip row ════════════════════════════════════════ */
.chiprow { display: flex; flex-wrap: wrap; gap: 8px; margin: 24px 0 10px; }
/* A chip row under a heading takes the heading's own bottom margin. Adjacent
   margins collapse to the larger of the two, so the row's 24px was outranking
   the 11px every other h2 gives its content and putting one heading on every
   article 13px further from its block than all the others. */
.content h2 + .chiprow, .content h3 + .chiprow { margin-top: 0; }
.linkchip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 14px; border-radius: var(--r-pill);
  background: var(--surface); border: 1.5px solid var(--line);
  font: 700 .8rem/1 var(--f-body); text-decoration: none;
  transition: border-color .13s, color .13s;
}
/* The reduced leading inset is an ICON SLOT, so it only exists when there is
   an icon. Flat, it was applied to every chip on every guide page — none of
   which has one — and the label sat 4px off-centre in its own capsule. */
.linkchip:has(> img:first-child), .linkchip:has(> .dot:first-child) { padding-left: 12px; }
.content a.linkchip { color: var(--body); }
.content a.linkchip:hover { border-color: var(--accent); color: var(--text); }
.linkchip img { width: 16px; height: 16px; }
.linkchip .dot { width: .55rem; height: .55rem; border-radius: 50%; flex: none; }

/* ══ 11 · filter bar ════════════════════════════════════════════════════
   Emitted with the hidden attribute and only revealed by pages.js — a dead
   search box is worse than no search box. Sticky offset clears the secnav
   on dex pages (--sticky-h) and is zero everywhere else. */
.filterbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  /* A fully-round 53px bar whose contents sat 9.4px from its widest point
     read as pinned to the edge; on a pill this deep the optical inset wants
     to be nearer the corner radius. */
  padding: 8px 16px; margin: 12px 0 20px;
  border-radius: var(--r-pill);
  background: var(--glass); backdrop-filter: blur(10px) saturate(1.2);
  box-shadow: var(--sh-nav);
  position: sticky; top: calc(var(--sticky-h, 0rem) + .5rem); z-index: 4;
}
.filterbar[hidden] { display: none; }
.filterbar input[type="search"], .filterbar select, .fb-reset {
  height: var(--ctl-h); border-radius: var(--ctl-r);
  background: var(--surface); color: var(--text); border: 1px solid var(--line);
  font: 600 .82rem/1 var(--f-body);
}
.filterbar input[type="search"] { width: 100%; padding: 0 12px 0 28px; }
.filterbar select { padding: 0 12px; }
.fb-search { position: relative; flex: 1 1 15rem; display: flex; align-items: center; }
.fb-ico { position: absolute; left: .7rem; color: var(--dim); font-size: .95rem; pointer-events: none; }
.fb-check {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: .8rem; color: var(--dim); cursor: pointer;
  padding: 0 6px; white-space: nowrap;
}
.fb-reset { padding: 0 14px; background: transparent; color: var(--dim); cursor: pointer; }
.fb-reset:hover { color: var(--text); border-color: var(--accent); }
.fb-count { margin-left: auto; font: 700 .72rem/1 var(--num); color: var(--dim); white-space: nowrap; }
/* On a phone the bar wraps to three or four rows; pinned, that block ate a
   quarter of the viewport for a whole 299-card scroll. It scrolls away. */
@media (max-width: 640px) { .filterbar { position: static; } }

/* ══ 12 · pal cards ═════════════════════════════════════════════════════ */
.palgrid {
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  margin: 6px 0 16px;
}
.palgrid-lg { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 10px; }
.palgrid.is-empty::after, #hubList.is-empty::after, .plist.is-empty::after {
  content: 'Nothing matches those filters.';
  display: block; grid-column: 1 / -1; padding: 30px 0; text-align: center;
  color: var(--dim); font-size: .86rem;
}

.palcard {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  /* 5.625px of side padding sat under the 8px edge floor and ran the badge
     row straight into an 18px corner curve; top and bottom disagreed by
     1.875px with nothing in the render to justify it. */
  padding: 10px 8px; text-align: center; text-decoration: none;
  background: transparent; border: 0; border-radius: var(--r-card);
  font-size: .76rem; line-height: 1.25;
  transition: background .14s, box-shadow .14s;
}
.palcard[hidden] { display: none; }
/* Each card carries its own --tint, so a grid of 299 pals is nine palettes
   rather than 299 identical grey wells. */
.palcard img {
  width: 48px; height: 48px; border-radius: 22%;
  background: radial-gradient(circle at 50% 122%, color-mix(in srgb, var(--tint) 30%, var(--art)) 0%, var(--art) 82%);
}
.palcard-lg img { width: 64px; height: 64px; }
.pcname { font-weight: 700; color: var(--text); }
.pcdex { font: 600 .66rem/1 var(--num); color: var(--dim); }
.pczone { font: 600 .62rem/1.3 var(--f-body); color: var(--dim); }
.palcard-lg .badges { justify-content: center; margin-top: 2px; }

.content a.palcard { color: var(--dim); text-decoration: none; }
.content a.palcard:hover { background: var(--surface); box-shadow: var(--shadow-sm); }

/* ══ 13 · combo cards ═══════════════════════════════════════════════════ */
.combolist {
  list-style: none; padding: 0; margin: 6px 0 16px;
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
}
.combolist.is-empty::after {
  content: 'No match in the pairs shown — open the planner above for the full set.';
  grid-column: 1 / -1; padding: 24px 0; text-align: center;
  color: var(--dim); font-size: .86rem;
}
.combo {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: end;
  /* Asymmetric on purpose: .cotag is absolutely positioned across the top of
     the card, so the top inset is the tag's slot. The sides used to be 7.5px
     — under the 8px edge floor, and a third of the top — which pinned the two
     parent tiles against the card walls. */
  gap: 4px; padding: 24px 12px 12px;
  background: var(--surface); border: 0; border-radius: var(--r-card);
  box-shadow: var(--shadow-sm);
  transition: transform .14s cubic-bezier(.2,.7,.3,1), box-shadow .14s;
}
.combo[hidden] { display: none; }
.combo:hover { transform: translateY(-2px); box-shadow: var(--sh-hover); }
.cotag {
  position: absolute; top: 8px; left: 12px; right: 12px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.combo.is-easy .cotag { color: var(--good-text); }
.combo.is-hard .cotag { color: var(--warn-text); }
.combo .cop {
  align-self: center; width: 1.4rem; height: 1.4rem; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--surface-2); color: var(--dim);
  font: 700 .8rem/1 var(--num); flex: none;
}

.cslot {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  flex: 1 1 0; min-width: 0; padding: 4px 2px;
  text-decoration: none; border-radius: var(--r-sq);
  transition: background .14s;
}
.content a.cslot { color: var(--dim); text-decoration: none; }
.content a.cslot:hover { background: var(--surface-2); color: var(--text); }
.cslot img {
  width: 52px; height: 52px; border-radius: 22%;
  background: radial-gradient(circle at 50% 122%, color-mix(in srgb, var(--tint) 22%, var(--art)) 0%, var(--art) 84%);
}
.csname {
  font: 700 .74rem/1.2 var(--f-body); color: var(--text);
  text-align: center; overflow-wrap: anywhere;
}
.csmeta { font: 600 .64rem/1 var(--num); color: var(--dim); }
.cslot.is-child .csname { color: var(--accent-text); }
.cop { font: 600 .95rem/1 var(--num); color: var(--dim); }

/* The hero pair is the same component one size up. It carries no .cotag, so
   it takes an even inset — and a horizontal one at least as large as the
   vertical, which is the ratio every other card in the set uses. */
.combo.combo-hero {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: 8px; padding: 18px 20px; margin: 6px 0 8px;
}
.combo.combo-hero:hover { transform: none; box-shadow: var(--shadow-sm); }
.combo-hero .cslot img { width: 72px; height: 72px; }
.combo-hero .csname { font-size: .84rem; }
.combo-hero .cop { width: 1.7rem; height: 1.7rem; font-size: .95rem; }
/* 'wild · Lv 1' is one token — it never breaks mid-phrase, it shrinks. */
.combo-hero .csmeta { white-space: nowrap; }
/* Three pal names and two operators across a 291px card: the inset has to
   come off the sides before it comes off the type, or a name like "Blazamut"
   breaks mid-word. Symmetric, so the trio still sits square in the card. */
@media (max-width: 480px) {
  .combo.combo-hero { padding: 12px; gap: 6px; }
  .combo-hero .csmeta { font-size: .58rem; }
}

/* ══ 14 · route: numbered steps ═════════════════════════════════════════ */
ol.route { list-style: none; padding: 0; margin: 8px 0 14px; }
.rstep-row { display: flex; align-items: center; gap: 12px; padding: 8px 4px; }
.rstep-row + .rstep-row { border-top: 1px solid var(--line-soft); }
.rstep {
  flex: none; display: grid; place-items: center;
  width: 1.7rem; height: 1.7rem; border-radius: 50%;
  background: var(--accent); color: var(--accent-ink);
  font: 700 .78rem/1 var(--num);
}
.rchain { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; flex: 1 1 0; min-width: 0; }
.rchain .cslot { flex: 0 1 auto; min-width: 4.6rem; }
.rchain .cslot img { width: 46px; height: 46px; }

/* ══ 15 · lineage diagram (6a) ══════════════════════════════════════════ */
.lineage {
  background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--sh);
  padding: 16px 20px 18px; margin: 20px 0 26px;
}
.lin-head { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; flex-wrap: wrap; }
.lin-head b {
  font: var(--dis-w) 1.02rem/1.2 var(--f-dis); letter-spacing: var(--dis-ls); color: var(--text);
}
.lin-head span {
  font: 700 .68rem/1 var(--num); letter-spacing: .1em; text-transform: uppercase; color: var(--dim);
}
/* The design's column captions: "pairs that make it" / "what it makes next". */
.lin-collab {
  display: block; margin-bottom: 8px;
  font: 700 .6rem/1.3 var(--num); letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint);
}
.lin-col.is-right .lin-collab { text-align: right; }
.lin-grid {
  display: grid; grid-template-columns: 1fr 150px 1fr;
  align-items: center; gap: 0; margin-top: 14px;
}
/* The egg stays vertically centred; the two columns top-align so their
   PAIRS / MAKES NEXT captions share one line whatever the row counts are. */
.lin-col { display: flex; flex-direction: column; gap: 14px; min-width: 0; align-self: start; }
.lin-col.is-right { align-items: flex-end; }

.linrow { display: flex; align-items: center; gap: 0; }
.linthread { flex: 1; min-width: 18px; border-top: 3px dotted var(--line-dash); }
/* --page, not --surface-2: the parent names inside are --link, which is
   4.34:1 on --surface-2 in daylight and 4.63:1 on --page. The chip still
   reads as inset because .lineage itself is --surface. */
.linpair {
  display: inline-flex; align-items: center; gap: 10px; min-width: 0;
  background: var(--page); border: 1.5px solid var(--line);
  border-radius: var(--r-pill); padding: 6px 16px 6px 8px;
  text-decoration: none; transition: border-color .14s, background .14s;
}
.content a.linpair { color: var(--body); }
.content a.linpair:hover { border-color: var(--tint); background: var(--surface); }
.linfaces { display: flex; flex: none; }
.linface {
  width: 36px; height: 36px; border-radius: 50%; background: var(--art);
  display: grid; place-items: center; box-shadow: 0 2px 6px rgb(0 0 0 / .16);
}
.linface + .linface { margin-left: -10px; }
.linface img { width: 28px; height: 28px; object-fit: contain; }
.lintext { display: flex; flex-direction: column; min-width: 0; }
.lintext b { font: 700 .78rem/1.2 var(--f-body); color: var(--text); overflow-wrap: anywhere; }
.lintext span { font: 600 .66rem/1.3 var(--f-body); color: var(--dim); }

.lin-egg { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.lin-shell { position: relative; width: 122px; height: 140px; }
.lin-shell::before, .lin-shell::after {
  content: ""; position: absolute; border-radius: 50% 50% 46% 46% / 62% 62% 38% 38%;
}
.lin-shell::before { inset: 0; background: var(--tint); opacity: .22; }
.lin-shell::after {
  inset: 6px;
  background: radial-gradient(circle at 50% 125%, color-mix(in srgb, var(--tint) 28%, var(--surface)) 0%, var(--surface) 82%);
  box-shadow: var(--sh);
}
.lin-shell img {
  position: relative; z-index: 1; width: 86px; height: 86px;
  object-fit: contain; margin: 28px 18px;
}
.lin-egg b { font: var(--dis-w) .95rem/1 var(--f-dis); letter-spacing: var(--dis-ls); color: var(--text); }
.linnote { margin: 12px 0 0; font-size: .78rem; color: var(--dim); }
.linnote a { color: var(--link); }

/* Degraded states collapse rather than render an empty frame. */
.lineage.is-catch .lin-grid { grid-template-columns: 1fr; justify-items: center; }
.lineage.is-catch .lin-col { display: none; }
@media (max-width: 900px) {
  .lin-grid { grid-template-columns: 1fr; gap: 16px; justify-items: stretch; }
  .lin-col.is-right { align-items: stretch; }
  /* Right-alignment is the desktop MIRROR. Stacked, it left the second
     column's caption floating 111px away from the rows it labels and from the
     single left edge the stack otherwise shares. */
  .lin-col.is-right .lin-collab { text-align: left; }
  .linthread { display: none; }
}

/* Breeding chain — the pre-lineage markup, kept working and re-tokenised. */
.chain { margin: 0 0 26px; }
.chainlab { margin: 0 0 12px; }
.chainrow {
  position: relative;
  display: flex; align-items: stretch; justify-content: center; gap: 6px; flex-wrap: wrap;
  padding: 16px 14px;
  background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--sh);
}
.chainpal {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 8px 8px; border-radius: var(--r-card);
  text-decoration: none; min-width: 5.2rem;
  background: var(--surface-2);
}
.content a.chainpal { color: var(--dim); }
.content a.chainpal:hover { background: var(--surface-3); }
.chainpal img {
  width: 62px; height: 62px; border-radius: 22%;
  background: radial-gradient(circle at 50% 122%, color-mix(in srgb, var(--tint) 26%, var(--art)) 0%, var(--art) 84%);
}
.cpname { font: 700 .74rem/1.15 var(--f-body); color: var(--text); text-align: center; }
.cpel { transform: scale(.8); }
.chainpal.is-target { background: var(--accent-soft); box-shadow: inset 0 0 0 1.5px var(--accent-line); }
.chainpal.is-target .cpname { color: var(--accent-text); }
.chainarrow { align-self: center; font: 600 1rem/1 var(--num); color: var(--dim); }
.chainnote { margin: 12px 0 0; font-size: .78rem; color: var(--dim); }

/* ══ 16 · podium (4c) ═══════════════════════════════════════════════════
   The mockup's white-on-hill fails AA, so the plinths use the accent pair,
   which is safe in both themes and is still "the green plinth". */
/* Pulled up so the plinths stand on the hill line rather than on the paper
   below it — the podium is part of the hero picture, not a widget under it. */
.podium {
  position: relative; z-index: 2;
  display: flex; align-items: flex-end; justify-content: center;
  gap: clamp(10px, 2vw, 24px); margin-top: -90px;
  padding: 0 var(--shell);
}
.pod { display: flex; flex-direction: column; align-items: center; text-decoration: none; }
/* Frame-filling art reads as a hard square against the scenery; the same
   proportional mask the pal cards use turns it into a deliberate tile. */
.pod img {
  width: var(--pod-art, 108px); height: var(--pod-art, 108px); object-fit: contain;
  margin-bottom: -14px; position: relative; z-index: 2; border-radius: 18%;
  filter: drop-shadow(0 16px 16px rgb(0 0 0 / .28));
}
/* One step, three risers. It used to fall 30px then 20px, so #3 read as
   nearly level with #2 — three risers of one podium is the clearest possible
   case of "equal things get equal gaps". */
.pod-1 { --pod-art: 130px; --pod-h: 168px; order: 2; }
.pod-2 { --pod-h: 140px; order: 1; }
.pod-3 { --pod-h: 112px; order: 3; }
/* Flat --accent-hi, not a gradient: it is the darker green in daylight and
   the lighter one after dark, so --accent-ink clears 4.5:1 on the whole
   plinth in both themes instead of only at one end of a ramp. */
/* The riser is a fixed height and the text used to be top-aligned against
   padding-bottom:0, so ONE component had three different bottom insets — 92px
   of empty green under #1 against 42px under #3, which is why the podium read
   as three unrelated blocks instead of one object. The text now sits on the
   plinth, one inset from the bottom on all three. */
.pod-block {
  width: min(220px, 24vw); height: var(--pod-h, 130px);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: var(--accent-hi);
  color: var(--accent-ink);
  box-shadow: inset 0 3px 0 rgb(255 255 255 / .22);
  display: flex; flex-direction: column; align-items: center;
  justify-content: flex-end; gap: 6px;
  padding: 16px 10px 16px; text-align: center;
}
.pod-name { font: var(--dis-w) 1.05rem/1.1 var(--f-dis); letter-spacing: var(--dis-ls); color: inherit; }
.pod-stat {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface); color: var(--text);
  border-radius: var(--r-pill); padding: 4px 12px; font: 700 .74rem/1 var(--f-body);
}
.pod-stat img { width: 15px; height: 15px; }
.pod-sub { font: 700 .7rem/1.3 var(--f-body); color: var(--accent-ink); }
.pod-crown {
  background: var(--yellow); color: var(--yellow-ink);
  border-radius: var(--r-pill); padding: 4px 12px; margin-bottom: 8px;
  font: 800 .68rem/1 var(--f-body); box-shadow: var(--shadow-sm);
}
/* The base runs to the content column's own edges. Its old clamp maximum was
   an arbitrary 60px inset that aligned with nothing: 60px inboard of the
   column every other block on the page starts at, and 118px outboard of the
   podium it is the base for. This element already sits inside .content's
   padding, so it needs no inset of its own. */
.pod-plinth {
  height: 18px; margin: 0;
  border-radius: var(--r-card) var(--r-card) 0 0;
  background: var(--accent-hi); position: relative; z-index: 2;
}
/* The podium is the only place ranks 1-3 appear — the compared list under it
   starts at 4 — so on a narrow screen it reflows into three rows rather than
   being hidden, which would leave a phone reading a "ten best" that opens at
   #4 with an empty plinth above it. */
@media (max-width: 700px) {
  .podium {
    display: grid; gap: 8px; align-items: stretch;
    margin-top: -36px; padding: 0 var(--shell);
  }
  .pod {
    flex-direction: row; align-items: center; gap: 12px; flex-wrap: wrap;
    background: var(--accent-hi); color: var(--accent-ink);
    border-radius: var(--r-card); padding: 8px 12px;
    box-shadow: inset 0 3px 0 rgb(255 255 255 / .22);
  }
  .pod-1 { --pod-art: 60px; order: 1; }
  .pod-2 { --pod-art: 52px; order: 2; }
  .pod-3 { --pod-art: 52px; order: 3; }
  .pod img { order: 1; margin: 0; }
  .pod-block {
    order: 2; flex: 1 1 8rem; width: auto; height: auto;
    background: none; box-shadow: none; border-radius: 0; padding: 0;
    align-items: flex-start; text-align: left; gap: 4px;
  }
  .pod-crown { order: 3; margin: 0 0 0 auto; }
  .pod-plinth { display: none; }
}

/* ══ 17 · tier pyramid + "the bin" (6d) ═════════════════════════════════ */
.tierrule { display: flex; align-items: center; gap: 12px; margin: 24px 0 14px; }
.tierrule i { flex: 1; height: 0; border-top: 2px solid var(--tier-line, var(--line)); }
.tierrule b {
  font: 700 .66rem/1 var(--num); letter-spacing: .1em; text-transform: uppercase;
  color: var(--tier-ink, var(--dim));
}
.t-hi  { --tier-line: var(--violet-line); --tier-ink: var(--violet-text); --tier-bg: var(--violet-bg); }
.t-mid { --tier-line: var(--accent-line); --tier-ink: var(--accent-text-aa); --tier-bg: var(--accent-soft); }
.t-lo  { --tier-line: var(--line);        --tier-ink: var(--dim);       --tier-bg: var(--surface-2); }

.tiergrid { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
.tiergrid.is-hero { grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); }
.tcard {
  background: var(--surface); border-radius: var(--r-card); box-shadow: var(--shadow-sm);
  padding: var(--card-pad); display: flex; flex-direction: column; gap: 6px;
  text-decoration: none;
  transition: box-shadow .14s, transform .14s cubic-bezier(.2,.7,.3,1);
}
.content a.tcard { color: var(--body); }
.content a.tcard:hover { box-shadow: var(--sh-hover); transform: translateY(-2px); }
.tcard-top { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.tcard-top b { font: var(--dis-w) .95rem/1.2 var(--f-dis); letter-spacing: var(--dis-ls); color: var(--text); }
.tpill {
  flex: none; background: var(--tier-bg, var(--surface-2)); color: var(--tier-ink, var(--dim));
  border-radius: var(--r-pill); padding: 4px 8px; font: 800 .62rem/1.3 var(--f-body);
}
.tcard-meta { display: flex; align-items: center; gap: 8px; font: 600 .72rem/1 var(--num); color: var(--dim); }

.bin {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  background: var(--bad-soft); border: 2px dashed var(--bad-line);
  border-radius: var(--r-lg); padding: 16px 16px; margin-top: 16px;
}
.bin-lab {
  flex: none; writing-mode: vertical-rl; transform: rotate(180deg);
  font: 800 .6rem/1 var(--num); letter-spacing: .14em; color: var(--bad);
}
.bin-list { display: flex; flex-wrap: wrap; gap: 8px; flex: 1 1 20rem; }
.binchip {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--surface); border: 1.5px solid var(--bad-line);
  border-radius: var(--r-pill); padding: 6px 12px; text-decoration: none;
}
.binchip b { font: 700 .76rem/1 var(--f-body); color: var(--bad); text-decoration: line-through; }
.binchip span { font: 600 .68rem/1 var(--num); color: var(--dim); }
.bin-why { flex: none; max-width: 28ch; font-size: .78rem; line-height: 1.5; color: var(--body); }
@media (max-width: 640px) { .bin-lab { writing-mode: horizontal-tb; transform: none; } }

/* ══ 18 · % bar rows (6c) ═══════════════════════════════════════════════
   .barlist had NO rule at all in either sheet, so it contributed nothing
   below itself and `.content p` gives the following paragraph no top margin —
   the next line of prose started exactly on the last white row's bottom edge,
   with the row's shadow behind the text. It takes .tablewrap's rhythm, which
   is the only reason tables escaped the same bug. */
.barlist { margin: 8px 0 20px; }
.barrow {
  /* The label column ran to a 16rem ceiling and wrapped, which set a 65px row
     height around a 12px track and a 12px number — the row read hollow rather
     than padded. */
  display: grid; grid-template-columns: minmax(8rem, 22rem) minmax(4rem, 1fr) auto;
  align-items: center; gap: 14px; padding: var(--row-pad);
  background: var(--surface); border-radius: var(--r-card); box-shadow: var(--shadow-sm);
}
.barrow + .barrow { margin-top: var(--row-gap); }
.bartrack { height: 12px; border-radius: 6px; background: var(--surface-3); overflow: hidden; }
.bartrack i { display: block; height: 100%; border-radius: 6px; background: var(--accent); }
.barpct { font: 700 .82rem/1 var(--num); color: var(--text); text-align: right; min-width: 3.2rem; }

/* Table skin: keeps table/thead/tbody/tr/td semantics intact while reading
   as separated white rows. border-spacing, never display:block. */
.content table.rowtable {
  border-collapse: separate; border-spacing: 0 .4rem;
  background: transparent; box-shadow: none; border-radius: 0; overflow: visible;
}
.content .rowtable th {
  background: transparent; border: 0; padding: 0 16px 2px;
  font: 700 .6rem/1 var(--num); letter-spacing: .12em; text-transform: uppercase; color: var(--dim);
}
.content .rowtable td {
  background: var(--surface); border: 0; padding: 12px 16px;
  vertical-align: middle; white-space: normal;
}
.content .rowtable tbody tr:hover td { background: var(--surface-2); }
.rowtable tr > td:first-child { border-radius: var(--r-card) 0 0 var(--r-card); }
.rowtable tr > td:last-child  { border-radius: 0 var(--r-card) var(--r-card) 0; }
.content .rowtable tr.slothead td {
  background: transparent; border-radius: 0; padding: 16px 16px 4px;
  font: 700 .62rem/1 var(--num); letter-spacing: .12em; text-transform: uppercase; color: var(--dim);
}

/* The chance cell: track + fill + number, right-aligned. */
.oddcell { display: flex; align-items: center; gap: 12px; justify-content: flex-end; }
.oddtrack {
  flex: 1 1 6rem; min-width: 3rem; max-width: 14rem; height: 12px;
  border-radius: 6px; background: var(--surface-3); overflow: hidden;
}
.oddbar { display: block; border-radius: 6px; background: var(--tint); }
.oddtrack .oddbar { height: 100%; }
/* The pre-track spelling — a bare bar sitting straight in the cell. */
.oddcell > .oddbar { height: 12px; flex: none; }
.oddcell > strong { font: 700 .84rem/1 var(--num); min-width: 3.2rem; text-align: right; }
.el-bar-neutral  { background: var(--el-neutral); }
.el-bar-fire     { background: var(--el-fire); }
.el-bar-water    { background: var(--el-water); }
.el-bar-grass    { background: var(--el-grass); }
.el-bar-electric { background: var(--el-electric); }
.el-bar-ice      { background: var(--el-ice); }
.el-bar-ground   { background: var(--el-ground); }
.el-bar-dark     { background: var(--el-dark); }
.el-bar-dragon   { background: var(--el-dragon); }

/* ══ 19 · portrait list rows (6b) ═══════════════════════════════════════ */
.plist { display: grid; gap: var(--row-gap); margin: 10px 0 20px; }
/* The work-hub top ten is a real <ol> so the ranking is extractable as a list
   snippet. The rank digit used to be hand-written into a .prank span, which
   put a number where the pal's name should be the first text in the row; it
   is the list counter now, styled exactly as .prank was. Beats .content ol
   on source order, not on specificity, so it stays below that rule. */
ol.plist { list-style: none; padding: 0; counter-reset: prow; }
ol.plist > li { margin: 0; counter-increment: prow; }
ol.plist > li > .prow::before {
  content: counter(prow); flex: none; width: 1.6rem; text-align: right;
  font: var(--dis-w) .95rem/1 var(--f-dis); color: var(--dim);
}
.prow {
  display: flex; align-items: center; gap: 14px;
  padding: var(--row-pad); background: var(--surface);
  border-radius: var(--r-card); box-shadow: var(--shadow-sm);
  text-decoration: none; transition: box-shadow .14s;
}
.prow[hidden] { display: none; }
.content a.prow { color: var(--body); }
.content a.prow:hover { box-shadow: var(--sh-hover); }
.pface {
  flex: none; width: 44px; height: 44px; border-radius: 50%;
  background: radial-gradient(circle at 50% 120%, color-mix(in srgb, var(--tint) 34%, var(--art)) 0%, var(--art) 80%);
  display: grid; place-items: center;
}
.pface img { width: 34px; height: 34px; object-fit: contain; }
/* An ITEM in the slot, not a pal. Two changes, both because the art is a
   different shape: the well loses its circle (a square inventory sprite in a
   round hole crops at the corners and reads as a mistake), and it loses the
   element-tinted radial — an item has no element, so the tint was the page's
   colour bleeding onto an object that has nothing to do with it. Flat, square,
   quiet, so the item reads as the subject rather than as a pal's accessory. */
.pface.is-item {
  border-radius: var(--r-sm);
  background: var(--surface-2);
}
.pface.is-item img { width: 32px; height: 32px; }
/* Same, in the loot tables, where the icon is the <img> itself. */
.srcpal img.is-item {
  border-radius: var(--r-sm);
  background: var(--surface-2);
  object-fit: contain; padding: 2px;
}
.pname {
  flex: 0 1 auto; min-width: 6.5rem; overflow-wrap: anywhere;
  font: 700 .86rem/1.2 var(--f-body); color: var(--text);
}
.pnote { flex: 1 1 8rem; min-width: 0; font-size: .8rem; line-height: 1.45; color: var(--dim); }
.prank { flex: none; width: 1.6rem; text-align: right; font: var(--dis-w) .95rem/1 var(--f-dis); color: var(--dim); }
.pgo { flex: none; font: 800 .76rem/1 var(--f-body); color: var(--link); text-decoration: none; white-space: nowrap; }
/* On a phone the row folds instead of pushing the page sideways. */
@media (max-width: 640px) {
  .prow { flex-wrap: wrap; gap: 8px 12px; }
  .pname { min-width: 0; }
  .pnote { flex: 1 1 100%; }
  .pgo { margin-left: auto; }
}

/* Table cells re-using the same vocabulary. */
.srctable td { vertical-align: middle; }
.srcpal { display: inline-flex; align-items: center; gap: var(--face-gap); font-weight: 700; color: var(--text); text-decoration: none; }
.srcpal img {
  width: var(--face); height: var(--face); border-radius: 50%;
  background: radial-gradient(circle at 50% 120%, color-mix(in srgb, var(--tint) 30%, var(--art)) 0%, var(--art) 82%);
}
/* Rows whose item has no face reserve the slot anyway, so every name in a
   table shares one left edge. */
.srcpal .iph { width: var(--face); height: var(--face); flex: none; }
.srcrate { color: var(--dim); font: 600 .74rem/1 var(--num); }
.tablewrap td.tpal a { display: flex; align-items: center; gap: 10px; color: var(--text); font-weight: 700; }
.tablewrap td.tpal img { border-radius: 50%; background: var(--art); }

.castrow { display: flex; gap: 4px; }
.castrow img { border-radius: 50%; background: var(--art); }
.castrow-stack { display: inline-flex; gap: 0; vertical-align: middle; }
.castrow-stack img { margin-left: -10px; border-radius: 50%; background: var(--art); box-shadow: 0 0 0 2px var(--surface); }
.castrow-stack img:first-child { margin-left: 0; }
.castmore { font: 600 .72rem/1 var(--num); color: var(--dim); margin-left: 8px; }

/* ══ 20 · magazine lead card + hub cards (4d) ═══════════════════════════ */
.leadcard {
  position: relative; overflow: hidden; text-decoration: none;
  display: grid; grid-template-columns: minmax(0, 280px) minmax(0, 1fr);
  gap: clamp(16px, 3vw, 28px); align-items: center;
  background: var(--panel); color: var(--on-panel);
  border-radius: var(--r-lg); padding: 26px 30px; margin: 18px 0 16px;
  transition: box-shadow .14s;
}
.content a.leadcard { color: var(--on-panel); }
.content a.leadcard:hover { box-shadow: var(--sh-hover); }
.leadcard::before {
  content: ""; position: absolute; right: -100px; top: -130px;
  width: 420px; height: 420px; border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle, color-mix(in srgb, var(--on-panel) 12%, transparent) 0%, transparent 70%);
}
.leadcard img {
  position: relative; width: 100%; max-width: 200px; margin: 0 auto; display: block;
  border-radius: var(--r-card);
  /* Sized to land inside the card's 26px bottom padding. The card clips
     (overflow:hidden, for the ::before blob that hangs off the corner), so a
     20/24 shadow needed 44px against 27px of room and got sliced straight
     across — invisible under a head-crop icon, obvious under a pal standing
     on its own feet. */
  filter: drop-shadow(0 10px 14px rgb(0 0 0 / .45));
}
.lead-body { position: relative; display: flex; flex-direction: column; gap: 10px; }
.lead-body h2, .leadname {
  margin: 0; font: var(--dis-w) clamp(1.3rem, 2.4vw, 1.9rem)/1.08 var(--f-dis);
  letter-spacing: var(--dis-ls); color: var(--on-panel);
}
.content .lead-body p { margin: 0; color: var(--on-panel-2); max-width: 52ch; }
.lead-go { font: 800 .84rem/1 var(--f-body); color: color-mix(in srgb, var(--accent) 60%, var(--on-panel)); }
.lead-tag {
  align-self: flex-start; background: var(--yellow); color: var(--yellow-ink);
  border-radius: var(--r-pill); padding: 4px 12px; font: 800 .66rem/1.3 var(--f-body);
}
@media (max-width: 720px) {
  .leadcard { grid-template-columns: 1fr; padding: 18px 18px 22px; }
  .leadcard img { max-width: 150px; }
}

.hubgrid {
  display: grid; gap: 12px; margin: 12px 0 18px;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
}
/* The guide hub's shelf is four cards; a 2x2 (the 4d mockup) rather than an
   auto-fill 3+1 with an orphan on its own row. */
.page-guidehub .hubgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 620px) { .page-guidehub .hubgrid { grid-template-columns: 1fr; } }
/* The face rides on the left and the text stacks beside it. The markup is a
   flat list of spans whose count varies from card to card, so the face is
   taken out of flow rather than fought with in a grid — the same trick
   .hubcard-art already used for its marquee pal. */
/* Top-aligned, not centred: in a row of hub cards the boxes are equal but
   the descriptions are not, and centring set each title's baseline from its
   own description's line count — three cards in one row, three different
   title heights. */
.hubcard {
  position: relative;
  display: flex; flex-direction: column; justify-content: flex-start; gap: 2px;
  min-height: 80px; padding: var(--card-pad); border-radius: var(--r-card);
  background: var(--surface); box-shadow: var(--shadow-sm); text-decoration: none;
  transition: transform .14s cubic-bezier(.2,.7,.3,1), box-shadow .14s;
}
.hubcard[hidden] { display: none; }
.hubcard > img:first-child,
.hubcard > .castrow:first-child,
.hubcard > .hub-face:first-child {
  position: absolute; left: 16px; top: 50%; transform: translateY(-50%);
}
.hubcard:has(> img:first-child) { padding-left: 80px; }
.hubcard.hubcard-guide:has(> img:first-child) { padding-left: 80px; }
.hubcard.hubcard-el:has(> img:first-child) { padding-left: 88px; }
.hubcard:has(> .castrow:first-child) { padding-left: 106px; }
.hubcard:has(> .hub-face:first-child) { padding-left: 94px; min-height: 90px; }
.content a.hubcard { color: var(--dim); }
.content a.hubcard:hover { transform: translateY(-2px); box-shadow: var(--sh-hover); }
.hubcard img { width: 48px; height: 48px; border-radius: 22%; }
/* Three faces overlap into one mark rather than reading as a strip. */
.hubcard > .castrow { gap: 0; }
.hubcard > .castrow img {
  width: 30px; height: 30px; margin-left: -10px; border-radius: 50%;
  box-shadow: 0 0 0 2px var(--surface);
}
.hubcard > .castrow img:first-child { margin-left: 0; }
.hub-face {
  flex: none; width: 66px; height: 66px; border-radius: var(--r-card);
  background: radial-gradient(circle at 50% 125%, color-mix(in srgb, var(--tint) 38%, var(--art)) 0%, var(--art) 80%);
  display: grid; place-items: center;
}
.hub-face img { width: 50px; height: 50px; object-fit: contain; border-radius: 0; }
/* On a 291px card the desktop 94px gutter is a third of the card reserved for
   a 66px face, and the text column drops to 180px — enough to push one of
   four otherwise-identical cards to three lines and 126px tall. The face
   steps down with the card. */
@media (max-width: 620px) {
  .hub-face { width: 52px; height: 52px; }
  .hub-face img { width: 40px; height: 40px; }
  .hubcard > img:first-child,
  .hubcard > .castrow:first-child,
  .hubcard > .hub-face:first-child { left: 12px; }
  .hubcard:has(> .hub-face:first-child) { padding-left: 76px; min-height: 76px; }
  .hubcard:has(> img:first-child),
  .hubcard.hubcard-guide:has(> img:first-child) { padding-left: 76px; }
  .hubcard.hubcard-el:has(> img:first-child) { padding-left: 84px; }
  .hubcard:has(> .castrow:first-child) { padding-left: 102px; }
}
.hubtag { font: 800 .62rem/1 var(--num); letter-spacing: .1em; text-transform: uppercase; color: var(--dim); }
.hubname { font: var(--dis-w) 1rem/1.15 var(--f-dis); letter-spacing: var(--dis-ls); color: var(--text); }
.hubn { font: 600 .78rem/1.35 var(--f-body); color: var(--dim); }
.hubtop { font: 600 .68rem/1.3 var(--f-body); color: var(--dim); }

/* Hub-card variants: guide mascots, work-hub star pals, element bands. */
.hubcard-guide img { width: 50px; height: 50px; border-radius: var(--r-sq); background: var(--art); }
/* Both art gutters are one slot width. They used to be 76.875px and 75px —
   one 0.125rem step apart, which is the difference the snap step was too
   coarse to resolve — so the text block sat off centre between them. */
.hubcard-art { position: relative; padding-right: 80px; }
.hubcard-art .hubpal {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  width: 64px; height: 64px; border-radius: 22%;
}
/* The element grid is the loud one: the full band, with --on-el ink. */
.hubcard.hubcard-el { background: var(--dex-tint); box-shadow: var(--sh); }
.hubcard.hubcard-el img { width: 56px; height: 56px; }
.hubcard.hubcard-el .hubname,
.hubcard.hubcard-el .hubn,
.hubcard.hubcard-el .hubtag { color: var(--on-el); }
.content a.hubcard.hubcard-el { color: var(--on-el); }
.hubcard-el .hub-face { background: var(--dex-tint); }

/* Cross-link riding a section heading. */
h2 .elcross { font: 600 .78rem/1.3 var(--f-body); margin-left: 12px; color: var(--link); text-decoration: none; }
h2 .elcross:hover { text-decoration: underline; }

/* ══ 21 · info cards + callouts ═════════════════════════════════════════ */
.infocard {
  border-radius: var(--r-card); padding: 16px 20px;
  display: flex; flex-direction: column; gap: 8px; background: var(--info-bg);
}
.infocard > b, .infocard .ihead { font: 700 .84rem/1.3 var(--f-body); color: var(--info-text); }
.content .infocard p { margin: 0; font-size: .84rem; line-height: 1.55; color: var(--body); }
.infocard.is-violet { background: var(--violet-bg); }
.infocard.is-violet > b, .infocard.is-violet .ihead { color: var(--violet-text); }
.infocard.is-amber { background: var(--amber-bg); }
.infocard.is-amber > b, .infocard.is-amber .ihead { color: var(--warn-text); }
.infocard.is-plain { background: var(--surface); box-shadow: var(--sh); }
.infocard.is-plain > b, .infocard.is-plain .ihead { color: var(--text); }
.infogrid {
  display: grid; gap: 12px; margin: 16px 0;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}

.content .pull, .callout {
  margin: 16px 0; padding: 14px 16px;
  background: var(--amber-bg); border: 1px solid var(--amber-line);
  border-radius: var(--r-card); color: var(--body); font-size: .9rem; line-height: 1.6;
}
.content .pull strong, .callout b { color: var(--warn-text); }

/* ══ 22 · type matchups ═════════════════════════════════════════════════ */
.matchups {
  display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  margin: 10px 0 12px;
}
.mu { display: flex; flex-direction: column; gap: 8px; padding: var(--card-pad); border-radius: var(--r-card); }
.mubadges { display: flex; flex-wrap: wrap; gap: 4px; }
.mu-good { background: var(--accent-soft); }
.mu-good .mulabel { color: var(--accent-text-aa); }
.mu-bad { background: var(--bad-bg); }
.mu-bad .mulabel { color: var(--bad); }

/* ══ 23 · work suitability ══════════════════════════════════════════════ */
.workrow { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.worktile {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 2px; min-height: 2.9rem;
  background: transparent; border: 0; border-radius: var(--r-card);
}
.wicon { position: relative; flex: none; width: 28px; height: 28px; }
.wicon img { width: 28px; height: 28px; display: block; }
.wicon b {
  position: absolute; right: -5px; bottom: -4px;
  min-width: 15px; height: 15px; padding: 0 4px;
  display: grid; place-items: center;
  background: var(--accent); color: var(--accent-ink);
  border-radius: 8px; font: 700 .62rem/1 var(--num);
}
.wlabel {
  font: 700 .76rem/1.2 var(--f-body); color: var(--text);
  flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere;
}
.worktile-link {
  text-decoration: none; background: var(--surface); box-shadow: var(--shadow-sm);
  padding: 10px 12px; transition: box-shadow .14s;
}
.content a.worktile-link { color: var(--dim); }
.content a.worktile-link:hover { box-shadow: var(--sh-hover); }
.worktile-link .wlabel small { display: block; font: 600 .68rem/1.3 var(--f-body); color: var(--dim); }

/* ══ 24 · stat bars ═════════════════════════════════════════════════════ */
.statbars { display: grid; gap: 6px; }
/* margin:0 is load-bearing: styles.css's app .statbar carries `margin: .25rem
   0`, which this rule re-declares display/gap over but never cleared — so the
   grid's 6px gap was silently a 13.5px one, and the only fractional spacing
   left inside a dex column. The list owns the rhythm; the row owns none. */
.statbar { display: grid; grid-template-columns: 5.8rem 2.4rem 1fr 4.6rem; align-items: center; gap: 8px; margin: 0; }
.sblabel { font-size: .76rem; color: var(--dim); }
.sbval-l { font: 700 .8rem/1 var(--num); color: var(--text); text-align: right; }
.sbtrack { height: 8px; border-radius: 4px; background: var(--surface-3); overflow: hidden; }
.sbtrack i { display: block; height: 100%; border-radius: 4px; background: var(--accent); }
.sbtag { text-align: right; }
/* The design's fixed stat identities — HP green, Attack red, Defence blue —
   exactly the tokens the app drawer's .statbar.st-* rules use, so a bar means
   the same thing on both shells. Everything past the triad stays the neutral
   accent; the percentile verdict is the bar length and the tier-coloured
   WEAK/ELITE word, never the fill. */
/* The same --stat-* triad the app declares in styles.css, so a red ATK bar
   means the same thing in the drawer and on a generated page. */
.statbar.st-hp .sbtrack i { background: var(--stat-hp, var(--el-grass)); }
.statbar.st-atk .sbtrack i { background: var(--stat-atk, var(--bad)); }
.statbar.st-def .sbtrack i { background: var(--stat-def, var(--el-water)); }
.statbar.is-t4 .sbtag { color: var(--good-text); }
.statbar.is-t3 .sbtag { color: color-mix(in srgb, var(--good-text) 62%, var(--warn-text)); }
.statbar.is-t2 .sbtag { color: var(--warn-text); }
.statbar.is-t1 .sbtag { color: var(--bad); }
.statbar.is-none .sblabel, .statbar.is-none .sbval-l { color: var(--dim); }
.statbar.is-none .sbtag { display: none; }

/* ══ 25 · active skills ═════════════════════════════════════════════════ */
ul.moves { list-style: none; padding: 0; margin: 6px 0 10px; display: grid; gap: 4px; }
.move {
  display: grid; align-items: center; gap: 10px;
  grid-template-columns: 3rem 5.2rem minmax(6rem, 1fr) minmax(5rem, 9rem) 2.6rem;
  padding: 8px 12px;
  background: transparent; border: 0; border-bottom: 1px solid var(--line-soft);
  border-radius: 0; font-size: .8rem;
}
.mlvl { font: 700 .7rem/1 var(--num); color: var(--dim); }
.mname { font-weight: 700; color: var(--text); min-width: 0; overflow-wrap: anywhere; }
a.mname, .content a.mname { color: var(--link); text-decoration: none; }
a.mname:hover { text-decoration: underline; }
.mpower { display: flex; align-items: center; gap: 8px; }
.mpower i { flex: 1 1 auto; height: 8px; border-radius: 4px; background: var(--tint); min-width: 4px; }
.mpower b { flex: none; font: 700 .74rem/1 var(--num); color: var(--dim); }
.mcd { font: 600 .72rem/1 var(--num); color: var(--dim); text-align: right; }

@media (max-width: 640px) {
  /* Four tracks, two rows: name gets the full top row past the badges, the
     power bar the second, and the cooldown ITS OWN cell at the bar's right —
     the old negative-margin pull-up printed it on top of the power number. */
  .move { grid-template-columns: 2.6rem 4.6rem 1fr auto; row-gap: 4px; }
  .mname { grid-column: 3 / 5; }
  .mpower { grid-column: 2 / 4; grid-row: 2; }
  .mcd { grid-column: 4; grid-row: 2; text-align: right; align-self: center; }
}

/* ══ 26 · code blocks + tables ══════════════════════════════════════════
   Nothing in either sheet had ever styled <pre>, so the save-file guide's
   most important content — the four paths — rendered as UA monospace on a
   1em margin off the page's rhythm, and on a phone an 850px unwrapped Linux
   path in a 291px column pushed the whole DOCUMENT sideways by 548px. It is a
   card like every other block here, and IT scrolls instead of the page. */
.content pre {
  margin: 8px 0 20px; padding: 12px 16px;
  background: var(--surface); border-radius: var(--r-card); box-shadow: var(--shadow-sm);
  overflow-x: auto; -webkit-overflow-scrolling: touch;
}
.content pre code {
  background: none; padding: 0; border-radius: 0;
  /* The block scrolls, so the path stays one readable line inside it. */
  overflow-wrap: normal; white-space: pre;
}

.tablewrap { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 8px 0 20px; }
.content table {
  width: 100%; border-collapse: collapse; font-size: .86rem;
  background: var(--surface); border: 0; border-radius: var(--r-card); overflow: hidden;
  box-shadow: var(--shadow-sm);
}
.content th {
  text-align: left; padding: 12px 16px;
  font: 700 .62rem/1 var(--num); letter-spacing: .12em; text-transform: uppercase;
  color: var(--dim); background: transparent; border-bottom: 1px solid var(--line);
}
.content td { text-align: left; padding: 10px 16px; border-bottom: 1px solid var(--line-soft); }
.content tbody tr:last-child td { border-bottom: 0; }
.content tbody tr:hover { background: var(--surface-2); }
.content .dim { color: var(--dim); }
/* Numbers are set in the mono face everywhere else in the system; table
   body cells are no exception. table.odds is numeric from column two on,
   and any cell can opt in with .num (the cake ingredients table does). */
.content table.odds td:nth-child(n+2), .content td.num {
  font-family: var(--num); font-size: .84rem;
}
.content table a { color: var(--link); text-decoration: none; }
.content table a:hover { text-decoration: underline; }
.slothead td {
  font: 700 .62rem/1.2 var(--num); text-transform: uppercase; letter-spacing: .12em;
  color: var(--dim); background: var(--surface-2); padding-top: 12px;
}

/* ══ 27 · drops, tiers, schematic chips ═════════════════════════════════ */
.droplist { list-style: none; margin: 6px 0 0; padding: 0; }
.droplist li { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 4px 0; }
.droplist a { color: var(--link); font-weight: 700; text-decoration: none; }
.droplist a:hover { text-decoration: underline; }
.dropqty { font: 600 .74rem/1 var(--num); color: var(--dim); white-space: nowrap; }
/* Inline after the item name, the tier chips started wherever that name
   happened to end — 28 strips scattered across 137px of a column in which
   every other value is hard-aligned. They get their own line, indented to the
   name they belong to. */
.tiers {
  display: flex; flex-wrap: wrap; gap: 4px;
  margin: 6px 0 0 calc(var(--face) + var(--face-gap));
}
.tierchip {
  font: 600 .7rem/1.3 var(--num); color: var(--dim);
  background: var(--accent-soft); border-radius: var(--r-pill); padding: 2px 8px;
  white-space: nowrap;
}
.tierchip strong { color: var(--text); }

/* ══ 28 · FAQ ═══════════════════════════════════════════════════════════
   styles.css caps dl.faq at 70ch for the app's own FAQ. On a content page the
   answers are CARDS, and they were stopping 384px (36% of the column) short
   of a CTA band and a table that both run full width — which read as a
   mistake, not as a measure. The measure belongs on the answer text. */
dl.faq { margin: 12px 0 0; max-width: none; }
dl.faq dt {
  margin: 0; padding: 16px 16px 6px;
  background: var(--surface); border-radius: var(--r-card) var(--r-card) 0 0;
  font: var(--dis-w) .98rem/1.35 var(--f-dis); letter-spacing: var(--dis-ls); color: var(--text);
}
.content dl.faq dd {
  margin: 0 0 8px; padding: 0 16px 16px;
  background: var(--surface); border-radius: 0 0 var(--r-card) var(--r-card);
  color: var(--body); font-size: .9rem; line-height: 1.65;
}
/* The CARD runs the column, so it lines up with the CTA band and the tables
   around it; the SENTENCE inside it still stops at the page measure. Padding
   rather than max-width, because the text here is a bare text node and a
   max-width would narrow the white card instead of the line. Collapses to the
   plain inset the moment the column is narrower than the measure. */
.content dl.faq dt, .content dl.faq dd {
  padding-right: max(16px, calc(100% - var(--measure) - 16px));
}
dl.faq dd a { color: var(--link); }

/* ══ 29 · CTA band + the three variants ═════════════════════════════════
   Three visually distinct bands, so the site does not end the same way 480
   times. --accent-2 is navy in daylight and cream in nightfall, and --page
   is its exact inverse in both, so the pair is AA-safe either way. */
/* styles.css's .card is a column with a hover lift and overflow:hidden; the
   CTA band is a row that does not move. Reset those three explicitly. */
.cta.card {
  display: flex; flex-direction: row; align-items: center; gap: 20px; flex-wrap: wrap;
  margin: 40px 0 18px; padding: 20px 24px;
  border: 0; box-shadow: none; border-radius: var(--r-lg);
  background: var(--panel); color: var(--on-panel);
  overflow: visible; transition: none;
}
.cta.card:hover { transform: none; box-shadow: none; border-color: transparent; }
.cta.card h2 { margin: 0; flex: 1 1 18ch; font-size: clamp(1.1rem, 1.7vw, 1.4rem); color: inherit; }
.content .cta.card p { margin: 4px 0 0; flex: 1 1 26ch; color: var(--on-panel-2); font-size: .88rem; }
.cta.card code { background: color-mix(in srgb, var(--on-panel) 15%, transparent); color: inherit; }
.content .cta.card p:last-child { flex: 0 0 auto; margin-left: auto; }
/* Stacked, `margin-left: auto` is not "out to the right of the copy", it is
   "adrift from it" — the button hung 87px right of the heading and body it
   belongs to, flush against the card's padding while nothing else was. */
@media (max-width: 640px) {
  .content .cta.card p:last-child { margin-left: 0; }
}
.content a.cta-btn {
  display: inline-block; padding: 12px 18px; border-radius: var(--r-pill);
  background: var(--btn-fill); color: var(--accent-ink);
  font: 800 .86rem/1 var(--f-body); text-decoration: none; white-space: nowrap;
  box-shadow: inset 0 -2px 0 rgb(0 0 0 / .18);
  transition: background .13s, transform .14s cubic-bezier(.2,.7,.3,1);
}
.content a.cta-btn:hover { background: var(--btn-fill-hover); transform: translateY(-1px); }

.cta.card.cta-hub {
  background: var(--accent-soft); color: var(--text);
  box-shadow: inset 0 0 0 1.5px var(--accent-line);
}
.content .cta.card.cta-hub p { color: var(--body); }
.cta.card.cta-hub code { background: var(--surface); }
.cta.card.cta-farm {
  background: var(--amber-bg); color: var(--text);
  box-shadow: inset 0 0 0 1.5px var(--amber-line);
}
.content .cta.card.cta-farm p { color: var(--body); }
.cta.card.cta-farm code { background: var(--surface); }

/* ══ 30 · dex chrome: prev/next, section nav, tiles ═════════════════════ */
.dexstep { display: flex; justify-content: space-between; gap: 16px; margin: 18px 0 4px; }
/* styles.css's .step is a shared app surface and brings a 1px hairline with
   its shadow. On a content page the card system is shadow-and-no-border —
   .combo, .lineage, .mapframe, .tcard, .hubcard, .prow, .barrow, table.odds
   and .cta.card all measure border-width 0 — so these two chips were the only
   cards on the page carrying both, and read heavier than everything near
   them. Scoped to .content a.step, which is static pages only. */
.content a.step {
  display: flex; align-items: center; gap: 8px;
  color: var(--dim); text-decoration: none;
  padding: 6px 10px; border-radius: var(--r-pill);
  background: var(--surface); border: 0; box-shadow: var(--shadow-sm);
  transition: box-shadow .14s;
}
.content a.step:hover { box-shadow: var(--sh-hover); color: var(--text); }
.step img { width: 40px; height: 40px; border-radius: 50%; background: var(--art); }
.stept { display: flex; flex-direction: column; font: 700 .82rem/1.2 var(--f-body); color: var(--text); }
.stept small { font: 700 .6rem/1.4 var(--num); color: var(--dim); letter-spacing: .08em; text-transform: uppercase; }
.step.next { text-align: right; }
.step.next .stept { align-items: flex-end; }

/* Section nav: anchors, not tabs — nothing is hidden from a crawler. */
.secnav {
  display: flex; gap: 4px; flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
  margin: 18px 0 24px;
  position: sticky; top: 0; z-index: 6;
  background: var(--page);
  padding-top: 6px;
}
.content .secnav a {
  padding: 8px 14px; text-decoration: none;
  font: 700 .78rem/1 var(--f-body); color: var(--dim);
  border-bottom: 2px solid transparent; margin-bottom: -2px;
}
.content .secnav a:hover { color: var(--text); }
.content .secnav a.active { color: var(--text); border-bottom-color: var(--tint); }
/* On a phone the four tabs stay ONE row that scrolls sideways — wrapping
   dropped the last tab under the hairline, where it read as a stray label
   and doubled the sticky bar's height for the whole page. */
@media (max-width: 640px) {
  .secnav { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .secnav::-webkit-scrollbar { display: none; }
  .content .secnav a { flex: none; white-space: nowrap; padding: 8px 12px; }
}

.tiles { display: grid; gap: 8px; margin: 12px 0 8px; grid-template-columns: repeat(auto-fit, minmax(116px, 1fr)); }
.tile {
  padding: 10px 12px; border-radius: var(--r-card);
  background: var(--surface); box-shadow: var(--shadow-sm);
  border-left: 3px solid color-mix(in srgb, var(--tint) 55%, transparent);
  display: flex; flex-direction: column; gap: 2px;
}
.tile-wide { grid-column: span 2; }
.tlab .skew { color: var(--warn-text); letter-spacing: .03em; }
.tval { font: 700 1.05rem/1.15 var(--num); color: var(--text); }
.tval small { font-size: .58em; color: var(--dim); margin-left: 4px; font-weight: 600; }
.tval-sm { font: 700 .82rem/1.3 var(--f-body); }
.tsub { font-size: .68rem; color: var(--dim); }

/* Gender split as one bar: the proportion is the point. */
.genderbar {
  display: flex; height: 10px; border-radius: 5px; overflow: hidden;
  margin: 4px 0 0; background: var(--surface-3);
}
.genderbar i { display: block; height: 100%; }
.genderbar .gm { background: var(--male); }
.genderbar .gf { background: var(--female); }

/* ══ 31 · spawn map ═════════════════════════════════════════════════════ */
.spawnmap { margin: 8px 0 16px; }
.mapframe {
  position: relative; overflow: hidden;
  border-radius: var(--r-lg);
  background: var(--map-sea, var(--surface-2));
  aspect-ratio: 1 / 1;
  box-shadow: var(--sh);
}
.maptiles { position: absolute; inset: 0; }
.maptiles img { position: absolute; width: 50%; height: 50%; display: block; }
.mapframe svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.spawnmap .dots circle { fill: var(--accent); opacity: .42; }
.spawnmap .dots circle.night { fill: var(--el-dark); opacity: .5; }
.mapopen {
  position: absolute; right: .7rem; bottom: .7rem;
  padding: 8px 14px; border-radius: var(--r-pill);
  background: var(--btn-fill); color: var(--accent-ink);
  font: 700 .76rem/1 var(--f-body); text-decoration: none;
}
.content a.mapopen { color: var(--accent-ink); }
.mapopen:hover { background: var(--btn-fill-hover); }
.spawnmap figcaption {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-top: 8px; font-size: .74rem; color: var(--dim);
}
.key { display: inline-flex; align-items: center; gap: 6px; }
.key i { width: 10px; height: 10px; border-radius: 50%; display: block; }
.key .k-day { background: var(--accent); }
.key .k-night { background: var(--el-dark); }
.key-n { font-weight: 700; }

/* ══ 32 · two columns below the fold ════════════════════════════════════ */
.dexcols { display: grid; gap: 30px; align-items: start; }
/* Grid tracks default to a min-content floor; without this the widest combo
   card decides the whole column's width and the page scrolls sideways on
   phones. */
.dexcols > * { min-width: 0; }
@media (min-width: 1000px) {
  .dexcols { grid-template-columns: minmax(260px, 320px) minmax(0, 1fr); }
  /* Sticky, but never taller than the viewport: the column scrolls
     internally instead of hiding its own stat bars for the whole page. */
  .dexcol-side {
    position: sticky; top: calc(var(--sticky-h, 3.4rem) + .5rem);
    max-height: calc(100vh - var(--sticky-h, 3.4rem) - 1.5rem);
    overflow-y: auto; overscroll-behavior: contain;
    padding-right: 4px;
  }
  .dexcol-side .statbar { grid-template-columns: 4.6rem 2.2rem 1fr; position: relative; }
  .dexcol-side .sbtag {
    /* Off-grid but still read aloud — the verdict word is information. */
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap;
  }
  .dexcol-side .tiles { grid-template-columns: repeat(2, 1fr); }
  .dexcol-side .tile-wide { grid-column: span 2; }
}
.dexcol-side > section + section,
.dexcol-main > section + section { margin-top: 34px; }

/* ══ 33 · ad slots ══════════════════════════════════════════════════════
   No markup is added anywhere; these are guardrails only, so an auto-ad
   placed in-flow cannot land inside a plan. styles.css already collapses
   unfilled slots — that rule is not repeated here. */
.content .ad-slot { margin: 24px 0; }
.lineage .ad-slot, ol.route .ad-slot, .combolist .ad-slot, .combo .ad-slot,
.podium .ad-slot, .cta.card .ad-slot, .chainrow .ad-slot { display: none; }

/* ══ 34 · mobile ════════════════════════════════════════════════════════ */
@media (max-width: 900px) {
  .dexhero-row { grid-template-columns: 1fr; text-align: left; }
  /* Both ceilings come down together, for the reason stated where they go up:
     the <picture> is the item that is laid out. Left at its desktop cap it
     filled the whole stacked column, and its ::before glow — deliberately
     18% WIDER than the picture — then hung 52px off the right of the page and
     scrolled the document sideways. */
  .hero-art, .masthead picture:has(> .hero-art),
  .masthead picture:has(> .hero-art.has-render) { max-width: 150px; }
}
@media (max-width: 720px) {
  .content-page { --hero-h: 340px; }
  .content-page.page-farmitem, .content-page.page-lootpage,
  .content-page.page-skill, .content-page.page-guidepage { --hero-h: 300px; }
  .content-page .mast-body { padding-top: 26px; }
  .dexstep { gap: 6px; }
  .stept { font-size: .76rem; }
  .tiles { grid-template-columns: repeat(2, 1fr); }
  .tile-wide { grid-column: span 2; }
  .lin-grid { margin-top: 10px; }
}

/* ══ 35 · reduced motion: last, so nothing re-animates after it ═════════ */
@media (prefers-reduced-motion: reduce) {
  .palcard, .combo, .cslot, .hubcard, .worktile-link, .linpair, .tcard,
  .prow, .linkchip, .leadcard, .binchip, .rail-cta, .fact,
  .content a.step, .content a.cta-btn { transition: none; }
  .combo:hover, .content a.hubcard:hover, .content a.cta-btn:hover,
  .content a.tcard:hover, .content a.palcard:hover { transform: none; }
}
