/* ---- dex filter bar ---- */
.filter-bar {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px;
  margin-bottom: 16px;
}

.filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin-bottom: 10px;
}

.filter-row:last-child { margin-bottom: 0; }

.dex-search-wrap { position: relative; flex: 1; min-width: 200px; }
/* #dex-search is type="text", not type="search" -- deliberately, so there's
   no browser-drawn built-in clear-x to fight with (Chrome's own one kept
   showing up doubled, underneath/behind .dex-search-clear below, even with
   -webkit-appearance:none on ::-webkit-search-cancel-button). One single
   custom X, same construction as every other clearable field in the app
   (see .searchable-clear). */
#dex-search {
  width: 100%;
  padding: 8px 28px 8px 10px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
}
/* Edge (and old IE) draws its OWN small clear-x on any text input once it
   has content, completely separate from Chrome's type="search" one -- not
   fixed by switching this field to type="text" above, since Edge's version
   isn't gated on the input type at all. ::-ms-clear is that button's own
   pseudo-element; ::-ms-reveal is the same idea for password fields' show/
   hide eye icon, harmless to blank here too since Edge pairs them. */
#dex-search::-ms-clear, #dex-search::-ms-reveal { display: none; width: 0; height: 0; }
/* Same construction as .searchable-clear (see its own comment) -- drawn via
   two rotated pseudo-element bars rather than the &times; glyph, so the X
   is exactly centered regardless of font metrics. */
.dex-search-clear {
  display: none;
  position: absolute; top: 50%; right: 5px; transform: translateY(-50%);
  width: 18px; height: 18px; padding: 0; z-index: 1;
  border: none; border-radius: 5px; background: transparent;
  color: transparent;
  font-size: 0; /* belt-and-suspenders alongside color:transparent above --
    zeroes out the &times; text node itself so it can't show as a second,
    smaller X even if something overrides the button's own text color. */
  cursor: pointer;
}
.dex-search-clear::before, .dex-search-clear::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 10px; height: 2px; border-radius: 1px; background: var(--text-dim); opacity: 0.85;
}
.dex-search-clear::before { transform: translate(-50%, -50%) rotate(45deg); }
.dex-search-clear::after { transform: translate(-50%, -50%) rotate(-45deg); }
.dex-search-clear.visible { display: block; }
.dex-search-clear:hover { background: rgba(255,255,255,0.18); }
.dex-search-clear:hover::before, .dex-search-clear:hover::after { opacity: 1; background: #fff; }

.filter-bar select, .filter-bar button {
  padding: 7px 10px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg-panel-alt);
  color: var(--text);
}

.filter-bar button { cursor: pointer; }
.filter-bar button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.filter-bar button:hover { filter: brightness(1.15); }

/* Location filter condition: a custom button+dropdown, not a native
   <select> -- same reasoning and the same visual pattern as the Team
   Builder's own Stage picker (see #team-stage-btn/.team-stage-dropdown).
   ~110 locations is long enough that a native <select> popup runs into a
   real Chrome rendering bug on some machines (only the first screenful
   paints, the rest stays blank until scrolled); this list is ordinary
   in-page DOM instead, so it always paints fully. */
.cond-location-wrap { position: relative; }
.cond-location-btn { white-space: nowrap; }
.cond-location-dropdown {
  position: absolute; top: 100%; left: 0; margin-top: 4px; z-index: 15;
  width: 260px; max-height: 320px; overflow-y: auto;
  background: var(--bg-panel-alt); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.4); padding: 4px;
}
.cond-location-row { padding: 7px 8px; font-size: 0.82rem; cursor: pointer; border-radius: 6px; }
.cond-location-row:hover { background: var(--bg-panel); }
.cond-location-row.active { background: color-mix(in srgb, var(--accent) 12%, transparent); font-weight: 600; }

/* Only shown once there are 2+ groups -- controls whether a Pokemon needs
   to satisfy every group or just one of them, mirroring the ALL/ANY
   selector each individual group already has for its own conditions. */
.groups-combine-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
  color: var(--text-dim);
  margin-bottom: 10px;
}

.condition-group {
  border: 1px dashed var(--border);
  border-radius: 8px;
  padding: 10px;
  margin-bottom: 8px;
}

.condition-group-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
  font-size: 0.78rem;
  color: var(--text-dim);
}

.cond-level-up-only { display: flex; align-items: center; gap: 4px; font-size: 0.8rem; color: var(--text-dim); white-space: nowrap; cursor: pointer; }
.cond-level-label { font-size: 0.8rem; color: var(--text-dim); white-space: nowrap; }
/* Stat/Level conditions' own number field -- otherwise a completely
   unstyled <input type="number">, which renders as the browser/OS's own
   plain white text box with its native spin-button widget glued to the
   side, clashing hard against every other dark, flat, rounded field in
   this bar (#dex-search, .searchable-input, every <select>). Matches
   those same colors/border/radius. The native spinner buttons are hidden
   (not restyled -- there's no reliable cross-browser way to reskin an
   OS-drawn spin button to match a custom palette) rather than left in
   their own default light-chrome look; the field still accepts typed
   numbers and the usual up/down arrow-key increment/decrement, it just
   no longer has an OS widget bolted onto it that no CSS here controls. */
.cond-num {
  padding: 7px 10px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}
.cond-num:focus { outline: none; border-color: var(--accent); }
.cond-num::-webkit-outer-spin-button, .cond-num::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.cond-num[type="number"] { -moz-appearance: textfield; appearance: textfield; }
.stage-filter-wrap { display: flex; align-items: center; gap: 6px; }
.stage-exact-only-label {
  display: flex; align-items: center; gap: 4px;
  font-size: 0.78rem; color: var(--text-dim); white-space: nowrap; cursor: pointer;
}
.stage-filter-wrap select {
  font-size: 0.8rem;
  padding: 6px 8px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg-panel-alt);
  color: var(--text);
  cursor: pointer;
}

.condition-row {
  display: flex;
  gap: 6px;
  align-items: center;
  margin-bottom: 6px;
  flex-wrap: wrap;
}

/* Type condition's = / =/= reverse-search toggle (see renderConditionEl's
   negateControl in dex.js) -- inherits the base .filter-bar button look for
   its default ("=", requires the type) state, and only overrides colors for
   the active ("=/=", excludes the type) state, so it stays visually in
   step with every other button in this bar until it's actually flipped. */
.cond-negate-btn { min-width: 34px; font-weight: 700; }
.cond-negate-btn.cond-negate-active {
  background: var(--tier-4x);
  border-color: var(--tier-4x);
  color: #fff;
}

/* Removes one condition from its group -- same red "danger" treatment and
   same pseudo-element X construction as .team-slot-remove (the &times;
   glyph itself isn't reliably centered within its own text box across
   fonts; two bars drawn at exact top/left:50% + translate(-50%,-50%) are).
   Previously just inherited the neutral .filter-bar button look, which
   read as visually identical to every non-destructive control in the row
   -- everywhere else in the app a remove/reset/clear action gets this red
   treatment, so this was the one inconsistent one.

   The color/background overrides are scoped as `.filter-bar
   .remove-cond-btn` (two classes), not bare `.remove-cond-btn` (one) --
   this button always sits inside .filter-bar, and the earlier bare version
   LOST the cascade to `.filter-bar select, .filter-bar button` above
   (one class + one type selector also beats one class alone), which is
   exactly why the background stayed neutral and the &times; text glyph
   showed through in var(--text) instead of staying hidden: this rule's
   own `color: transparent` was simply being outranked, not applied at
   all. font-size: 0 is a second, independent guard against that same text
   node ever becoming visible again (same "belt and suspenders" as
   .dex-search-clear) -- even if some future rule wins the color fight,
   a zero font-size glyph still can't show. */
.filter-bar .remove-cond-btn {
  position: relative;
  width: 30px;
  height: 30px;
  padding: 0;
  border-radius: 6px;
  border: 1px solid var(--tier-4x);
  background: var(--tier-4x);
  color: transparent;
  font-size: 0;
  cursor: pointer;
}
.remove-cond-btn::before, .remove-cond-btn::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 12px; height: 2px; border-radius: 1px; background: #fff;
}
.remove-cond-btn::before { transform: translate(-50%, -50%) rotate(45deg); }
.remove-cond-btn::after { transform: translate(-50%, -50%) rotate(-45deg); }
.filter-bar .remove-cond-btn:hover { filter: brightness(1.15); }

/* Searchable combobox (Ability / "Can learn move" condition values) --
   replaces a plain <select> for fields with hundreds of possible entries,
   where a native dropdown is just an unfilterable wall of options. Empty
   by default; typing at least one character reveals a filtered list
   below the input instead of dumping everything up front. */
.searchable-select { position: relative; display: inline-block; }
.searchable-input {
  width: 190px;
  padding: 7px 10px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.85rem;
}
.searchable-input:focus { outline: none; border-color: var(--accent); }
/* Only rendered when searchableSelectHtml() is called with `clearable`
   (see helpers.js) -- the team page's move fields, so far. Hidden until a
   value is actually picked (nothing to clear on an empty field), toggled
   via the .visible class in wireSearchableSelect rather than the markup
   itself, since the same element persists across selections.

   Drawn as two rotated bars rather than relying on the &times; glyph in
   the markup -- the same fix as .team-slot-remove's own X, and the same
   reason: font glyphs aren't reliably centered within their own text box,
   so no amount of flexbox/absolute-centering the BUTTON fixes an X that's
   off-center within itself. top/left: 50% + translate(-50%, -50%) is
   exact regardless of font, since it's geometry, not glyph metrics. */
.searchable-clear {
  display: none;
  position: absolute; top: 50%; right: 4px; transform: translateY(-50%);
  width: 18px; height: 18px; padding: 0; z-index: 1;
  border: none; border-radius: 5px; background: transparent;
  color: transparent; /* hides the &times; text node in the markup -- the pseudo-elements below draw the visible X */
  font-size: 0; /* belt-and-suspenders alongside color:transparent above, same as
    .dex-search-clear's own -- zeroes out the &times; text node itself so it
    can't show as a second, smaller X even if something with higher
    specificity ever overrides this rule's own `color`. Needed in practice,
    not just in theory: `.filter-bar select, .filter-bar button` (one class
    + the `button` type selector) outranks this bare single-class selector,
    so plain `color: transparent` alone was silently losing that fight for
    every clearable field added inside the filter bar (the Ability/Move
    conditions), showing a faint second X through the text node's own
    default color. font-size:0 can't be out-specificity'd the same way
    since nothing else in this app's cascade sets a font-size on buttons. */
  cursor: pointer;
}
.searchable-clear::before, .searchable-clear::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 10px; height: 2px; border-radius: 1px; background: var(--text-dim); opacity: 0.85;
}
.searchable-clear::before { transform: translate(-50%, -50%) rotate(45deg); }
.searchable-clear::after { transform: translate(-50%, -50%) rotate(-45deg); }
.searchable-clear.visible { display: block; }
.searchable-clear:hover { background: rgba(255,255,255,0.18); }
.searchable-clear:hover::before, .searchable-clear:hover::after { opacity: 1; background: #fff; }
/* Same cascade fight as .remove-cond-btn above (see its own comment) --
   `.filter-bar select, .filter-bar button` also matches this clear button
   wherever a clearable Ability/Move field lives inside the filter bar, and
   its one-class-plus-type-selector specificity beats this button's own
   bare `.searchable-clear` background/border, leaving a visible
   bg-panel-alt box with a real border around what should be a plain,
   borderless, background-less X. Scoped to `.filter-bar .searchable-clear`
   (two classes) to reliably outrank it. */
.filter-bar .searchable-clear { border: none; background: transparent; }
.filter-bar .searchable-clear:hover { background: rgba(255,255,255,0.18); }
.searchable-select-clearable .searchable-input { padding-right: 26px; }
/* Wins over .team-slot-fields .searchable-input's own padding shorthand
   (same specificity otherwise, source order would decide it) via the
   extra class -- needed since the move fields are exactly where this
   button lives. */
.team-slot-fields .searchable-select-clearable .searchable-input { padding-right: 26px; }
.searchable-dropdown {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 220px;
  max-height: 260px;
  overflow-y: auto;
  background: var(--bg-panel-alt);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.4);
  z-index: 20;
  padding: 4px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.searchable-option {
  display: block;
  width: 100%;
  text-align: left;
  padding: 6px 8px;
  border: none;
  border-left: 3px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--text);
  font-size: 0.82rem;
  cursor: pointer;
}
/* .searchable-dropdown's own `display: flex` above is an author-stylesheet
   rule, which always wins the cascade over the browser's built-in
   `[hidden] { display: none }` UA rule regardless of specificity -- so
   without this, setting dropdownEl.hidden = true in JS (see
   wireSearchableSelect's closeDropdown()) was having NO visual effect. The
   dropdown was staying laid out the whole time, just empty of options most
   of the time -- which still rendered as a small visible padded rectangle
   (the 4px padding + border + shadow) sitting under the search box, most
   noticeable right after a quick-add filter navigates to the dex page with
   a freshly-created condition row that's never been focused/blurred to
   (harmlessly) re-trigger the broken toggle at all. This rule has higher
   specificity than the plain class rule above, so it actually wins. */
.searchable-dropdown[hidden] { display: none; }
.searchable-option:hover { background: var(--bg-panel); }
.searchable-empty { padding: 6px 8px; font-size: 0.8rem; color: var(--text-dim); font-style: italic; }
/* Banned move option (team.js's move fields only, see learnableMoveItemsFor
   and wireSearchableSelect in helpers.js) -- still visible in the dropdown
   so it can be found/seen, but not-allowed cursor + crossed-out label +
   the same BANNED tag the moves panel uses (see .banned-tag) makes clear
   up front that clicking it won't do anything (the mousedown handler in
   helpers.js backs this up by actually ignoring the click). */
.searchable-option-disabled { cursor: not-allowed; }
.searchable-option-disabled:hover { background: transparent; }
.searchable-option-label-banned { text-decoration: line-through; opacity: 0.6; }

.result-count { font-size: 0.85rem; color: var(--text-dim); margin: 6px 2px 14px; }

/* ---- sort bar ---- */
.sort-bar {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
  margin-bottom: 16px;
}
.sort-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.sort-row + .sort-row { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); }
.sort-row-label { font-size: 0.78rem; color: var(--text-dim); margin-right: 4px; }

/* Base size is the old 6px/10px/0.8rem box widened by a flat 1.25x; the
   active-default/active-reversed states then grow a little further still
   on top of that, so an active sort button is visibly the largest state. */
.sort-btn {
  padding: 7.5px 12.5px;
  border-radius: 7px;
  border: 1px solid var(--border);
  background: var(--bg-panel-alt);
  color: var(--text);
  cursor: pointer;
  font-size: 0.85rem;
}
.sort-btn:hover { filter: brightness(1.15); }
.sort-btn.active-default,
.sort-btn.active-reversed {
  padding: 8.5px 15px;
  font-size: 0.9rem;
  font-weight: 700;
}
.sort-btn.active-default { background: color-mix(in srgb, var(--tier-half) 28%, transparent); border-color: var(--tier-half); color: #bdf5d6; }
.sort-btn.active-reversed { background: color-mix(in srgb, var(--tier-4x) 28%, transparent); border-color: var(--tier-4x); color: #ffc9c4; }

/* List-view column headers share the same active-default/active-reversed
   color scheme, but sit in fixed-width grid columns so they signal "active"
   through color/weight only, not literal growth. */
.sortable-head { cursor: pointer; user-select: none; }
.sortable-head:hover { color: var(--text); }
.sortable-head.active-default { color: #bdf5d6; font-weight: 700; }
.sortable-head.active-reversed { color: #ffc9c4; font-weight: 700; }

.toggle-row { align-items: center; }
.ios-toggle-wrap { display: flex; align-items: center; gap: 8px; font-size: 0.8rem; color: var(--text-dim); cursor: pointer; }
.ios-toggle {
  position: relative;
  width: 38px;
  height: 22px;
  border-radius: 11px;
  background: var(--bg-panel-alt);
  border: 1px solid var(--border);
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
}
.ios-toggle.on { background: var(--accent); border-color: var(--accent); }
.ios-toggle-knob {
  position: absolute;
  top: 1px;
  left: 1px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #fff;
  transition: left 0.15s ease;
  box-shadow: 0 1px 2px rgba(0,0,0,0.4);
}
.ios-toggle.on .ios-toggle-knob { left: 17px; }

/* Faint vertical divider after the Shiny toggle, same visual language as
   the list-view stat column dividers. */
.toggle-separator { width: 1px; align-self: stretch; background: var(--border); opacity: 0.7; }

/* Show All Forms and Reset are pinned to the far right of the toggle row
   via this wrapper -- lining up under the view-slot toggle + Matrix/List
   control at the end of the row above. */
.toggle-row-right { display: flex; align-items: center; gap: 16px; margin-left: auto; }

/* Resets the sort buttons (back to Dex# ascending) without touching any
   other filter/toggle state. Sized to match the Matrix/List segmented
   control's total width (two 34px segments + divider) so the two line up. */
.reset-sort-btn {
  width: 69px;
  height: 30px;
  border-radius: 7px;
  border: 1px solid var(--tier-4x);
  background: var(--tier-4x);
  color: #fff;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  cursor: pointer;
}
.reset-sort-btn:hover { filter: brightness(1.15); }

/* Show Starters -- sits immediately left of Reset (per spec). Uses the
   accent color while inactive (an ordinary navigation action, not a
   destructive one) and switches to the same red "danger" treatment as
   reset-sort-btn once active ("Go Back"), since leaving the starter layer
   discards nothing but is still a "step back out" action worth visually
   distinguishing from the ordinary accent color. */
.starter-mode-btn {
  height: 30px;
  padding: 0 14px;
  border-radius: 7px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: #fff;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  cursor: pointer;
  white-space: nowrap;
}
.starter-mode-btn:hover { filter: brightness(1.15); }
.starter-mode-btn-active {
  border-color: var(--tier-4x);
  background: var(--tier-4x);
}

/* Same red treatment as reset-sort-btn -- both are destructive/clearing
   actions, so they should read as visually the same "danger" affordance. */
.remove-group-btn {
  border: 1px solid var(--tier-4x);
  background: var(--tier-4x);
  color: #fff;
  font-size: 0.72rem;
  font-weight: 700;
  padding: 4px 10px;
  border-radius: 6px;
  cursor: pointer;
}
.remove-group-btn:hover { filter: brightness(1.15); }

/* Three-step segmented control for the Megas/Legendaries toggles (off /
   include-them / only-them) -- a plain on-off switch can't express "only",
   so this borrows the same segmented look as the Matrix/List control. */
.tri-toggle-wrap { display: flex; align-items: center; gap: 8px; }
.tri-toggle-label { font-size: 0.8rem; color: var(--text-dim); }
.tri-toggle { display: inline-flex; border: 1px solid var(--border); border-radius: 7px; overflow: hidden; }
.tri-toggle button {
  padding: 5px 9px;
  background: var(--bg-panel-alt);
  border: none;
  color: var(--text-dim);
  font-size: 0.74rem;
  cursor: pointer;
}
.tri-toggle button + button { border-left: 1px solid var(--border); }
.tri-toggle button.active { background: var(--accent); color: #fff; }
.tri-toggle button:hover:not(.active) { filter: brightness(1.2); }

.type-value-select { font-weight: 700; text-shadow: 0 1px 1px rgba(0,0,0,0.35); }

/* Form/variant abbreviation badge -- shared look across the Matrix card,
   List row, and the detail page's ID line. Colored by that specific
   entry's own primary type (a form can change type, e.g. Charizard's Mega
   X becomes Fire/Dragon where base/Mega Y stay Fire/Flying), not a fixed
   color, so it doubles as a hint about what the form actually changed. */
.form-badge {
  display: inline-block;
  padding: 1px 5px;
  border-radius: 4px;
  color: #fff;
  font-size: 0.62rem;
  font-weight: 700;
  text-shadow: 0 1px 1px rgba(0,0,0,0.35);
}

/* Ability/type badges that double as "click to filter the dex by this"
   controls, in both Matrix and List view. */
.filter-clickable { cursor: pointer; }
.filter-clickable:hover { text-decoration: underline; filter: brightness(1.2); }

/* ---- dex grid (Matrix view) ---- */
.dex-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
}

.dex-card {
  position: relative;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px;
  text-align: center;
  cursor: pointer;
  transition: transform 0.08s ease, border-color 0.08s ease;
}

.dex-card:hover { border-color: var(--accent); transform: translateY(-2px); }

/* Fixed-size clipping box the sprite <img> is shifted down inside of (see
   spriteGroundStyle() in helpers.js), so every card's creature sits on the
   same visual "ground line" regardless of how much blank canvas its own
   sprite art leaves below it. */
.sprite-ground-box { width: 70px; height: 70px; margin: 0 auto; overflow: hidden; position: relative; }
/* color:transparent is deliberate, not decorative -- these sprites always
   carry alt="" (the name is already shown as its own visible text right
   below the sprite, so the alt is empty rather than duplicating it), but a
   cold-cache image (e.g. the FIRST time a given Pokemon is toggled shiny,
   before its shiny sprite URL has ever been fetched) can still take longer
   to load/decode than a re-render takes to land in the DOM. Without this,
   the browser's built-in "no image yet" fallback glyph/box could still be
   visible for that gap even with an empty alt; forcing the (nonexistent)
   fallback text transparent closes that gap completely rather than relying
   on timing. */
.dex-card-sprite-img { width: 70px; height: 70px; image-rendering: pixelated; display: block; color: transparent; }

/* Dex # and form badge stack vertically in the top-left corner (rather
   than sitting inline) so a badge never collides with the sprite next to
   it -- the sprite has its own fixed-size box below this corner, not
   beside it, but a long inline row was still crowding it visually. */
.dex-card-corner { position: absolute; top: 8px; left: 10px; display: flex; flex-direction: column; align-items: flex-start; gap: 3px; }
.dex-card-num { font-size: 0.68rem; color: var(--text-dim); line-height: 1; }
/* The form badge (and, on the Benched/Favorites cards, the star below it)
   visually sit closer to the item above them than the flex `gap` alone
   would suggest -- both are tightly-padded, low-leading elements next to
   plain text spans that carry a bit of the font's own natural leading, so
   the *geometric* gap (equal everywhere) doesn't read as an *equal* gap.
   A small extra nudge on each closes that gap. */
.dex-card-corner .form-badge { margin-top: 2px; }
.dex-card-corner .team-aux-favorite-star { margin-top: 2px; }
/* Same reasoning as the form badge/star nudges above -- the "!"
   stage-conflict marker (see team.js's conflictMarkerHtml) is a small
   solid circle with no internal leading, and without its own nudge it
   reads noticeably closer to the form badge above it than the other pairs
   in this stack. */
.dex-card-corner .team-stage-conflict { margin-top: 4px; }

/* Mirrors .dex-card-corner on the opposite side -- BST on top, the
   favorite star stacked directly below it. */
.dex-card-corner-right { position: absolute; top: 8px; right: 10px; display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
.dex-card-bst { font-size: 0.72rem; color: var(--text); font-weight: 700; line-height: 1; }
.dex-card .dex-name { font-weight: 600; margin: 6px 0 6px; }
/* No wrapping here -- a wrapped second row changes the card's height,
   which knocks the sprite/stat rows below it out of alignment with the
   rest of the row in the grid. Instead the badges are sized down enough
   (padding/font/letter-spacing all reduced from the base .type-badge) that
   even the widest realistic pair (two 8-letter type names, e.g. Fighting +
   Electric) fits on one line within the card's own padding. */
.dex-card .dex-types { display: flex; gap: 3px; justify-content: center; }
.dex-card .dex-types .type-badge { padding: 2px 6px; font-size: 0.62rem; letter-spacing: 0.01em; }

/* 2x3 grid: row 1 = HP/Atk/Def, row 2 = Spe/SpA/SpD, so Atk lines up over
   SpA and Def lines up over SpD by column. The divider above it is only as
   wide as this grid itself (i.e. from the left edge of HP to the right
   edge of Def), not the full card, since the grid doesn't stretch to the
   card's own padded edges. */
.dex-card-stats {
  display: grid;
  grid-template-columns: repeat(3, max-content);
  /* Full width (not fit-content -- see the over-constrained margin:auto
     overflow bug that caused on narrow cards, now avoided entirely) with
     justify-content: space-between instead of a fixed gap: this distributes
     whatever width is actually LEFT OVER (card width minus the 3 packs'
     own content width) as the gap between them. A 3-digit stat (Charizard's
     100+) makes its own pack wider, which leaves less to distribute, which
     shrinks the gap automatically -- keeping the divider (and the box
     itself) locked to the card's own width instead of growing wider to fit
     the extra digit the way a fixed gap value would force. The team page's
     own instance (.team-slot-stats .dex-card-stats, further down) uses a
     fixed gap instead, since it sizes to its own content rather than a
     shared card width and doesn't have leftover space to distribute. */
  justify-content: space-between;
  gap: 3px 0;
  margin-top: 8px;
  padding-top: 6px;
  border-top: 1px solid #8d9199;
}
.dex-card-stat-cell { display: flex; justify-content: space-between; gap: 4px; font-size: 0.64rem; color: var(--text-dim); }
.dex-card-stat-cell b { color: var(--text); font-weight: 600; }

/* ---- dex list (List view) ---- */
.dex-list { display: flex; flex-direction: column; border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
.dex-list-head, .dex-list-row {
  display: grid;
  grid-template-columns: 52px 44px 1.6fr 1.15fr 1.4fr auto;
  gap: 14px;
  align-items: center;
  padding: 9px 14px;
}
.dex-list-head {
  background: var(--bg-panel-alt);
  font-size: 0.68rem;
  text-transform: uppercase;
  color: var(--text-dim);
  font-weight: 600;
}
.dex-list-row {
  background: var(--bg-panel);
  border-top: 1px solid var(--border);
  cursor: pointer;
  font-size: 0.82rem;
}
.dex-list-row:hover { filter: brightness(1.12); }
/* Dex #, form badge, and favorite star stack the same way as the Matrix
   card's corner, for consistency, in the row's first column. align-self:
   stretch overrides the row's own align-items:center (which would
   otherwise shrink this column to its content height like every other
   cell) so this column fills the row's full height -- letting
   justify-content:center below center the group of 3 as a block, rather
   than pinning it to the top the way a content-height column would. gap
   (not the row's own nudge tricks) is what evenly spaces the 3 items,
   since a stretched column has real space to distribute. */
.dlr-id-col { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; align-self: stretch; }
.dlr-num { color: var(--text-dim); font-size: 0.75rem; }
.dlr-sprite { width: 36px; height: 36px; image-rendering: pixelated; transition: width 0.1s ease, height 0.1s ease; color: transparent; }
.dlr-name { font-weight: 600; }
.dlr-types { display: grid; grid-template-columns: 78px 78px; justify-items: start; gap: 6px; }
.dlr-abilities { font-size: 0.75rem; color: var(--text-dim); }
.dlr-abilities .hidden-ability { color: #e79bd6; }

/* Larger Sprites toggle (List view, on by default) roughly doubles sprite
   size and gives rows the extra vertical room to fit them. The sprite's
   own grid column has to widen too here -- otherwise a 72px image just
   overflows its old 44px-wide cell straight into the Name column next to
   it, which is exactly what was happening before this rule existed. */
.dex-list.large-sprites .dlr-sprite { width: 72px; height: 72px; }
.dex-list.large-sprites .dex-list-row,
.dex-list.large-sprites .dex-list-head {
  grid-template-columns: 52px 78px 1.6fr 1.15fr 1.4fr auto;
}
.dex-list.large-sprites .dex-list-row { padding-top: 14px; padding-bottom: 14px; }

/* Stat cells live in their own tight, gap-less flex row so each divider
   line is exactly the boundary of the cell next to it -- centering text
   inside that same cell then genuinely centers it between the lines,
   instead of centering within a box that a grid `gap` then pads on one
   side only. */
.dlr-stats-row { display: flex; justify-self: end; }
.dlr-stat, .dlr-stat-head {
  width: 58px;
  flex-shrink: 0;
  text-align: center;
  border-left: 1px solid var(--border);
  font-variant-numeric: tabular-nums;
  font-size: 0.8rem;
}
.dlr-stat-head { font-size: inherit; }
.dlr-stat-first { border-left: none; }
.dlr-bst { font-weight: 700; }

@media (max-width: 900px) {
  .dex-list-head, .dex-list-row { grid-template-columns: 40px 36px 1.2fr 36px 1fr 1fr auto; gap: 8px; padding: 7px 10px; font-size: 0.72rem; }
  .dlr-stat, .dlr-stat-head { width: 44px; }
}

/* ---- floating back-to-top button (Pokedex page) ---- */
/* Was a circular icon-only button (first a &uarr; text glyph, then a
   rotated-border chevron, then an SVG arrow) -- dropped the icon
   entirely after two centering attempts still read as off, in favor of a
   plain labeled pill button. Same rounded-rect treatment as the app's
   other pill buttons (.credits-link/.header-data-btn), just fixed-position
   and accent-colored since it's a floating action rather than inline
   chrome. */
.back-to-top-btn {
  position: fixed;
  left: 20px;
  bottom: 24px;
  padding: 10px 18px;
  border-radius: 8px;
  border: none;
  background: var(--accent);
  color: #fff;
  font-size: 0.82rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
  box-shadow: 0 2px 10px rgba(0,0,0,0.4);
  z-index: 5;
}
.back-to-top-btn.visible { opacity: 1; pointer-events: auto; }
.back-to-top-btn:hover { filter: brightness(1.15); }

