/* Components: cards, detail view, stats, badges */
/* CHARACTER CARD */
.char-card {
  border-radius: var(--card-radius);
  overflow: hidden; cursor: pointer;
  transition: transform var(--transition), box-shadow var(--transition);
  position: relative;
  background: var(--color-panel-dark);
  box-shadow: var(--shadow);
  /* Mobile: suppress native long-press callout / text-selection so the
     hold-to-add-to-team gesture isn't hijacked by the OS context menu. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  /* Establish a query container so the name text can size relative to the
     card's own width (1cqi = 1% of card width) instead of the viewport. */
  container-type: inline-size;
}
/* Hover effects only on true hover-capable pointers (desktop mouse), so they
   don't "stick" on touch devices after a tap. */
@media (hover: hover) {
  .char-card:hover:not(.long-press-active) { transform: translateY(-5px); box-shadow: var(--shadow-lg); }
  .char-card:hover .char-card-badge { opacity: 1; }
  .char-card:hover .char-card-fav-btn { opacity: 1; }
}
/* Keyboard highlight (arrow-key navigation from the search box). Outline doesn't
   shift layout; mirrors the hover emphasis. */
.char-card.kbd-active { outline: 3px solid var(--red); outline-offset: 2px; box-shadow: var(--shadow-lg); }
.char-card-img {
  width: 100%; aspect-ratio: 196/283;
  background: linear-gradient(135deg, var(--color-panel-dark), var(--color-panel-dark-2));
  position: relative; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.char-card-img img {
  width: 100%; height: 100%; object-fit: cover;
  display: block;
  /* Prevent the "save image" drag affordance; pointer-events:none keeps card
     click/closest('.char-card') working since the card handles all clicks. */
  -webkit-user-drag: none;
  user-drag: none;
  pointer-events: none;
}
.char-card-placeholder {
  font-family: 'Amboy', sans-serif;
  font-size: 18px; color: rgba(255,255,255,0.25);
  text-align: center; padding: 8px; line-height: 1.3;
}
.char-card-name-overlay {
  position: absolute; bottom: 0; left: 0; right: 0;
  background: var(--color-black);
  height: 25%;
  display: flex; align-items: center; justify-content: center;
  /* Relative padding (scales with card width like the cqi font) so the
     text-to-box ratio is constant across widths — borderline names don't
     flip between 1 and 2 lines as the window resizes. */
  padding: 0 3%;
}
.char-card-name-text {
  font-family: 'Amboy', sans-serif;
  color: white;
  text-align: center;
  line-height: 1.2;
  letter-spacing: var(--tracking-tight);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Never break inside a word; --name-scale (set per card in grid.js) is a
     length-based coefficient on the card-relative unit, capped at today's max
     (24px) so the largest text matches the previous design and never grows past
     it as cards get bigger. */
  word-break: normal;
  overflow-wrap: normal;
  font-size: clamp(10px, calc(var(--name-scale, 11) * 1cqi), 24px);
  width: 100%;
}
/* Non-breaking units (words, hyphenated names, whole parentheticals) so they
   never split across lines — wrapping happens only between these units. */
.char-card-name-text .nb { white-space: nowrap; }

/* === HERO SPECIALTY PILL === */
/* Inset rounded "pill" on the black name banner marking a hero's dominant icon
   type. Inset from the sides so it never reaches the card's rounded corners
   (this avoids the edge-clip artifact the full-width stripe had). Hidden unless
   the view is on (body.specialty-view); colored by the .spec-* class. */
/* Shared sizing vars, defined only while the view is on, so they inherit to BOTH
   the pill and the overlay (which are siblings). */
body.specialty-view .char-card[data-tab="heroes"] {
  --mu-pill-gap: 3px;                                          /* gap pill -> card bottom */
  /* 25% thicker than before (old: clamp(4px,2.2cqi,8px)). round(up, …, 1px) snaps
     the fluid cqi result UP to a whole px so the bar is the same height in every
     row (fixes the 4-vs-5px subpixel difference between rows). */
  --mu-pill-h: round(up, clamp(5px, 2.75cqi, 10px), 1px);      /* pill thickness */
  --mu-pill-foot: calc(var(--mu-pill-gap) + var(--mu-pill-h)); /* pill's vertical footprint */
  /* Name-text band frozen at its pre-change height (25% banner − half the OLD pill
     footprint) so the thicker pill no longer squeezes names. */
  --mu-name-box: calc(25% - (var(--mu-pill-gap) + clamp(4px, 2.2cqi, 8px)) / 2);
}
.char-card-specialty {
  position: absolute;
  left: 20%; right: 20%;            /* centered pill (~60% width) */
  bottom: var(--mu-pill-gap, 3px);  /* small gap below */
  height: var(--mu-pill-h, round(up, clamp(5px, 2.75cqi, 10px), 1px));
  border-radius: 999px;
  /* Below .char-card-action-scrim's z-index:3 (not equal to it) — it lives inside
     .char-card-img, which paints AFTER the scrim in DOM order, so an equal z-index
     would win the tie and render sharp on top of the blur while a card is armed. */
  z-index: 2; display: none; pointer-events: none;
}
body.specialty-view .char-card-specialty { display: block; }
.spec-fight  { background: var(--fight-color); }
.spec-move   { background: var(--move-color); }
.spec-heroic { background: var(--heroic-color); }
.spec-wild   { background: var(--wild-color); }

/* Specialty view makes room for the pill: the overlay is slightly taller than the
   base 25%, and padding-bottom equals EXACTLY the pill footprint (gap + height) so
   the flex-centered name text sits in the space between the overlay top and the pill
   top, with no black gap above the pill. Hero cards only; reverts when off. No-pill
   hero cards get the same height + padding so text position stays consistent. */
body.specialty-view .char-card[data-tab="heroes"] .char-card-name-overlay {
  height: calc(var(--mu-name-box) + var(--mu-pill-foot)); /* frozen text band + pill footprint */
  padding-bottom: var(--mu-pill-foot);                    /* exactly the pill footprint */
}

/* Specialty view shortens the name band to make room for the pill, so only the
   TALLEST two-line names risk overflowing. Cap the max two-line scale (12 -> 11)
   rather than scaling every name: names already below 11 (long names that shrank
   to fit their width, e.g. "Magneto (Age of Apocalypse)") are left untouched.
   Heroes only — the shortened band / pill exists only on hero cards. The 11 cap
   is the one tunable knob (≈ the old 12 × 0.92); nudge to ~10 if maxed names
   should sit a touch smaller. */
body.specialty-view .char-card[data-tab="heroes"] .char-card-name-text.name-two-line {
  font-size: clamp(10px, calc(min(var(--name-scale, 11), 11) * 1cqi), 24px);
}

/* === GRID TEAM BADGE ===
   --card-accent lets the badge, its checkmark fill, and the hold-progress ring all
   read as one system that recolors per tab from a single declaration — green for
   "on your team" (heroes), red for "selected" (villains) — instead of duplicating
   every color rule per tab. */
.char-card { --card-accent: #2ed573; }
.char-card[data-tab="villains"] { --card-accent: var(--red); }
.char-card-badge {
  position: absolute; top: 8px; right: 8px;
  width: 32px; height: 32px; z-index: 3;
  display: flex; align-items: center; justify-content: center;
}
.char-card-badge-inner {
  width: 24px; height: 24px; border-radius: 50%;
  border: 2px solid rgba(255,255,255,0.7);
  background: rgba(0,0,0,0.35);
  display: flex; align-items: center; justify-content: center;
  position: relative; z-index: 1;
  transition: background var(--transition), border-color var(--transition);
}
.char-card-badge-inner::after {
  content: '✓'; color: white; font-size: 13px; font-weight: 900;
  line-height: 1;
  opacity: 0; transition: opacity var(--transition);
}
.char-card.in-team .char-card-badge-inner { background: var(--card-accent); border-color: var(--card-accent); }
.char-card.in-team .char-card-badge-inner::after { opacity: 1; }
/* Hidden by default; desktop hover reveals it (see @media (hover: hover) block
   above); always visible when in-team (on-team hero, or the selected villain) */
.char-card-badge { opacity: 0; transition: opacity var(--transition); }
.char-card.in-team .char-card-badge { opacity: 1; }
/* Reveal the badge during a hold so the add-progress ring is visible on touch
   (where the badge is otherwise hidden until the hero is in-team). */
.char-card.long-press-active .char-card-badge { opacity: 1; }
/* Team full: non-member add-circles read as disabled (the click still surfaces a
   toast). Hero cap only — excludes villain cards, which render into the same
   #card-grid/#extra-grid, so a full hero roster must not dim villain badges. */
.card-grid.team-full .char-card:not(.in-team):not([data-tab="villains"]) .char-card-badge-inner,
#team-heroes-grid.team-full .char-card:not(.in-team):not([data-tab="villains"]) .char-card-badge-inner,
#team-heroes-extra-grid.team-full .char-card:not(.in-team):not([data-tab="villains"]) .char-card-badge-inner { opacity: 0.4; }

/* === GRID FAVORITE HEART — mirrors the team badge above (top-LEFT instead of
   top-right; .char-card-badge owns top-right). Heart, not star: the app's heroic
   specialty symbol is already a star, so reusing it would show two different
   meanings as the same glyph on a specialized+favorited card. === */
.char-card-fav-btn {
  position: absolute; top: 8px; left: 8px;
  width: 32px; height: 32px; z-index: 3;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; padding: 0; cursor: pointer;
  color: white;
  /* Hidden by default; desktop hover reveals it (see @media (hover: hover) above);
     always visible once favorited — same rule as .char-card.in-team. */
  opacity: 0; transition: opacity var(--transition);
}
.char-card-fav-btn.favorited { opacity: 1; }
/* Every property here mirrors .char-card-badge-inner so the two top-corner toggles
   read as one system: the heart's own silhouette provides the same translucent dark
   backdrop the badge's circle does, so the faded white stroke composites over an
   identical surface on both (no drop-shadow — a dark halo behind the stroke made the
   70% white resolve brighter than the badge's and cancelled the fade). */
.fav-heart-icon {
  width: 24px; height: 24px; /* matches .char-card-badge-inner's circle height */
  fill: rgba(0,0,0,0.35); stroke: rgba(255,255,255,0.7); stroke-width: 1.667;
  /* viewBox is cropped tight to the glyph's bbox (was "0 0 24 24" with ~3 units of
     empty space above/below the path, reading shorter than the badge circle). The
     tighter viewBox scales user units to screen px at 1.2x (24/20), so stroke-width
     is divided by 1.2 here (2 / 1.2) to keep rendering at the same 2px as the badge. */
  transition: fill var(--transition), stroke var(--transition);
  /* The halo path below strokes past the glyph's edge and past the viewBox itself.
     Let it bleed rather than growing the viewBox, which would shrink the heart again. */
  overflow: visible;
}
.char-card-fav-btn.favorited .fav-heart-icon { fill: var(--favorite); stroke: rgba(255,255,255,0.7); }
/* Outer halo — mirrors .char-card-ring-track, the faint ring painted just outside the
   team badge's circle. Same glyph drawn underneath at a wider stroke so ~2px show
   beyond the main outline, matching the track's ~2.4px bleed past the badge edge.
   (On the badge that ring doubles as the long-press progress track; here it is purely
   cosmetic, so the heart gets no hold-gesture affordance it can't honor.) */
.fav-heart-halo {
  fill: none; stroke: rgba(255,255,255,0.15); stroke-width: 5; /* 6 / 1.2, see .fav-heart-icon */
  stroke-linejoin: round;
}

/* === GRID PLAY-COUNT BADGE (villains only) — same top-left corner as the hero fav
   heart above, since villains have neither that nor the team badge. Functional-first:
   a plain numeral rather than matching the badge/heart's icon treatment — a styling
   pass can bring it in line with that system later. Always visible (not hover-gated
   like the heart) since it's informational, not an action. */
.char-card-play-badge {
  position: absolute; top: 8px; left: 8px;
  min-width: 24px; height: 24px; z-index: 3;
  display: flex; align-items: center; justify-content: center;
  padding: 0 6px; border-radius: 12px;
  background: rgba(0,0,0,0.55); color: white;
  font-family: var(--font-sans); font-size: 12px; font-weight: 700;
}

.char-card-progress-ring {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  transform: rotate(-90deg); pointer-events: none;
}
.char-card-ring-track { fill: none; stroke: rgba(255,255,255,0.15); stroke-width: 3; }
.char-card-ring-fill {
  fill: none; stroke: var(--card-accent); stroke-width: 3; stroke-linecap: round;
  stroke-dasharray: 100.53; stroke-dashoffset: 100.53;
}
@keyframes ring-fill { to { stroke-dashoffset: 0; } }
/* Remove-hold (card already in team/selected): the ring starts full and drains to
   empty as you hold, signalling the membership running out. */
@keyframes ring-drain { from { stroke-dashoffset: 0; } to { stroke-dashoffset: 100.53; } }
.char-card.long-press-active { transform: scale(0.93); transition: transform 80ms ease; }
.char-card.long-press-active .char-card-ring-fill {
  animation: ring-fill 650ms linear forwards;
}
.char-card.in-team.long-press-active .char-card-ring-fill {
  animation: ring-drain 650ms linear forwards;
}

/* === GRID CARD ARMED ACTION PILL (mobile long-press) — the hold gesture no longer
   commits directly (see js/main.js's armCard); instead it reveals this pill so a
   resting finger mid-scroll can't silently add/remove/select. Scrim guarantees the
   pill's contrast against any card's own art; the corner badge, fav heart, and
   progress ring are lifted above it (z-index 4) so they don't fade into the scrim
   while a card is armed. === */
.char-card-action-scrim {
  position: absolute; inset: 0; z-index: 3;
  background: rgba(0,0,0,0.3);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  opacity: 0; pointer-events: none;
  transition: opacity var(--transition);
}
/* .char-card-action-wrap holds 1 button (a plain remove/select/clear pill, or
   "Add to team" in a single-team mode) or 2 (the grid's per-band "which team" split
   in a 2-band mode — see js/team.js's heroAddBandConfigs). align-items:stretch on a
   shrink-to-fit inline-flex column means every button matches the WIDEST one
   automatically, so a two-pill stack always reads as one clean block. */
.char-card-action-wrap {
  position: absolute; left: 50%; top: 50%; z-index: 4;
  transform: translate(-50%, -50%);
  display: none;
  flex-direction: column; align-items: stretch;
  gap: 6px;
  /* min-width (not just max-width) so every variant — the plain single pill, the
     two-pill band split, villain select/clear — renders at the same width instead
     of each shrink-wrapping its own shorter text. Sized off the widest real case
     (the two-line band split). */
  min-width: 80%;
  max-width: calc(100% - 20px);
}
.card-armed .char-card-action-wrap { display: inline-flex; }
.char-card-action {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px;
  min-height: 46px; padding: 8px 10px;
  border: none; border-radius: 12px;
  /* Color follows the ACTION (add/select vs. remove/clear), not the tab — unlike
     --card-accent (badge/ring), which stays tab-colored so the persistent "on your
     team" indicator keeps reading green-for-hero/red-for-villain either way. Uses
     the app's real --green token (team-sheet-handle etc.), not the ad-hoc #2ed573
     the checkmark badge uses — those two greens are deliberately different. A
     per-band pill overrides this with an inline background (--team-gold/etc, set in
     js/long-press-arm.js from js/team.js's heroAddBandConfigs) since the color there
     depends on WHICH band, not just add-vs-remove. */
  background: var(--green); color: var(--color-white);
  font-family: 'Amboy', sans-serif; font-weight: normal; font-size: 12px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer;
}
.char-card-action.is-remove { background: var(--red); }
/* A full band's pill still carries its team's inline color (so it stays
   identifiable), just dimmed as a whole via opacity rather than recoloring —
   works uniformly regardless of which color it is. */
.char-card-action.is-full { opacity: 0.55; cursor: default; }
.char-card-action-avatars { display: flex; margin-bottom: 1px; }
.char-card-action-avatar {
  width: 20px; height: 20px; border-radius: 50%; object-fit: cover;
  border: 1.5px solid rgba(255,255,255,0.55);
  background: rgba(255,255,255,0.2);
  margin-left: -6px;
}
.char-card-action-avatar:first-child { margin-left: 0; }
.char-card-action-label { display: flex; flex-direction: column; }
.char-card-action-label span { display: block; line-height: 1.25; }
/* .card-armed alone is the marker (not scoped to .char-card) — js/team.js's
   .team-slot and .team-villain-tile reuse this same action-pill markup/classes,
   see css/overlays.css's own .team-slot.card-armed rules for their corner-element
   equivalent of the z-index lift below. */
.card-armed .char-card-action-scrim { opacity: 1; pointer-events: auto; }
.char-card.card-armed .char-card-badge,
.char-card.card-armed .char-card-fav-btn,
.char-card.card-armed .char-card-play-badge { z-index: 4; }

/* LOADING/EMPTY */
#loading {
  text-align: center; padding: 80px 20px;
  font-family: 'Amboy', sans-serif; font-size: 18px; color: var(--mid-grey);
}
.loading-spinner {
  width: 40px; height: 40px; border-radius: 50%;
  border: 3px solid var(--off-grey);
  border-top-color: var(--red);
  animation: spin 0.8s linear infinite;
  margin: 0 auto 16px;
}
@keyframes spin { to { transform: rotate(360deg); } }
#empty { display: none; text-align: center; padding: 60px; font-family: 'Amboy', sans-serif; font-size: 18px; color: var(--mid-grey); }
#cross-tab-hint { display: none; text-align: center; padding: 24px 20px 8px; font-size: 14px; color: var(--mid-grey); }
#empty .empty-sub { font-size: 14px; margin-top: 10px; }
.empty-jump { cursor: pointer; text-decoration: none; }
.empty-jump .cth-name { font-family: 'Amboy', sans-serif; }
.empty-jump[data-goto-tab="heroes"]   { color: var(--color-hero-tag); }
.empty-jump[data-goto-tab="villains"] { color: var(--red); }
.empty-jump[data-goto-tab="modes"]    { color: var(--purple); }

/* "Not in your collection" expandable section. The toggle and #extra-grid each
   constrain to content-max so they line up exactly with #card-grid above. */
#not-owned-toggle, #team-heroes-not-owned-toggle, #char-picker-not-owned-toggle {
  display: block;
  box-sizing: border-box;
  width: 100%;
  max-width: var(--content-max);
  margin: 36px auto 4px;
  padding: 12px var(--gutter);
  background: none;
  border: 0;
  border-top: 1px solid var(--off-grey);
  font-family: inherit;
  font-size: 14px;
  color: var(--mid-grey);
  text-align: center;
  cursor: pointer;
}
/* In the team modal and the character picker the toggle spans its own section/panel
   (no page content-max / gutter). */
#team-heroes-not-owned-toggle, #char-picker-not-owned-toggle { max-width: none; margin: 24px 0 8px; padding: 12px 0; }
#not-owned-toggle .no-chevron, #team-heroes-not-owned-toggle .no-chevron,
#char-picker-not-owned-toggle .no-chevron {
  display: inline-block;
  margin-left: 4px;
  transition: transform 0.2s ease;
}
#not-owned-section.expanded #not-owned-toggle .no-chevron,
#team-heroes-not-owned-toggle[aria-expanded="true"] .no-chevron,
#char-picker-not-owned-toggle[aria-expanded="true"] .no-chevron { transform: rotate(180deg); }
#extra-grid { padding-bottom: 8px; }

/* OVERLAY BACKDROP */
.overlay-backdrop {
  display: none; position: fixed; inset: 0; z-index: 200;
  background: rgba(0,0,0,0.6);
  backdrop-filter: blur(4px);
  align-items: flex-start; justify-content: center;
  padding: 8px; overflow-y: auto; overflow-x: hidden;
}
@media (min-width: 700px) {
  .overlay-backdrop { padding: 20px; }
}
.overlay-backdrop.open { display: flex; }

/* DETAIL OVERLAY */
#detail-panel {
  background: var(--surface-card); border-radius: var(--overlay-radius);
  width: 100%; max-width: 1000px;
  /* `clip` clips exactly like `hidden` (the art does its own clipping — see #detail-art)
     but does NOT establish a scroll container, which is what lets the mobile
     #detail-action-bar's `position: sticky` resolve against .overlay-backdrop (the real
     scroller) instead of this panel. Declared after `hidden` so browsers without `clip`
     keep the old clipping; detail.js gates the bar relocation on CSS.supports so those
     browsers simply keep the inline button. */
  overflow: hidden;
  overflow: clip;
  box-shadow: var(--shadow-lg);
  position: relative; margin: auto;
}
/* Prev/next navigation animation: slide + fade the panel around the instant content
   swap (see animateNavSwap in detail.js). Only one character's content is ever in the
   DOM, so we animate this same element out toward the travel direction, swap while
   hidden, then bring it back from the far side. */
#detail-panel { transition: transform 0.17s ease, opacity 0.17s ease; }
#detail-panel.nav-out-next { transform: translateX(-22px); opacity: 0; }
#detail-panel.nav-out-prev { transform: translateX(22px); opacity: 0; }
/* Entering start-state: pre-placed on the far side with NO transition, then the class
   is removed so it eases back to rest. */
#detail-panel.nav-in-next { transform: translateX(22px); opacity: 0; transition: none; }
#detail-panel.nav-in-prev { transform: translateX(-22px); opacity: 0; transition: none; }
@media (prefers-reduced-motion: reduce) {
  #detail-panel { transition: none; }
}
.overlay-close {
  position: absolute; top: 14px; right: 14px; z-index: 10;
  color: var(--dark-grey);
  width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  font-size: 18px; transition: color var(--transition); line-height: 1;
}
.overlay-close:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }
/* Detail overlay sits over card art on the stacked layout — give the close button a
   solid white chip so the glyph stays legible over any artwork and reads as its own
   target. On hover/press the chip fills red and the glyph flips to white for contrast. */
#detail-close {
  background: var(--white);
  border-radius: 50%;
  width: 30px; height: 30px;
  /* Sync the red-circle fill with the glyph color flip so the × inverts at the
     same rate the circle fills (was: background snapped instantly while color
     transitioned, so the glyph lagged behind the circle). */
  transition: background var(--transition), color var(--transition);
}
#detail-close:active {
  background: var(--red);
  color: var(--white);
}
/* Drill-in back arrow: a white pill in the top-left mirroring the close chip, with
   the Amboy-styled name of the item it returns to. Hidden until a trail exists. */
.overlay-back {
  position: absolute; top: 14px; left: 14px; z-index: 10;
  display: flex; align-items: center; gap: 4px;
  height: 30px; padding: 0 12px;
  background: var(--white); border-radius: 15px;
  font-family: 'Amboy', sans-serif; font-size: 13px; line-height: 1;
  color: var(--dark-grey);
  max-width: 45%;
  transition: background var(--transition), color var(--transition);
}
.overlay-back #detail-back-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.overlay-back:active { background: var(--red); color: var(--white); }
.overlay-back:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }
/* Both columns scale together proportionally (~1.7:1) until they stack at <=800px */
#detail-top { display: grid; grid-template-columns: 1.2fr 1fr; }
#detail-art {
  background: var(--surface-card);
  min-height: 560px; position: relative; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  padding: 30px 30px 0;
}
#detail-art.no-image {
  background: linear-gradient(135deg, var(--color-panel-dark), var(--color-panel-dark-2));
}
/* Desktop: the card image is centered in the art column (sized by the taller
   info column) and shrinks to fit. Using max-width/height with auto sizing (rather
   than a fixed box + object-fit) lets the <img> element hug the actual image so the
   border-radius rounds the visible edges — a fixed box letterboxes landscape art
   (mode/villain covers, card backs) and the rounding ends up in the empty padding.
   (Moved here from inline styles in detail.js so the mobile query below can override.) */
#detail-art img {
  max-width: 100%; max-height: 100%;
  width: auto; height: auto;
  object-fit: contain; object-position: center;
  background: var(--surface-card); cursor: zoom-in;
  border-radius: var(--card-radius);
}
#detail-art-placeholder {
  font-family: 'Amboy', sans-serif;
  font-size: 36px; color: rgba(255,255,255,0.15);
  text-align: center; padding: 24px; position: relative; z-index: 1;
  width: 100%;
}
/* The placeholder is only for the no-image fallback. Hide it when an image is
   present so it isn't a second flex child fighting the in-flow image for space
   (this offset the mobile image to the right). */
#detail-art:not(.no-image) #detail-art-placeholder { display: none; }
/* Info-card carousel nav — circular prev/next over the art, plus an N/total
   counter. JS toggles display:flex/block only when >1 card is found, so single
   card villains and heroes are unaffected. Mirrors the lightbox nav language. */
.detail-art-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 40px; height: 40px; z-index: 2;
  display: none; align-items: center; justify-content: center;
  border: none; cursor: pointer;
  font-size: 26px; line-height: 1; color: white;
  background: rgba(0,0,0,0.45); border-radius: 50%;
  transition: background 0.2s;
}
.detail-art-nav:disabled { opacity: 0.25; cursor: default; }
.detail-art-nav.prev { left: 12px; }
.detail-art-nav.next { right: 12px; }
#detail-art-counter {
  position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%);
  z-index: 2; display: none;
  font-family: 'Amboy', sans-serif; font-size: 13px;
  letter-spacing: var(--tracking-wide); color: white;
  background: rgba(0,0,0,0.5); padding: 3px 12px; border-radius: 12px;
}
/* Prev/next between HEROES in the current grid list (not to be confused with
   .detail-art-nav, which pages through one hero's own info-card images). Fixed to
   the viewport edges outside #detail-panel, mirroring .lightbox-nav. Desktop only —
   #detail-overlay.has-detail-nav gates visibility (set by updateDetailNavButtons in
   detail.js); mobile relies on the swipe gesture instead (see main.js). */
.detail-nav-btn {
  position: fixed; top: 50%; transform: translateY(-50%); z-index: 201;
  width: 52px; height: 52px; border-radius: 50%;
  display: none; align-items: center; justify-content: center;
  border: none; cursor: pointer;
  background: rgba(0,0,0,0.45); color: rgba(255,255,255,0.85);
  transition: background 0.2s;
}
.detail-nav-btn::before {
  content: ''; display: block; width: 13px; height: 13px;
  border-right: 3px solid currentColor; border-bottom: 3px solid currentColor;
}
.detail-nav-btn.prev { left: 24px; }
.detail-nav-btn.prev::before { transform: rotate(135deg); margin-left: 5px; }
.detail-nav-btn.next { right: 24px; }
.detail-nav-btn.next::before { transform: rotate(-45deg); margin-right: 5px; }
.detail-nav-btn:disabled { opacity: 0.3; cursor: default; }
#detail-overlay.has-detail-nav.open .detail-nav-btn { display: flex; }
@media (max-width: 600px) {
  /* Swipe replaces the arrows on touch. */
  .detail-nav-btn { display: none !important; }
}
#detail-info {
  padding: var(--space-7); display: flex; flex-direction: column;
  /* A little headroom so borderline-length breakdowns (e.g. Moonstone) grow to fit
     instead of triggering a scrollbar; the art column (min-height 560) stretches
     to match when info is taller. */
  overflow-y: auto; max-height: 600px;
}
/* Holds everything except the anti-hero toggle and carries the vertical centering
   + gap. The toggle hugs the top of #detail-info (below), so its position never
   shifts when switching hero<->villain (whose content heights differ). */
#detail-info-body {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; gap: 0;
  justify-content: center;
}
#detail-name {
  font-family: 'Amboy', sans-serif; font-size: 24px; line-height: 1.1; color: var(--color-text-primary);
}
#detail-type-badge {
  display: inline-block; font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--tracking-wide);
  padding: 3px 10px; border-radius: 10px; margin-top: var(--space-2-5);
  background: var(--off-grey); color: var(--dark-grey);
}
#detail-type-badge.hero { background: rgba(75,184,214,0.15); color: var(--color-hero-tag); }
#detail-type-badge.villain { background: rgba(204,41,54,0.12); color: var(--red); }
#detail-type-badge.antihero { background: rgba(91,63,166,0.12); color: var(--purple); }
#detail-set { font-size: 13px; color: var(--dark-grey); margin-top: var(--space-5); }
.ks-exclusive-badge { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--mid-grey); margin-top: var(--space-2); }
.ks-exclusive-icon { width: 14px; height: 14px; object-fit: contain; flex-shrink: 0; }

/* A little breathing room above the CARD BREAKDOWN title (shared hero + villain). */
#detail-stats-wrap { padding-top: var(--space-5); }
.stat-section-title {
  font-family: 'Amboy', sans-serif; font-size: 16px;
  color: var(--dark-grey); letter-spacing: var(--tracking-wide);
  text-transform: uppercase; margin-bottom: var(--space-3);
}
.stat-section-title--lg { font-size: 20px; margin-bottom: var(--space-5); }
#detail-stats { display: flex; flex-direction: column; gap: 14px; }
/* ANTI-HERO VIEW TOGGLE — sliding pill (mirrors the main nav pill). Pinned to the
   top of #detail-info via flex:0 0 auto so it never moves between hero/villain. A
   thumb slides between the two halves; blue for hero, red for villain. */
#ah-view-toggle {
  display: none;
  position: relative;
  flex: 0 0 auto;
  background: var(--off-grey);
  border-radius: 20px;
  padding: 3px;
  margin: 0 auto 16px;
  width: 75%;
  overflow: hidden;
}
#ah-view-toggle.visible { display: flex; }
.ah-toggle-thumb {
  position: absolute; top: 3px; left: 3px; bottom: 3px;
  width: calc(50% - 3px);
  border-radius: 20px;
  background: var(--color-hero-tag);   /* hero = blue (default) */
  transition: transform var(--transition), background var(--transition);
  z-index: 0;
}
#ah-view-toggle.villain-mode .ah-toggle-thumb {
  transform: translateX(100%);
  background: var(--red);              /* villain = red */
}
.ah-toggle-btn {
  position: relative; z-index: 1;
  flex: 1; text-align: center;
  font-family: 'Amboy', sans-serif; font-size: 11px;
  padding: 6px 0; cursor: pointer;
  border: none; background: none;
  color: var(--mid-grey);
  transition: color var(--transition);
}
.ah-toggle-btn.active { color: white; }

.stat-row { display: flex; align-items: center; gap: 12px; }
.stat-icon {
  width: 42px; height: 42px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.stat-bar-wrap { flex: 1; }
.stat-bar-label { display: none; }
.stat-bar-track { background: var(--off-grey); border-radius: 6px; height: 12px; overflow: hidden; }
.stat-bar-fill { height: 100%; border-radius: 6px; transition: width 0.6s ease; }
.stat-value { font-family: var(--font-sans); font-variant-numeric: tabular-nums; font-size: 22px; font-weight: 900; color: var(--color-text-secondary); width: 64px; flex: 0 0 64px; text-align: left; display: flex; align-items: center; justify-content: flex-start; gap: 6px; }
.stat-count { font-family: var(--font-sans); font-variant-numeric: tabular-nums; font-size: 15px; color: var(--mid-grey); font-weight: 600; line-height: 1; display: flex; align-items: center; }

#add-to-team-btn {
  align-self: center;
  background: var(--green); color: white;
  font-family: 'Amboy', sans-serif; font-size: 13px;
  padding: 0 28px; height: 44px; display: flex; align-items: center; justify-content: center;
  border-radius: 999px; letter-spacing: var(--tracking-tight);
  transition: background var(--transition);
}
#add-to-team-btn.in-team { background: var(--red); }
#add-to-team-btn.team-full { background: var(--off-grey); color: var(--mid-grey); cursor: default; }

/* Wraps ADD TO TEAM + the favorite heart as one relocatable unit — see detail.js's
   syncAddBtnPlacement for why hidden-state lives on this row rather than the button. */
#detail-action-row {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  /* Matches .stat-section-title's margin-bottom so the stats block is evenly
     padded above and below. Lives on the row (not the button) so the fav
     heart shifts down in step with ADD TO TEAM instead of the two misaligning. */
  margin-top: var(--space-3);
}
#detail-action-row.hidden { display: none; }
#detail-fav-btn {
  flex: 0 0 auto;
  width: 44px; height: 44px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--white); border: 1px solid var(--off-grey); cursor: pointer;
  color: var(--mid-grey);
  transition: border-color var(--transition), color var(--transition);
}
#detail-fav-btn .fav-heart-icon {
  width: 20px; height: 20px;
  fill: none; stroke: currentColor; stroke-width: 1.75;
}
#detail-fav-btn.favorited { border-color: var(--favorite); color: var(--favorite); }
#detail-fav-btn.favorited .fav-heart-icon { fill: var(--favorite); }

/* DETAIL BOTTOM */
/* The min-height transition only fires when switchAhView() pins an inline
   min-height during a HERO<->VILLAIN toggle, easing the settle to the new view's
   height; it's inert during normal opens (min-height is otherwise never set). */
#detail-bottom { padding: var(--space-6) var(--space-8) var(--space-9); transition: min-height 0.25s ease; }
.section-title {
  font-family: 'Amboy', sans-serif; font-size: 16px;
  color: var(--dark-grey); letter-spacing: var(--tracking-wide);
  text-transform: uppercase; margin-bottom: 12px;
}
#cards-section-title { margin-top: 24px; }
#cards-section-title.flush-top { margin-top: 0; }

/* CARD TABS (hero/villain for anti-heroes) */
#card-tabs { display: flex; gap: 8px; margin-bottom: 14px; }
.card-tab-btn {
  font-family: 'Amboy', sans-serif; font-size: 11px;
  padding: 5px 14px; border-radius: 14px; letter-spacing: var(--tracking-tight);
  border: 2px solid var(--off-grey); color: var(--dark-grey);
  transition: all var(--transition);
}
.card-tab-btn.active { background: var(--red); border-color: var(--red); color: white; }
.card-tab-btn.villain-tab.active { background: var(--color-villain-dark); border-color: var(--color-villain-dark); color: var(--color-white); }

#detail-cards-grid {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px;
}
/* Team modal HEROES grids: their own column scale (5 → 4 → 3, never below 3);
   responsive steps live in overlays.css. */
/* Give HEROES breathing room below the team-deck gallery above it (the section is
   display:none when empty, so the margin only applies once shown). */
#team-heroes-section:not(.is-hidden) { margin-top: var(--space-6); }
#team-heroes-grid, #team-heroes-extra-grid {
  display: grid; grid-template-columns: repeat(5, 1fr); gap: 20px;
}
.detail-card-thumb, .hero-card-wrap {
  border-radius: 7px; overflow: visible;
  background: var(--off-grey); aspect-ratio: 196/283;
  cursor: pointer; transition: transform var(--transition), box-shadow var(--transition);
  box-shadow: 3px 4px 4px rgba(0,0,0,0.3);
}
/* Lift on hover (was scale(1.04), which softened the card art on GPU rescale).
   The deeper, more-offset shadow reads as the card rising off the page. Hover-capable
   pointers only — otherwise the lift sticks after a tap on touch devices. */
@media (hover: hover) {
  .detail-card-thumb:hover, .hero-card-wrap:hover { transform: translateY(-6px); box-shadow: 4px 12px 16px rgba(0,0,0,0.32); }
}
.detail-card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; border-radius: 7px; }
.detail-card-thumb.missing { display: flex; align-items: center; justify-content: center; }
.detail-card-thumb.missing span { font-size: 10px; color: var(--mid-grey); text-align: center; padding: 4px; }

/* VILLAIN DETAIL */
.villain-health-label {
  font-family: 'Amboy', sans-serif; font-size: 13px; color: var(--color-label-grey);
  margin-bottom: var(--space-3);
}
#villain-health-row { display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
.health-badge {
  background: var(--fight-color); color: white;
  font-family: 'Amboy', sans-serif; font-size: 16px;
  padding: 8px 14px; border-radius: 10px; text-align: center; min-width: 60px;
}
.health-badge span { display: block; font-size: 9px; text-transform: uppercase; letter-spacing: var(--tracking-wide); opacity: 0.85; margin-bottom: 2px; font-family: system-ui; font-weight: 600; }
#villain-special { font-size: 13px; color: var(--color-text-secondary); line-height: 1.6; }

/* Villain threat-deck summary */
#villain-threat { margin-bottom: 12px; }
.threat-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: var(--space-3); }
.threat-title { font-family: 'Amboy', sans-serif; font-size: 13px; color: var(--color-label-grey); }
.threat-count { font-size: 12px; color: var(--mid-grey); }
.threat-variable { font-size: 12px; color: var(--mid-grey); }
.threat-icons { display: flex; gap: 8px; margin-bottom: 8px; }
.threat-cell { flex: 1; background: var(--hairline); border-radius: 8px; padding: 8px 4px; display: flex; flex-direction: column; align-items: center; justify-content: center; border: 1.5px solid transparent; }
.threat-cell:not(.threat-zero) { border-color: var(--color-border, #c5c9ce); border-width: 2.5px; }
.threat-cell img { width: 36px; height: auto; object-fit: contain; }
.threat-cell span { font-size: 20px; font-weight: 600; color: var(--color-text-primary); }
.threat-cell:not(.threat-zero) span { margin-top: calc(-1 * var(--space-1)); }
.threat-cell.threat-zero { opacity: 0.35; }
.threat-cell.threat-zero img { width: 26px; height: auto; }
.threat-cell.threat-zero span { font-size: 17px; font-weight: 300; }
.threat-cell.threat-zero { display: flex; flex-direction: column; align-items: center; justify-content: center; }
/* Minion card: dark "active" state (has minions) and a light, muted "is-empty"
   state reusing the same shell. The Health icon is a black square with a white
   pulse, so it seats directly on the dark card; on the light empty card it is
   dimmed to read as muted. */
.threat-minion { display: flex; align-items: center; gap: 12px; background: var(--color-panel-dark); border-radius: var(--radius-lg); padding: 12px 14px; }
.threat-minion.is-empty { background: var(--hairline); opacity: 0.35; }
.threat-minion.is-empty .threat-minion-icon { opacity: 1; }
.threat-minion-icon { width: 48px; height: 48px; border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.threat-minion-icon img { width: 40px; height: 40px; object-fit: contain; }
.threat-minion-text { flex: 1; display: flex; flex-direction: column; }
.threat-minion-count { font-size: 12px; color: var(--mid-grey); }
.threat-minion-hp-label { font-size: 11px; font-weight: 700; letter-spacing: 0.6px; text-transform: uppercase; color: #C9CDD3; }
.threat-minion-val-wrap { width: 48px; height: 48px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.threat-minion-val { font-size: 22px; font-weight: 600; color: #fff; }
.threat-minion-empty { flex: 1; font-size: 13px; color: var(--mid-grey); }

/* MODE DETAIL */
#mode-details { font-size: 13px; color: var(--color-text-secondary); line-height: 1.7; }
.mode-badge {
  display: inline-block; font-size: 10px; font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--tracking-wide);
  padding: 3px 10px; border-radius: 10px; margin-bottom: 10px;
}
.mode-badge.challenge { background: rgba(204,41,54,0.12); color: var(--red); }
.mode-badge.advantage { background: rgba(19,147,83,0.12); color: var(--green); }
.mode-badge.campaign { background: rgba(91,63,166,0.12); color: var(--purple); }

/* ===== MODES TAB: SECTIONED GRID ===== */
/* Same single grid as heroes/villains; each section header is a full-width grid
   item (spans every column) and the section's cards flow after it in that grid. */
.modes-section-head {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: 10px;
  margin: 12px 0 2px;
}
.modes-section-head:first-child { margin-top: 0; }
.modes-section-title {
  font-family: 'Amboy', sans-serif; font-size: 22px; margin: 0;
  color: var(--dark-grey); letter-spacing: var(--tracking-wide); text-transform: uppercase;
}
.modes-section-count {
  font-family: 'Amboy', sans-serif; font-size: 12px; color: var(--mid-grey);
  background: var(--off-grey); border-radius: 999px; padding: 2px 10px;
}

.detail-type-badge.mode { background: rgba(91,63,166,0.12); color: var(--purple); }

/* ===== MODES DETAIL: METADATA + GALLERY ===== */
#detail-modes-meta { margin: 8px 0; display: flex; flex-direction: column; gap: 8px; }
.modes-meta-row { display: flex; gap: 8px; font-size: 13px; line-height: 1.5; }
.modes-meta-label {
  flex: 0 0 78px; font-family: 'Amboy', sans-serif; font-size: 11px;
  text-transform: uppercase; letter-spacing: var(--tracking-wide); color: var(--mid-grey);
  padding-top: 1px;
}
.modes-meta-val { color: var(--color-text-primary); }
.modes-meta-block { display: flex; flex-direction: column; margin-top: 10px; gap: 8px; }
/* The label's flex-basis is a fixed *width* for row layout; as a block header it's a
   column child, so that basis would become a fixed height (a tall box with the text
   pinned to the top). Size it to content here — row labels keep their 78px column. */
.modes-meta-block .modes-meta-label { flex: 0 0 auto; }
/* Team Members roster is redundant on phones (tight space); hide it there. */
@media (max-width: 600px) { .modes-meta-block--team { display: none; } }
.modes-meta-text { font-size: 13px; color: var(--color-text-secondary); line-height: 1.6; }
.modes-meta-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.modes-meta-chip {
  font-size: 11px; background: var(--off-grey); color: var(--dark-grey);
  border-radius: 6px; padding: 3px 8px;
}
/* Member chips (Teams + Campaigns): the Amboy display face at a slightly larger
   size, near-black, to read as names/places rather than generic tags. */
.modes-meta-chips.member-chips .modes-meta-chip {
  font-family: 'Amboy', sans-serif; font-size: 13px;
  color: var(--color-text-primary); letter-spacing: var(--tracking-tight);
  padding: 4px 9px;
}

/* Modes gallery cards are portrait + identical to the hero deck cards. Flip pairs
   (which would otherwise inherit the landscape equipment ratio) are forced portrait
   — except dashboards, which match the villain-dashboard size (landscape, wide). */
#detail-cards-grid.modes-gallery .equip-card-wrap:not(.modes-dash) .equip-card-inner { aspect-ratio: 196/283; }
#detail-cards-grid .equip-card-wrap.modes-dash { grid-column: 1 / -1; max-width: 720px; }
#detail-cards-grid .equip-card-wrap.modes-dash .equip-card-inner { aspect-ratio: 2324/1032; }

/* Campaign cards are taller/narrower (~786x1265, r≈0.62) than the 196/283 standard.
   Keep the column width; let height follow the card's true ratio (no crop). */
.char-card.modes-campaign .char-card-img { aspect-ratio: 786 / 1265; }
#detail-cards-grid.modes-campaign-gallery .detail-card-thumb,
#detail-cards-grid.modes-campaign-gallery .equip-card-wrap:not(.modes-dash) .equip-card-inner {
  aspect-ratio: 786 / 1265;
}

/* The Deadpool Chaos Challenge cover is a landscape card (232×194); stand it upright on the
   grid (rotate 90° CCW) so it matches the portrait neighbours. The detail modal renders via a
   separate path and keeps it horizontal, so this stays scoped to the grid card. The swapped
   width/height percentages are the cell aspect (196/283) inverted, so after the rotation the
   image's bounding box exactly fills the portrait cell and object-fit:cover crops cleanly. */
.char-card.modes-cover-rotate-ccw .char-card-img img {
  position: absolute; top: 50%; left: 50%;
  width: 144.39%;   /* = 283/196 → becomes the horizontal extent after rotate */
  height: 69.26%;   /* = 196/283 → becomes the vertical extent after rotate */
  transform: translate(-50%, -50%) rotate(-90deg);
  object-fit: cover; object-position: center;
}

/* Symbiotes detail: one card sub-grid per symbiote. Each section spans the full
   #detail-cards-grid (which is itself a 4-col grid) so the symbiote headers stack
   vertically full-width instead of being crammed into one column each. */
#detail-cards-grid .modes-sub-section { grid-column: 1 / -1; }
.modes-sub-section { margin-bottom: 20px; }
.modes-sub-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
@media (max-width: 700px) { .modes-sub-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 600px) { .modes-sub-grid { grid-template-columns: repeat(2, 1fr); } }
.modes-empty {
  grid-column: 1 / -1; text-align: center; color: var(--mid-grey);
  font-size: 13px; padding: 24px 0;
}

/* Hover effects only on true hover-capable pointers (desktop mouse), so they
   don't "stick" on touch devices after a tap. */
@media (hover: hover) {
  .empty-jump:hover { text-decoration: underline; }
  #not-owned-toggle:hover, #team-heroes-not-owned-toggle:hover { color: var(--dark-grey); }
  .overlay-close:hover { color: var(--red); }
  #detail-close:hover { background: var(--red); color: var(--white); }
  .overlay-back:hover { background: var(--red); color: var(--white); }
  .detail-art-nav:hover { background: rgba(0,0,0,0.7); }
  .detail-nav-btn:hover { background: rgba(0,0,0,0.7); }
  #add-to-team-btn:hover { background: var(--color-green-dark); }
  #add-to-team-btn.in-team:hover { background: var(--red-dark); }
  #detail-fav-btn:hover { border-color: var(--favorite); color: var(--favorite); }
}

/* Dark mode: the type/mode badge tints are ~12% brand-color washes tuned for a
   white surface — too faint on charcoal. Deepen the tint so the pill reads. */
:root[data-theme="dark"] #detail-type-badge.hero { background: rgba(75,184,214,0.30); }
:root[data-theme="dark"] #detail-type-badge.villain,
:root[data-theme="dark"] .mode-badge.challenge { background: rgba(226,70,81,0.28); }
:root[data-theme="dark"] #detail-type-badge.antihero,
:root[data-theme="dark"] .mode-badge.campaign,
:root[data-theme="dark"] .detail-type-badge.mode { background: rgba(138,115,216,0.30); }
:root[data-theme="dark"] .mode-badge.advantage { background: rgba(27,174,104,0.28); }
