/* /patch-notes — the patch diff page.
   Markup comes from scripts/make-patch-notes-page.py; behaviour from
   js/patch-notes.js. Class prefix is pn-, the way the tier board uses tl-.

   The page is a long vertical read, so almost everything here is about making
   it scannable: one row per change, the before/after pinned to the right where
   the eye can run down it, and colour used only as a second signal behind a
   word that already says the same thing. */

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

/* ---------- the masthead: one card per stream ----------
   A patch day is two posts on two dates (the balance post lands days before the
   build; the update post lands on it), so each carries its own version and date
   rather than sharing one heading that would be wrong about at least one. */

.pn-streams {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 22px 0 0;
}
@media (max-width: 760px) { .pn-streams { grid-template-columns: 1fr; } }
/* Only one stream on record — the state between a balance post landing and the
   season update following it, which is where this page sits for days at a time.
   A two-column grid there leaves half the masthead empty and reads as a card
   that failed to load. */
.pn-streams-one { grid-template-columns: 1fr; }

.pn-stream {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; gap: 10px;
  padding: 16px 18px 18px;
  background: var(--bg-raise, #121732);
  border: 1px solid var(--line, #2a3158); border-radius: 14px;
}
/* The stripe is what makes the two cards read as two streams at a glance,
   before any of the words are read. */
.pn-stream::before {
  content: ""; position: absolute; inset: 0 auto 0 0; width: 3px; background: var(--accent);
}
.pn-stream-balance { --accent: var(--gold, #f2c744); }
.pn-stream-update { --accent: var(--update, #9d8bff); }
.pn-stream-kind {
  font: 600 11px/1 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--accent);
}
.pn-stream-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 12px; }
.pn-stream-title h2 {
  margin: 0; font: 700 clamp(21px, 3vw, 27px)/1.1 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.5px;
}
.pn-stream-date { color: var(--ink-dim, #8a92b2); font-size: 14px; }
.pn-stream-line { margin: 0; color: var(--ink-dim, #8a92b2); font-size: 15px; }
.pn-stream-line strong { color: var(--ink, #edf0fa); font-weight: 700; }
.pn-stream-foot { margin: auto 0 0; padding-top: 4px; }

/* ---------- chips ----------
   One vocabulary across the masthead, the roster and the archive: a chip means
   the same thing wherever it appears. */

.pn-chip {
  display: inline-flex; align-items: center; gap: 6px;
  border-radius: 999px; padding: 3px 10px;
  font: 600 11px/1.6 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.08em; text-transform: uppercase;
  border: 1px solid var(--line, #2a3158); color: var(--ink-dim, #8a92b2);
  background: var(--bg-panel, #151b3a);
}
.pn-chip-balance { color: var(--gold, #f2c744); border-color: rgba(242, 199, 68, .4); }
.pn-chip-update { color: var(--update, #9d8bff); border-color: rgba(157, 139, 255, .4); }
.pn-chip-hotfix { color: var(--hotfix, #ffb347); border-color: rgba(255, 179, 71, .45); }
.pn-chip-live { color: var(--buff, #46d38a); border-color: rgba(70, 211, 138, .4); }

/* ---------- jump bar ----------
   Deliberately not sticky. The filter bar below it is, the site nav on this page
   is not (see nav() in the generator), and three fixed bars stacking at top:0 is
   how a phone ends up with no page left. */

.pn-jump {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin: 20px 0 0; padding: 10px 0 0; border-top: 1px solid var(--line, #2a3158);
}
.pn-jump a {
  color: var(--ink-dim, #8a92b2); text-decoration: none; font-size: 14px;
  border: 1px solid transparent; border-radius: 999px; padding: 6px 13px;
}
.pn-jump a:hover { color: var(--ink, #edf0fa); background: var(--bg-panel, #151b3a); }
.pn-jump a:focus-visible { outline: 2px solid var(--gold, #f2c744); outline-offset: 2px; }

/* ---------- one post ---------- */

.pn-post { margin: 34px 0 0; scroll-margin-top: 12px; }
.pn-post-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 14px;
  padding-bottom: 10px; border-bottom: 2px solid var(--accent, var(--line, #2a3158));
}
.pn-post-balance { --accent: rgba(242, 199, 68, .5); }
.pn-post-update { --accent: rgba(157, 139, 255, .5); }
.pn-post-head h2 {
  margin: 0; font: 700 clamp(22px, 3.4vw, 30px)/1.1 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.5px;
}
.pn-post-date { color: var(--ink-dim, #8a92b2); font-size: 15px; }
.pn-counts { margin: 12px 0 0; color: var(--ink-dim, #8a92b2); font-size: 15px; }
.pn-counts strong { color: var(--ink, #edf0fa); font-weight: 700; }
.pn-dot { opacity: .5; }
.pn-summary { margin: 14px 0 0; max-width: 70ch; }
.pn-official-wrap { margin: 18px 0 0; }

/* ---------- where this post stands against the game files ----------
   The balance post is published before the build it describes, so "unverified"
   is the normal state on the day people read it. Saying which of three states
   the page is in costs one line and is the difference between a claim and a
   report. Muted by default because waiting is not a warning. */

.pn-status {
  display: flex; gap: 10px; align-items: flex-start;
  margin: 14px 0 0; padding: 11px 14px; font-size: 14px; max-width: 78ch;
  background: var(--bg-raise, #121732);
  border: 1px solid var(--line, #2a3158);
  border-left: 3px solid var(--ink-dim, #8a92b2);
  border-radius: 10px; color: #c3cae6;
}
.pn-status b { color: var(--ink, #edf0fa); }
.pn-status-mark { flex: none; }
.pn-status-ok { border-left-color: var(--buff, #46d38a); }
.pn-status-ok .pn-status-mark { color: var(--buff, #46d38a); }
.pn-status-bad { border-left-color: var(--nerf, #ff6b81); }
.pn-status-bad .pn-status-mark { color: var(--nerf, #ff6b81); }

/* The one outbound link that has to be findable: this page summarises somebody
   else's document and the reader must always be one click from the original. */
.pn-official {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--gold, #f2c744); color: #1a1400;
  border-radius: 10px; padding: 10px 18px;
  font-weight: 700; text-decoration: none;
}
.pn-official:hover { filter: brightness(1.08); }
.pn-official:focus-visible { outline: 2px solid var(--ink, #edf0fa); outline-offset: 2px; }

/* ---------- sections ---------- */

.pn-section { margin: 34px 0 0; }
.pn-section > h2, .pn-wrap > .pn-section h2 {
  font: 700 22px/1.2 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.5px; margin: 0 0 6px;
}
.pn-lead { margin: 0 0 16px; color: var(--ink-dim, #8a92b2); max-width: 74ch; font-size: 15px; }
.pn-global { margin: 0; padding-left: 20px; max-width: 74ch; }
.pn-global li { margin-bottom: 6px; }

.pn-btn {
  display: inline-block;
  background: var(--bg-panel, #151b3a); border: 1px solid var(--line, #2a3158);
  color: var(--ink, #edf0fa); border-radius: 10px; padding: 9px 16px;
  font: inherit; font-weight: 600; text-decoration: none;
}
.pn-btn:hover { border-color: var(--gold, #f2c744); }
.pn-btn:focus-visible { outline: 2px solid var(--gold, #f2c744); outline-offset: 2px; }
.pn-btn-balance { background: var(--gold, #f2c744); border-color: var(--gold, #f2c744); color: #1a1400; }
.pn-btn-update { background: var(--update, #9d8bff); border-color: var(--update, #9d8bff); color: #0d0a26; }
.pn-btn-balance:hover, .pn-btn-update:hover { filter: brightness(1.08); }
.pn-more { display: flex; flex-wrap: wrap; gap: 10px; margin: 16px 0 0; }

.pn-recent { list-style: none; margin: 0; padding: 0; }
.pn-recent li {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px;
  padding: 9px 0; border-top: 1px solid var(--line, #2a3158);
}
.pn-recent li:first-child { border-top: 0; }
.pn-recent a { font: 700 17px/1.1 var(--font-display, "Rajdhani", sans-serif); text-decoration: none; }
.pn-recent a:hover { text-decoration: underline; }

/* ---------- filters ----------
   Pinned, and the site nav on this page deliberately is not — see nav() in
   scripts/make-patch-notes-page.py. Two bars stuck at top:0 stack on each
   other, and of the two this is the one you reach for while scrolling. */

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

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

/* ---------- role groups ---------- */

.pn-role-group { margin: 0 0 26px; }
.pn-role-head {
  font: 700 15px/1 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.14em; text-transform: uppercase;
  margin: 0 0 12px; padding-bottom: 8px;
  border-bottom: 1px solid var(--line, #2a3158);
  display: flex; align-items: center; gap: 10px;
}
.pn-role-vanguard { color: var(--vanguard, #ffa53e); }
.pn-role-duelist { color: var(--duelist, #ff4d6d); }
.pn-role-strategist { color: var(--strategist, #3fd2ff); }
.pn-count {
  font-size: 12px; color: var(--ink-dim, #8a92b2);
  border: 1px solid var(--line, #2a3158); border-radius: 999px; padding: 2px 8px;
}

/* ---------- hero card ---------- */

.pn-hero {
  background: var(--bg-raise, #121732);
  border: 1px solid var(--line, #2a3158); border-radius: 12px;
  padding: 14px 16px; margin: 0 0 10px;
  scroll-margin-top: 76px; /* clears the sticky filter bar on a #hero- deep link */
}
.pn-hero-head { display: flex; align-items: center; gap: 14px; }
/* Keep the px in step with the width/height attributes in
   scripts/make-patch-notes-page.py — those reserve the box before the image
   arrives, and a mismatch is a layout shift on every card on the page. */
.pn-portrait {
  width: 64px; height: 64px; border-radius: 12px; object-fit: cover;
  border: 1px solid var(--line, #2a3158); background: var(--bg-panel, #151b3a);
  flex: none;
}
.pn-hero-name {
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
  font: 700 19px/1.15 var(--font-display, "Rajdhani", sans-serif); letter-spacing: 0.4px;
}
.pn-hero-name a { color: var(--ink, #edf0fa); text-decoration: none; }
.pn-hero-name a:hover { color: var(--gold, #f2c744); }
.pn-role {
  font: 600 11px/1 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.12em; text-transform: uppercase;
}

/* The verdict tag carries a word as well as a colour and a glyph, so it still
   reads on a monochrome display and through a screen reader. */
.pn-verdict {
  margin-left: auto; flex: none;
  font: 600 12px/1 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.1em; text-transform: uppercase;
  border-radius: 999px; padding: 6px 11px;
  border: 1px solid currentColor;
}
.pn-verdict.pn-buff { color: var(--buff, #46d38a); }
.pn-verdict.pn-nerf { color: var(--nerf, #ff6b81); }
.pn-verdict.pn-adjust { color: var(--ink-dim, #8a92b2); }
.pn-verdict.pn-new { color: var(--gold, #f2c744); }

.pn-info {
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  background: transparent; border: 1px solid var(--line, #2a3158);
  color: var(--ink-dim, #8a92b2); font: 700 13px/1 Barlow, sans-serif;
  cursor: pointer; transition: color .15s, border-color .15s;
}
.pn-info:hover { color: var(--gold, #f2c744); border-color: var(--gold, #f2c744); }
.pn-info:focus-visible { outline: 2px solid var(--gold, #f2c744); outline-offset: 2px; }

/* ---------- change rows ---------- */

.pn-changes { list-style: none; margin: 12px 0 0; padding: 0; }
.pn-change {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  padding: 7px 0 7px 13px; border-top: 1px solid rgba(42, 49, 88, .6);
  position: relative; font-size: 15px;
}
.pn-changes > li:first-child { border-top: 0; }
/* The stripe is the colour signal; the arrow beside the numbers is the one that
   survives without colour. */
.pn-change::before {
  content: ""; position: absolute; left: 0; top: 10px; bottom: 10px;
  width: 3px; border-radius: 3px; background: var(--line, #2a3158);
}
.pn-change.pn-buff::before { background: var(--buff, #46d38a); }
.pn-change.pn-nerf::before { background: var(--nerf, #ff6b81); }

/* ---------- sub-rows ----------
   A change the post stated in reference to the one above it: "…reduce the area
   damage from 40 to 30, and increase the cooldown from 12s to 15s" never says
   which cooldown. Indented, because the indent is the sentence — read flat, the
   second row claims a nerf that only applies inside the first one's condition.

   .pn-change is a wrapping flex row, so the sub-list takes a full line of its
   own by being a 100%-basis flex item rather than by any change of display. */
.pn-sub {
  flex: 1 0 100%; margin: 6px 0 1px; padding-left: 15px;
  border-left: 1px solid rgba(42, 49, 88, .8);
}
.pn-sub .pn-change { font-size: 14px; padding-top: 5px; padding-bottom: 5px; }
.pn-sub .pn-move { font-size: 15px; }
/* The parent's own stripe stops at its own line. Run down the full height and
   it reads as a bracket around the children, which is the job the sub-list's
   left border is already doing. */
.pn-change.pn-parent::before { bottom: auto; height: 1.4em; }

.pn-what { flex: 1 1 auto; min-width: 0; }
.pn-move {
  flex: none; display: inline-flex; align-items: baseline; gap: 7px;
  font-variant-numeric: tabular-nums; white-space: nowrap;
  font-family: var(--font-display, "Rajdhani", sans-serif); font-weight: 700; font-size: 17px;
}
.pn-from { color: var(--ink-dim, #8a92b2); text-decoration: line-through; text-decoration-thickness: 1px; }
.pn-arrow { color: var(--ink-dim, #8a92b2); font-size: 14px; }
.pn-change.pn-buff .pn-to { color: var(--buff, #46d38a); }
.pn-change.pn-nerf .pn-to { color: var(--nerf, #ff6b81); }

.pn-verified {
  margin: 10px 0 0; font-size: 13px; color: var(--ink-dim, #8a92b2);
}
.pn-verified span { color: var(--buff, #46d38a); }

/* The only per-hero mark left, and the one thing on this page nobody else can
   print: the official post said one number and the shipped build has another.
   Everything that merely agreed stays unmarked — forty green ticks is forty
   lines of ink spent on the unremarkable case. */
.pn-mismatch {
  margin: 10px 0 0; font-size: 13px; color: var(--nerf, #ff6b81);
}

/* A row that came from a hotfix rather than from the patch. Folded into the
   hero's card as well as listed in the band above, so somebody scrolling the
   roster cannot read a number that has already been replaced. */
.pn-src {
  flex: none; align-self: center;
  font: 600 10px/1 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--hotfix, #ffb347); border: 1px solid rgba(255, 179, 71, .4);
  border-radius: 999px; padding: 3px 7px;
}
.pn-change[data-src]::before { background: var(--hotfix, #ffb347); }
.pn-hero.pn-amended { border-color: rgba(255, 179, 71, .5); }

/* Named in the post, not on the roster — a hero announced with the season and
   shipping with the build. No portrait, so the card is left-aligned text. */
.pn-unmatched { border-style: dashed; }

/* ---------- hotfixes ----------
   Above the patch they amend, because "what changed since I last read this" is
   the question a returning reader has, and it is the shorter read. */

.pn-hotfixes {
  margin: 22px 0 0; border-radius: 14px; overflow: hidden;
  border: 1px solid rgba(255, 179, 71, .35);
  background: rgba(255, 179, 71, .045);
}
.pn-hotfixes > h2 {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin: 0; padding: 12px 16px;
  font: 700 15px/1.2 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--hotfix, #ffb347);
  border-bottom: 1px solid rgba(255, 179, 71, .25);
}
.pn-hf { border-bottom: 1px solid rgba(255, 179, 71, .18); scroll-margin-top: 76px; }
.pn-hf:last-child { border-bottom: 0; }
.pn-hf > summary {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 12px;
  padding: 12px 16px; cursor: pointer; list-style-position: inside;
}
.pn-hf > summary:focus-visible { outline: 2px solid var(--hotfix, #ffb347); outline-offset: -2px; }
.pn-hf-label { font: 700 17px/1.15 var(--font-display, "Rajdhani", sans-serif); letter-spacing: 0.4px; }
.pn-hf-date, .pn-hf-n { color: var(--ink-dim, #8a92b2); font-size: 14px; }
.pn-hf-body { padding: 0 16px 14px; }
.pn-hf-gist { margin: 0 0 6px; color: var(--ink-dim, #8a92b2); font-size: 15px; max-width: 72ch; }

/* ---------- the update post ----------
   A different document, so a different shape: headed lists, no portraits, no
   verdicts, no before/after column. Long reference lists ship collapsed because
   twenty bug fixes are something you search, not something you read. */

.pn-upd {
  margin: 0 0 10px; border-radius: 12px; overflow: hidden;
  background: var(--bg-raise, #121732); border: 1px solid var(--line, #2a3158);
}
.pn-upd > summary {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 12px;
  padding: 13px 16px; cursor: pointer; list-style-position: inside;
}
.pn-upd > summary:focus-visible { outline: 2px solid var(--update, #9d8bff); outline-offset: -2px; }
.pn-upd[open] > summary { border-bottom: 1px solid var(--line, #2a3158); }
.pn-upd-title { font: 700 17px/1.15 var(--font-display, "Rajdhani", sans-serif); letter-spacing: 0.4px; }
.pn-upd-group {
  font: 600 10px/1 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-dim, #8a92b2);
}
.pn-upd-n { margin-left: auto; color: var(--ink-dim, #8a92b2); font-size: 13px; }
.pn-upd-body { padding: 12px 16px 14px; }
.pn-upd-body ul { margin: 0; padding-left: 20px; }
.pn-upd-body li { margin: 0 0 7px; max-width: 78ch; }
.pn-upd-body li:last-child { margin-bottom: 0; }
.pn-official-update { background: var(--update, #9d8bff); color: #0d0a26; }

.pn-undoc .pn-changes { margin-top: 8px; }
.pn-undoc .pn-changes li {
  display: block; padding: 6px 0 6px 13px; border-top: 1px solid rgba(42, 49, 88, .6);
  position: relative; font-size: 15px;
}
.pn-undoc .pn-changes li:first-child { border-top: 0; }

/* ---------- team-ups ---------- */

.pn-teamup {
  background: var(--bg-raise, #121732);
  border: 1px solid var(--line, #2a3158); border-radius: 12px;
  padding: 14px 16px; margin: 0 0 10px;
}
.pn-tu-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 12px; }
.pn-tu-kind {
  font: 600 11px/1 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.12em; text-transform: uppercase;
  border-radius: 999px; padding: 5px 10px; border: 1px solid currentColor;
}
.pn-tu-new { color: var(--buff, #46d38a); }
.pn-tu-adjusted { color: var(--gold, #f2c744); }
.pn-tu-removed { color: var(--nerf, #ff6b81); }
.pn-tu-name {
  font: 700 18px/1.15 var(--font-display, "Rajdhani", sans-serif); letter-spacing: 0.4px;
}
.pn-tu-name a { color: var(--ink, #edf0fa); text-decoration: none; }
.pn-tu-name a:hover { color: var(--gold, #f2c744); }
.pn-tu-members { color: var(--ink-dim, #8a92b2); font-size: 14px; }

/* ---------- archive ---------- */

.pn-archive-item {
  background: var(--bg-raise, #121732);
  border: 1px solid var(--line, #2a3158); border-radius: 12px;
  margin: 0 0 10px; padding: 0 16px;
}
.pn-archive-item > summary {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 14px;
  padding: 14px 0; cursor: pointer; list-style-position: inside;
}
.pn-archive-item > summary:focus-visible { outline: 2px solid var(--gold, #f2c744); outline-offset: 2px; }
.pn-archive-label {
  font: 700 18px/1.15 var(--font-display, "Rajdhani", sans-serif); letter-spacing: 0.4px;
}
.pn-archive-date, .pn-archive-counts { color: var(--ink-dim, #8a92b2); font-size: 14px; }
.pn-archive-counts strong { color: var(--ink, #edf0fa); }
.pn-archive-body { padding: 4px 0 16px; }
/* Nested one level down, so the cards inside an archived patch don't compete
   with the current one's. */
.pn-archive-body .pn-hero,
.pn-archive-body .pn-teamup { background: var(--bg, #0b0e1a); }
/* The one entry that is also a live page, so the archive says which row is
   "now" rather than leaving the reader to compare dates. */
.pn-archive-current {
  font: 700 10px/1 var(--font-display, "Rajdhani", sans-serif);
  letter-spacing: 0.11em; text-transform: uppercase;
  padding: 3px 7px; border-radius: 4px;
  background: var(--gold-soft, rgba(242, 199, 68, 0.16)); color: var(--gold, #f2c744);
}
.pn-archive-page .pn-lead { max-width: 74ch; margin-top: 20px; }
/* A hotfix hangs off the post it amends, and the indent is the only thing that
   says so on a timeline where everything else is a sibling. */
.pn-archive-nested { margin-left: 26px; border-left: 2px solid rgba(255, 179, 71, .35); }
.pn-archive-item[data-type="update"] .pn-archive-label { color: var(--update, #9d8bff); }
.pn-archive-item[data-type="hotfix"] .pn-archive-label { color: var(--hotfix, #ffb347); }

/* ---------- filtering ----------
   Applied by toggling data attributes on #pn-page rather than by touching every
   card, so a filter change is one style recalculation and the markup that ships
   in the HTML is the unfiltered truth a crawler sees. */

#pn-page[data-role-filter="vanguard"] #heroes .pn-hero:not([data-role="vanguard"]),
#pn-page[data-role-filter="duelist"] #heroes .pn-hero:not([data-role="duelist"]),
#pn-page[data-role-filter="strategist"] #heroes .pn-hero:not([data-role="strategist"]),
#pn-page[data-verdict-filter="buff"] #heroes .pn-hero:not([data-verdict="buff"]),
#pn-page[data-verdict-filter="nerf"] #heroes .pn-hero:not([data-verdict="nerf"]),
#pn-page[data-verdict-filter="adjust"] #heroes .pn-hero:not([data-verdict="adjust"]) {
  display: none;
}
/* "Patch only" hides the rows a hotfix contributed, so the roster reads as the
   post was written. The default is the other way round — the current truth —
   and this exists for checking the page against the official notes. */
#pn-page[data-source-filter="patch"] #heroes .pn-change[data-src] { display: none; }
/* A role group whose every card is filtered out keeps its heading otherwise,
   which reads as "nothing was buffed here" when the truth is "nothing here
   matches both filters". js/patch-notes.js sets this. */
.pn-role-group[hidden] { display: none; }

/* ---------- filter transition ----------
   Filtering is display:none, which cannot be transitioned — and shouldn't be:
   a card that fades out while still holding its space makes the list jump
   twice. So the cards that SURVIVE a filter animate in, which reads as the list
   reflowing to the new answer rather than as content vanishing.

   js/patch-notes.js sets data-filtering on #pn-page for one frame per change.
   The stagger is capped at six steps: past that the last card in a long list
   arrives noticeably after the first, which turns a 200ms transition into a
   wait. */
#pn-page[data-filtering] #heroes .pn-hero {
  animation: pn-filter-in 220ms var(--ease, ease) both;
}
#pn-page[data-filtering] #heroes .pn-hero:nth-child(2) { animation-delay: 18ms; }
#pn-page[data-filtering] #heroes .pn-hero:nth-child(3) { animation-delay: 36ms; }
#pn-page[data-filtering] #heroes .pn-hero:nth-child(4) { animation-delay: 54ms; }
#pn-page[data-filtering] #heroes .pn-hero:nth-child(5) { animation-delay: 72ms; }
#pn-page[data-filtering] #heroes .pn-hero:nth-child(n+6) { animation-delay: 90ms; }
#pn-page[data-filtering] .pn-role-head { animation: pn-filter-in 200ms var(--ease, ease) both; }

@keyframes pn-filter-in {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: none; }
}

/* The pressed pill gets a small kick, so a tap on a phone confirms itself even
   when the filter it applied happens to change nothing on screen. */
.pn-seg button[aria-pressed="true"] { animation: pn-seg-pop 200ms var(--ease, ease); }
@keyframes pn-seg-pop {
  0% { transform: scale(0.94); }
  60% { transform: scale(1.04); }
  100% { transform: scale(1); }
}

.pn-empty { animation: pn-filter-in 200ms var(--ease, ease) both; }

/* ---------- phone ----------
   Same pass the tier board had: the filter bar becomes a scrollable strip
   rather than a four-line stack, and every change row wraps its before/after
   under the label instead of squeezing it. */

@media (max-width: 720px) {
  .pn-wrap { padding: 0 14px 60px; }
  .pn-head { margin: 16px 0 8px; padding: 15px 16px; border-radius: 12px; }
  .pn-official { width: 100%; justify-content: center; }

  .pn-controls {
    margin: 0 -14px 16px; padding: 10px 14px 12px;
    flex-wrap: nowrap; align-items: stretch; gap: 14px;
    overflow-x: auto; scrollbar-width: none;
  }
  .pn-controls::-webkit-scrollbar { display: none; }
  .pn-field { flex: none; }
  .pn-seg { flex-wrap: nowrap; }
  .pn-seg button { white-space: nowrap; }

  .pn-hero, .pn-teamup { padding: 12px 13px; }
  .pn-portrait { width: 52px; height: 52px; border-radius: 10px; }
  .pn-hero-name { font-size: 17px; }
  /* The verdict tag drops onto its own line rather than shrinking the name to
     an ellipsis — which of the four it is matters more than saving a row. */
  .pn-hero-head { flex-wrap: wrap; }
  .pn-verdict { order: 3; margin-left: 0; }
  .pn-info { margin-left: auto; }

  .pn-change { gap: 2px 12px; }
  .pn-what { flex: 1 0 100%; }
  .pn-move { font-size: 16px; }
  /* The label already takes a full line here, so a sub-list only needs the
     indent, not the extra breathing room it gets on a wide screen. */
  .pn-sub { margin-top: 4px; padding-left: 12px; }

  .pn-movers { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  .pn-seg button, .pn-info { transition: none; }
  /* The filtering itself still works — only the motion goes. */
  #pn-page[data-filtering] #heroes .pn-hero,
  #pn-page[data-filtering] .pn-role-head,
  .pn-seg button[aria-pressed="true"],
  .pn-empty { animation: none; }
}
