/* ============================================================
   REACTION GACHA — main stylesheet
   Themes: body[data-theme="pokemon"] | body[data-theme="mtg"]
   ============================================================ */

* { margin: 0; padding: 0; box-sizing: border-box; }

:root {
  --card-w: 230px;
  --radius: 14px;
  --ease-pop: cubic-bezier(.22, 1.4, .36, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
}

html { scrollbar-gutter: stable; }

body {
  min-height: 100vh;
  font-family: var(--font-ui);
  color: var(--text);
  background: var(--bg);
  background-attachment: fixed;
  overflow-x: hidden;
}

button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
img { max-width: 100%; display: block; }
[hidden] { display: none !important; }

/* ---------- THEME TOKENS ---------- */

body[data-theme="pokemon"] {
  --font-ui: "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-card-title: "Segoe UI", system-ui, sans-serif;
  --font-card-body: "Segoe UI", system-ui, sans-serif;
  --bg: radial-gradient(1200px 600px at 85% -10%, #dff1fb 0%, transparent 60%),
        radial-gradient(900px 500px at -10% 110%, #ffe3dc 0%, transparent 55%),
        linear-gradient(180deg, #f4f6fa 0%, #e9edf4 100%);
  --text: #1c2430;
  --text-dim: #5a6577;
  --accent: #e3350d;
  --accent-2: #30a7d7;
  --panel: rgba(255, 255, 255, .82);
  --panel-border: rgba(28, 36, 48, .1);
  --panel-shadow: 0 8px 30px rgba(28, 36, 48, .12);

  --card-frame: linear-gradient(160deg, #ffffff 0%, #eef1f6 60%, #dfe4ec 100%);
  --card-edge: #c9d2de;
  --card-title-c: #17202b;
  --card-box-bg: rgba(255, 255, 255, .78);
  --card-box-text: #2a3442;
  --card-box-border: rgba(28, 36, 48, .14);

  --pack-a: #e3350d;
  --pack-b: #8c1500;
  --pack-crimp: #c9d2de;
  --pack-text: #fff;
}

body[data-theme="mtg"] {
  --font-ui: "Segoe UI", system-ui, sans-serif;
  --font-card-title: Georgia, "Times New Roman", serif;
  --font-card-body: Georgia, "Times New Roman", serif;
  --bg: radial-gradient(1100px 700px at 80% -20%, #3d2b52 0%, transparent 60%),
        radial-gradient(800px 600px at -10% 115%, #4a3116 0%, transparent 55%),
        linear-gradient(180deg, #17121e 0%, #0e0a13 100%);
  --text: #ecdfc3;
  --text-dim: #a5977c;
  --accent: #c9a54c;
  --accent-2: #7a4fd8;
  --panel: rgba(26, 20, 34, .8);
  --panel-border: rgba(201, 165, 76, .28);
  --panel-shadow: 0 10px 36px rgba(0, 0, 0, .55);

  --card-frame: linear-gradient(165deg, #2b2118 0%, #1b140e 55%, #120d09 100%);
  --card-edge: #c9a54c;
  --card-title-c: #f0e2bd;
  --card-box-bg: #e4d5ae;
  --card-box-text: #241a10;
  --card-box-border: #8f7434;

  --pack-a: #4a2d7a;
  --pack-b: #1c1030;
  --pack-crimp: #c9a54c;
  --pack-text: #f0e2bd;
}

body { transition: background .4s ease, color .4s ease; }

/* Nothing here is a document, so nothing here is selectable.

   Clicking through a pack is repeated clicks in one spot, and the second one
   is a double-click as far as the browser is concerned — which selects the
   word under it. The cards themselves already opted out, but everything they
   sit on top of (the counter, "PACK COMPLETE", the buttons that appear right
   where you were clicking) did not, so the selection landed there instead and
   Opera GX popped its Search/Copy/Snapshot bar over the card.

   Applied to `body` rather than `#app` because the overlays, context menu and
   modals are all siblings of it. Fields the user genuinely needs to copy out
   of — the minted embed link above all — opt back in below. */
body {
  user-select: none;
  -webkit-user-select: none;
}
input, textarea, [contenteditable] {
  user-select: text;
  -webkit-user-select: text;
}

/* ---------- TOP BAR ---------- */

.topbar {
  position: sticky; top: 0; z-index: 50;
  display: flex; align-items: center; gap: 22px; flex-wrap: wrap;
  padding: 12px 26px;
  background: var(--panel);
  border-bottom: 1px solid var(--panel-border);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

.logo { display: flex; align-items: center; gap: 10px; user-select: none; }
.logo-orb {
  width: 26px; height: 26px; border-radius: 50%;
  background: linear-gradient(180deg, var(--accent) 0 46%, #111 46% 54%, #fff 54% 100%);
  box-shadow: inset 0 0 0 2px #111, 0 2px 8px rgba(0,0,0,.3);
  position: relative;
}
.logo-orb::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 8px; height: 8px; border-radius: 50%;
  background: #fff; border: 2px solid #111;
}
body[data-theme="mtg"] .logo-orb {
  background: radial-gradient(circle at 35% 30%, #b98aff, var(--accent-2) 55%, #2a1650);
  box-shadow: inset 0 0 0 2px var(--accent), 0 0 14px rgba(122, 79, 216, .8);
}
body[data-theme="mtg"] .logo-orb::after { display: none; }

.logo-text { font-weight: 300; font-size: 19px; letter-spacing: 3px; }
.logo-text b { font-weight: 800; color: var(--accent); }

.tabs { display: flex; gap: 6px; margin-inline: auto; }
.tab-btn {
  padding: 9px 20px; border-radius: 999px; font-weight: 600; font-size: 15px;
  color: var(--text-dim); transition: color .2s, background .2s, box-shadow .2s;
}
.tab-btn:hover { color: var(--text); }
.tab-btn.active {
  color: #fff; background: var(--accent);
  box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 50%, transparent);
}
body[data-theme="mtg"] .tab-btn.active { color: #16100a; }

.pill {
  display: inline-block; min-width: 22px; padding: 1px 7px; margin-left: 4px;
  border-radius: 999px; font-size: 12px; font-weight: 700;
  background: rgba(0, 0, 0, .22);
}
.tab-btn:not(.active) .pill { background: color-mix(in srgb, var(--text) 14%, transparent); }

/* ---------- SIGNATURE (header) ---------- */

.signature {
  display: flex; align-items: center; gap: 7px;
  padding: 5px 6px 5px 12px;
  border-radius: 999px;
  border: 1px solid transparent;
  max-width: 240px;
  transition: background .2s, border-color .2s;
}
.signature:hover,
.signature:focus-within {
  background: color-mix(in srgb, var(--text) 7%, transparent);
  border-color: var(--panel-border);
}
.sig-pen { font-size: 14px; opacity: .55; }
.sig-name {
  font-size: 14px; font-weight: 700;
  font-style: italic;
  letter-spacing: .3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sig-name.is-unset { opacity: .5; font-weight: 500; }

.sig-edit {
  flex: none;
  width: 24px; height: 24px;
  display: grid; place-items: center;
  border-radius: 50%;
  font-size: 12px;
  opacity: 0;
  transform: scale(.75);
  transition: opacity .18s, transform .18s var(--ease-pop), background .18s;
  pointer-events: none;
}
.signature:hover .sig-edit,
.signature:focus-within .sig-edit {
  opacity: 1; transform: scale(1); pointer-events: auto;
}
.sig-edit:hover { background: color-mix(in srgb, var(--text) 14%, transparent); }
/* keyboard users get it unconditionally */
.sig-edit:focus-visible {
  opacity: 1; transform: scale(1); pointer-events: auto;
  outline: 2px solid var(--accent-2); outline-offset: 1px;
}

.sig-hint { font-size: 13px; color: var(--accent); }

#sig-input {
  width: 100%;
  padding: 12px 14px;
  border-radius: 10px;
  font: 600 15px var(--font-ui);
  color: var(--text);
  background: color-mix(in srgb, var(--text) 8%, transparent);
  border: 1px solid var(--panel-border);
  outline: none;
}
#sig-input:focus { border-color: var(--accent-2); }

.theme-switch {
  display: flex; padding: 3px; border-radius: 999px;
  background: color-mix(in srgb, var(--text) 9%, transparent);
}
.thm-btn {
  padding: 6px 16px; border-radius: 999px; font-size: 13px; font-weight: 700;
  color: var(--text-dim); transition: all .25s;
}
.thm-btn.active { background: var(--panel); color: var(--text); box-shadow: 0 2px 8px rgba(0,0,0,.18); }

/* ---------- TABS / LAYOUT ---------- */

main { max-width: 1280px; margin: 0 auto; padding: 30px 26px 90px; }
.tab-panel { display: none; }
.tab-panel.active { display: block; animation: panel-in .35s var(--ease-out); }
@keyframes panel-in { from { opacity: 0; transform: translateY(10px); } }

/* ---------- PACK SHELF ---------- */

.pack-shelf { text-align: center; padding-top: 26px; }
.shelf-title { font-size: clamp(26px, 4vw, 40px); font-weight: 800; letter-spacing: .5px; }
body[data-theme="mtg"] .shelf-title { font-family: var(--font-card-title); font-weight: 700; }
.shelf-sub { color: var(--text-dim); margin-top: 8px; font-size: 15px; }
.shelf-stats { color: var(--text-dim); margin-top: 34px; font-size: 14px; }

.shelf-row {
  display: flex; justify-content: center; gap: clamp(16px, 4vw, 48px);
  margin-top: 44px; flex-wrap: wrap; perspective: 1200px;
}

.pack {
  position: relative; width: 188px; height: 288px;
  display: flex; flex-direction: column;
  filter: drop-shadow(0 14px 22px rgba(0, 0, 0, .35));
  transition: transform .35s var(--ease-pop), filter .35s;
  transform-style: preserve-3d;
}
.pack:hover {
  transform: translateY(-14px) rotateX(6deg) rotateZ(-1.5deg) scale(1.05);
  filter: drop-shadow(0 26px 34px rgba(0, 0, 0, .45));
}
.pack:active { transform: translateY(-8px) scale(.98); }

.pack-crimp {
  height: 16px; flex: none;
  background: repeating-linear-gradient(90deg,
    var(--pack-crimp) 0 5px,
    color-mix(in srgb, var(--pack-crimp) 60%, #000) 5px 10px);
}
.pack-crimp.top { border-radius: 8px 8px 0 0; }
.pack-crimp.bottom { border-radius: 0 0 8px 8px; }

.pack-body {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
  background:
    radial-gradient(140% 90% at 50% 0%, color-mix(in srgb, var(--pack-a) 70%, #fff) 0%, transparent 42%),
    linear-gradient(165deg, var(--pack-a) 0%, var(--pack-b) 90%);
  overflow: hidden; position: relative;
}
.pack-art { font-size: 74px; filter: drop-shadow(0 4px 8px rgba(0,0,0,.4)); }
.pack-name {
  font-weight: 900; font-size: 19px; letter-spacing: 3px; line-height: 1.3;
  color: var(--pack-text); text-shadow: 0 2px 6px rgba(0,0,0,.5);
}
body[data-theme="mtg"] .pack-name { font-family: var(--font-card-title); font-weight: 700; }

.pack-shine {
  position: absolute; inset: 0; pointer-events: none; overflow: hidden; border-radius: 8px;
}
.pack-shine::after {
  content: ""; position: absolute; top: -20%; bottom: -20%; left: -80%; width: 45%;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,.5), transparent);
  transform: skewX(-18deg);
  transition: left .6s ease;
}
.pack:hover .pack-shine::after { left: 140%; }

/* ---------- PACK STAGE ---------- */

.pack-stage {
  display: flex; flex-direction: column; align-items: center; gap: 26px;
  padding-top: 40px; perspective: 1200px;
}
.pack-stage .pack {
  transform: scale(1.55);
  margin: 60px 0 70px;
  animation: pack-wobble 2.4s ease-in-out infinite;
  cursor: pointer;
}
@keyframes pack-wobble {
  0%, 100% { transform: scale(1.55) rotateZ(-2deg); }
  50%      { transform: scale(1.58) rotateZ(2deg); }
}
.pack-stage .pack.ripping {
  animation: pack-rip .55s ease-in forwards;
}
@keyframes pack-rip {
  0%   { transform: scale(1.55) rotateZ(-2deg); }
  25%  { transform: scale(1.62) rotateZ(4deg); }
  45%  { transform: scale(1.5) rotateZ(-6deg); }
  70%  { transform: scale(1.8) rotateZ(3deg); opacity: 1; filter: brightness(1.6) drop-shadow(0 0 40px #fff); }
  100% { transform: scale(2.6) rotateZ(0); opacity: 0; filter: brightness(3) blur(6px); }
}
.stage-hint { color: var(--text-dim); font-size: 15px; }
.stage-hint b { color: var(--text); }

/* ---------- REVEAL ---------- */

.reveal-stage {
  display: flex; flex-direction: column; align-items: center; gap: 20px; padding-top: 20px;
}
.reveal-counter { color: var(--text-dim); font-size: 14px; letter-spacing: 2px; font-weight: 700; }

.reveal-stack {
  position: relative;
  width: calc(var(--card-w) + 60px);
  height: calc(var(--card-w) * 1.75 + 40px);
  margin-top: 26px;
  perspective: 1400px;
}
.flipcard {
  position: absolute; inset: 0; margin: auto;
  width: var(--card-w);
  cursor: pointer;
  transition: transform .45s var(--ease-pop), opacity .4s;
}
.flip-inner {
  position: relative;
  transform-style: preserve-3d;
  transition: transform .55s var(--ease-pop);
}
.flipcard.facedown .flip-inner { transform: rotateY(180deg); }
.flip-back {
  position: absolute; inset: 0;
  backface-visibility: hidden;
  transform: rotateY(180deg);
  border-radius: var(--radius);
  display: flex; align-items: center; justify-content: center;
  background:
    radial-gradient(circle at 50% 45%, color-mix(in srgb, var(--accent) 75%, #fff) 0%, transparent 34%),
    repeating-conic-gradient(from 0deg at 50% 45%, var(--accent) 0 20deg, color-mix(in srgb, var(--accent) 55%, #000) 20deg 40deg);
  border: 6px solid var(--card-edge);
  box-shadow: 0 12px 30px rgba(0,0,0,.4);
}
.flip-back::after { content: "🎴"; font-size: 64px; filter: drop-shadow(0 4px 8px rgba(0,0,0,.5)); }
.flip-front { backface-visibility: hidden; }
.flipcard .card { width: 100%; }

.flipcard.flyoff {
  transform: translate(-120%, -14%) rotateZ(-24deg) !important;
  opacity: 0;
  pointer-events: none;
}

.badge {
  position: absolute; top: -14px; right: -14px; z-index: 10;
  padding: 5px 12px; border-radius: 999px;
  font-size: 12px; font-weight: 900; letter-spacing: 1.5px;
  color: #fff; background: var(--accent-2);
  box-shadow: 0 4px 14px rgba(0,0,0,.35);
  transform: scale(0) rotate(12deg);
  animation: badge-pop .4s var(--ease-pop) .25s forwards;
}
.badge.b-new      { background: #21b573; }
.badge.b-upgraded { background: #7a4fd8; }
.badge.b-dupe     { background: #7b8698; }
.badge.b-maxed    { background: linear-gradient(120deg, #f5a623, #e3358d); }
@keyframes badge-pop { to { transform: scale(1) rotate(4deg); } }

.reveal-done { text-align: center; animation: panel-in .4s var(--ease-out); }
.reveal-summary {
  display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-bottom: 22px;
}
.sum-chip {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 14px; border-radius: 12px;
  background: var(--panel); border: 1px solid var(--panel-border);
  font-size: 13px; font-weight: 600; box-shadow: var(--panel-shadow);
}
.sum-chip .r-tag { font-size: 11px; font-weight: 800; letter-spacing: 1px; color: var(--text-dim); }

.cta-btn {
  display: inline-block; text-decoration: none;
  padding: 13px 30px; border-radius: 999px;
  background: var(--accent); color: #fff; font-weight: 800; font-size: 16px; letter-spacing: .5px;
  box-shadow: 0 6px 20px color-mix(in srgb, var(--accent) 55%, transparent);
  transition: transform .2s var(--ease-pop), box-shadow .2s;
}
body[data-theme="mtg"] .cta-btn { color: #16100a; }
.cta-btn:hover { transform: translateY(-2px) scale(1.03); }
.ghost-btn {
  padding: 12px 24px; border-radius: 999px; font-weight: 600;
  color: var(--text-dim); border: 1px solid var(--panel-border);
  transition: all .2s;
}
.ghost-btn:hover { color: var(--text); border-color: var(--text-dim); }
.reveal-actions { display: flex; gap: 14px; justify-content: center; }

/* ---------- COLLECTION ---------- */

.coll-toolbar {
  display: flex; gap: 12px; margin-bottom: 26px; flex-wrap: wrap;
}
.coll-search {
  flex: 1; min-width: 220px;
  padding: 12px 18px; border-radius: 12px; font-size: 15px;
  color: var(--text); background: var(--panel);
  border: 1px solid var(--panel-border); outline: none;
  box-shadow: var(--panel-shadow);
  transition: border-color .2s, box-shadow .2s;
}
.coll-search:focus { border-color: var(--accent-2); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-2) 25%, transparent); }
.coll-search::placeholder { color: var(--text-dim); }
.coll-sort {
  padding: 12px 16px; border-radius: 12px; font-size: 14px; font-weight: 600;
  color: var(--text); background: var(--panel);
  border: 1px solid var(--panel-border); outline: none; cursor: pointer;
  box-shadow: var(--panel-shadow);
}
.coll-sort option { color: #1c2430; background: #fff; }

.coll-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(212px, 1fr));
  gap: 26px 20px;
  align-items: start;
  justify-items: center;
}
.coll-empty { text-align: center; color: var(--text-dim); padding: 60px 0; font-size: 16px; }
.coll-grid .card { width: 100%; max-width: 258px; }

/* ============================================================
   THE CARD
   ============================================================ */

/* ONE card design, drawn at whatever size it happens to be.

   Every length inside the card is a multiple of `--u`, where 1u is one pixel of
   the 230px reference design — so `calc(14.5 * var(--u))` still reads as "the
   14.5px title". The card is a size container, so `u` tracks the card's real
   width and the layout is resolution-independent: the 420px expanded preview,
   a 258px grid tile and a 178px phone tile are the same card at three scales.

   They used to be fixed px, which meant the expanded card kept grid-sized text
   in a frame nearly twice as wide and read as sparse and under-filled. Note
   that `cqw` in a container's OWN properties resolves against its ancestor
   container, not itself — `--u` is declared here but only ever consumed by
   descendants, where it resolves against this card. */
.card {
  position: relative;
  container-type: inline-size;
  --u: calc(100cqw / 230);
  width: var(--card-w);
  aspect-ratio: 5 / 7;
  /* Percentage radii resolve against the box's own width/height, so these two
     figures stay a circular 14px-at-230 corner at every size without needing a
     container unit — which the rule above can't use on itself anyway. */
  border-radius: 6.087% / 4.348%;
  transform: perspective(1100px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) scale(var(--s, 1));
  transform-style: preserve-3d;
  transition: box-shadow .3s;
  box-shadow: 0 10px 26px rgba(0, 0, 0, .35);
  user-select: none;
  -webkit-user-select: none;
}
.card.tilting { will-change: transform; z-index: 5; }
.card:not(.locked) { cursor: pointer; }
/* Full art is NOT a taller card — every rarity shares the same 5:7 frame.
   It differs by letting the artwork fill the frame edge to edge. */

.card-inner {
  position: absolute; inset: 0;
  border-radius: inherit;
  overflow: hidden;
  background: var(--card-frame);
  border: calc(3 * var(--u)) solid var(--card-edge);
  /* Browsers FLOOR border widths to whole pixels, so a 229.6px card computes
     2.99 and paints 2 — visibly thinner than the 3 it was drawn at, and a
     hairline border could truncate to nothing. Round instead. Declared after
     the shorthand so an engine without round() keeps the plain calc. */
  border-width: round(calc(3 * var(--u)), 1px);
  isolation: isolate;
}
body[data-theme="mtg"] .card-inner {
  border-width: calc(2 * var(--u));
  border-width: round(calc(2 * var(--u)), 1px);
  box-shadow: inset 0 0 0 calc(2 * var(--u)) #16100a,
              inset 0 0 0 calc(4 * var(--u)) color-mix(in srgb, var(--card-edge) 55%, transparent);
}

/* body layout
   z-index is deliberately `auto` so the children below can interleave with the
   foil layers that are its siblings — a stacking context here would trap the
   text underneath the glint. */
.card-body {
  position: absolute; inset: 0; z-index: auto;
  display: flex; flex-direction: column;
  padding: calc(8 * var(--u));
  gap: calc(6 * var(--u));
}

/* Layer order inside .card-inner:
     1 frame foil      (under everything; shows through translucent boxes)
     2 artwork         (opaque, so it masks the frame foil — reverse holo)
     3 full-card foil  (over the artwork)
     4 cosmic symbols
     5 text + boxes    (always on top, so the glint never hurts readability)
     6 pointer glare                                                        */
.card-head, .card-bottom, .card-usage, .card-stats, .card-sig {
  position: relative;
  z-index: 5;
}

/* Flavour text, stats and credit travel together. In the standard frame this
   is a transparent pass-through that reproduces the old flat layout exactly;
   in the full-art frame it becomes the one element carrying the scrim. */
.card-bottom {
  flex: 1; min-height: 0;
  display: flex; flex-direction: column; gap: calc(6 * var(--u));
}

.card-head {
  display: flex; align-items: center; justify-content: space-between; gap: calc(6 * var(--u));
  padding: calc(4 * var(--u)) calc(6 * var(--u));
  min-height: calc(30 * var(--u));
}
.card-title {
  flex: 1; min-width: 0;   /* claim the space the smaller badge frees up */
  font-family: var(--font-card-title);
  font-size: calc(14.5 * var(--u)); font-weight: 800; line-height: 1.1;
  color: var(--card-title-c);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
body[data-theme="mtg"] .card-title { font-weight: 700; letter-spacing: calc(.3 * var(--u)); }

/* Three-letter badge: deliberately compact so the title gets the width. */
.type-pill {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: calc(30 * var(--u));
  padding: calc(2 * var(--u)) calc(6 * var(--u)); border-radius: calc(5 * var(--u));
  font-size: calc(9.5 * var(--u)); font-weight: 800; letter-spacing: calc(.8 * var(--u));
  color: #fff;
  background: linear-gradient(160deg, var(--type-color), color-mix(in srgb, var(--type-color) 55%, #000));
  box-shadow: 0 1px 4px rgba(0, 0, 0, .3);
  text-shadow: 0 1px 2px rgba(0, 0, 0, .45);
}
body[data-theme="mtg"] .type-pill {
  border-radius: calc(3 * var(--u));
  font-family: var(--font-card-title);
}

.card-art {
  position: relative;
  z-index: 2;          /* above the frame foil, below the full-card foil */
  flex: none;
  height: 46%;
  border-radius: calc(8 * var(--u));
  overflow: hidden;
  border: calc(2 * var(--u)) solid color-mix(in srgb, var(--card-edge) 80%, #000 10%);
  border-width: round(calc(2 * var(--u)), 1px);
  background: #0b0d12;
}
body[data-theme="mtg"] .card-art { border-radius: calc(3 * var(--u)); border-color: #0d0905; }
/* cover so art always fills its frame and crops, whatever its native aspect */
.card-art img,
.card-art canvas,
.card-art video {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center;
  position: relative; z-index: 1;
  background: #0b0d12;
  /* The art is decoration, not a player. Without this the card's own hover
     tilt and click-to-expand fight the video element for the pointer. */
  pointer-events: none;
}
/* Opt-out for art whose edges carry meaning (cards.php `fit => contain`):
   show the whole image and letterbox it against the art window's own backdrop
   instead of cropping. Exports letterbox to the same colour. */
.card-art[data-fit="contain"] img,
.card-art[data-fit="contain"] canvas,
.card-art[data-fit="contain"] video {
  object-fit: contain;
}

/* No ::-webkit-media-controls rules here any more: video art is painted into a
   <canvas> from a detached video element (see cards.js), so the browser has no
   media element in the page to hang controls on. Those rules only ever covered
   Chromium's own overlays anyway — Opera GX's video pop-out button is
   proprietary and ignored both them and disablePictureInPicture. */

.card-usage {
  flex: 1;
  font-family: var(--font-card-body);
  font-size: calc(11 * var(--u)); line-height: 1.42;
  color: var(--card-box-text);
  background: var(--card-box-bg);
  border: calc(1.5 * var(--u)) solid var(--card-box-border);
  border-width: round(calc(1.5 * var(--u)), 1px);
  border-radius: calc(8 * var(--u));
  padding: calc(7 * var(--u)) calc(9 * var(--u));
  overflow: hidden;
  display: flex; align-items: center;
}
body[data-theme="mtg"] .card-usage { border-radius: calc(3 * var(--u)); font-style: italic; }

.card-stats {
  flex: none;
  display: flex; align-items: center; gap: calc(5 * var(--u));
  padding: calc(2 * var(--u)) calc(2 * var(--u)) 0;
}
.stat {
  flex: 1; text-align: center;
  font-size: calc(9.5 * var(--u)); font-weight: 800; letter-spacing: calc(.6 * var(--u));
  padding: calc(4 * var(--u)) 0; border-radius: calc(6 * var(--u));
  color: var(--card-box-text);
  background: color-mix(in srgb, var(--card-box-bg) 70%, transparent);
  border: calc(1 * var(--u)) solid var(--card-box-border);
  border-width: round(calc(1 * var(--u)), 1px);
}
.stat b { font-size: calc(12 * var(--u)); display: block; }
.rarity-gem {
  flex: none; width: calc(20 * var(--u)); height: calc(20 * var(--u)); border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: calc(10 * var(--u));
  border: calc(1 * var(--u)) solid var(--card-box-border);
  border-width: round(calc(1 * var(--u)), 1px);
  background: var(--card-box-bg);
}
.rarity-gem[data-r="standard"]::after { content: "●"; color: #98a2b3; }
.rarity-gem[data-r="reverse"]::after  { content: "◐"; color: #30a7d7; }
.rarity-gem[data-r="holo"]::after     { content: "★"; color: #b76ef0; }
.rarity-gem[data-r="fullart"]::after  { content: "✦"; color: #f5a623; }
.rarity-gem[data-r="cosmic"]::after   { content: "✹"; background: linear-gradient(120deg,#ff5f8f,#ffc24b,#59d4ff); -webkit-background-clip: text; background-clip: text; color: transparent; }

/* illustrator credit — matches the watermark burned into exports */
.card-sig {
  flex: none;
  padding: calc(3 * var(--u)) calc(4 * var(--u)) 0;
  font-size: calc(7.5 * var(--u));
  font-weight: 600;
  letter-spacing: calc(.3 * var(--u));
  color: color-mix(in srgb, var(--card-box-text) 62%, transparent);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body[data-theme="mtg"] .card-sig { font-family: var(--font-card-title); font-style: italic; }

/* ----- FULL ART LAYOUT ----- */

.card.full .card-body { padding: 0; }
.card.full .card-art {
  position: absolute; inset: 0; height: auto;
  border: none; border-radius: 0; z-index: 0;
}
.card.full .card-head {
  position: absolute; top: 0; left: 0; right: 0; z-index: 5;
  padding: calc(10 * var(--u)) calc(10 * var(--u)) calc(18 * var(--u));
  background: linear-gradient(180deg, rgba(0, 0, 0, .68), transparent);
}
.card.full .type-pill { box-shadow: 0 1px 5px rgba(0, 0, 0, .75); }
/* Full-art text lies straight on the artwork, so it carries a layered dark
   shadow: a tight opaque one for edge definition plus a wide soft one that
   darkens whatever bright pixels sit behind it. */
.card.full .card-title {
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .95), 0 2px 10px rgba(0, 0, 0, .85);
}
/* ONE scrim for the whole bottom stack.
   These used to be three separately positioned bands (bottom: 48px / 14px / 0)
   each painting its own rgba(0,0,0,.72). The offsets were hand-tuned and did
   not actually meet: a few pixels of bright artwork showed through between the
   stats and the credit line, reading as a stray horizontal rule, and the gaps
   moved whenever a font size changed. Letting the stack size itself and
   carrying the backdrop on the parent makes seams impossible by construction. */
.card.full .card-bottom {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 5;
  flex: none;
  gap: calc(4 * var(--u));
  padding: calc(26 * var(--u)) calc(10 * var(--u)) calc(4 * var(--u));
  background: linear-gradient(0deg,
              rgba(0, 0, 0, .82) 0%, rgba(0, 0, 0, .78) 62%, transparent 100%);
}
.card.full .card-usage {
  flex: none;
  background: none;
  border: none; border-radius: 0;
  color: #f2f4f8;
  padding: 0;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .95), 0 2px 8px rgba(0, 0, 0, .8);
  /* grows upward, so cap it before it eats into the artwork */
  max-height: 38%;
  display: block;
  overflow: hidden;
}
.card.full .card-stats {
  padding: 0;
  background: none;
}
.card.full .card-sig {
  padding: 0;
  background: none;
  color: rgba(255, 255, 255, .78);
  text-shadow: 0 1px 3px rgba(0, 0, 0, .9);
}
.card.full .stat {
  background: rgba(255, 255, 255, .1);
  border-color: rgba(255, 255, 255, .2);
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .9);
}
.card.full .rarity-gem { background: rgba(255,255,255,.12); border-color: rgba(255,255,255,.25); }

/* ----- LOCKED (not yet owned) ----- */

.card.locked .card-inner { border-style: dashed; opacity: .55; background: transparent; }
.card.locked { box-shadow: none; }
.locked-face {
  position: absolute; inset: 0; z-index: 3;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: calc(10 * var(--u));
  color: var(--text-dim); font-weight: 800;
  letter-spacing: calc(3 * var(--u)); font-size: calc(14 * var(--u));
}
.locked-face span { font-size: calc(46 * var(--u)); filter: grayscale(1); opacity: .5; }

/* ============================================================
   FOIL / RARITY EFFECTS
   ============================================================ */

.foil, .cosmic-layer, .glare {
  position: absolute; inset: 0;
  pointer-events: none;
  display: none;
  border-radius: inherit;
}

/* The sheen layer is an oversized gradient slid around by the pointer.
   COVERAGE IS THE WHOLE GAME HERE. For a background image bigger than its box,
   `background-position: P%` puts the image at an offset of -(S-100)·P/100, so
   the image only still covers the box while P stays within 0-100%. Past that
   it slides off its own box, the browser tiles it, and the tile edge crosses
   the card as a hard seam — which is exactly what a 1.6 multiplier used to do
   for any --px or --py above 62.5, i.e. the lower-right corner.
   So the multiplier is 1: P can never leave the safe range, whatever S is.
   Travel is bought back with a larger S instead (460% gives 360% of movement,
   against the 384% the broken version had), and the stripe stops are scaled by
   340/460 so the bands stay the width they were tuned at. */
.foil {
  background:
    radial-gradient(circle at calc(var(--px, 50) * 1%) calc(var(--py, 35) * 1%),
      rgba(255, 255, 255, .8) 0%, rgba(255, 255, 255, 0) 42%),
    repeating-linear-gradient(115deg,
      rgba(255, 80, 120, .55) 0%,
      rgba(255, 205, 80, .55) 5.17%,
      rgba(120, 255, 170, .55) 10.35%,
      rgba(90, 200, 255, .55) 15.52%,
      rgba(190, 120, 255, .55) 20.7%,
      rgba(255, 80, 120, .55) 25.87%);
  background-size: 220% 220%, 460% 460%;
  background-position:
    center,
    calc(var(--px, 50) * 1%) calc(var(--py, 50) * 1%);
  /* Belt and braces: with the position bounded above there is nothing left to
     tile, and no-repeat means a future regression shows up immediately instead
     of as a seam subtle enough to need reporting. */
  background-repeat: no-repeat;
  mix-blend-mode: color-dodge;
  opacity: .78;
  /* per-layer, so the artwork foil can be more saturated than the frame
     foil without forking the hue animation below */
  --foil-sat: 1.5;
  filter: saturate(var(--foil-sat)) brightness(1.02);
  animation: foil-hue 9s linear infinite;
}
/* light frame (Pokémon): color-dodge blows out to white, use a glossy film instead */
body[data-theme="pokemon"] .foil { mix-blend-mode: normal; opacity: .48; }
/* Foil over ARTWORK — screen, never color-dodge.
   color-dodge is base / (1 - blend). It clips to pure white as soon as the
   blend approaches 1, and it MULTIPLIES whatever noise the source already has:
   the highlight below peaked at .8, i.e. a 5x gain on the grain in a dark,
   heavily compressed reaction GIF. The result read as harsh speckle and
   crushed blacks rather than as foil. Screen only ever lightens, so it adds
   sheen without amplifying anything.

   Measured on popcorn-time (dark, grainy source): high-frequency energy 11.5
   -> 5.8, and mean brightness lands near what the canvas exporter produces
   (87 vs 79) instead of blowing past it. The iridescence lost with the
   division is bought back with --foil-sat, which saturates the foil layer
   BEFORE it blends and so cannot touch the artwork's noise.

   Frame foil is deliberately untouched: it sits over flat card stock, which
   has no grain to amplify. */
body[data-theme="pokemon"] .card .foil-art,
body[data-theme="pokemon"] .card.full .foil-full,
body[data-theme="mtg"] .card .foil-art,
body[data-theme="mtg"] .card.full .foil-full {
  mix-blend-mode: screen;
  opacity: .5;
  --foil-sat: 2;
  /* background-image only — the shorthand would reset the size/position set
     on .foil, which is what makes the sheen track the pointer */
  background-image:
    radial-gradient(circle at calc(var(--px, 50) * 1%) calc(var(--py, 35) * 1%),
      rgba(255, 255, 255, .4) 0%, rgba(255, 255, 255, 0) 42%),
    repeating-linear-gradient(115deg,
      rgba(255, 80, 120, .5) 0%,
      rgba(255, 205, 80, .5) 5.17%,
      rgba(120, 255, 170, .5) 10.35%,
      rgba(90, 200, 255, .5) 15.52%,
      rgba(190, 120, 255, .5) 20.7%,
      rgba(255, 80, 120, .5) 25.87%);
}
@keyframes foil-hue {
  to { filter: saturate(var(--foil-sat)) brightness(1.02) hue-rotate(360deg); }
}

/* reverse holo: foil sits UNDER the body — visible everywhere except the
   opaque art image (and softened under translucent boxes) */
.card.r-reverse .foil-frame { display: block; z-index: 1; }
.card.r-reverse .card-usage,
.card.r-reverse .stat { background: color-mix(in srgb, var(--card-box-bg) 55%, transparent); }

/* holofoil: foil only over the art */
.card.r-holo .foil-art { display: block; z-index: 2; }

/* full art: one foil across the whole frame, since the art IS the frame */
.card.r-fullart .foil-full { display: block; z-index: 3; }

/* Cosmic keeps the standard frame, so it foils the two regions separately —
   frame and artwork need opposite blend modes on the light Pokémon frame, and
   a single overlay across both leaves the art looking milky. */
.card.r-cosmic .foil-frame { display: block; z-index: 1; }
.card.r-cosmic .foil-art   { display: block; z-index: 2; }
.card.r-cosmic .card-usage,
.card.r-cosmic .stat { background: color-mix(in srgb, var(--card-box-bg) 55%, transparent); }

.cosmic-layer { z-index: 4; overflow: hidden; }
.card.r-cosmic .cosmic-layer { display: block; }
.cosmic-layer .sym {
  position: absolute;
  color: #fff;
  mix-blend-mode: screen;
  text-shadow: 0 0 6px rgba(255, 255, 255, .9), 0 0 18px currentColor;
  opacity: 0;
  animation: sym-twinkle var(--dur, 3s) ease-in-out var(--delay, 0s) infinite;
}
@keyframes sym-twinkle {
  0%, 100% { opacity: 0; transform: scale(.6) rotate(-8deg); }
  50%      { opacity: var(--peak, .85); transform: scale(1) rotate(8deg); }
}

.glare {
  z-index: 6;
  background: radial-gradient(circle at calc(var(--px, 50) * 1%) calc(var(--py, 30) * 1%),
    rgba(255, 255, 255, .5) 0%, rgba(255, 255, 255, 0) 55%);
  opacity: 0;
  transition: opacity .25s;
}
.card.tilting .glare { display: block; opacity: .55; }
.card.r-standard.tilting .glare { opacity: .3; }

/* rarity ring glow on hover for shiny cards */
.card.r-holo.tilting,
.card.r-reverse.tilting { box-shadow: 0 18px 40px rgba(0, 0, 0, .4), 0 0 24px rgba(122, 200, 255, .35); }
.card.r-fullart.tilting { box-shadow: 0 18px 44px rgba(0, 0, 0, .45), 0 0 30px rgba(245, 166, 35, .4); }
.card.r-cosmic          { box-shadow: 0 12px 30px rgba(0, 0, 0, .4), 0 0 26px rgba(190, 120, 255, .35); }
.card.r-cosmic.tilting  { box-shadow: 0 20px 48px rgba(0, 0, 0, .5), 0 0 44px rgba(190, 120, 255, .6); }

/* ============================================================
   EXPANDED CARD OVERLAY
   ============================================================ */

.expand-overlay {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  background: rgba(8, 10, 16, .62);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
  animation: overlay-in .25s ease;
}
@keyframes overlay-in { from { opacity: 0; } }
.expand-overlay.closing { animation: overlay-out .22s ease forwards; }
@keyframes overlay-out { to { opacity: 0; } }

.expand-slot .card {
  --card-w: min(420px, 86vw, 54vh);
  box-shadow: 0 30px 80px rgba(0, 0, 0, .65);
}
/* no separate sizing for full art — it shares the standard 5:7 frame */

/* ============================================================
   CONTEXT MENU
   ============================================================ */

.ctx-menu {
  position: fixed; z-index: 300;
  min-width: 224px;
  padding: 8px;
  border-radius: 14px;
  background: rgba(22, 26, 36, .92);
  border: 1px solid rgba(255, 255, 255, .12);
  box-shadow: 0 18px 50px rgba(0, 0, 0, .5);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  color: #eef1f7;
  animation: ctx-in .16s var(--ease-pop);
  transform-origin: top left;
}
@keyframes ctx-in { from { opacity: 0; transform: scale(.9); } }

.ctx-title {
  padding: 8px 12px 10px;
  font-size: 13px; font-weight: 800;
  border-bottom: 1px solid rgba(255, 255, 255, .1);
  margin-bottom: 6px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ctx-rarity-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 6px 12px 10px;
  font-size: 12.5px; color: #aab3c5;
}
.ctx-rarity-row select {
  flex: 1;
  background: rgba(255, 255, 255, .08);
  color: #eef1f7;
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 8px;
  padding: 5px 8px; font-size: 12.5px;
  outline: none;
}
.ctx-rarity-row select option { background: #1c2230; }
.ctx-note {
  padding: 0 12px 8px;
  font-size: 11.5px; line-height: 1.4;
  color: #8f9ab0;
}
.ctx-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  padding: 9px 12px;
  border-radius: 9px;
  font-size: 14px; text-align: left;
  transition: background .12s;
}
.ctx-item:hover { background: rgba(255, 255, 255, .1); }
.ctx-item[disabled] { opacity: .4; pointer-events: none; }

/* ---------- LINK FALLBACK MODAL ---------- */

.link-modal {
  position: fixed; inset: 0; z-index: 350;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  background: rgba(8, 10, 16, .62);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
  animation: overlay-in .2s ease;
}
.link-box {
  width: min(520px, 100%);
  padding: 26px;
  border-radius: 18px;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
  display: flex; flex-direction: column; gap: 14px;
}
.link-box h2 { font-size: 20px; font-weight: 800; }
.link-box p { font-size: 14px; color: var(--text-dim); line-height: 1.5; }
#link-input {
  width: 100%;
  padding: 12px 14px;
  border-radius: 10px;
  font: 13px ui-monospace, "Cascadia Code", Consolas, monospace;
  color: var(--text);
  background: color-mix(in srgb, var(--text) 8%, transparent);
  border: 1px solid var(--panel-border);
  outline: none;
}
#link-input:focus { border-color: var(--accent-2); }
.link-actions { display: flex; gap: 10px; justify-content: flex-end; }

/* ---------- TOAST ---------- */

.toast {
  position: fixed; left: 50%; bottom: 30px; z-index: 400;
  transform: translate(-50%, 80px);
  padding: 12px 22px;
  border-radius: 999px;
  background: rgba(22, 26, 36, .94);
  color: #fff; font-size: 14px; font-weight: 600;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .4);
  opacity: 0;
  transition: transform .35s var(--ease-pop), opacity .3s;
  pointer-events: none;
  max-width: 86vw; text-align: center;
}
.toast.show { transform: translate(-50%, 0); opacity: 1; }

/* ---------- RESPONSIVE / MOTION ---------- */

@media (max-width: 640px) {
  :root { --card-w: 172px; }
  .topbar { padding: 10px 14px; gap: 10px; }
  .tabs { order: 3; width: 100%; justify-content: center; margin: 0; }
  main { padding: 20px 14px 70px; }
  .coll-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 18px 12px; }
  .pack { width: 150px; height: 232px; }
  .pack-stage .pack { transform: scale(1.25); margin: 40px 0 50px; }
  @keyframes pack-wobble {
    0%, 100% { transform: scale(1.25) rotateZ(-2deg); }
    50%      { transform: scale(1.28) rotateZ(2deg); }
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
