:root {
  /* Faded black, not pure: lifted enough to read as a soft charcoal wash
     wherever the aurora blobs don't reach. */
  --bg: #16161f;
  --accent: #ffcb05;
  --accent2: #3d7dca;
  --text: #f4f5ff;
  --muted: #9aa0c5;

  /* Rounded type everywhere: "SF Pro Rounded" when installed (macOS),
     ui-rounded for Safari-engine contexts, then the regular system stack. */
  --font: "SF Pro Rounded", ui-rounded, -apple-system, BlinkMacSystemFont,
    "Segoe UI", Roboto, sans-serif;

  /* Card geometry — Pokémon cards are 2.5 × 3.5 (ratio 0.714). */
  --card-w: 220px;
  --card-h: 308px;
  --overlap: 142px; /* how much each card slides under its left neighbour */
  /* Corner radius scales WITH the card (~6% of width, like a real card's
     ~⅛" corner) instead of a fixed px that reads too round when cards shrink.
     Resolves live as sizeToViewport updates --card-w; the card-page hero uses
     the same 6% so the zoom corner stays seamless. */
  --card-radius: calc(var(--card-w) * 0.066);
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  min-height: 100%;
}

body {
  font-family: var(--font);
  color: var(--text);
  background: var(--bg);
  overflow-x: hidden;
}

/* Form controls don't inherit the body font by default. */
button,
input,
select {
  font-family: inherit;
}

/* Animated aurora backdrop ------------------------------------------------- */
.aurora {
  position: fixed;
  inset: -20%;
  z-index: 0;
  background:
    radial-gradient(40% 50% at 15% 20%, rgba(61, 125, 202, 0.35), transparent 70%),
    radial-gradient(45% 45% at 85% 15%, rgba(255, 203, 5, 0.18), transparent 70%),
    radial-gradient(50% 50% at 70% 90%, rgba(155, 81, 224, 0.28), transparent 70%),
    radial-gradient(45% 45% at 20% 85%, rgba(224, 81, 122, 0.22), transparent 70%);
  filter: blur(20px);
  animation: drift 26s ease-in-out infinite alternate;
}

@keyframes drift {
  0% { transform: translate3d(0, 0, 0) scale(1); }
  100% { transform: translate3d(2%, -2%, 0) scale(1.08); }
}

/* Header ------------------------------------------------------------------- */
.top {
  position: relative;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  /* Top-aligned, not centered: the right-hand pill stack changes height
     between Dreamkeeper (4 pills) and Dreamseeker (1 "Results" pill) — the
     title/subtitle must hold the exact same y in both modes. */
  align-items: flex-start;
  justify-content: space-between;
  padding: 22px 32px;
  backdrop-filter: blur(8px);
}

.brand {
  display: flex;
  align-items: center;
  gap: 14px;
  /* anchor for the logo's star burst (.pw-star is positioned absolutely
     at the ball's center) */
  position: relative;
}

.brand__logo {
  width: 46px;
  height: 46px;
  /* The Dreamkeeper mark is a transparent round badge, not a square app icon —
     shadow follows the artwork. IMPORTANT frame of reference: the SVG artwork
     is PRE-ROTATED -28.56° — CSS rotate(0) displays the jaunty tilt (the
     default pose), and visually-upright is CSS rotate(+28.56deg). */
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.45));
  cursor: pointer;
  /* NOTE: rotations pivot on the element CENTER (default origin) — tried a
     bottom-center "contact point" pivot and it made everything worse: the
     final upright lean parks the mark off its seat and every rock double-
     sways. Keep the pivot centered; the translateX track owns all sideways
     motion. */
}

/* Entrance roll (angles in CSS degrees = artwork + 28.56°): the ball ROLLS
   IN along the x-axis — starting 56px left of its seat and arriving during
   the first swing — while the rotation goes resting tilt (CSS 0 / visual
   -28.56°) → overshoot past visually-upright (visual +18°) → back (visual
   -10°) → once more (visual +6°) → wiggle → upright (CSS +28.56°). Every
   keyframe carries a translateX so both axes interpolate component-wise.
   Runs on load (class in the HTML) and replays on logo click. */
/* Roll-in v2 (user-tuned, 2026-06-10; playground default matches): slide
   from -56px while the entry decelerates into a BIG +28° (visual) first
   swing, then tight springy rocks (-18 → +12 → -8 → +2), a fast 2° settle
   onto upright (54→58% — keep settle legs SHORT; a long leg doing 2° reads
   as a slow smear), a true HOLD at upright (58→80%, identical keyframes =
   zero motion, the ~730ms beat), and the lean-back to the brand's resting
   tilt (CSS 0° / visual -28.56°) baked into the roll itself — the animation
   ends exactly on the base (transform: none) pose, where the star burst
   takes over (JS triggers .pw-star on animationend). Per-leg timing
   functions own the motion; the animation itself runs linear. */
@keyframes pw-logo-roll {
  0% {
    transform: translateX(-56px) rotate(0deg);
    animation-timing-function: cubic-bezier(0.2, 0.7, 0.3, 1);
  }
  20% {
    transform: translateX(14px) rotate(56.56deg);
    animation-timing-function: cubic-bezier(0.34, 1.2, 0.4, 1);
  }
  35% {
    transform: translateX(-6px) rotate(10.56deg);
    animation-timing-function: cubic-bezier(0.34, 1.2, 0.4, 1);
  }
  45% {
    transform: translateX(4px) rotate(40.56deg);
    animation-timing-function: cubic-bezier(0.34, 1.2, 0.4, 1);
  }
  50% {
    transform: translateX(-3px) rotate(20.56deg);
    animation-timing-function: cubic-bezier(0.34, 1.2, 0.4, 1);
  }
  54% {
    transform: translateX(1px) rotate(30.56deg);
    animation-timing-function: cubic-bezier(0.34, 1.2, 0.4, 1);
  }
  58% {
    transform: translateX(0px) rotate(28.56deg);
    animation-timing-function: linear;
  }
  80% {
    transform: translateX(0px) rotate(28.56deg);
    animation-timing-function: cubic-bezier(0.34, 1.2, 0.4, 1);
  }
  100% {
    transform: translateX(0px) rotate(0deg);
  }
}

.brand__logo.is-rolling {
  animation: pw-logo-roll 3.3s linear forwards;
}

/* Star burst: three of the brand sheet's yellow sparks scale up out of the
   BALL'S CENTER and arc outward (up-right, up-left, left-and-down — the
   directions pictured on the sheet) the moment the roll lands on its resting
   tilt. Each star rides a quadratic offset-path from the logo's center
   (positioned by JS), growing from nothing mid-flight and fading out at the
   end; staggered starts + varied sizes give the trail depth. One-shot — the
   elements remove themselves on animationend. */
.pw-star {
  position: absolute;
  width: 18px;
  height: 18px;
  margin: -9px 0 0 -9px; /* anchor = the star's own center */
  pointer-events: none;
  z-index: 5;
  opacity: 0;
  offset-rotate: 0deg;
  animation: pw-star-fly 0.95s cubic-bezier(0.22, 0.8, 0.4, 1) forwards;
}
.pw-star svg {
  display: block;
  width: 100%;
  height: 100%;
}
/* Flight arcs: shallow but VISIBLE bows (control points ~13px off the
   chord, pinwheel sweep) — the trails' wedge geometry in viewer.js is built
   from these same curves; change them together. */
.pw-star--0 {
  offset-path: path("M 0 0 Q 30 -8 46 -40"); /* launches rightward, curls up */
}
.pw-star--1 {
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;
  offset-path: path("M 0 0 Q -4 -32 -38 -46"); /* launches upward, curls left */
  animation-delay: 0.09s;
}
.pw-star--2 {
  width: 11px;
  height: 11px;
  margin: -5.5px 0 0 -5.5px;
  offset-path: path("M 0 0 Q -14 16 -48 14"); /* dips down-left, curls level */
  animation-delay: 0.17s;
}
@keyframes pw-star-fly {
  0% {
    offset-distance: 0%;
    transform: scale(0) rotate(0deg);
    opacity: 0;
  }
  16% {
    opacity: 1;
  }
  62% {
    transform: scale(1) rotate(38deg);
    opacity: 1;
  }
  100% {
    offset-distance: 100%;
    transform: scale(0.45) rotate(80deg);
    opacity: 0;
  }
}

/* Soft origin streams: each star's trail is an ARCING TAPERED WEDGE — a
   curved triangle whose sharp point sits at the ball's center and whose
   body widens along the star's exact flight arc (the wedge geometry lives
   in viewer.js next to starBurst, built from the same quadratic curves as
   the .pw-star offset-paths). Scale-revealed from the ORIGIN (transform-
   origin 0 0) on the stars' timing, so the stream grows out of the ball
   underneath its star, then softens away. */
/* Animatable inner radius for the trail's EXIT: a transparent hole in the
   mask that grows from the origin, evaporating the stream origin-first so
   it chases its star out the tip (registered so keyframes can tween it —
   same trick as the holo --pw-hue). */
@property --pw-hole {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}
.pw-trail {
  position: absolute;
  pointer-events: none;
  z-index: 4;
  opacity: 0;
  filter: blur(0.8px);
  /* The stream is DRAWN along its arc: a radial clip centered on the origin
     grows with the star's flight, revealing the curved wedge progressively
     outward — the trail arcs out WITH the star instead of popping whole.
     On EXIT the mask's --pw-hole grows from that same origin, peeling the
     stream away ball-first (a 7px feather keeps the receding edge soft). */
  clip-path: circle(2px at 1px 1px);
  mask-image: radial-gradient(
    circle at 1px 1px,
    transparent var(--pw-hole),
    #000 calc(var(--pw-hole) + 7px)
  );
  animation: pw-trail-fly 0.95s cubic-bezier(0.22, 0.8, 0.4, 1) forwards;
}
.pw-trail svg {
  display: block;
  width: 2px;
  height: 2px;
  overflow: visible; /* path coords are app-px offsets from the origin */
}
.pw-trail--1 {
  animation-delay: 0.09s;
}
.pw-trail--2 {
  animation-delay: 0.17s;
}
@keyframes pw-trail-fly {
  0% {
    clip-path: circle(2px at 1px 1px);
    opacity: 0;
    --pw-hole: 0px;
  }
  18% {
    opacity: 0.9;
  }
  70% {
    clip-path: circle(62px at 1px 1px);
    opacity: 0.75;
    --pw-hole: 0px;
  }
  100% {
    clip-path: circle(64px at 1px 1px);
    opacity: 0.55;
    --pw-hole: 74px; /* outruns the longest wedge (~64px) + feather: fully gone */
  }
}

/* Origin bloom: a soft yellow glow that blossoms at the ball's center the
   instant the stars leave it, selling "they came from inside". */
.pw-burst-glow {
  position: absolute;
  width: 40px;
  height: 40px;
  margin: -20px 0 0 -20px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 3;
  background: radial-gradient(circle, rgba(255, 255, 85, 0.5), rgba(255, 255, 85, 0) 70%);
  animation: pw-burst-glow 0.6s ease-out forwards;
}
@keyframes pw-burst-glow {
  0% {
    transform: scale(0.2);
    opacity: 0;
  }
  25% {
    opacity: 0.8;
  }
  100% {
    transform: scale(1.5);
    opacity: 0;
  }
}

.brand__title {
  margin: 0;
  font-size: 22px;
  font-weight: 800;
  letter-spacing: 0.3px;
  /* Clicking the title toggles Dreamseeker (marketplace search). */
  cursor: pointer;
  transition: color 0.2s ease;
}

.brand__title:hover {
  color: var(--accent);
}

body.is-seek .brand__title {
  color: #ff8fab; /* seeker active — title doubles as the exit toggle */
}

/* Dreamseeker bar: centered search + filters between header and rows. */
.seeker {
  position: relative;
  /* The seeker is a stacking context, so its "Browse a set…" dropdown
     (.setpick__menu) is capped at THIS z-index in the root order. Keep it
     above the first strip's spacing slider (root z-index 6) so the open
     dropdown isn't painted over; still well under the dock (200). */
  z-index: 20;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  padding: 4px 32px 14px;
}

.seeker[hidden] {
  display: none;
}

#seekQ {
  width: min(420px, 50vw);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 999px;
  padding: 11px 18px;
  color: var(--text);
  font-size: 15px;
  font-family: inherit;
  outline: none;
}

#seekQ:focus {
  border-color: rgba(255, 203, 5, 0.55);
}

.seeker__seller {
  width: 130px;
}

/* Set browser: wider than the other selects to fit long set names. */
.seeker__set {
  max-width: 230px;
}

/* Filters toggle: a mobile-only affordance (the desktop seeker shows every
   control inline) — display flips in the mobile media block. */
.seeker__filters {
  display: none;
  align-items: center;
  gap: 6px;
}
.seeker__filterchev {
  display: inline-block;
  font-size: 11px;
  opacity: 0.7;
  transition: transform 0.2s ease;
}
.seeker__filters[aria-expanded="true"] .seeker__filterchev {
  transform: rotate(180deg);
}
/* A hidden-but-engaged filter narrows results invisibly — dot the toggle so
   the collapsed state can't silently lie about what's being searched. */
.seeker__filters.has-active::after {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
}

/* --- Set picker: custom dropdown with set-code badges --------------------- */
.setpick {
  position: relative;
  display: inline-block;
}
.setpick__btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 200px;
  max-width: 260px;
  cursor: pointer;
  text-align: left;
}
.setpick__cur {
  flex: 1 1 auto;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.setpick__curname {
  overflow: hidden;
  text-overflow: ellipsis;
}
.setpick__chev {
  flex: 0 0 auto;
  opacity: 0.7;
  font-size: 12px;
}
.setpick__menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 30;
  width: min(320px, 86vw);
  background: #14141d;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 12px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6);
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.setpick__menu[hidden] { display: none; }
.setpick__search {
  width: 100%;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 8px;
  padding: 8px 10px;
  color: var(--text);
  font: inherit;
  font-size: 13px;
  outline: none;
}
.setpick__search:focus { border-color: rgba(255, 203, 5, 0.5); }
.setpick__list {
  max-height: 320px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
.setpick__opt {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 7px 8px;
  border: none;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 13px;
  text-align: left;
  border-radius: 8px;
  cursor: pointer;
}
.setpick__opt:hover { background: rgba(255, 255, 255, 0.07); }
.setpick__opt.is-sel { background: rgba(255, 203, 5, 0.16); }
.setpick__badge {
  flex: 0 0 auto;
  height: 19px;
  width: auto;
  display: block;
}
/* Blank slot keeps names aligned whether or not a set has a badge. */
.setpick__badge--none { width: 36px; height: 19px; }
.setpick__optname {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.setpick__optcount {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}
.setpick__empty {
  padding: 12px 8px;
  color: var(--muted);
  font-size: 12.5px;
  text-align: center;
}
.setpick .setpick__cur .setpick__badge { height: 18px; }

/* --- Trending booster series dock (below the search field) ---------------- */
.seek-trending {
  flex-basis: 100%; /* full-width row under the seeker controls */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-top: 2px;
  user-select: none; /* badges are buttons, not selectable text */
  -webkit-user-select: none;
}
.seek-trending[hidden] { display: none; }
.seek-trending__lbl {
  flex: 0 0 auto;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: var(--muted);
}
.seek-trending__row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
  min-width: 0;
}
/* The badge graphic IS the button — no border/container. */
.seek-trending__badge {
  flex: 0 0 auto;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  line-height: 0;
  opacity: 0.85;
  transition: transform 0.12s ease, opacity 0.12s ease, filter 0.12s ease;
}
.seek-trending__badge:hover {
  transform: translateY(-2px);
  opacity: 1;
  filter: drop-shadow(0 3px 8px rgba(0, 0, 0, 0.5));
}
.seek-trending__badge .setpick__badge { height: 24px; width: auto; display: block; }

/* Opt-in toggle for sealed/non-single products (cards-only by default). */
.seeker__toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
}

.seeker__toggle input {
  accent-color: var(--accent);
}

/* Marketplace results aren't in any collection — hearts don't apply. */
body.is-seek .card__fav {
  display: none;
}

/* --- Landing (hosted site, no share hash) ---------------------------------- */
body.is-landing .statstack,
body.is-landing .controls,
body.is-landing .sharebar {
  display: none;
}
.landing {
  max-width: 680px;
  margin: 0 auto;
  padding: 8vh 28px 0;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}
.landing__h {
  font-size: clamp(26px, 4.4vw, 44px);
  font-weight: 800;
  line-height: 1.08;
  margin: 0;
  letter-spacing: -0.01em;
  background: linear-gradient(180deg, #fff, #b8bce0);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.landing__p {
  font-size: 15.5px;
  line-height: 1.6;
  color: var(--muted, #9aa0c5);
  margin: 0;
  max-width: 540px;
}
.landing__cta {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 6px;
}
.landing__btn {
  font-weight: 700;
  font-size: 14px;
  text-decoration: none;
  border-radius: 999px;
  padding: 12px 24px;
  background: var(--accent, #ffcb05);
  color: #1a1a06;
  border: 1px solid transparent;
  transition: filter 0.15s ease, transform 0.15s ease;
}
.landing__btn:hover {
  filter: brightness(1.07);
  transform: translateY(-1px);
}
.landing__btn--ghost {
  background: transparent;
  color: var(--text, #f4f5ff);
  border-color: rgba(255, 255, 255, 0.22);
}
.landing__btn--soon {
  background: rgba(255, 255, 255, 0.07);
  color: var(--muted, #9aa0c5);
  border: 1px solid rgba(255, 255, 255, 0.16);
  cursor: default;
}
.landing__foot {
  font-size: 12.5px;
  color: var(--muted, #9aa0c5);
  opacity: 0.75;
  margin: 8px 0 0;
}

/* --- Hand-sharing ----------------------------------------------------------
   Shift-click picks cards; every copy of a picked card wears the gold ring.
   The Share bar slides in top-center while anything is selected. */
.card.is-picked .card__inner {
  outline: 3px solid rgba(255, 203, 5, 0.95);
  outline-offset: -3px;
  box-shadow: 0 0 18px rgba(255, 203, 5, 0.35);
}
.card.is-picked::after {
  content: "✓";
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 6;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--accent, #ffcb05);
  color: #1a1a06;
  font-size: 13px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}

.sharebar {
  position: fixed;
  top: 14px;
  left: 50%;
  transform: translate(-50%, -64px);
  z-index: 940; /* under the control dock, above everything else */
  display: flex;
  gap: 8px;
  align-items: center;
  transition: transform 0.25s cubic-bezier(0.34, 1.2, 0.4, 1), opacity 0.2s ease;
  opacity: 0;
  pointer-events: none;
}
.sharebar.is-on {
  transform: translate(-50%, 0);
  opacity: 1;
  pointer-events: auto;
}
.sharebar__send {
  font: inherit;
  font-weight: 800;
  font-size: 13.5px;
  cursor: pointer;
  border: none;
  border-radius: 999px;
  padding: 10px 20px;
  background: var(--accent, #ffcb05);
  color: #1a1a06;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
}
.sharebar__send:hover {
  filter: brightness(1.08);
}
.sharebar__clear {
  font: inherit;
  font-size: 12px;
  cursor: pointer;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(20, 20, 32, 0.85);
  color: var(--text, #f4f5ff);
}
.sharebar__clear:hover {
  border-color: var(--accent, #ffcb05);
}

.brand__sub {
  display: inline-block;
  margin: 4px 0 0;
  padding: 3px 10px;
  border-radius: 999px;
  background: rgba(255, 203, 5, 0.1);
  border: 1px solid rgba(255, 203, 5, 0.28);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1.3px;
  color: var(--accent);
}

.controls {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* The control dock: pinned to the top CENTER, hidden above the viewport
   until the cursor approaches the top-center zone (proximity logic in
   viewer.js toggles .is-open; :focus-within keeps it down while a field or
   native select dropdown holds focus). */
.dock {
  position: fixed;
  top: 0;
  left: 50%;
  z-index: 200;
  flex-wrap: nowrap;
  max-width: calc(100vw - 48px);
  padding: 12px 18px 13px;
  background: rgba(17, 19, 27, 0.86);
  backdrop-filter: blur(16px) saturate(1.2);
  -webkit-backdrop-filter: blur(16px) saturate(1.2);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-top: none;
  border-radius: 0 0 20px 20px;
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.45);
  transform: translate(-50%, calc(-100% - 6px));
  transition: transform 0.38s cubic-bezier(0.22, 1, 0.36, 1);
}

.dock.is-open,
.dock:focus-within {
  transform: translate(-50%, 0);
}

/* Peek handle: an iOS-style white pill bar hanging just below the dock's
   bottom edge — the only hint the dock exists while it's tucked away.
   Inert: proximity opens the dock, not a click. */
.dock__tab {
  position: absolute;
  top: calc(100% + 12px);
  left: 50%;
  transform: translateX(-50%);
  width: 124px;
  height: 5px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.85);
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.45);
  font-size: 0; /* hide the glyph — the bar IS the handle */
  pointer-events: none;
  opacity: 1;
  transition: opacity 0.25s ease;
}

.dock.is-open .dock__tab,
.dock:focus-within .dock__tab {
  opacity: 0; /* open dock needs no hint */
}

.search {
  display: flex;
  align-items: center;
  gap: 8px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 999px;
  padding: 8px 14px;
  color: var(--muted);
}

.search input {
  background: transparent;
  border: none;
  outline: none;
  color: var(--text);
  font-size: 14px;
  width: 150px;
}

.select {
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.12);
  color: var(--text);
  border-radius: 999px;
  padding: 8px 14px;
  font-size: 13px;
  outline: none;
  cursor: pointer;
}

.select option {
  color: #111;
}

/* Top-right stat pill stack: OWNED / SAVED / CASS / CASP. */
.statstack {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
}

.vpill {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 5px 15px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.12);
  white-space: nowrap;
}

.vpill__l {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--muted);
}

.vpill__v {
  font-size: 13.5px;
  font-weight: 800;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* CASS/CASP double as refresh buttons — click polls prices in place. */
.vpill--adj {
  cursor: pointer;
  transition:
    background 0.2s ease,
    border-color 0.2s ease;
}

.vpill--adj:hover {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.28);
}

.statstack.is-refreshing .vpill--adj {
  pointer-events: none;
}

.statstack.is-refreshing .vpill--adj .vpill__v {
  animation: pw-refresh-pulse 0.9s ease-in-out infinite;
}

@keyframes pw-refresh-pulse {
  50% {
    opacity: 0.3;
  }
}

.vpill--owned .vpill__v {
  color: #c79aff;
}

.vpill--saved .vpill__v {
  color: var(--accent);
}

.vpill--up .vpill__v {
  color: #5ff0a8;
}

.vpill--down .vpill__v {
  color: #ff8fab;
}

/* Stage: three fixed section strips (Saved / Purchases / Owned) ------------ */
.stage {
  position: relative;
  /* No z-index here on purpose: a stacking context would trap a spotlit card's
     z-index inside the stage and let the header (z-index 5) paint over its
     floating title panel. Without it, the hovered card's z-index:50 competes at
     the top level and rides above the header. */
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* Vertical reserve so the hovered card's floating title panel + its cart
     bubble (above) have room on the top row, and the bottom row's lift +
     shadow aren't flush with the viewport edge. */
  padding: 84px 0 32px;
  /* Fill everything below the header; the three strips split this equally. */
  height: calc(100vh - var(--header, 92px));
}

/* One strip per source: a small label plus its card row. */
.strip {
  position: relative; /* anchors the centered spacing slider */
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* Card-spacing slider: 5 detents, centered above the row. Dots mark the
   positions; thumb snaps between them (native range, step=1). Dot centers
   match the thumb's travel: 6px + (150-12)px × position/4. */
.strip__spacing {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 150px;
  height: 16px;
  margin: 0;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  cursor: pointer;
  z-index: 6;
  /* No browser focus ring — keyboard users get a thumb glow instead (below). */
  outline: none;
}

.strip__spacing:focus-visible::-webkit-slider-thumb {
  box-shadow:
    inset 0 0 0 1px #16182b,
    0 0 0 3px rgba(255, 203, 5, 0.45),
    0 2px 8px rgba(0, 0, 0, 0.5);
}

.strip__spacing::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 999px;
  background:
    radial-gradient(circle 2px at 7px 50%, rgba(255, 255, 255, 0.55) 98%, transparent),
    radial-gradient(circle 2px at 41px 50%, rgba(255, 255, 255, 0.55) 98%, transparent),
    radial-gradient(circle 2px at 75px 50%, rgba(255, 255, 255, 0.55) 98%, transparent),
    radial-gradient(circle 2px at 109px 50%, rgba(255, 255, 255, 0.55) 98%, transparent),
    radial-gradient(circle 2px at 143px 50%, rgba(255, 255, 255, 0.55) 98%, transparent),
    linear-gradient(rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.14));
}

/* The thumb is a Pokéball: red top hemisphere, white bottom, black band,
   center button — all layered gradients, painted top layer first. */
.strip__spacing::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  margin-top: -5px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 50%, #fff 0 1.8px, #16182b 1.8px 3.2px, transparent 3.2px),
    linear-gradient(
      to bottom,
      transparent 0 calc(50% - 1px),
      #16182b calc(50% - 1px) calc(50% + 1px),
      transparent calc(50% + 1px)
    ),
    linear-gradient(to bottom, #ee1515 0 50%, #f4f5ff 50% 100%);
  box-shadow:
    inset 0 0 0 1px #16182b,
    0 2px 8px rgba(0, 0, 0, 0.5);
  transition: transform 0.12s ease;
}

.strip__spacing:hover::-webkit-slider-thumb {
  transform: scale(1.25);
}

.strip__label {
  margin: 0;
  padding: 0 36px 2px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 2.2px;
  text-transform: uppercase;
  color: var(--muted);
}

.strip__count {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* Per-row filter pill in the strip label (e.g. Owned → verified purchases). */
.strip__filter {
  margin-left: 10px;
  padding: 2px 10px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: transparent;
  color: var(--muted);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}

.strip__filter:hover {
  color: var(--text);
  border-color: rgba(255, 255, 255, 0.3);
}

.strip__filter.is-active {
  background: linear-gradient(135deg, var(--accent), #ffa600);
  border-color: transparent;
  color: #20160a;
}

.row {
  /* The row fills the rest of its strip, so the three strips always span the
     viewport. overflow MUST stay visible on both axes: a hovered card lifts
     above its row and must ride over the neighbouring strips without being
     clipped. (When a fan doesn't fit, the row switches to .row--scroll and the
     inner .track is translated in JS — never CSS overflow scrolling, which
     would clip that lift.) */
  position: relative;
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  align-items: flex-end;
  /* Left-anchored in BOTH modes — static fans are centered via a transform on
     the .track instead of flexbox, so flipping static↔scroll at a spacing
     detent can't cause an alignment snap (the "double resize"). */
  justify-content: flex-start;
  padding: 8px 32px;
  overflow: visible;
  /* Touch: horizontal strokes pan the row (JS drag in layoutRow); vertical
     strokes stay the browser's for page scrolling (mobile layout). */
  touch-action: pan-y;
}

/* Infinite-scroll rows: clip horizontally (hides the repeated copies) but keep
   vertical overflow visible so the spotlight lift still rides free. */
.row--scroll {
  overflow-x: clip;
  overflow-y: visible;
}

/* The translated card strip inside a row. */
.track {
  display: flex;
  align-items: flex-end;
  will-change: transform;
}

/* STATIC fans only: the centering transform animates in step with the cards'
   margin transition, so a spacing change breathes around the center. (Scroll
   tracks must NOT transition — their transform is driven per-frame by rAF.) */
.row:not(.row--scroll) .track {
  transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}

/* A translated .track is a stacking context, which would trap a spotlit card's
   z-index below the header — so lift the whole track while it holds one. */
.track:has(.is-spotlight) {
  position: relative;
  z-index: 60;
}

.row-empty {
  align-self: center;
  margin: 0 auto;
  font-size: 13px;
  color: var(--muted);
  background: rgba(255, 255, 255, 0.04);
  border: 1px dashed rgba(255, 255, 255, 0.14);
  border-radius: 12px;
  padding: 14px 22px;
}

/* Edge indicators: pure visuals for the row's edge-band drift (the ROW senses
   pointer position in JS — these elements must NEVER intercept the pointer or
   paint over a hovered card, so they're inert and hide during a spotlight). */
@keyframes pw-zone-in {
  from {
    opacity: 0;
  }
}

.edgezone {
  position: absolute; /* overlay layer — NEVER affects the row's layout */
  top: 0;
  bottom: 0;
  width: 72px;
  z-index: 70;
  display: flex;
  align-items: center;
  pointer-events: none;
  transition: opacity 0.25s ease;
  animation: pw-zone-in 0.35s ease both; /* fade in on (re)build, no pop */
}

/* Never overlay a spotlit card or its floating buttons/title. */
.row:has(.is-spotlight) .edgezone {
  opacity: 0;
}

.edgezone--left {
  left: 0;
  justify-content: flex-start;
  background: linear-gradient(90deg, rgba(7, 7, 15, 0.78), transparent);
}

.edgezone--right {
  right: 0;
  justify-content: flex-end;
  background: linear-gradient(270deg, rgba(7, 7, 15, 0.78), transparent);
}

.edgezone__chev {
  font-size: 30px;
  line-height: 1;
  padding: 0 10px;
  color: var(--muted);
  opacity: 0.55;
  transition: opacity 0.2s ease, color 0.2s ease, transform 0.2s ease;
}

/* Light up the chevron on the side currently driving the drift. */
.row[data-drift="left"] .edgezone--left .edgezone__chev,
.row[data-drift="right"] .edgezone--right .edgezone__chev {
  opacity: 1;
  color: var(--accent);
  transform: scale(1.25);
}

/* Card --------------------------------------------------------------------- */
.card {
  position: relative;
  flex: 0 0 auto;
  /* --z (set per card in applyLayering) picks the stacking direction: ascending
     = tuck under the right neighbour (DOM-order look), descending = tuck under
     the left. Flipped per row by right-click. */
  z-index: var(--z, auto);
  /* Cards are hover/click surfaces, not content — no text/image selection,
     and no iOS long-press image callout. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  width: var(--card-w);
  height: var(--card-h);
  margin-left: calc(-1 * var(--overlap));
  border-radius: var(--card-radius);
  transform-style: preserve-3d;
  transition:
    transform 0.32s cubic-bezier(0.22, 1, 0.36, 1),
    margin 0.32s cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 0.32s ease;
  cursor: pointer;
  will-change: transform;
  --rx: 0deg;
  --ry: 0deg;
  --mx: 50%;
  --my: 50%;
}

.card:first-child {
  margin-left: 0;
}

.card__inner {
  position: absolute;
  inset: 0;
  border-radius: var(--card-radius);
  /* overflow:hidden is load-bearing for the hover effects: BOTH holo layers
     (.card__holo sheen and the .card--holo ::after foil) live inside this
     element so they clip to the card graphic and can never bleed onto the
     floating title panel above or the action buttons below. Don't move those
     layers out of .card__inner. */
  overflow: hidden;
  background: #14152a;
  /* Shadows live on the TILTING element so their silhouette swivels with the
     card — on a static ancestor, the untilted rounded-rect shadow outline
     peeks out at the corners whenever the card tilts away from them. (They
     once lived on the static .card shell to protect the action buttons that
     floated below cards; those buttons are long gone.) */
  box-shadow:
    0 8px 22px rgba(0, 0, 0, 0.55),
    0 1px 0 rgba(255, 255, 255, 0.06) inset;
  /* perspective() = real depth (foreshortening) — parity with the product
     page's card treatment. */
  transform: perspective(900px) rotateX(var(--rx)) rotateY(var(--ry));
  transition: box-shadow 0.32s ease;
}

.card__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  background: linear-gradient(135deg, #1e2040, #0c0d1c);
  /* Block the browser's native image drag-ghost (Chrome). */
  -webkit-user-drag: none;
  user-select: none;
}

/* Iridescent holofoil flash for holo-printed cards (.card--holo, set from the
   card's condition). A rainbow band gradient in `difference` blend, masked to
   the SAME cursor-following circle as the .card__holo highlight, so the foil
   only "catches the light" inside the glow. Lives on .card__inner::after (not
   inside .card__holo): that element's 0.6 opacity isolates its blending group,
   which would make `difference` blend against the holo gradients instead of
   the artwork. Painted after .card__holo in tree order, blends with the image
   below inside .card__inner's stacking context. */
.card--holo .card__inner::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Two stacked backgrounds: the rainbow bands AND a copy of the card art
     itself (--art, set per-card in JS). background-blend-mode color-dodges the
     bands against the art's own pixels — bright artwork blooms with
     iridescence, dark areas swallow it — so the foil is an interference with
     the print, not a colorization laid on top. Band scale matters both ways:
     a 35% cycle puts ~2-3 broad hue sweeps inside the highlight circle —
     tighter (15%) reads as stripy noise once the art-interference is in play,
     wider (50%) collapses to a 1-2 color tint and loses the rainbow. */
  background-image:
    /* Surface-grain "bump map": fractal noise compressed to ±6% around
       mid-gray (overlay's identity), modulating the foil's luminance
       per-pixel like fine card texture — nuanced, not high-contrast. It's
       anchored to the card while the bands sweep beneath, like real foil. */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' seed='11' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0.12 0 0 0 0.44 0 0.12 0 0 0.44 0 0 0.12 0 0.44 0 0 0 0 1'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E"),
    repeating-linear-gradient(
      115deg,
      #ff005e 0%,
      #ff9f00 5.8%,
      #ffe700 11.6%,
      #00ff8f 17.5%,
      #00c7ff 23.3%,
      #8c52ff 29.1%,
      #ff005e 35%
    ),
    var(--art, linear-gradient(#808080, #808080));
  /* Rainbow oversized + cursor-positioned (bands sweep like foil tilting under
     light); the art copy mirrors the img's object-fit: cover so the
     interference stays pixel-aligned with the real artwork below. */
  background-size: 180px 180px, 200% 200%, cover;
  background-position: 0 0, var(--mx, 50%) var(--my, 50%), center;
  /* grain overlays the rainbow, then difference: rainbow XOR art — the
     banding locks onto the art's edges and forms instead of striping
     straight across. */
  background-blend-mode: overlay, difference;
  /* hard-light re-engages the real pixels a second time: color punches into
     both the art's lights (screen) and darks (multiply), so linework holds
     while bright zones ignite. (Tested vs double color-dodge — which only
     bloomed highlights into a warm wash — this keeps the iridescence anchored
     to the artwork.) */
  mix-blend-mode: hard-light;
  filter: brightness(0.95) contrast(1.4) saturate(1.55);
  /* FLOOD foil (parity with the product page): no cursor radial — the whole
     card goes iridescent, bands still sweeping via background-position. Edge
     fades keep the foil inset from the border (real holos inset the foil
     inside the frame anyway). The cursor accent is the .card__pocket layer.
     FIRST mask layer = the art itself in LUMINANCE mode: the card's own
     tonal range dims the foil — full strength in bright regions, fading
     smoothly into the darks (where .card__deeps takes over instead). */
  mask-image:
    var(--art, linear-gradient(#fff, #fff)),
    linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.35) 5%, rgba(0, 0, 0, 0.75) 10%, #000 16%, #000 82%, rgba(0, 0, 0, 0.75) 88%, rgba(0, 0, 0, 0.35) 94%, transparent),
    linear-gradient(to right, transparent, rgba(0, 0, 0, 0.35) 3.5%, rgba(0, 0, 0, 0.75) 7%, #000 12%, #000 88%, rgba(0, 0, 0, 0.75) 93%, rgba(0, 0, 0, 0.35) 96.5%, transparent);
  mask-mode: luminance, alpha, alpha;
  mask-size: cover, auto, auto;
  mask-position: center, center, center;
  mask-composite: intersect;
  opacity: 0;
  transition: opacity 0.32s ease;
  pointer-events: none;
}

.card--holo.is-spotlight .card__inner::after {
  /* Base coat under the cursor pocket — parity with the product page. */
  opacity: 0.5;
}

/* The textured printings need more presence than the rainbow flood. */
.card--holo.is-spotlight:is(.foil--reverse, .foil--cosmos, .foil--ice, .foil--sunburst, .foil--gold)
  .card__inner::after {
  opacity: 0.78;
}

/* --- Foil printing variants (class foil--* picked per card by productId
   hash in createCard). Each overrides only the ::after's paint — the edge
   fade mask, opacity gating, and the sheen/pocket layers above are shared.
   --pw-hue (registered below) is the slow idle hue drift all rainbow-based
   printings share; gold deliberately ignores it. ----------------------------*/

@property --pw-hue {
  syntax: "<angle>";
  inherits: true;
  initial-value: 0deg;
}

@keyframes pw-hue-drift {
  to {
    --pw-hue: 360deg;
  }
}

/* Hue drift only ticks on the SPOTLIT card — running it on every holo card
   (×2 copies in scroll rows) cost dozens of per-frame style recalcs for
   foils sitting at opacity 0. You can only see the drift while hovering
   anyway. (The product page keeps its idle drift: one element, showcase.) */
.card--holo.is-spotlight .card__inner {
  animation: pw-hue-drift 16s linear infinite;
}

/* flood (default): the rainbow interference recipe above + hue drift. */
.card--holo .card__inner::after {
  filter: brightness(0.95) contrast(1.4) saturate(1.55) hue-rotate(var(--pw-hue, 0deg));
}

/* REVERSE HOLO: foil everywhere EXCEPT the artwork window (an elliptical
   cutout over the art area joins the edge-fade mask). */
.card--holo.foil--reverse .card__inner::after {
  /* Feathered art-window (see card-fx.css) — a hard cutout reads as a
     stamped white ellipse on light artwork. */
  mask-image:
    radial-gradient(
      ellipse 46% 26% at 50% 29%,
      transparent 0 55%,
      rgba(0, 0, 0, 0.6) 80%,
      #000 96%
    ),
    linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.35) 5%, rgba(0, 0, 0, 0.75) 10%, #000 16%, #000 82%, rgba(0, 0, 0, 0.75) 88%, rgba(0, 0, 0, 0.35) 94%, transparent),
    linear-gradient(to right, transparent, rgba(0, 0, 0, 0.35) 3.5%, rgba(0, 0, 0, 0.75) 7%, #000 12%, #000 88%, rgba(0, 0, 0, 0.75) 93%, rgba(0, 0, 0, 0.35) 96.5%, transparent);
  mask-composite: intersect;
}

/* COSMOS / GALAXY: starfield speckle (SVG feTurbulence, sparse white via a
   hard alpha threshold) over nebula blobs, all interfering with the art. */
.card--holo.foil--cosmos .card__inner::after {
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='s'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' seed='7'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 18 -13'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23s)'/%3E%3C/svg%3E"),
    radial-gradient(60% 50% at 30% 35%, rgba(120, 60, 220, 0.55), transparent 70%),
    radial-gradient(50% 45% at 72% 60%, rgba(0, 140, 255, 0.45), transparent 70%),
    radial-gradient(45% 40% at 55% 18%, rgba(255, 60, 180, 0.35), transparent 70%),
    var(--art, linear-gradient(#808080, #808080));
  background-size: 220px 220px, cover, cover, cover, cover;
  background-position: 0 0, center, center, center, center;
  background-blend-mode: screen, screen, screen, difference;
  mix-blend-mode: hard-light;
  filter: contrast(1.3) saturate(1.5) hue-rotate(var(--pw-hue, 0deg));
}

/* CRACKED ICE: hard-stop conic shards pivoting around the CURSOR — each
   facet catches the light at a different angle as the pointer moves. */
.card--holo.foil--ice .card__inner::after {
  background-image:
    conic-gradient(
      from 15deg at var(--mx, 50%) var(--my, 50%),
      rgba(255, 255, 255, 0.5) 0deg 14deg,
      rgba(120, 200, 255, 0.25) 14deg 40deg,
      rgba(255, 255, 255, 0.05) 40deg 70deg,
      rgba(180, 160, 255, 0.35) 70deg 96deg,
      rgba(255, 255, 255, 0.45) 96deg 110deg,
      rgba(140, 230, 255, 0.12) 110deg 152deg,
      rgba(255, 255, 255, 0.3) 152deg 168deg,
      rgba(160, 190, 255, 0.08) 168deg 220deg,
      rgba(220, 240, 255, 0.4) 220deg 238deg,
      rgba(120, 160, 255, 0.18) 238deg 285deg,
      rgba(255, 255, 255, 0.25) 285deg 300deg,
      rgba(150, 220, 255, 0.1) 300deg 360deg
    ),
    var(--art, linear-gradient(#808080, #808080));
  background-size: auto, cover;
  background-position: center, center;
  background-blend-mode: hard-light;
  mix-blend-mode: hard-light;
  filter: contrast(1.35) saturate(1.3) hue-rotate(var(--pw-hue, 0deg));
}

/* SUNBURST / RADIANT: thin conic rays from the card center, their rotation
   coupled to the tilt (--ry) so the burst turns subtly with the card. */
.card--holo.foil--sunburst .card__inner::after {
  background-image:
    repeating-conic-gradient(
      from calc(var(--ry, 0deg) * 4) at 50% 50%,
      rgba(255, 210, 110, 0.45) 0deg 4deg,
      rgba(255, 255, 255, 0.06) 4deg 9deg,
      rgba(255, 160, 80, 0.3) 9deg 12deg,
      rgba(255, 255, 255, 0.02) 12deg 18deg
    ),
    var(--art, linear-gradient(#808080, #808080));
  background-size: auto, cover;
  background-position: center, center;
  background-blend-mode: screen;
  mix-blend-mode: screen;
  filter: saturate(1.4) hue-rotate(var(--pw-hue, 0deg));
}

/* GOLD STAMP: warm metallic ramp sweeping with the cursor + tight emboss
   lines, on overlay. NO hue drift — gold stays gold. */
.card--holo.foil--gold .card__inner::after {
  background-image:
    repeating-linear-gradient(
      115deg,
      rgba(255, 255, 255, 0.18) 0px 1px,
      transparent 2px 5px
    ),
    linear-gradient(
      105deg,
      #3a2c0b 0%,
      #b98c2e 22%,
      #ffe9a8 38%,
      #f7c84a 50%,
      #fff6d8 62%,
      #c89a31 78%,
      #4a3a10 100%
    ),
    var(--art, linear-gradient(#808080, #808080));
  background-size: auto, 260% 260%, cover;
  background-position: 0 0, var(--mx, 50%) var(--my, 50%), center;
  background-blend-mode: overlay, overlay;
  mix-blend-mode: hard-light;
  filter: contrast(1.2) saturate(0.9);
}

/* Deep shimmer: the ALTERNATE effect for the card's dark regions. Low-
   luminance iridescent bands in `lighten` blend — per-pixel max() means they
   only surface where the art is DARKER than these tones, auto-keying to
   shadows with no mask at all. Subtle by construction (the tones are ~10-20%
   luminance), sweeping and hue-drifting with the main foil. */
.card--holo .card__deeps {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: repeating-linear-gradient(
    115deg,
    #220a3a 0%,
    #0a2b3d 8%,
    #301044 16%,
    #0d3328 24%,
    #2b0d20 32%,
    #220a3a 40%
  );
  background-size: 220% 220%;
  background-position: var(--mx, 50%) var(--my, 50%);
  mix-blend-mode: lighten;
  filter: hue-rotate(var(--pw-hue, 0deg));
  mask-image:
    linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.35) 5%, rgba(0, 0, 0, 0.75) 10%, #000 16%, #000 82%, rgba(0, 0, 0, 0.75) 88%, rgba(0, 0, 0, 0.35) 94%, transparent),
    linear-gradient(to right, transparent, rgba(0, 0, 0, 0.35) 3.5%, rgba(0, 0, 0, 0.75) 7%, #000 12%, #000 88%, rgba(0, 0, 0, 0.75) 93%, rgba(0, 0, 0, 0.35) 96.5%, transparent);
  mask-composite: intersect;
  opacity: 0;
  transition: opacity 0.32s ease;
}

.card--holo.is-spotlight .card__deeps {
  opacity: 0.8;
}

/* Cursor pocket: blurred prismatic glint streaks riding the mouse over the
   foil flood, in `screen` blend (pure light, no saturation push). Applied to
   ALL cards — it's light catching the surface, not a foil printing. Mirrors
   .pw-fx__pocket in card-fx.css. */
.card__pocket {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  /* Glint spacing: line centers every ~18px (was ~11px) — airier rake. */
  background-image: repeating-linear-gradient(
    115deg,
    transparent 0px,
    transparent 14px,
    rgba(255, 0, 94, 0.65) 16px,
    transparent 18px,
    transparent 32px,
    rgba(255, 159, 0, 0.65) 34px,
    transparent 36px,
    transparent 50px,
    rgba(255, 231, 0, 0.65) 52px,
    transparent 54px,
    transparent 68px,
    rgba(0, 255, 143, 0.65) 70px,
    transparent 72px,
    transparent 86px,
    rgba(0, 199, 255, 0.65) 88px,
    transparent 90px,
    transparent 104px,
    rgba(140, 82, 255, 0.65) 106px,
    transparent 108px
  );
  background-size: 300% 300%;
  background-position: var(--mx, 50%) var(--my, 50%);
  mix-blend-mode: screen;
  filter: blur(2px); /* soft-focus — light, not overlay graphics */
  mask-image:
    radial-gradient(
      circle at var(--mx, 50%) var(--my, 50%),
      rgba(0, 0, 0, 0.95) 0%,
      rgba(0, 0, 0, 0.7) 14%,
      rgba(0, 0, 0, 0.4) 24%,
      rgba(0, 0, 0, 0.18) 33%,
      rgba(0, 0, 0, 0.06) 40%,
      transparent 48%
    ),
    linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.35) 5%, rgba(0, 0, 0, 0.75) 10%, #000 16%, #000 82%, rgba(0, 0, 0, 0.75) 88%, rgba(0, 0, 0, 0.35) 94%, transparent),
    linear-gradient(to right, transparent, rgba(0, 0, 0, 0.35) 3.5%, rgba(0, 0, 0, 0.75) 7%, #000 12%, #000 88%, rgba(0, 0, 0, 0.75) 93%, rgba(0, 0, 0, 0.35) 96.5%, transparent);
  mask-composite: intersect;
  opacity: 0;
  transition: opacity 0.32s ease;
}

.card.is-spotlight .card__pocket {
  opacity: 0.9;
}

/* The chrome glare is the pointer while spotlit (parity with the product
   page) — but the floating title panel keeps a usable cursor for its
   cart/heart buttons. */
.card.is-spotlight,
.card.is-spotlight .card__inner {
  cursor: none;
}

.card.is-spotlight .card__top {
  cursor: default;
}

/* Holographic sheen that tracks the cursor (only shown when spotlit). */
.card__holo {
  position: absolute;
  inset: 0;
  opacity: 0;
  mix-blend-mode: color-dodge;
  /* Same edge-inset fade as the foil ::after — keeps the white glow from
     slamming against the border next to the floating buttons/panel. */
  mask-image:
    linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.35) 5%, rgba(0, 0, 0, 0.75) 10%, #000 16%, #000 82%, rgba(0, 0, 0, 0.75) 88%, rgba(0, 0, 0, 0.35) 94%, transparent),
    linear-gradient(to right, transparent, rgba(0, 0, 0, 0.35) 3.5%, rgba(0, 0, 0, 0.75) 7%, #000 12%, #000 88%, rgba(0, 0, 0, 0.75) 93%, rgba(0, 0, 0, 0.35) 96.5%, transparent);
  mask-composite: intersect;
  /* The white highlight needs an EASED multi-stop falloff: color-dodge is
     highly non-linear, so a simple two-stop radial blows out to a hard-edged
     white disc against dark artwork (a visible ring where "white meets
     black"). Smaller peak + long tail keeps the sheen soft. */
  background:
    radial-gradient(
      circle at var(--mx) var(--my),
      rgba(255, 255, 255, 0.4) 0%,
      rgba(255, 255, 255, 0.26) 14%,
      rgba(255, 255, 255, 0.13) 27%,
      rgba(255, 255, 255, 0.05) 39%,
      rgba(255, 255, 255, 0) 54%
    ),
    linear-gradient(
      115deg,
      transparent 30%,
      rgba(255, 0, 230, 0.35) 45%,
      rgba(0, 240, 255, 0.35) 55%,
      transparent 70%
    );
  transition: opacity 0.32s ease;
  pointer-events: none;
}

/* Floating header above the card: title (top) + chips (just above the card).
   Hover-only; lives outside .card__inner so it never sits on the artwork. */
/* Collector number — stripped from the title in createCard, pinned as its own
   pill riding the panel's top-right edge. */
.card__num {
  position: absolute;
  top: -11px;
  right: 12px;
  padding: 3px 11px;
  border-radius: 999px;
  background: rgba(244, 245, 255, 0.96);
  color: #16161f;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.4px;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45);
  z-index: 1;
}

.card__top {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% + 12px);
  display: flex;
  flex-direction: column;
  gap: 7px;
  /* Rounded-rect glass panel holding the title + chips. */
  padding: 10px 12px;
  background: rgba(12, 14, 28, 0.92);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 12px;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(8px);
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.28s ease, transform 0.28s ease;
  pointer-events: none;
  z-index: 4;
}

/* Safe-area bridge: the 12px gap between the panel and the card belongs to
   neither element, so crossing it would fire pointerleave on the card and
   collapse the spotlight mid-journey. This invisible extension keeps the
   pointer inside the card subtree on the way to the title panel. (Active only
   while spotlit — pointer-events flips with the panel.) */
.card__top::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 14px;
}

.card__info {
  text-align: left;
}

.card__name {
  margin: 0;
  font-size: 16px;
  /* Semibold — SF Pro Rounded reads heavy enough without the 800 weight. */
  font-weight: 600;
  line-height: 1.2;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.95);
}

.card__set {
  margin: 3px 0 0;
  font-size: 11px;
  color: var(--muted);
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.9);
}

/* Source + price chips: sit just above the card, below the title. */
.card__chips {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0 4px;
}

.card__badge {
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  padding: 4px 8px;
  border-radius: 999px;
  color: #fff;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.5);
}

.card__badge--saved {
  background: rgba(61, 125, 202, 0.96);
}

.card__badge--order {
  background: rgba(63, 174, 110, 0.96);
}

.card__badge--owned {
  background: rgba(155, 81, 224, 0.96);
}

/* Glyph badges: BARE SF Symbols — no chip, no background, sized to match the
   cart glyph (22px), tinted in their source color, same drop shadow as the
   other bare glyphs on the panel. */
.card__badge--glyph {
  display: inline-flex;
  align-items: center;
  padding: 0;
  background: none;
  box-shadow: none;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5));
}

.card__badge--glyph svg {
  height: 22px;
  width: auto;
  display: block;
}

.card__badge--saved.card__badge--glyph {
  color: #6fb6ff; /* matches the Save for Later button blue */
}

.card__badge--order.card__badge--glyph {
  color: #4ed98a;
}

.card__badge--owned.card__badge--glyph {
  color: #c79aff; /* matches the OWNED pill value */
}

.card__badge--seek {
  background: rgba(0, 160, 150, 0.96);
}

/* "×N" copies chip: shown on Owned-row cards when the collection holds more
   than one of the same card + condition. */
.card__copies {
  font-size: 10px;
  font-weight: 800;
  padding: 4px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.25);
  color: var(--text);
  white-space: nowrap;
}

.card__price {
  background: rgba(255, 203, 5, 0.97);
  color: #20160a;
  font-weight: 800;
  font-size: 11.5px;
  padding: 4px 9px;
  border-radius: 999px;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.5);
}

/* Gain/loss chip (purchases vs paid; saved vs price when saved). */
.delta {
  font-size: 10px;
  font-weight: 800;
  padding: 3px 7px;
  border-radius: 999px;
  white-space: nowrap;
}

.delta--up {
  background: rgba(63, 174, 110, 0.22);
  color: #5ff0a8;
  border: 1px solid rgba(63, 174, 110, 0.55);
}

.delta--down {
  background: rgba(224, 81, 122, 0.22);
  color: #ff8fab;
  border: 1px solid rgba(224, 81, 122, 0.55);
}

/* Portfolio gain/loss in the header stat. */
.pl {
  font-weight: 800;
}

/* Card count rider on the if-bought chip. */
.stat__n {
  color: var(--muted);
  font-weight: 600;
}
.pl--up {
  color: #5ff0a8;
}
.pl--down {
  color: #ff8fab;
}

/* Per-card price sparkline (inside the floating panel). */
.card__spark {
  margin-top: 2px;
}
.spark {
  width: 100%;
  height: 28px;
  display: block;
}

/* Header toggle button active state. */
.btn-toggle {
  cursor: pointer;
}
.btn-toggle.is-active {
  background: linear-gradient(135deg, var(--accent), #ffa600);
  color: #20160a;
  border-color: transparent;
  font-weight: 700;
}

/* View switching: these win over .stage{display:flex}. */
.stage[hidden],
.dashboard[hidden] {
  display: none;
}

/* Dashboard ---------------------------------------------------------------- */
.dashboard {
  position: relative;
  z-index: 2;
  height: calc(100vh - var(--header, 92px));
  overflow-y: auto;
  padding: 20px 32px 48px;
}

.dash {
  max-width: 1120px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.dash__metrics {
  display: grid;
  /* auto-fit: the wishlist "if bought" metric appears only once baselines +
     current prices exist, so the grid flexes between 4 and 5 cards. */
  grid-template-columns: repeat(auto-fit, minmax(185px, 1fr));
  gap: 12px;
}

.metric {
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 14px;
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.metric span {
  font-size: 11px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 1.2px;
}
.metric strong {
  font-size: 22px;
  font-weight: 800;
}
.metric small {
  font-size: 11px;
  color: var(--muted);
}
.metric--pl.up strong {
  color: #5ff0a8;
}
.metric--pl.down strong {
  color: #ff8fab;
}

.dash__chart,
.dash__list {
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 14px;
  padding: 16px;
}
.dash__chart h3,
.dash__list h3 {
  margin: 0 0 12px;
  font-size: 12px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 1.2px;
}
.dash__chartbox svg {
  width: 100%;
  height: 160px;
  display: block;
}

.dash__cols {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
.dash__list ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.dash__list li {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 10px;
  align-items: center;
  font-size: 13px;
}
.mv-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mv-pct {
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.mv-pct.up {
  color: #5ff0a8;
}
.mv-pct.down {
  color: #ff8fab;
}
.mv-val {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.dash-empty {
  color: var(--muted);
  text-align: center;
  padding: 28px;
}

@media (max-width: 860px) {
  .dash__metrics {
    grid-template-columns: repeat(2, 1fr);
  }
  .dash__cols {
    grid-template-columns: 1fr;
  }
}

/* Cart button + price pill travel together at the chips row's right end. */
.card__buy {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* Add to Cart: JUST the glyph — no container — sitting immediately LEFT of
   the price pill, glyph in the pill's yellow and matched to its height.
   (Viewing is just clicking the card itself, so there is no separate View
   control.) Inherits the panel's pointer-events gate — spotlit-only clicks. */
.card__cart {
  padding: 0;
  border: none;
  background: none;
  color: var(--accent); /* same yellow as the price pill */
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5));
  transition: transform 0.15s ease, filter 0.2s ease;
}

.card__cart svg {
  height: 22px; /* matches the price pill's rendered height exactly */
  width: auto;
  display: block;
}

.card__cart:hover {
  transform: scale(1.12);
  filter: brightness(1.06);
}

/* Save for Later: SF "plus.rectangle.portrait", left of the cart glyph.
   Same bare-glyph treatment, slightly shorter (the symbol is portrait). */
.card__save {
  padding: 0;
  border: none;
  background: none;
  color: #6fb6ff; /* save-for-later blue — distinct from the cart's yellow */
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5));
  transition: transform 0.15s ease, filter 0.2s ease, color 0.2s ease;
}

.card__save svg {
  height: 20px;
  width: auto;
  display: block;
}

.card__save:hover {
  transform: scale(1.12);
  filter: brightness(1.06);
}

.card__save.is-busy {
  animation: pw-refresh-pulse 0.9s ease-in-out infinite;
  pointer-events: none;
}

.card__save.is-done {
  color: #5ff0a8; /* it's in the list now */
}

.card__save.is-fail {
  color: #ff8fab;
}

/* Guest galleries are read-only — you can't push cards onto someone
   else's Save for Later (matches the hidden hearts). */
body.is-guest .card__save {
  display: none;
}

/* Favorite heart: revealed by hovering the card's TITLE — an outline heart
   fades in to the right of the name and stays clickable while the cursor is
   near the title row (the row is the hover zone, heart included). No
   always-present chrome. */
.card__titlerow {
  display: flex;
  align-items: center;
  gap: 8px;
}

.card__fav {
  padding: 0;
  border: none;
  background: none;
  color: #ff8fab;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  opacity: 0;
  transform: translateX(-3px);
  transition: opacity 0.18s ease, transform 0.18s ease, color 0.2s ease;
}

.card__titlerow:hover .card__fav,
.card__fav:focus-visible {
  opacity: 1;
  transform: translateX(0);
}

.card__fav svg {
  width: 16px;
  height: 16px;
  display: block;
}

/* Outline heart when not favorited; fills when it is. */
.card__fav svg path {
  fill: transparent;
  stroke: currentColor;
  stroke-width: 2;
}

.card__fav:hover {
  color: #ff5c8a;
}

.card__fav.is-active svg path {
  fill: currentColor;
}

/* Guest view (shared gallery, read-only): no hearting someone else's cards,
   and the subtitle banner picks up an accent so the mode is unmistakable. */
body.is-guest .card__fav {
  display: none;
}

body.is-guest .brand__sub {
  color: #ff8fab;
  background: rgba(255, 143, 171, 0.1);
  border-color: rgba(255, 143, 171, 0.32);
}

/* Spotlight state ---------------------------------------------------------- */
.card.is-spotlight {
  z-index: 999; /* above any --z layering base (scroll rows can reach 100+) */
  /* --nudge (set in wireSpotlight) slides a viewport-clipped card inward so
     the spotlight is never cut off by the screen edge. */
  transform: translateX(var(--nudge, 0px)) translateY(-34px) scale(1.14);
}

.card.is-spotlight .card__inner {
  /* Lifted shadow + glow on the TILTING element — the silhouette orbits with
     the card instead of betraying the untilted rect at the corners. */
  box-shadow:
    0 30px 60px rgba(0, 0, 0, 0.7),
    0 0 0 1px rgba(255, 255, 255, 0.14) inset,
    0 0 40px rgba(61, 125, 202, 0.35);
}

.card.is-spotlight .card__holo {
  opacity: 0.6;
}

.card.is-spotlight .card__top {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

/* Neighbours slide aside so the spotlit card is never obscured. */
.card.shift-left {
  transform: translateX(-46px);
}
.card.shift-right {
  transform: translateX(46px);
}

/* Toasts -------------------------------------------------------------------- */
/* Bottom-center action feedback (save for later / add to cart). Click to
   dismiss; they slide in, hold a few seconds, fade down and out. */
.toasts {
  position: fixed;
  bottom: 26px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  z-index: 2000;
  pointer-events: none;
}

.toast {
  pointer-events: auto;
  max-width: min(560px, 86vw);
  padding: 10px 18px;
  border-radius: 999px;
  background: rgba(17, 19, 27, 0.92);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(95, 240, 168, 0.38);
  box-shadow: 0 10px 32px rgba(0, 0, 0, 0.5);
  color: var(--text);
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  animation: pw-toast-in 0.3s cubic-bezier(0.22, 1, 0.36, 1);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.toast--err {
  border-color: rgba(255, 143, 171, 0.45);
}

.toast.is-out {
  opacity: 0;
  transform: translateY(10px);
}

@keyframes pw-toast-in {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
}

/* Empty state -------------------------------------------------------------- */
.empty {
  position: relative;
  z-index: 3;
  text-align: center;
  margin: 12vh auto;
  max-width: 460px;
  padding: 0 24px;
  color: var(--muted);
}

.empty h2 {
  color: var(--text);
  font-size: 22px;
  margin-bottom: 10px;
}

.empty strong {
  color: var(--accent);
}

@media (prefers-reduced-motion: reduce) {
  .aurora,
  .card,
  .card__inner,
  .card__cart,
  .card__info {
    transition: none;
    animation: none;
  }
}

/* --- Card page (click a card → foreground detail view) --------------------- */
/* The overlay itself never fades — its CHILDREN do (backdrop / panel / close).
   That keeps the hero opaque while it reverse-FLIPs back onto its source card
   on close, instead of fading out mid-flight. */
.cardpage {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
}

.cardpage__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(8, 8, 14, 0.74);
  backdrop-filter: blur(14px) saturate(0.85);
  -webkit-backdrop-filter: blur(14px) saturate(0.85);
  opacity: 0;
  transition: opacity 0.3s ease;
}
.cardpage.is-open .cardpage__backdrop { opacity: 1; }

.cardpage__close {
  position: absolute;
  top: 20px;
  right: 24px;
  z-index: 3;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: rgba(20, 20, 32, 0.7);
  color: var(--text);
  font-size: 15px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.2s ease, border-color 0.15s ease, transform 0.15s ease;
}
.cardpage.is-open .cardpage__close { opacity: 1; }
.cardpage__close:hover { border-color: var(--accent); transform: scale(1.06); }

/* No transform on the content: the hero FLIP measures this container's
   layout rect, so transforming it here would throw the measurement off. The
   entrance is carried by the hero FLIP + the panel slide + the overlay fade. */
.cardpage__content {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: clamp(20px, 4vw, 56px);
  max-width: min(1200px, 94vw);
  max-height: 92vh;
  padding: 24px;
}

/* FLIP target — the open animation inverts this to the clicked card's rect. */
.cardpage__herowrap {
  flex: 0 0 auto;
  transition: transform 0.42s cubic-bezier(0.4, 1, 0.3, 1);
  will-change: transform;
}
/* The hero IS a real .card.card--holo, so the ENTIRE row holofoil system —
   the ::after foil interference, .card__deeps deep-shimmer, .card__pocket
   cursor glint, .card__holo sheen, the hue drift — applies verbatim. We only
   override the row sizing and light those layers up via this scope (the row
   gates them behind .is-spotlight; here we want them lit WITHOUT the
   spotlight's lift transform, so the hero just sits there glowing). */
.cardpage__hero.card {
  /* Big, prominent showcase — the second min() arm caps the width so the 1.4×
     height never exceeds ~86vh (height = width / 0.714), shrinking the hero on
     short viewports instead of overflowing. */
  --hw: min(clamp(280px, 42vw, 480px), calc(86vh * 0.714));
  width: var(--hw);
  height: auto;
  aspect-ratio: 2.5 / 3.5;
  margin: 0;
  flex: 0 0 auto;
  border-radius: calc(var(--hw) * 0.066);
  cursor: default;
}
.cardpage__hero .card__inner {
  border-radius: calc(var(--hw) * 0.066);
  box-shadow:
    0 30px 80px rgba(0, 0, 0, 0.6),
    0 0 0 1px rgba(255, 255, 255, 0.12) inset,
    0 0 50px rgba(61, 125, 202, 0.3);
}
/* Light up the holo layers (parity with .is-spotlight opacities). */
.cardpage__hero .card__holo { opacity: 0.6; }
.cardpage__hero .card__pocket { opacity: 0.9; }
.cardpage__hero.card--holo .card__inner::after { opacity: 0.5; }
.cardpage__hero.card--holo .card__deeps { opacity: 0.8; }
.cardpage__hero.card--holo .card__inner { animation: pw-hue-drift 16s linear infinite; }

.cardpage__panel {
  flex: 1 1 360px;
  min-width: 0;
  max-width: 460px;
  max-height: 86vh;
  overflow-y: auto;
  color: var(--text);
  /* Slide in just behind the hero zoom. */
  opacity: 0;
  transform: translateX(16px);
  transition: opacity 0.3s ease 0.06s, transform 0.36s cubic-bezier(0.34, 1.1, 0.4, 1) 0.06s;
}
.cardpage.is-open .cardpage__panel { opacity: 1; transform: none; }
/* Title row: name takes the width, heart pinned to the right edge. */
.cardpage__titlerow {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  margin: 0 0 6px;
}
.cardpage__name {
  font-size: clamp(22px, 3vw, 32px);
  font-weight: 800;
  line-height: 1.1;
  margin: 0;
  letter-spacing: -0.01em;
}
/* Heart on the title's right edge — always visible here (unlike the row
   card's hover-reveal); outline by default, fills when favorited. */
.cardpage__fav {
  flex: 0 0 auto;
  margin-top: 2px;
  padding: 0;
  border: none;
  background: none;
  color: #ff8fab;
  cursor: pointer;
  display: inline-flex;
  transition: color 0.2s ease, transform 0.15s ease;
}
.cardpage__fav svg { width: 24px; height: 24px; display: block; }
.cardpage__fav svg path { fill: transparent; stroke: currentColor; stroke-width: 2; }
.cardpage__fav:hover { color: #ff5c8a; transform: scale(1.1); }
.cardpage__fav.is-active svg path { fill: currentColor; }
.cardpage__meta { color: var(--muted); font-size: 14px; margin: 0; }
.cardpage__cond { color: var(--muted); font-size: 13px; margin: 8px 0 0; }

.cardpage__pricebox {
  margin: 22px 0 0;
  padding: 16px 18px;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
}
.cardpage__price {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.cardpage__pricelbl {
  color: var(--muted);
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 1px;
}
.cardpage__priceval { font-size: 26px; font-weight: 800; }
.cardpage__cost {
  margin-top: 10px;
  color: var(--muted);
  font-size: 13px;
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}
.cardpage__spark { margin: 18px 0 0; }

/* Interactive price chart (scrubbable) */
.cardpage__chartwrap { margin: 18px 0 0; }
.cpchart {
  display: block;
  width: 100%;
  height: auto;
  cursor: crosshair;
  touch-action: none;
  overflow: visible;
}
.cpchart__cross,
.cpchart__dot {
  transition: opacity 0.12s ease;
  pointer-events: none;
}
/* While scrubbing, the headline price reads from the chart — give the label a
   tabular feel so it doesn't jitter as the date changes. */
.cardpage__pricelbl {
  font-variant-numeric: tabular-nums;
}

.cardpage__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 24px 0 4px;
}
.cardpage__btn {
  font: inherit;
  font-weight: 700;
  font-size: 13.5px;
  cursor: pointer;
  border-radius: 999px;
  padding: 11px 18px;
  border: 1px solid transparent;
  text-decoration: none;
  transition: filter 0.15s ease, transform 0.15s ease, border-color 0.15s ease;
}
.cardpage__btn:hover { transform: translateY(-1px); }
.cardpage__btn--cart { background: var(--accent); color: #1a1a06; }
.cardpage__btn--save {
  background: rgba(255, 255, 255, 0.08);
  color: var(--text);
  border-color: rgba(255, 255, 255, 0.18);
}
.cardpage__btn--ghost {
  background: transparent;
  color: var(--muted);
  border-color: rgba(255, 255, 255, 0.18);
  display: inline-flex;
  align-items: center;
}
.cardpage__btn.is-busy { opacity: 0.6; cursor: default; }
.cardpage__btn.is-done { background: rgba(95, 240, 168, 0.16); color: #5ff0a8; border-color: rgba(95, 240, 168, 0.4); }

@media (max-width: 720px) {
  .cardpage__content {
    flex-direction: column;
    gap: 18px;
    padding: 16px;
    max-height: 94vh;
    overflow-y: auto;
  }
  .cardpage__hero.card { --hw: min(clamp(200px, 64vw, 320px), calc(64vh * 0.714)); }
  .cardpage__panel { max-height: none; overflow: visible; }
}

/* ===== Mobile breakpoint =====================================================
   Keep in sync with MOBILE_MQ in viewer.js (700px). The layout flips from
   "three rows split the viewport height" to "the page scrolls vertically and
   each row is one near-full-width card tall" — sizeToViewport derives
   --card-w from the viewport WIDTH here, and each strip takes its height from
   the card instead of a flex share. Rows swipe horizontally (touch drag in
   layoutRow); tap a card to spotlight it, tap again to open it. */
@media (max-width: 700px) {
  /* The page scrolls VERTICALLY only — body's overflow-x:hidden doesn't stop
     the viewport (html) itself from side-scrolling when something (a lifted
     card, a dropdown) pokes past the edge. clip forbids it without creating a
     scroll container. */
  html {
    overflow-x: clip;
  }

  /* Header: tighter, and the stat pills wrap in a row instead of stacking. */
  .top {
    padding: 14px 16px;
    gap: 10px;
  }
  .brand {
    gap: 10px;
  }
  .brand__logo {
    width: 38px;
    height: 38px;
  }
  .brand__title {
    font-size: 19px;
  }
  .statstack {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 6px;
  }
  .vpill {
    padding: 4px 10px;
    gap: 7px;
  }

  /* The page scrolls; strips size themselves from the card height. */
  .stage {
    height: auto;
    padding: 18px 0 96px;
    gap: 26px;
  }
  .strip {
    flex: 0 0 auto;
  }
  /* The spacing slider can't stay centered — it lands on the strip label at
     phone widths (worst on Owned, whose label carries the "verified" pill).
     Park it top-right and reserve that width in the label so long labels
     wrap beside it instead of underneath it. */
  .strip__spacing {
    left: auto;
    right: 16px;
    transform: none;
  }
  .strip__label {
    padding: 0 176px 4px 18px;
  }
  .row {
    flex: 0 0 auto;
    height: calc(var(--card-h) + 16px);
    padding: 8px 20px;
  }

  /* Touch swipes the row directly — the hover edge-drift indicators (and the
     bands they mark) don't exist here. */
  .edgezone {
    display: none;
  }

  /* Floating title panel BELOW the card: above, it would clip off the top of
     the document on the first row — and thumbs live at the bottom anyway. The
     safe-area bridge flips with it (moot on touch, correct for narrow-window
     mouse use). */
  .card__top {
    bottom: auto;
    top: calc(100% + 10px);
    transform: translateY(-12px);
  }
  .card.is-spotlight .card__top {
    transform: translateY(0);
  }
  .card__top::after {
    top: auto;
    bottom: 100%;
  }
  /* No hover to coax the heart out — it rides the panel whenever spotlit. */
  .card.is-spotlight .card__fav {
    opacity: 1;
    transform: translateX(0);
  }

  /* Gentler lift: near-full-width cards have no room for the 1.14× pop. */
  .card.is-spotlight {
    transform: translateX(var(--nudge, 0px)) translateY(-10px) scale(1.04);
  }

  /* Dock: opened by tapping the peek pill (a real target here — see the
     .dock__tab overrides below); controls wrap. */
  .dock {
    flex-wrap: wrap;
    justify-content: center;
    width: calc(100vw - 20px);
    max-width: calc(100vw - 20px);
    gap: 8px;
    padding: 10px 12px 12px;
  }
  .dock .search {
    flex: 1 1 100%;
  }
  .dock .search input {
    width: auto;
    flex: 1 1 auto;
    min-width: 0;
  }
  /* The peek pill becomes a REAL tap target: a generous invisible hit area
     with the visual bar redrawn by ::after. (Desktop keeps the inert 5px
     pill — proximity opens the dock there.) */
  .dock__tab {
    pointer-events: auto;
    cursor: pointer;
    width: 150px;
    height: 30px;
    top: 100%;
    background: transparent;
    box-shadow: none;
  }
  .dock__tab::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 12px;
    transform: translateX(-50%);
    width: 124px;
    height: 5px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.85);
    box-shadow: 0 1px 6px rgba(0, 0, 0, 0.45);
  }
  /* While open the hint fades out — don't leave an invisible tap-blocker
     floating over the page (outside-taps close the dock instead). */
  .dock.is-open .dock__tab,
  .dock:focus-within .dock__tab {
    pointer-events: none;
  }

  /* Dreamseeker bar: the query and the Filters toggle share the first row;
     the filter/browse controls live behind the toggle (collapsed by default —
     setSeekMode adds .is-collapsed on entry here) and wrap in two columns
     when expanded. */
  .seeker {
    padding: 4px 14px 12px;
    gap: 8px;
  }
  /* Small flex-basis on purpose: wrapping decisions use the HYPOTHETICAL
     size, so the desktop min(420px, 50vw) basis would kick the Filters
     toggle onto its own row — collapsed, the whole seeker is one row. */
  #seekQ {
    flex: 1 1 140px;
    width: auto;
    min-width: 0;
  }
  .seeker .select,
  .seeker .setpick {
    flex: 1 1 44%;
    min-width: 0;
  }
  .seeker__set,
  .setpick__btn {
    width: 100%;
    min-width: 0;
    max-width: none;
  }
  .seeker__seller {
    width: auto;
  }
  .seeker__filters {
    display: inline-flex;
    flex: 0 0 auto;
  }
  .seeker.is-collapsed
    :is(.setpick, #seekCondition, #seekPrinting, #seekSeller, #seekSort, .seeker__toggle, .seek-trending) {
    display: none;
  }
  /* The set-picker dropdown anchors left and would spill past the right
     viewport edge from its right-column button, sideways-scrolling the whole
     page. Anchor it right and cap it to the viewport. */
  .setpick__menu {
    left: auto;
    right: 0;
    width: min(320px, calc(100vw - 28px));
  }

  .dashboard {
    padding: 16px 16px 40px;
  }
}
